Skip to content

auth: support mobile paste in segmented code input - #204

Draft
Kzoeps wants to merge 8 commits into
mainfrom
fix/paste-code-mobile
Draft

auth: support mobile paste in segmented code input#204
Kzoeps wants to merge 8 commits into
mainfrom
fix/paste-code-mobile

Conversation

@Kzoeps

@Kzoeps Kzoeps commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace per-character OTP inputs with one full-length input behind the existing visual slots
    • this is how its done in shadcn and is the recommended way for accessbility. Heavily referenced from input-otp
  • preserve mobile paste, one-time-code autofill, and keyboard replacement behavior
  • keep client branding selectors and browser-driven OTP helpers compatible
  • add pure normalization and selection tests plus a patch changeset

Closes #212

Testing

  • pnpm typecheck
  • pnpm lint
  • pnpm test — 1,057 tests passed
  • pnpm test:coverage
  • Chromium mobile-context smoke test for formatted paste and keyboard replacement

Summary by CodeRabbit

  • Bug Fixes
    • Pasting or autofilling a mobile sign-in code now fills the complete one-time code instead of only the first character.
    • Improved OTP input behavior, including caret movement, selection, validation, normalization, and automatic submission.
    • OTP controls now receive clearer focus, accessibility, and disabled-state handling.
  • Style
    • Updated OTP active-state styling guidance for themed interfaces.

Summary by CodeRabbit

  • New Features
    • Improved one-time-code entry on mobile, including full-code paste and autofill.
    • Added easier selection and replacement of individual code positions.
    • Improved screen-reader announcements and support for reduced-motion preferences.
    • OTP entry now uses a single input while preserving the segmented visual design.
  • Bug Fixes
    • Corrected code normalization, caret movement, focus handling, and automatic submission.
    • Updated active-state styling for OTP fields.

Summary by CodeRabbit

  • New Features
    • Improved mobile sign-in code entry with long-press paste and one-time-code autofill.
    • Added flexible code selection, replacement, normalization, and automatic submission.
    • Added screen-reader announcements, accessibility metadata, and reduced-motion support.
  • Style
    • Improved compatibility with existing sign-in branding and input styles.
    • Added clearer focus, placeholder, active-slot, and caret styling.
  • Documentation
    • Updated guidance for OTP styling and browser-based code entry.

@changeset-bot

changeset-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 63ede11

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview Aug 4, 2026 10:26pm

Request Review

@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-204 July 20, 2026 11:51 Destroyed
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The OTP login flow now uses one real #code input over segmented visual slots. Shared helpers normalize values and resolve selections. Client logic supports paste, autofill, accessibility metadata, caret synchronization, and auto-submit.

Changes

OTP input flow

Layer / File(s) Summary
OTP helper contracts and tests
packages/auth-service/src/otp-input.ts, packages/auth-service/src/__tests__/otp-input.test.ts
Added shared OTP normalization and selection helpers. Tests cover numeric, alphanumeric, paste, caret, click, and serialized execution behavior.
OTP branding compatibility
packages/auth-service/src/lib/otp-branding-compat.ts, packages/auth-service/src/__tests__/otp-branding-compat.test.ts, packages/auth-service/package.json
Added CSS adaptation for legacy OTP selectors. Invariant styles protect the real input. Parsing and serialization failures return escaped fallback CSS.
Single-input login page behavior
packages/auth-service/src/routes/login-page.ts, packages/auth-service/src/__tests__/login-page.test.ts
Replaced per-slot inputs with one overlaid #code input. Added selection handling, normalized paste, accessibility metadata, reduced-motion styling, focus recovery, and auto-submit.
Validation and integration
e2e/..., features/client-branding.feature, packages/demo/..., docs/..., .changeset/mobile-code-paste.md, vitest.config.ts
Updated automation, branding checks, theme CSS, documentation, release notes, compiler settings, and coverage thresholds.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant codeInput
  participant LoginPage
  participant otpSlots
  participant Verification
  User->>codeInput: paste or autofill complete code
  codeInput->>LoginPage: input and selection events
  LoginPage->>otpSlots: normalize value and render slots
  LoginPage->>Verification: auto-submit complete code
  Verification-->>LoginPage: verification result
  LoginPage->>codeInput: refocus after failure or step change
