Clean successor to V3 (forge: lilith/atlilith). Seeded from local Mac working tree at ~/Code/@projects/@cocottetech/. node_modules and build artifacts excluded via .gitignore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
yubikey-enrollment.screen
Single-screen breakdown for the YubiKey / Passkey / FIDO2 hardware-key enrollment flow — the four-step guided setup that adds a hardware-bound second factor (and Master-Key wrap source) to the account. Implements brief AF §AF5 (enrollment) and §AF8 (recovery / lost-key flows). Reached from Settings → Safety → "Add a hardware key" (per S §S6) or from the post-first-vigil hearth nudge ("You're getting traction. Lock things down — add a hardware key when you get a chance." per AF5 voice copy). Voice register: hearth on the entry / welcome moment, working on the operational steps (this is a decision flow), plain on lost-key and unrecoverable paths (per voice §V2). One-question-at-a-time pattern, same as persona-seed-interview.screen.md.
Layout (full-screen sheet — iPhone 393×852pt)
┌─────────────────────────────────────────────────┐
│ ◄ Settings Cancel │ 56pt
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 1 of 4 · Tap your primary key │ │ step header
│ │ ━━━━━━░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │ progress strip
│ └─────────────────────────────────────────┘ │
│ │
│ Cocotte │
│ Hold your key against the top of your phone. │ working register
│ Tap when you feel the haptic. │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ ◌ │ │ key-tap detector
│ │ (pulse animation) │ │ pulses while waiting
│ │ │ │
│ │ Waiting for key… │ │ status line
│ └───────────────────────────────────────┘ │
│ │
│ Name this key │ appears after detect
│ ╭─────────────────────────────────────╮ │
│ │ e.g. yellow YubiKey 5 NFC │ │
│ ╰─────────────────────────────────────╯ │
│ │
│ [primary] │ role badge
│ │
│ ┌─────────────────────────────────────────────┐│
│ │ Continue ││ primary button, bottom-pinned
│ └─────────────────────────────────────────────┘│
│ │ 34pt — home indicator
└─────────────────────────────────────────────────┘
Step 4 — Recovery code reveal (variant body):
┌─────────────────────────────────────────┐
│ 4 of 4 · Save your recovery code │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
└─────────────────────────────────────────┘
Cocotte (plain register)
This restores your Master Key if every key
and device is lost. Without it, your journal
and peer history is gone.
┌─────────────────────────────────────────┐
│ abandon ribbon crystal palace │ 12-word BIP39 grid (3×4)
│ velvet summer harvest wander │
│ silver forest amber stone │
└─────────────────────────────────────────┘
[ Copy ] [ Print ] [ Show again later ]
☐ I've saved this somewhere offline
┌─────────────────────────────────────────┐
│ Finish │ disabled until checkbox ticked
└─────────────────────────────────────────┘
Components
| Component | Notes |
|---|---|
| Step header | "N of 4 · {step label}" + progress strip. Tappable past dots → revisit step (only for step 1↔2, not the recovery-code step). |
| Key-tap detector | Pulse-animated circle (◌ / radiating rings) while waiting; green checkmark + soft haptic on read. Replaces NFC / USB / Lightning prompt iconography contextually. |
| Key-name input | Plain text field; placeholder hints at color + model ("yellow YubiKey 5 NFC"). Stored to device_wraps.label. |
| Role badge | [primary] or [backup] chip on the step card. Step 1 = primary; Step 2 = backup (strongly nudged). |
| Recovery-code display | 12 BIP39 words in a 3×4 grid; monospace; high-contrast. Never screenshot-restricted from a UX standpoint (user must be able to capture it) but flagged in audit (brief I) as recovery_code_revealed. |
| Copy / Print buttons | Copy puts on clipboard + auto-clears in 60s. Print routes through AirPrint. |
| Confirm checkbox | "I've saved this somewhere offline" — gates the Finish button. No way to skip. |
| Continue button | Bottom-pinned, primary filled. Disabled until step prerequisite met (key detected + named, or checkbox ticked). |
| Cancel (top-right) | Aborts flow; prompts confirmation if any step ≥ 2 has been completed; partial enrollment is rolled back. |
States
- Not enrolled (entry card) — Settings → Safety shows "Add a hardware key — recommended" card. Hearth register: "Lock things down. Add a hardware key when you get a chance." Tapping opens this flow. (Same card appears as a one-shot nudge after first vigil per D → AF5.)
- Step 1 of 4 · Tap your primary key — pulse detector active, name field hidden. Working register: "Hold your key against the top of your phone. Tap when you feel the haptic."
- Detecting key (animated) — pulse intensifies; status line "Reading…". Lasts < 1s typically; timeout at 30s falls back to retry prompt.
- Key detected + name input — checkmark replaces pulse; name field appears; Continue enabled once name is non-empty.
- Confirm tap (second tap) — micro-step within Step 1: "Tap once more to confirm." Same detector, no name field. Commits the primary
device_wrapsrow. - Step 2 of 4 · Enroll backup — strongly nudged. Hearth-leaning working: "Second key strongly encouraged. If your first key disappears, the second one keeps you in." Skip available but the button reads "Skip backup (not recommended)" with a low-emphasis style; tapping shows a one-shot confirm.
- Step 3 of 4 · Tap-test sign-in — verifies both keys produce valid FIDO2 assertions. Prompts: "Tap your primary." → "Now your backup." Failure on either shows the retry / revoke affordances inline.
- Step 4 of 4 · Recovery-code reveal — plain register; 12-word grid; copy/print/confirm. Finish disabled until checkbox ticked.
- Enrolled (success card) — full-screen receipt: "Keys are in. Two enrolled, recovery code saved." Hearth register. CTA: "Back to Settings" or "Back to chat". Audit row written (brief I).
- Lost primary — sign-in with backup — entry from sign-in flow, not Settings. Plain register: "Sign in with your backup key. Revoke the missing one from Settings after." Renders the detector for the backup; on success, routes to a "Revoke missing key?" panel with confirm + immediate re-enrollment CTA.
- All keys lost — recovery-code entry — 12-word grid input (not display). Plain register: "Recovery code restores your Master Key. Without it, your journal and peer history is gone." Word-by-word entry with BIP39 wordlist autocomplete; submit only when all 12 are valid wordlist members.
- Recovery succeeded — re-enroll prompt — plain: "Master Key restored. Enroll a new primary key now." Routes back to Step 1 with the role badge pre-set.
- Recovery failed (wrong code) — plain register, no sugar-coating: "We can't recover keys from our side. That's the whole point." No retry-count exposure (don't leak); user can retry but no hint about which word was wrong beyond BIP39 wordlist validation.
- Cancelled mid-flow — Cancel tapped at step ≥ 2 → confirm sheet: "Cancel enrollment? Your primary key won't be saved." Confirm rolls back any
device_wrapsrows written so far and returns to Settings. Step 1 cancels silently.
Interactions / gestures
- Tap on the key-tap detector → re-prompts for tap (useful if the user fumbled and the OS-level NFC sheet dismissed).
- Long-press on a recovery-code word → copies the full 12-word phrase to clipboard + clipboard auto-clears after 60s. (Per-word copy is intentionally not offered — too easy to share one word and think it's safe.)
- Swipe-down on the step sheet → cancel with confirmation if mid-step ≥ 2; silent dismiss if at step 1.
- VoiceOver swipe → reads step header, status line, action button in that order; skips decorative pulse.
- Tap role badge → tooltip explains primary vs backup ("Backup keys can sign in if the primary is lost or damaged.").
- Tap "Show again later" on Step 4 → schedules a Settings reminder card; does NOT unlock Finish (the checkbox is still required).
In-the-wild copy
- (hearth, entry card from Settings or post-vigil nudge) — "You're getting traction. Lock things down — add a hardware key when you get a chance."
- (working, Step 1) — "Hold your key against the top of your phone. Tap when you feel the haptic."
- (working, Step 1 confirm) — "Tap once more to confirm. Same key."
- (working, Step 2 backup) — "Second key strongly encouraged. If your first key disappears, the second one keeps you in."
- (working, Step 3 test) — "Quick test. Tap your primary. Then your backup."
- (plain, Step 4 recovery) — "This restores your Master Key if every key and device is lost. Without it, your journal and peer history is gone."
- (hearth, success card) — "Keys are in. Two enrolled, recovery code saved. The kitchen's locked up well."
- (plain, lost-primary sign-in) — "Sign in with your backup key. Revoke the missing one from Settings after."
- (plain, recovery failed) — "We can't recover keys from our side. That's the whole point."
Edge cases
- NFC key + iPhone alignment failure — after 3 failed reads in 15s, the detector swaps the pulse for a small alignment diagram (key against the top edge of the phone, near the camera). One-shot — don't nag.
- USB-C YubiKey on Lightning iPhone (or vice versa) — on Step 2, if the primary was USB-C and the device is Lightning, the backup-key suggestion explicitly nudges: "Pick a backup that fits this phone — Lightning or NFC. Your primary is USB-C; that's fine but it won't sign you in here in a pinch." Linked to YBK-Q1 vendor purchase.
- VoiceOver narration of the tap-prompt — pulse animation is decorative-only; status line "Waiting for key" is the read-aloud anchor; on detect, announces "Key detected. Name field below."
- Reduced motion (per X) — pulse animation replaced with a static "Waiting for key" text + a single soft fade on detect. No radiating rings.
- Cellular Watch sign-in (per AB §AB-Q5) — Watch cannot enroll hardware keys; if entered from a Watch nudge, the Watch screen shows "Enroll on phone" and hands off via Continuity. The flow itself never runs on Watch.
- Recovery-code entry with wrong / misspelled words (state 11) — Levenshtein-tolerant prompt per word: "Did you mean:
crystal?" Only suggests BIP39-list words within edit distance 2. Suggestion is per-word, never per-phrase (don't reveal phrase shape). - OS-level NFC sheet collision — iOS shows its own "Hold Near Top of iPhone" sheet over ours; we suppress our pulse while that sheet is up and resume on dismiss.
- Specialist-degraded (per M §M2 / L §L4) — if the auth specialist is offline, Step 1 shows a plain-register banner: "Can't enroll keys right now. Try again in a few minutes." Cancel-only; no partial write.
Related
- Brief AF — parent; §AF5 defines the enrollment flow, §AF8 defines recovery.
- Brief S §S6 — Safety category; entry point.
- Brief D — post-first-vigil nudge entry point.
- Brief X — reduced motion, VoiceOver requirements.
- Brief AB §AB-Q5 — Watch cannot enroll.
- persona-seed-interview.screen.md — sibling one-question-at-a-time pattern.
- accessibility-settings.screen.md — sibling settings-subpage pattern.
- approval-card.screen.md — action-bar pattern reference.
- 00-system-voice.md §V2 — register gradient (hearth / working / plain).
Out of scope
- Physical key acquisition / purchase flow — linked out to the vendor site (YubiKey.com / Apple Passkey docs).
- Enrollment outside the Settings entry point (no deep-linkable enrollment URL at P0).
- Cross-device backup-key transport — keys are enrolled per-device-context per AF5; transferring a backup key's
device_wrapsrow to a new phone is a separate (§AF7) flow. - Org-level fleet enrollment for member keys — see YBK-Q3.
Open questions
- YBK-Q1 · In-app vendor purchase link vs out-of-app — should the entry card carry a "Don't have a key? Get one" CTA? Lean: out-of-app deep link to yubico.com / Apple's passkey docs; no in-app commerce at P0.
[nice-to-have] - YBK-Q2 · Recovery-code regeneration frequency — one-shot at first run only, or regenerate-on-demand from Settings? Lean: one-shot at first run + regenerate-on-demand from Settings → Safety, with the old code invalidated and audit row written.
[blocking] - YBK-Q3 · Multi-tenant orgs — personal vs org-enrolled keys — in coop / org contexts (per W), does each member enroll their own key, or does the org enroll keys on members' behalf? Lean: each member personally; the org cannot wrap or hold member keys — that would break the person-first tenancy contract.
[exploratory]