Skip to content

feat(onboarding): mnemonic-entry refactor + ASB UX hardening#507

Draft
yasin-ce wants to merge 4 commits into
mainfrom
yasince/asb
Draft

feat(onboarding): mnemonic-entry refactor + ASB UX hardening#507
yasin-ce wants to merge 4 commits into
mainfrom
yasince/asb

Conversation

@yasin-ce
Copy link
Copy Markdown
Collaborator

Summary

Stacked on #500. Four logically grouped commits cleaning up the ASB import flow:

  • style(onboarding): hero-scale shield illustration on AsbImportInfoScreen, replacing the small PWRoundIcon chip with a direct-SVG render at the same scale as WatchInfoScreen / ImportInfoScreen.
  • feat(security): usePreventScreenCapture on BackupVerificationScreen (quiz tiles include the real mnemonic words mixed with decoys).
  • feat(onboarding): extract useMnemonicWordEntry + MnemonicSuggestionBar + splitMnemonic from the 180-line slab inside ImportAccountScreen, adopted across both ImportAccountScreen and AsbImportKeyScreen. Same diff lands four hardenings on the two screens: screen-capture prevention, navigation.replace on success paths (typed mnemonic drops for GC on unmount), envelope-missing redirect on AsbImportKeyScreen, and useHeaderHeight() replacing the HEADER_HEIGHT = 50 magic.
  • fix(onboarding): sync AsbImportBackupScreen's local loadedFile to the store's envelope — when the wizard wipes the store post-import, the "Pasted backup" card now disappears so the user can't loop through Key→Backup with a stale state.

Test plan

  • pnpm pre-push --no-fail-on-error — all checks green (lint, typecheck, formatter, copyright headers, i18n, secrets)
  • onboarding-import-asb.test.tsx — 8/8 (1 new regression test for the back-nav scenario)
  • onboarding-import-algo25.test.tsx — 4/4
  • onboarding-import-hd.test.tsx — 7/7
  • backup.test.tsx — 5/5
  • useMnemonicWordEntry.spec.ts — 12 scenarios covering paste distribution (multiple separators), clipboard fallback (incl. iOS autocomplete regression), focus advancement at boundaries, error-callback wiring
  • MnemonicSuggestionBar.spec.tsx — render, empty-state hide, tap callback
  • splitMnemonic.spec.ts — 5 separator-mix variants
  • Smoke-test on device: pick ASB file → enter recovery key → select accounts → verify no back-nav loop; confirm screen capture blocked on all 3 hardened screens

Base automatically changed from yasince/rekey to main May 15, 2026 20:33
yasin-ce added 4 commits May 15, 2026 23:36
Replaces the small `PWRoundIcon` chip with a 4×xxl direct-SVG render of
shield-check, matching the hero-illustration scale used by every other
onboarding info screen (WatchInfoScreen, ImportInfoScreen, the backup
family). Tints via `theme.colors.textMain` since shield-check.svg uses
`currentColor`, so it adapts to light/dark mode automatically.

Adds a `vi.mock` for the SVG in the integration setup — jsdom chokes on
the long data URL `svgr` emits otherwise.
`BackupVerificationScreen` shows the quiz tiles whose `options` contain
the real mnemonic words (mixed with decoys). Even though the user
hasn't picked the correct order yet, an attacker who captures the
screen learns the candidate set. Aligns with the screen-capture
prevention already applied to `BackupReminderMnemonicScreen` and
`ViewPassphraseContent`.
Pulls the mnemonic-input mechanic out of ImportAccountScreen (where it
was an ~180-line slab of paste-distribution, clipboard fallback, and
suggestion derivation) into a reusable hook + util + component, then
adopts them across both import flows:

  - `useMnemonicWordEntry` — focus management, multi-separator paste
    distribution, iOS-autocomplete-safe clipboard fallback,
    wordlist-driven suggestions. Wordlist- and copy-agnostic; callers
    pass `onTooManyWords` / `onInsufficientSlots` so screen-specific
    i18n stays out.
  - `splitMnemonic` — accepts any mix of whitespace + commas.
  - `MnemonicSuggestionBar` — sticky horizontal pill bar that renders
    nothing when there are no suggestions, replaces the old
    per-row `WordSuggestionDropdown` (deleted).

Same diff also lands four orthogonal hardenings that touch the same
screens, so they're bundled here rather than fragmented:

  - Screen-capture prevention on AsbImportKeyScreen and
    ImportAccountScreen via `usePreventScreenCapture`.
  - Success-path uses `navigation.replace` so the typed mnemonic in the
    input hook's state is dropped for GC when the screen unmounts;
    back-nav from later steps no longer lands on a prefilled screen.
  - AsbImportKey redirects to the file-pick step when `envelope` is
    null (defensive; covers the rare flow where the store gets wiped
    while the screen is mounted).
  - Replaces the `HEADER_HEIGHT = 50` magic constant with
    `useHeaderHeight()` from `@react-navigation/elements` for the
    `KeyboardAvoidingView.keyboardVerticalOffset` on both screens.

Includes vitest mocks for `@react-navigation/elements.useHeaderHeight`
(throws under the headless test navigator) and unit/component/integration
test coverage for the new primitives.
After a successful import, SelectAccounts cleanup runs `reset()` on the
flow store to zero decrypted private keys. If the user then navigates
back into the backup screen (Android system back from Result, etc.),
the local "Pasted backup" card kept rendering because `loadedFile` is
React state separate from the store. Tapping Next jumped to a Key
screen with no envelope, which bounced back here — a silent loop.

Sync `loadedFile` to `envelope`: when the store is wiped externally,
clear the local indicator so Continue properly disables and the user
has to re-pick / re-paste.

Adds a regression integration test that drives the wipe through the
same `useAsbImportFlowStore.reset()` SelectAccounts cleanup performs
(no need to simulate the full stack-nav cascade through Result).
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