Loading

Possibly related PRs

Suggested reviewers: s-adamantine

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes replace segmented native inputs with one accessible input and add mobile paste, autofill, announcement, selection, and accessibility support.
Out of Scope Changes check ✅ Passed The reviewed changes support the OTP redesign, branding compatibility, testing, documentation, and validation objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding mobile paste support for the segmented OTP code input.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/paste-code-mobile
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/paste-code-mobile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls-official

coveralls-official Bot commented Jul 20, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 30956341598

Coverage increased (+1.1%) to 59.018%

Details

  • Coverage increased (+1.1%) from the base build.
  • Patch coverage: 77 of 77 lines across 3 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 3180
Covered Lines: 1863
Line Coverage: 58.58%
Relevant Branches: 1993
Covered Branches: 1190
Branch Coverage: 59.71%
Branches in Coverage %: Yes
Coverage Strength: 10.12 hits per line

💛 - Coveralls

@railway-app

railway-app Bot commented Jul 20, 2026

Copy link
Copy Markdown

🚅 Deployed to the ePDS-pr-204 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/demo untrusted ✅ Success (View Logs) Web Aug 4, 2026 at 10:26 pm
@certified-app/demo ✅ Success (View Logs) Web Aug 4, 2026 at 10:26 pm
@certified-app/auth-service ✅ Success (View Logs) Web Aug 4, 2026 at 10:25 pm
@certified-app/pds-core ✅ Success (View Logs) Web Aug 4, 2026 at 10:25 pm

@Kzoeps
Kzoeps requested review from aspiers and s-adamantine and removed request for aspiers July 20, 2026 13:02
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-204 July 20, 2026 13:05 Destroyed
@Kzoeps

Kzoeps commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

(reply generated by OpenAI Codex)

Addressed the SonarCloud finding reported in this comment in af18b8e — numeric code normalization now uses the concise \D character class.

@Kzoeps

Kzoeps commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

@Kzoeps, I’ll review the latest changes on this PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Kzoeps

Kzoeps commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

@Kzoeps, I’ll review the latest changes on this PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread .changeset/mobile-code-paste.md Outdated
@Kzoeps

Kzoeps commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

(reply generated by OpenAI Codex)

Regarding the latest CodeRabbit review:

  • Fixed the circular computed-style assertions in b6bb55d; the E2E step now checks five stable amber-theme signatures. This exposed and fixed missing !important declarations in the demo fixture.
  • Fixed swallowed parser details in b6bb55d; the warning remains concise and the structured err is emitted at debug level.
  • Verified that the installed selector parser accepts keyword and percentage keyframe offsets; added a regression covering the real caret keyframes rather than adding an unnecessary skip.
  • Did not add synthetic selector-parse-failed / serialization-failed tests: the suggested keyframe case does not fail, and no stable public input was identified that reaches serialization failure without mocking implementation internals.
  • Did not add tautological docstrings solely to satisfy the advisory percentage; the public adapter and invariant contract already have decision-focused documentation.

@Kzoeps

Kzoeps commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

(reply generated by OpenAI Codex)

Regarding the requested-changes review: addressed in a7f9064. The single native OTP input remains, while common legacy input/focus/placeholder branding selectors are projected onto the visual slots automatically. Existing branded clients do not need a coordinated CSS migration for those forms, and the release note now describes the bounded compatibility contract.

@Kzoeps

Kzoeps commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@aspiers was working on the css parser to maintain backwards compatibility since duplicate inputs dont really work.

However the css parser feels very fragile and also janky and adds a lot more code. Im leaning towards closing this pr and keeping the separate inputs and fixing the copy-paste only without the accessibility and navigation fixes.

One alternative here is to maybe have a field in the client metadata which is a field like OtpInputMode: 'single-input' | 'slotted-input' and based on that render the previous version or the new version.

let me know what you think

@aspiers

aspiers commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@Kzoeps commented on Aug 4, 2026, 14:51 GMT+1:

