Skip to content

feat(restore): enter the recovery phrase in word pills with suggestions - #205

Merged
theanmolsharma merged 3 commits into
masterfrom
theanmolsharma/shroud-seed-ux-build
Oct 16, 2026
Merged

theanmolsharma merged 3 commits into
masterfrom
theanmolsharma/shroud-seed-ux-build

Conversation

@theanmolsharma

Copy link
Copy Markdown
Collaborator

Restore now takes the recovery phrase one word per pill, with wordlist suggestions above the keyboard. The backup screen also fits without scrolling on current iPhones.

Restore: word pills

  • A "12 words / 24 words" toggle sits above one numbered pill per word. The pills are shaped like the ones on the backup screen. Words beyond a smaller grid are kept, so switching 24 → 12 → 24 loses nothing.
  • A bar above the keyboard suggests matching wordlist words:
    • space, return or a picked suggestion moves to the next pill;
    • a prefix that only one word can finish is completed ("aba" → "abandon");
    • backspace in an empty pill steps back;
    • the bar steps aside by itself when another input (the creation date) takes the keyboard.
  • Words are checked as they're typed. A word on the list gets a green number. A pill turns red, with a hint in the bar, once no word can start the way it does.
  • Restore stays off until the phrase passes the wordlist and checksum checks. A note says why: the words in red, a bad checksum, "Word 6 is empty.", or "5 of 12 words entered". This replaces the error alert that used to come after the tap.
  • A paste spreads the words over the pills, whether into any pill or via Paste from clipboard, and so does a scan:
    • the grid switches to 24 when needed, and 15/18/21-word phrases fit in the 24-word grid;
    • a valid scan still restores straight away; anything else stays in the pills instead of showing an alert.
  • All 10 BIP39 languages are still accepted, and suggestions follow the phrase's language.
  • Typing fast enough that letters land before focus moves on ("aba a" in one pill) still puts the words in the right pills.

Under the hood

  • Mnemonic normalisation moves unchanged from AbstractHDWallet.setSecret into modules/bip39.ts, so the pills and the wallet split words the same way. It's now normalizeMnemonic, mnemonicWords and mnemonicWordsAsTyped.
  • New modules:
    • modules/mnemonicWords.ts does the word lookups and suggestions, with one lazily built index per wordlist.
    • helpers/recoveryPhrase.ts holds the grid as plain data, with pure functions for placing pasted words and judging the phrase.
    • hooks/useRecoveryPhraseInput.ts handles state, moving focus between pills, and whether the keyboard belongs to a pill.
    • components/RecoveryPhraseGrid.tsx has memoised pills, so a keystroke re-renders only its own pill.
    • components/WordSuggestionBar.tsx is the bar, built on KeyboardStickyView.
  • SafeAreaScrollView takes a bottomOffset, so the focused pill stays clear of the bar.
  • Removed:
    • the text box (FieldMnemonicInput) and its iOS keyboard toolbar (DoneAndDismissKeyboardInputAccessory);
    • the unused label/triggerImport route params;
    • five strings that no longer have a use: restore_mnemonic_label, restore_mnemonic_placeholder, send.input_clear, send.input_paste, wallet_birth.error_empty_mnemonic.
  • New strings: restore_word_count, restore_word_label, restore_word_unknown, restore_no_matching_word, restore_unknown_words, restore_invalid_phrase, restore_missing_word, restore_words_entered.

Backup screen title

  • "This is your recovery phrase" wrapped to two lines, which made the seed step taller than its scroll area: by 17pt on an iPhone 17/17 Pro and 32pt on a 16e/17e. The checkbox's second line was cut off and a scroll bar showed.
  • "Your recovery phrase" fits on one line. The step now measures 603pt of content in 626pt on the 17 Pro and in 611pt on the 17e.
  • Settings › View recovery phrase gets the same title, so the two screens match.
  • The iPhone SE and large text sizes still scroll.

E2E

