Skip to content

feat(onboarding): make passphrase tip non-skippable with inline opt-in toggle#235

Merged
BitHighlander merged 2 commits into
developfrom
feat/passphrase-onboarding-optin
Jun 9, 2026
Merged

feat(onboarding): make passphrase tip non-skippable with inline opt-in toggle#235
BitHighlander merged 2 commits into
developfrom
feat/passphrase-onboarding-optin

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

What

During OOB setup, the post-setup "Hidden Wallets" tutorial card is now mandatory and carries the actual passphrase toggle — so every new user makes a deliberate on/off choice instead of skipping past the explanation. A hint tells them it can be changed later in Settings.

  • The passphrase tip card can no longer be skipped. Its own Skip button is hidden, and skipping an earlier tip now lands on the passphrase card (so it can't be bypassed).
  • The card shows an inline Enable passphrase protection toggle (default OFF) plus "You can turn this on or off anytime in Settings."
  • The choice is applied via applySettings({ usePassphrase: true }) only when the user clicks Continue (not the instant they flip the switch), so the device's PIN/passphrase re-prompt appears as the wizard moves to complete rather than popping over the tutorial mid-read. The Continue button shows a spinner while applying.

How

  • TutorialCards.tsx: TutorialCard gains interactive?: 'passphrase' and nonSkippable?. TutorialPage renders the toggle + settings hint for the interactive card, hides Skip on non-skippable cards, and supports a nextPending spinner state. New CardToggle switch.
  • OobSetupWizard.tsx: tipPassphraseEnabled (default false) + apply-on-Continue logic in the security-tips step; onSkip routes to the passphrase card. Synchronous re-entrancy guard mirrors the existing applyingLabelRef pattern.
  • en/setup.json: toggleLabel + settingsHint. The t() calls use defaultValue fallbacks so untranslated locales render correctly until the i18n gapfill runs.

Dependency / ordering

Stacked on #234 (fix/passphrase-toggle-stale-seed) and includes its commit. #234's clearSession is what makes enabling passphrase mid-session actually re-derive the correct seed — without it, opting in here would show the standard wallet's addresses until a reconnect. Merge #234 first (or together); this PR's diff narrows to the onboarding commit once #234 lands.

Test plan (requires an uninitialized device → OOB flow)

  1. Wipe/initialize a KeepKey to enter OOB setup; complete setup through the post-setup tips.
  2. On the first two tips, press Skip tips → ✅ lands on the Hidden Wallets card (not the dashboard).
  3. Hidden Wallets card → ✅ no Skip button; shows the toggle (OFF) + "change in Settings" hint.
  4. Leave toggle OFF → Start Using KeepKey → ✅ standard wallet, no passphrase prompt.
  5. Re-run; turn the toggle ONStart Using KeepKey → ✅ spinner, device confirms "Enable Passphrase", then PIN + passphrase prompts, then dashboard shows the hidden wallet (correct addresses, incl. "view on device" — relies on fix(passphrase): clear device session on passphrase toggle so addresses re-derive #234).
  6. Settings → ✅ passphrase toggle reflects the choice and can be changed.

…-derives

Enabling/disabling BIP-39 passphrase protection from Device Settings left
every wallet address — including 'view on device' — showing the previous
(standard) wallet until a physical reconnect.

Root cause is in firmware: storage_setPassphraseProtected() only flips the
passphrase_protection flag and does NOT invalidate session.seedCached. The
seed derived earlier in the session (empty passphrase) stays cached, so
storage_getRootNode() keeps returning it even after the user enters a new
passphrase. A USB unplug power-cycles the device and clears the cache, which
is why reconnecting 'fixed' it.

Fix without requiring a reconnect: engine.applySettings() now sends
ClearSession after toggling usePassphrase, dropping the cached seed +
passphrase + PIN so the device re-prompts and re-derives from the correct
seed. The device routes back through needs_pin -> needs_passphrase, which
also resets the in-memory account managers and remounts the dashboard, so
stale addresses/balances in the UI clear too. Skipped on the emulator.

DeviceSettingsDrawer: both enable and disable now re-auth, so neither path
calls getFeatures inline (would race promptPin's getPublicKeys); the drawer
re-fetches features when it next opens.
…n toggle

The post-setup 'Hidden Wallets' tutorial card is now mandatory and carries the
real passphrase toggle, so every new user makes a deliberate choice instead of
skipping past the explanation.

- TutorialCards: cards can be marked interactive:'passphrase' + nonSkippable.
  The passphrase card renders an on/off toggle and a 'change anytime in
  Settings' hint, and hides its Skip button. Next shows a spinner while the
  choice is applied.
- OobSetupWizard: the toggle choice is held locally (default OFF) and applied
  via applySettings({usePassphrase:true}) only when the user clicks Continue,
  then the wizard advances to 'complete' (the device's PIN/passphrase re-prompt
  overlays follow naturally). Skipping an earlier tip now lands on the
  passphrase card so it cannot be bypassed.
- en/setup.json: toggleLabel + settingsHint strings (defaultValue fallbacks
  keep other locales working until translated).
@BitHighlander
BitHighlander requested a review from pastaghost as a code owner June 9, 2026 18:07
@BitHighlander
BitHighlander merged commit 7de9054 into develop Jun 9, 2026
@BitHighlander
BitHighlander deleted the feat/passphrase-onboarding-optin branch June 9, 2026 18:37
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.

1 participant