@aspiers was working on the css parser to maintain backwards compatibility since duplicate inputs dont really work.

However the css parser feels very fragile and also janky and adds a lot more code. Im leaning towards closing this pr and keeping the separate inputs and fixing the copy-paste only without the accessibility and navigation fixes.

What parser is that? If you really needed to parse CSS, I would say use treesitter, but that sounds overkill in this case since it's such a simple change needed - I would have thought a basic regexp search would do the job? Probably easiest is search-and-replace, or it could automatically switch modes based on the search result.

One alternative here is to maybe have a field in the client metadata which is a field like OtpInputMode: 'single-input' | 'slotted-input' and based on that render the previous version or the new version.

I'm not a fan of that because you would need it to default to the old version in order to achieve backwards compatibility, which would mean that every client wanting the new recommended version would have to explicitly request it.

If there is a concrete reason why a simple regexp search doesn't work, I'd like to discuss that before considering other options, because I have a fairly strong intuition that it should work.

@aspiers
aspiers force-pushed the fix/paste-code-mobile branch from b30781f to 24dd529 Compare August 4, 2026 22:16
@railway-app
railway-app Bot temporarily deployed to ePDS / ePDS-pr-204 August 4, 2026 22:16 Destroyed
@socket-security

socket-security Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpostcss@​8.5.251001008195100
Addedpostcss-selector-parser@​7.1.41001009792100

View full report

@blacksmith-sh

This comment has been minimized.

Kzoeps added 8 commits August 4, 2026 23:24
Use one full-length input behind visual OTP slots so mobile paste and one-time-code autofill preserve the complete code. Keep keyboard replacement, client branding, and existing browser-driven flows compatible.
Project common legacy input selectors onto visual slots while keeping the real OTP input protected for paste, autofill, and hit testing.
Use fixed browser style signatures, log parser failures at debug level, and lock in keyframe compatibility.
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@Kzoeps

Kzoeps commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

I would have thought a basic regexp search would do the job? Probably easiest is search-and-replace

Search and replace works but we need to know the exact css customization from each of the consumers. For now I have maearth.com, gainforest.app, simocracy.org and certified.app if we optimize for only these then a regexp works. The parser is the AI's more generic solution, i thought a .otp replace would work as well but upon looking at me's css they have css such as input:focus input::placeholder which would fail with a simple regex swap.

I'm not a fan of that because you would need it to default to the old version in order to achieve backwards compatibility, which would mean that every client wanting the new recommended version would have to explicitly request it.

Makes sense.

If there is a concrete reason why a simple regexp search doesn't work, I'd like to discuss that before considering other options, because I have a fairly strong intuition that it should work.

I think if we do it for the 4 apps mentioned above and based on their css write the regex then it would work. can switch it to that since i think the apps are maybe the only users of certified.one for now?

@aspiers

aspiers commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@Kzoeps commented on Aug 5, 2026, 08:55 GMT+1:

I would have thought a basic regexp search would do the job? Probably easiest is search-and-replace

Search and replace works but we need to know the exact css customization from each of the consumers.

Why? Doesn't it only have to replace .otp-box:focus with .otp-box.active?

For now I have maearth.com, gainforest.app, simocracy.org and certified.app if we optimize for only these then a regexp works.

Probably that's good enough but we can survey all our trusted client brandings and check.

The parser is the AI's more generic solution, i thought a .otp replace would work as well but upon looking at me's css they have css such as input:focus input::placeholder which would fail with a simple regex swap.

We don't need to touch more general selectors like input:focus. Maybe you are talking about stuff like

.otp-box {
    &:focus ...

? What other combinations do we have to worry about in the absence of a parser?

If there is a concrete reason why a simple regexp search doesn't work, I'd like to discuss that before considering other options, because I have a fairly strong intuition that it should work.

I think if we do it for the 4 apps mentioned above and based on their css write the regex then it would work. can switch it to that since i think the apps are maybe the only users of certified.one for now?

Bit more than just those 4 but the nice thing is that we have the exact list of trusted clients so we can do a 100% reliable check! Would you be able to do that?

@aspiers
aspiers marked this pull request as draft August 5, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile OTP paste and accessibility

2 participants