03-wallet-import only runs when MNEMONIC_TEST is set, which CI doesn't do, so CI reports it as skipped. Its last two steps had never passed: on an emulator set up like CI's, it failed at the success sheet, and master's helper fails there too. The helper now:

  • puts the phrase into the first pill and waits for the last word to land before tapping Restore;
  • scrolls to Restore and lets the scroll settle before tapping;
  • uses .atIndex(0) for the sheet, since TrueSheet makes Espresso find each sheet view twice (as in 04-receive and 07-contacts);
  • retries short visibility waits (waitUntilVisible). With synchronization off, a wait that starts while a view is still arriving can miss it for its whole timeout.

Testing

  • Static checks: tsc is clean. Lint has no errors and adds no warnings.
  • Unit tests: 55 suites, 558 tests pass. New tests cover:
    • wordlist lookups and suggestions, including Spanish phrases and SeedQR;
    • the grid rules, including the hidden tail and every note;
    • the hook: focus moves through registered inputs, and keyboard ownership with simulated keyboard events;
    • the screen with real pills: focus movement, the grid growing to 24, the bar against the date field, the notes, and valid and invalid scans.
  • Detox 03-wallet-import, with 12 and 24-word phrases: 14/14 on an API 31 emulator set up like CI's (320×640dp, CI's options, animations off), and 4/4 on API 37.
  • iOS 26 simulator (iPhone 17 Pro), with real taps:
    • typing with suggestions, picking one, finishing a word with space, and a word that isn't on the list;
    • backspace across pills;
    • a full paste, a bad checksum, and 24 words;
    • dark and light mode.
      The backup title was measured on the 17 Pro and the 17e.
  • Android emulator, by hand:
    • the bar sits above Gboard;
    • picking, space and Gboard's own backspace;
    • Done hides the bar;
    • the bar stays off the creation date, whether it's opened directly or reached from a pill with the keyboard up.

Not verified

  • VoiceOver and TalkBack with the new labels.
  • Typing a non-English phrase on a device (covered by unit tests only).
  • The rest of the e2e suite locally (CI runs it).
  • In the simulator's dev build, the backup screen sometimes drew its header under the status bar. A device screenshot doesn't show this.

🤖 Generated with Claude Code

theanmolsharma and others added 3 commits October 15, 2026 20:05
…list suggestions

- One numbered pill per word in a 12 or 24-word grid, shaped like the backup screen's pills.
  Words past a smaller grid stay hidden, so switching to 12 words and back loses nothing.
- A bar above the keyboard suggests matching wordlist words. A space, return or a picked word
  moves to the next pill, backspace in an empty pill steps back, and the bar steps aside by
  itself when another input takes the keyboard.
- Each word is checked as it's typed. Restore stays off until the phrase passes the wordlist
  and checksum checks, with a note saying why: unknown words, a bad checksum, an empty pill, or
  how many words are in so far.
- A pasted or scanned phrase spreads over the pills, and a valid scan restores straight away.
- Mnemonic normalisation moves unchanged from AbstractHDWallet into modules/bip39, so the pills
  and the wallet split words the same way. The suggestion engine is modules/mnemonicWords and
  the grid rules are helpers/recoveryPhrase.
- Drops the old text box, its keyboard toolbar and the unused label/triggerImport params.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stops scrolling

"This is your recovery phrase" wrapped to two lines, which left the backup screen 17pt taller
than its scroll area on an iPhone 17/17 Pro (32pt on a 16e/17e): the checkbox's second line was
cut off and a scroll bar showed. "Your recovery phrase" fits on one line and both phones fit.
Settings > View recovery phrase gets the same title so the two screens match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… CI emulator

The import spec only runs when MNEMONIC_TEST is set, which CI doesn't do, so its last two steps
had never passed. Run on an API 31 emulator set up like CI's, and on API 37:

- TrueSheet makes Espresso find every view in a sheet twice, so the success sheet's Done button
  needs atIndex(0), like the sheet steps in 04-receive and 07-contacts.
- With synchronization off (the restore starts a scan that never lets the app go idle), a
  toBeVisible() wait that starts while the sheet is sliding up, or while home comes back from
  under the modal, can miss the view arriving. waitUntilVisible retries short waits instead.

18 runs in a row pass, with 12 and 24-word phrases, across both emulators.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@theanmolsharma
theanmolsharma merged commit 5d1ac5c into master Oct 16, 2026
4 checks passed
@theanmolsharma
theanmolsharma deleted the theanmolsharma/shroud-seed-ux-build branch October 16, 2026 06:10
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