Contract-freeze · UX approval · issue #251

MFA step-up & contact verification — mobile (375px) · Approval Frames

375px (Android TWA viewport) frames for the provider-neutral Security API step-up and contact-verification UX requested in issue #251: choose a factor, enter a one-time code, confirm a phone number, and a combined mobile security-status view. These land in this repo (not an external canvas link) so they can be diffed, schema/DS-conformance checked, and stamped per the design-first gate. Built in the fuse-seam design system (tokens only). No provider name appears anywhere in the UI.

Reached from sign-in (/login/mfa) and from the Account security surface (/account/security, /account/security/verify/phone).

Walk the flow

Frame 01 · /login/mfa

Choose a factor

Mobile factor picker (authenticator / email / SMS / recovery), plus the loading, load-error, and zero-factors states the desktop frame doesn't show.

defaultrecovery not offeredloadingload errorempty / no factors
Frame 02 · /login/mfa

Enter code

Touch-sized OTP entry, verifying in-flight, expiry countdown + resend cooldown, delivery failure. Fail-closed: wrong/expired code stays on screen.

defaultverifyinginvalid / expiredtoo many attempts (429)delivery failed
Frame 03 · /account/security/verify/phone

Confirm your phone

Phone-only OTP verification (genuinely new — email keeps the approved link-click pattern). Skippable; re-prompts from profile.

enter phoneenter codeinvalid codesend failedverified
Frame 04 · /account/security

Security status (profile)

Combined 2FA + contact-verification chips for the TWA shell. Additive to the desktop hub, not a replacement. Fail-closed 409 on removing your only factor.

defaultloadingload errorremove-last blocked (409)
Frame 05 · /login/mfa (from frame 01)

Use a recovery code

Full-width text field for an opaque recovery code (not OTP cells). Offered only when recoveryAvailable is true. Part of the step-up flow, same as frames 01–02.

entryverifyingcode didn't work (401)too many attempts (429)sign-in expiredcouldn't check

Build inventory — what approving these frames approves

Flows
MfaStepUpMobileFlow · /login/mfa ConfirmContactFlow · /account/security/verify/phone SecurityStatusMobileFlow · /account/security
Components
FactorPickerOtpInput (reused)MfaChallengeCard ContactVerifyPanelSecurityStatusList SecurityStatusRowRemoveFactorDialog
Revision note
Recovery-code redemption (contract 1.3.0, #1660) does not change this inventory: frame 05 is a state of MfaChallengeCard in MfaStepUpMobileFlow, built from the DS CodeField + StatusCallout.
Packages
@fuzefront/account-security-ui @fuzefront/security-client (existing)

Reconciliation with already-in-repo flows (not greenfield)

Open for owner ruling

  • Should email verification also move to OTP code-entry for consistency with phone? Assumed no (stay link-click) pending a ruling.
  • Should frame 04 become the TWA's canonical security entry point, or stay secondary to the desktop hub? Assumed secondary/additive pending a ruling.

Recovery-code revision — open question for the owner

After a recovery sign-in
Not framed: a “you used a recovery code — N left” prompt after signing in. Assumed not in scope. The count is already visible under Security → Recovery codes.
● Awaiting approval

Design system: @fuzefront/design-system (fuse-seam) · Contract: packages/security/openapi.yaml → @fuzefront/security-client · Package: @fuzefront/account-security-ui · Flag: fuzefront.security.mfa-stepup-mobile (default OFF).
Per-flow approval lives in manifest.json; approve by setting a flow's approved: true (+ approver + date) or reply @claude approve <flow-id> / @claude reject: <reason> on issue #251.

Review · mfa-stepup-mobile approve or reject each flow ↓
mfa-stepup-mobileapproved · izzywdev
confirm-contact-phoneapproved · izzywdev
security-status-mobileapproved · izzywdev