2026-05-01 โ Tiered Auth Recovery Phase 1 (#352) โ Complete โ
Tracking issue: #352 โ Tiered Auth Recovery Closed duplicate: #354Plan: docs/plans/2026-05-01_tiered_auth_recovery_plan.mdBranch: feat/352-auth-recovery-phase1 (worktree, merged locally โ not pushed)
What shipped โ
Phase 1 of the tiered auth recovery rollout: defer recovery setup out of signup and into a discoverable profile-settings card, without weakening the existing zero-knowledge guarantees.
Signup simplification โ
apps/web/src/screens/PhonePinSignup.jsx cut from 6 steps to 4:
- Removed in-flow
EmailCaptureStep(recovery email) andRecoveryPhraseDisplaystep. - Removed the standalone
RecoveryBackupModalstep. - Step 4 is now a single "Almost there" confirmation that triggers account creation.
- Recovery phrase is still generated silently; entropy is still cached in IndexedDB via the existing
getLastDerivedEntropy()โcacheEntropy()pipeline. No crypto change.
New profile recovery surface โ
- New
apps/web/src/components/AccountRecoveryCard.jsxrendered at the top ofProfile โ Privacy & Data. - Two actions: View recovery phrase (uses existing
RecoveryPhraseDisplay) and Send encrypted email backup (uses existingRecoveryBackupModal). - Card switches between a high-prominence red "Set up account recovery" state and a calm neutral "Account recovery is set up" state with a green
ShieldCheck. - Per-action emerald Done pills mark which Tier-2 options are claimed.
Discoverability ("you have something to do" signal) โ
- Red dot on the bottom-nav Me tab when no Tier-2 method is set up.
- Red dot on the Privacy & Data profile tab in the same condition.
- Both dots react in real time to claim/clear via custom event + cross-tab
storageevent.
Plumbing โ
- New
apps/web/src/lib/recoveryStatus.jsโ localStorage-backedgetRecoveryStatus(),hasRecoverySetup(),markRecoveryPhraseViewed(),markEmailBackupSent(),clearRecoveryStatus(). Phase 4 will move source-of-truth to Firestore so it follows the user across devices. - New
apps/web/src/hooks/useRecoverySetupStatus.jsโ keeps badges/banner reactive. packages/shared/encryptionnow re-exportsentropyToMnemonic+wordlistso the card can derive the phrase on demand.
Dev/test ergonomics โ
- New
window.recoverySpoof(dev-only) helpers inapps/web/src/main.jsx:recoverySpoof.status()/hasSetup()/claimPhrase()/claimEmailBackup()/claimAll()/clear()- Lets you flip between needs-setup and set-up states in the browser without going through the real flows.
- 4 new Vitest tests for
AccountRecoveryCard(all passing).
Out of scope (deferred to follow-up issues) โ
- #365 โ PIN re-entry gate before "View recovery phrase".
- #362 โ Escalating session/day-based nudge banner & modal.
- #363 โ TOTP authenticator setup flow.
- #364 โ Auth-API endpoints for TOTP + server-side recovery flag.
- #366 โ Security model documentation refresh.
Validation โ
npm run validate -- --workspace apps/web โ 11/11 PASS at the time of merge.
Notes for next agent โ
recoveryStatusflags currently live in localStorage only. Cross-device sync is Phase 4 work.- The signup flow no longer surfaces the recovery phrase; users reach it exclusively via the new card. The two red dots are the only nudge until Phase 2 (#362) lands.
- Worktree pathology (eslint plugin duplication, missing
.env.localsymlink) bit us during this work โ see docs/engineering/guides/PR_WORKFLOW.md for the symlink workaround. Lantern Control extension is also not worktree-aware (#360 tracks).