Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jvsena42
left a comment
There was a problem hiding this comment.
Approved — no HIGH/MEDIUM findings at head 097ab79a9. (Posted as a comment because GitHub does not allow approving your own PR.)
Inline: LOW — spread that completes the phrase with an invalid word leaves the pasted field focused but focusedIndex null
Checked: Traced MnemonicInputField.onValueChange at the PR head: non-whitespace input updates the local TextFieldValue and the view model together; whitespace input is forwarded only when isPastedInput sees an insertion longer than one character after subtracting a replaced selection, so a typed space is dropped and the controlled field reverts. In the view model, handlePastedWords, replaceAllWords, spreadWords and updateWordValidity all run under wordEditMutex and build the new word list inside _uiState.update, so an older edit cannot resurrect pre-paste words (the two greptile P1 threads are fixed in 4af0ab9 and the four detekt line-length threads are cleared by 097ab79; no line in the changed files exceeds 120 chars). spreadWords writes pastedWords.take(24 - startIndex) at startIndex..lastWrittenIndex, switches to 24 words only when lastWrittenIndex >= 12, and the 12-word layout never holds words at 12..23 because the only 24->12 transition (replaceAllWords with 12 words) clears them, so bip39Mnemonic (subList(0, wordCount)) and hasNoWordsExcept stay consistent. The 12->24 switch re-creates focusRequesters keyed on wordCount in the same state emission as focusedIndex, nextEmptyIndex is always < the new wordCount, and column-two nodes that change index positionally are re-synced by LaunchedEffect(value) because their value key changes. The 12-word full-replace rules (field 1 always; a later field only on an otherwise-empty 12-word form) and the no-way-back from 24 words are explicitly documented in the PR body and not re-raised. Passphrase handling is untouched; RestoreWalletViewModel has no Logger calls and the restore path (WalletViewModel.restoreWallet -> WalletRepo.restoreWallet) logs only a generic error without the mnemonic. No iOS twin PR exists; the PR body lists iOS as a follow-up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jvsena42
left a comment
There was a problem hiding this comment.
Approved — no HIGH/MEDIUM findings at head 9c9d483de. (Posted as a comment because GitHub does not allow approving your own PR.)
Checked: Follow-up on 9c9d483: the reported focus case is fixed. One LOW the new commit introduced (12->24 switching paste from a column-two field) is posted as a reply on the earlier thread; restore stays gated by checksum, so it is not blocking. Follow-up on 9c9d483 (delta over 097ab79). The original LOW is fixed on the path it described: spreadWords (RestoreWalletViewModel.kt:175) falls back to the first invalid index within wordCount only when no empty field remains, so zzzz ability able into field 10 with fields 1-9 filled now yields focusedIndex=9, scrollToFieldIndex=9 and shouldDismissKeyboard=false; the field stays OS-focused, isError stays hidden on it, updateSuggestions(value, 9) resolves and onSelectSuggestion writes to index 9. shouldDismissKeyboard (line 181) is still true exactly when no empty field and no invalid index remain, so the valid-paste path is unchanged: focusedIndex=null, focus cleared, keyboard hidden, Restore reachable. replaceAllWords (line 151) now focuses invalidIndices.minOrNull(), which is < pastedWords.size <= the new wordCount, so focusRequesters[index] is in range after the 12/24 switch; a paste with all valid words still gives null/dismiss. Typed-space splitting (isPastedInput, RestoreWalletScreen.kt:438) and the wordEditMutex ordering are untouched by the delta. The three new tests fail on the previous code (they assert 9/4 where the old code produced null, and non-empty suggestions where focusedIndex=null cleared them). No Logger calls added; no seed material logged. One narrow over-correction remains and is replied on the original thread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jvsena42
left a comment
There was a problem hiding this comment.
Approved — no HIGH/MEDIUM findings at head 66f701936. (Posted as a comment because GitHub does not allow approving your own PR.)
Checked: Follow-up on 66f7019: keying the focus effect on wordCount fixes the 12->24 switching-paste LOW from the previous thread. Both state writers set is24Words and focusedIndex in one _uiState.update (replaceAllWords at RestoreWalletViewModel.kt:146-152, spreadWords at 177-185), and focusedIndex is always below the new wordCount, so the re-run cannot index past the focusRequesters list rebuilt at RestoreWalletScreen.kt:131. The effect runs after the recomposition that attaches the new requesters, the same ordering the focusedIndex key already relied on. The rest of the PR is unchanged since the approval at 9c9d483.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed-seed-words # Conflicts: # journeys/README.md
jvsena42
left a comment
There was a problem hiding this comment.
Approved — no blocking HIGH/MEDIUM findings at head 5bea120c7. (Posted as a comment because GitHub does not allow approving your own PR.)
Inline: LOW — Clipboard setup gives no route, so runners fall back to input text or stop
Checked: Follow-up on 89920b3 (new journey): Round 4 delta is 89920b3 only: the new journey journeys/restore-wallet/paste-seed-fragment.xml plus two README rows. Every tag it names exists at head and the route matches OnboardingNav: Terms (Check1/Check2 are TermsText blocks, Continue is an always-enabled PrimaryButton) -> Intro SkipIntro -> last slide CreateWalletScreen RestoreWallet -> WarningMultipleDevices MultipleDevices-button -> Restore with Word-0..Word-11 and RestoreButton. Traced each step through RestoreWalletViewModel at head: 3 words into index 0 spread to 0..2 and focus 3; DEL in the empty Word-3 is consumed by onPreviewKeyEvent and moves focus to 2; the second DEL edits from the cursor end set by LaunchedEffect(value), giving abando; 9 words into index 3 end at 11 with no empty or invalid field, so focus clears, the keyboard hides and abandon x11 about passes the checksum, enabling Restore. With the fix reverted, step 8 fails (master's handlePastedWords ignores a 3-word paste, so Word-0 keeps the whole text and Word-1 stays empty), so the journey cannot pass on a regression. The phrase is the public BIP39 abandon x11 about vector, labelled public test words, and the journey never taps Restore, so nothing gets funded. No iOS twin exists, and the cross-platform row records it as not ported because the behaviour does not exist on iOS yet, which matches the SKIP rule.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jvsena42
left a comment
There was a problem hiding this comment.
Approved — no blocking HIGH/MEDIUM findings at head 4403315af. (Posted as a comment because GitHub does not allow approving your own PR.)
Checked: Follow-up: the earlier journey finding is fixed. Follow-up on 4403315: the earlier clipboard-route LOW is fixed. Checked the host route on the running emulator (Pixel_9, API 36 image, emulator 36.6.11, embedded in Android Studio): after printf '<nine-word vector>' | pbcopy the guest clipboard did not change while another app was frontmost, and brought to the front the emulator's host window, dumpsys clipboard showed a new 69-character text/plain primary clip within a second, matching the string length. So the step's instruction to focus the emulator window after copying is necessary and enough; the host clipboard was restored afterwards and the app was not touched. Neither long string goes through input text; the only typed input is the single character n. The added verify steps name the truncation and empty-paste symptoms and send them to a clipboard setup failure, so a failed copy cannot read as an app failure, and the copy-before-launch ordering matches the flow. The app-side tags and paste handling are unchanged by this commit.
|
@jvsena42 e2e red and conflicts |
|
Same as the others: covered centrally in #1275 (comment). The conflict is the shared I will confirm here once the merge lands and CI re-runs on the new head. |
…ed-seed-words # Conflicts: # journeys/README.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Test 6 is a real bug and it is fixed in d3ade08. What happened. The paste did not land in an empty Word 13 — it landed in Word 12, which already Fix. The field now forwards only the text the paste actually inserted, not the word it was dropped internal fun insertedText(previous: TextFieldValue, new: TextFieldValue): String {
val prefixLength = previous.selection.min
val suffixLength = previous.text.length - previous.selection.max
...
}So pasting 12 words onto a filled Word 12 now writes Proof. Four cases added to
|
# Conflicts: # journeys/README.md
addressed - reaudit confirmed
There was a problem hiding this comment.
Verdict: ✅ Approve
Reaudit: diff 1 file.
Retest suggested: item 6 (The base sync does not touch restore behavior; item 6 remains the only unproven QA path from the prior fix).
Findings:
N/A
Audit:
Already done in comment.
QA: emu-1 redroid emulator on Android 15, dev build 188
-
✅ passed: Pasting the final 12 words into Word 13 kept Words 1-12 unchanged and filled Words 13-24 without duplicating Word 13.
Tip
Test 6 worth a journey:
- Accept the terms and skip the intro
- Open Restore wallet and acknowledge the multiple-device warning
- Fill the first twelve words on a fresh twenty-four-word form
- Preload the final twelve public test words
- Return to Bitkit and tap Word 13
- Paste the final twelve words
- Verify Words 1-12 remain unchanged and Words 13-24 fill
Coverage:
QA: 12 of 12 manual tests passed
Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
# Conflicts: # journeys/README.md


