feat(onboarding): A4 — import flow (ImportPhrase → PIN → commit)#30
Merged
Conversation
Bumps vendor/core bc70f07 -> 13e2330 (core#85: validate_mnemonic) and regenerates the uniffi bindings (ubrn build android --and-generate, arm64-v8a). Machine-generated diff only — no hand-written code — so the manual A4 commits that follow (phraseInput, store, screen) stay reviewable on their own. validateMnemonic(phrase: string): void /*throws*/ now present in generated TS; tsc clean; old jest 150/150 (regen broke nothing).
…ber) Pure input-shaping for the import screen: splitPastedPhrase mirrors the core normalization (trim/lowercase/whitespace-collapse) for OS paste; suggestWords powers the autocomplete strip (>=2-char prefix, capped, stateless — no per-prefix cache per strip lock 2/NIT-5); isWordlistMember drives the per-cell underline. None gate validity — the Rust FFI is the only judge. 16 tests, red->green proved by mutating the prefix guard and the lowercase step.
…ract mock
Adds the import path to the step machine: flow='import', beginImport,
submitImportPhrase (Rust FFI is the only validity judge — InvalidInput->'invalid',
unexpected throw->'error'), and completeImport with an honest failure taxonomy
(exists/ffi/storage — NOT the create flow's catch{return 'error'}). confirmPin
now branches by flow: create backs up (ShowPhrase/Quiz), import skips straight to
commit. Jest bridge mock gains validateMnemonic + the tagged FfiError namespace.
24 store tests (11 new) + 24 deviceWallet green; red->green proved by collapsing
the commit taxonomy and the invalid/error discrimination. tsc's navigator
exhaustiveness for 'importPhrase' is closed by T5 (screen + navigator case).
ImportPhrase: 12-field grid with anti-leak TextInput props (visible-password suppresses Gboard suggestions), OS-paste layout with a whole-reject on >12 words (no silent truncation), a local-wordlist autocomplete strip (explicit-tap only, no cache), and a membership underline hint — none of which gate validity (the Rust FFI does). ConfirmPin becomes flow-aware: create is unchanged; import commits the already-validated phrase after the PIN, with a spinner and an honest per-class error + Retry. Navigator routes the new importPhrase step. 9 ImportPhrase tests + 2 navigator import-path tests (happy + commit-fail/retry).
…rdlist Welcome's 'import' button now enters the import flow (beginImport) instead of sitting inert. Adds a corruption pin over BIP39_ENGLISH (2048 words, abandon…zoo, FNV-1a fingerprint) so a silently dropped/reordered word fails CI rather than quietly degrading the strip/underline hints. red->green proved by mutating one word.
Pins the three distinct commit-failure messages (exists/ffi/storage) by driving the real completeImport with a per-class commitWallet rejection. Lifts ConfirmPin branch coverage 76.9%->92.3%.
…ck+overflow tests
M1: test hardware-back → cancel() on ImportPhrase (raw fields never survive).
M2: pin the index+tokens overflow guard with a paste from a non-zero field
(an index-blind regress would otherwise pass green).
M3: mock validateMnemonic accepts all valid BIP-39 lengths (12/15/18/21/24) —
stop baking the UI's 12-only rule into the FFI contract (real FFI is agnostic).
M4: SPINNER_COLOR exported from theme.ts, used by ConfirmPin + ImportPhrase.
N5: importErrorMessage gets an assertNever default (repo exhaustiveness idiom).
N6: assert cancel() wipes a validated import mnemonic + resets flow.
jest 196/196; M2 red->green proved by mutating the guard to index-blind.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что и зачем (A4 · Этап 2, последний A-слайс)
Восстановление кошелька записанной 12-словной BIP-39-фразой: Welcome → ImportPhrase → CreatePin → ConfirmPin → commit → unlocked. Активирует инертную кнопку «Импортировать фразу». Закрывает go-live-блокер #21 (KPI-recovery — после потери устройства/ключа фраза снова ведёт к кошельку).
Второй из двух PR арки; core-часть (
validate_mnemonic) уже на main (rustok-org/core#85), сабмодуль бампнут на неё.Ключевое
validateMnemonic, без KDF, честный тегInvalidInput). JS-словарь — лишь подсказка (полоса автодополнения + подчёркивание), не гейт.visible-passwordглушит Gboard-подсказки); ноль чтений клипборда/сети/логов/persist.Гейты
npm run typecheck→ чистоnpm run lint→ 0 ошибок (изменённые файлы 0/0)npm test→ 196 passed / 196 (+46 новых; покрытие phraseInput+onboardingStore 100%, ImportPhrase 95.8%, ConfirmPin 85.7%)Гейт-1 и Гейт-2 (fleet 5 линз + security ЧИСТО) — оба APPROVED; МИНОРы вшиты. Mobile CI = workflow_dispatch (заглушён, #3/#16) → мерж по локальному зелёному, build-android остаётся #16.
Вне скоупа (записано): #22 (ru→en sweep существующих экранов), #19 (FLAG_SECURE для ImportPhrase), #23 (вход без биометрии).