feat(mobile): A2a — onboarding shell + PIN screens, F1 fix#27
Merged
Conversation
…generate/commit) - F1 (audit crit): deviceWallet.createWallet() committed the wallet in the same call that generated the mnemonic — abandoning onboarding before the backup quiz left a committed-but-unbacked-up, unrecoverable wallet. Split into generateMnemonic() (pure, no I/O) + commitWallet(mnemonic) (the atomic commit); regression test proves generation alone never touches storage - 12-word mnemonic, not 24 (ratified Gate-1 A2a; matches the governing spec + the flipbook's ShowPhrase mock); spy-tested against the FFI call so a silent revert to 24 cannot pass jest with the word-count-agnostic bridge mock - onboardingStore: in-memory-only step machine (welcome/keepItSafe/ createPin/confirmPin/pinConfirmed) holding the mnemonic and pending PIN; PIN persists via pinSetupStore ahead of the wallet commit (the A1c reconcile() contract already anticipates this); pendingPin clears the moment it's hashed - PinKeypad: shared dots+keypad component (CreatePin/ConfirmPin now, A3's UnlockPin later) - Welcome screen ports the flipbook 1:1 (wordmark text, not the SVG logo — react-native-svg isn't a dependency yet, flagged rather than silently added); KeepItSafe/CreatePin/ConfirmPin built from the governing spec's language (no flipbook frames for these — ratified, closes carry-over #7) - RootNavigator's no_wallet phase now renders OnboardingNavigator instead of a placeholder - integration test caught a real bug: KeepItSafe's Continue button relied solely on the Pressable disabled prop, which a direct onPress call bypasses — onPress is now itself gated on all 3 acknowledgements - 98 tests total (23 new/changed), 2 mutations verified red before green
…ore setPin-failure test - MINOR #2: PinKeypad read/wrote a stale `digits` state closure on same-tick double-taps (multi-touch), letting onComplete fire twice with different strings. First fix attempt (functional setState update) traded this for a worse bug — calling onComplete (which drives a zustand set()) from inside a state updater fires during React's render phase ("Cannot update a component while rendering a different component"). Final fix: a digitsRef mirror read/written synchronously in the press() event handler (not during render), with onComplete called from press() itself. New double-tap test catches both the original bug and would catch the render-phase regression (backspace test also went red on the interim mutation, confirming ref/state sync matters) - MINOR #1: onboardingStore.confirmPin's security-relevant ordering (persist PIN before advancing step) had no regression test; a future reorder would go green silently. New test mocks pinAuth.setPin to reject and asserts the step never advances and nothing persists — mutation (reordering set() before the await) turns it red - NIT #3: stale createWallet.inFlightCreate reference in a pinAuth.ts docstring, now commitWallet.inFlightCommit - NIT #4: dropped a cannot-fail 'starts at welcome' test (asserted only the beforeEach-forced initial state) - all mutations verified red before green; 99/99 green, 0 lint errors
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.
Что это
Слайс A2a Этапа 2: онбординг-каркас (Welcome→KeepItSafe→CreatePin→ConfirmPin) + критический
security-фикс из аудита.
deviceWallet.createWallet()коммитил кошелёк на диск в тот же вызов, чтогенерировал мнемонику — до бэкапа и квиза. Расцеплено на
generateMnemonic()(чистая, без I/O)commitWallet(mnemonic)(атомарный коммит после подтверждённого бэкапа, в A2b). Regression-тествоспроизводит прежний баг.
onboardingStore— in-memory степ-машина; PIN коммитится до кошелька (уже предусмотреноA1c-контрактом
reconcile()), мнемоника никогда не персистится.PinKeypad— переиспользуемый компонент (пригодится в A3 для UnlockPin).смыслу управляющей спеки (ратифицировано, closes carry-over build(deps-dev): bump react-test-renderer and @types/react-test-renderer #7).
RootNavigator'sno_walletтеперь роутит наOnboardingNavigator.Проверено
match-check, setPin-failure ordering — все красные до фикса
пройден на реальном устройстве без крашей
pendingPinnull) и Гейт-2 (fleet,4 находки: 2 минора зафиксены с red→green, 2 нита) — оба APPROVED
Побочные находки, зафиксены по ходу
Продолжитьреагировал при неполном чек-листе (disabled-проп не блокирует прямойвызов
onPress) — теперьonPressсам гейтится.PinKeypad: same-tick double-tap (мультитач) мог дважды вызватьonCompleteс разными строками(stale state closure). Первая попытка фикса (functional setState) исправила это, но завела новый
баг — вызов
onComplete(→zustand.set()) изнутри state-updater'а триггерил React-предупреждение«update во время рендера другого компонента». Финальный фикс — синхронный
ref-зеркало, обновляемоев обработчике нажатия,
onCompleteзовётся из него же, не из редьюсера состояния.Спека:
core/.claude/specs/2026-07-03-stage2-a2a-onboarding-shell-pin.md· Отчёт:core/.claude/reports/2026-07-03-stage2-a2a-onboarding-shell-pin.md