Fixes #896
Related to #1275
This PR spreads a pasted partial recovery phrase across the Restore wallet word fields and keeps every field editable with Backspace.
Description
aban donno longer writesdonover the next field; only an insertion longer than one character is spread.Out of Scope
RestoreWalletViewModel: once a paste switches the form to 24 words, only a 12-word paste into field 1 switches it back.journeys/restore-wallet: the new journey is committed but its onboarding route has not been run on a device without a wallet.Design
There is no paste-specific design.
Preview
Screens use public BIP39 test words. The C3 and C9 buttons at the top right come from a temporary dev harness that copies test words to the clipboard and are not part of the app.
QA Notes
Manual Tests
abandon abandon abandoninto Word 1: Words 1-3 showabandonand Word 4 is focused.abando.regression:Restore wallet → paste a full 12-word phrase into Word 1: phrase restores as before.regression:Restore wallet → paste a full 24-word phrase into Word 1: form switches to 24 words and all fields fill.zzzz ability ableinto Word 10: Words 10-12 fill, Word 10 shows the error state and keeps focus with the keyboard up, and typing in it still shows suggestions that can be tapped.Steps 1-3 were run on an emulator through a temporary dev harness that opens the Restore wallet screen without wiping the wallet. The journey is committed at
journeys/restore-wallet/paste-seed-fragment.xmland follows the same steps from onboarding. It now fills the clipboard from the host (pbcopy/xclip, picked up over emulator clipboard sharing) instead of leaving "copy from another app" undefined, sincecmd clipboardis unimplemented on the emulator image andinput textdrops characters; each paste is followed by a check that reports a short paste as a clipboard setup failure. Its onboarding route has not been run on a device without a wallet.journeys/restore-wallet/paste-seed-fragment.xml
Automated Checks
RestoreWalletViewModelTest.ktcovers fragment spread from the first and middle fields, switching to 24 words, dropping words past field 24, invalid word flags, trailing-space words, whitespace-only input, focus and keyboard handling, when a 12-word paste replaces or spreads, that a word typed during a pending paste validation survives, and that a paste leaving an invalid word focuses that field so its suggestions and suggestion taps keep working.MnemonicInputPasteTest.ktcovers telling a paste from a typed space.LaunchedEffectinRestoreWalletScreen, andcompose.ui.test.junit4isandroidTestImplementationonly, so nothing inapp/src/testcan observe it. Manual test 10 covers it.MnemonicInputFieldTest.ktcovers whitespace text not kept in the field, a typed space not reaching the view model, showing only the saved word after a paste, Backspace deleting from the end, and Backspace in a field with text not jumping to the previous field. It compiles but has not been run, since connected tests were not run for this PR.just compile,just testandjust lintpass.