Repository navigation
fix: prepare the wallet switch once on send review - #1098
Conversation
SwiftUI can start the switch task twice for the same context when the confirmation reappears, which ran the on-chain preparation twice. The view now owns the run and keeps a live one for an unchanged context. The tags test now holds the resumed preparation until the pop back from tags has settled, so its push never overlaps that transition.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryThe review page now owns the wallet-switch task and keeps one live run for an unchanged context.
Acknowledgment: Reviews (1) · Last reviewed commit: "test: check the resumed wallet switch pr..." · Reviewed by Greptile |
talosmachina
left a comment
There was a problem hiding this comment.
No findings. Moves the send review's wallet-switch preparation from .task(id:) to a view-owned Task that is kept while its context is unchanged and cancelled when the context changes or the view disappears, and holds the resumed preparation in the tags test until the review is back on screen. Reviewed 9719d7c, full tier, from the code: iOS does not build here, so nothing was run.
What I checked, and 4 candidates I ruled out
Read in full: the lifecycle modifiers and startWalletSwitch in SendConfirmationView.swift, confirmationContext / sendContext / isEditingSendDetails, SendConfirmationLifecycleTests.swift (the tags test, the custom-fee tests and ControlledSendWallet)
CI: SwiftFormat, validate and Greptile green; Run Tests, Run Integration Tests and build-local still pending at review time
Ruled out
- No restart after returning from tags:
onChange(initial:)does not fire again when the view reappears, but while.tagis on topconfirmationContextis nil, so the watched value is nil, and the pop back changes it to the context. That fires the handler and starts the run, which is what the updated tags test checks. - A disappear and reappear with an unchanged non-nil value, which leaves the switch cancelled and never restarted: every route off the review (a pushed fee or tag screen, a sheet dismissal or replacement) changes
navigationPath.lastor the sheet'spresentationID, so the watched value changes in both directions. - A stale run blocks a new one: a finished run sets
walletSwitchContext = nil, which sends nil to the handler and clears the run. A run that ends without clearing it is removed by thedefer, which checksrunID, so a cancelled older task cannot clear a newer run. - Mock count drift: the second
suspend()returns beforefinished/wasCancelled, so the first assertions onwasCancelledandpreparationCount == 1keep their meaning.suspendsResumedPreparationis set only in the tags test.
Merge confidence: 4/5, no findings, but Run Tests is still pending, and the iOS 18 flake fix is proved only by those CI runs.
Reviewer model: claude-opus-5-5
Reasoning effort: Unknown (the run does not record it)
There was a problem hiding this comment.
Verdict: ✅ Approve
Findings: None.
QA for 9719d7c:
Test 1: send confirmation regression suites ✅ The lifecycle, confirmation and swipe suites passed on iOS 26.5, checked by XCTest assertions.
I built this head and ran SendConfirmationLifecycleTests, SendConfirmationViewTests and SendConfirmationSwipeTests on an iPhone 17 Pro simulator. The assertions cover a single preparation after custom-fee navigation, cancellation and resumption after tags, stale results after send teardown or replacement, and payment gating while preparation is pending.
T1-2x.2x.mp4
Automated test execution at 2× speed; the pass result comes from the XCTest assertions and result bundle.
Check 1: iOS 18 CI coverage ✅ The current-head unit-test job passed.
The Run Tests job passed at 9719d7c using the repository's Xcode 16.4 / iPhone 16 configuration. This is CI evidence, not a separate local device run.
Check 2: full code review ✅ I found no actionable defects in either changed file or the affected callers.
I checked task ownership and deduplication, cancellation, stale completions, sheet replacement, fee/tag navigation, manual coin selection and automatic-payment handoff. SwiftFormat lint and git diff --check passed.
No manual device journey was run: the QA notes list none and declare no user-visible behavior change. Actual payment submission was not driven; the automated suites exercise preparation and confirmation guards.
Coverage: full 35655ae..9719d7c, both changed files and their affected callers.
Android twin: none declared; this fixes SwiftUI lifecycle execution and iOS test navigation, so a matching Android change is not applicable.
Reviewer model: gpt-6.1-sol
Reasoning effort: high
Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest
jvsena42
left a comment
There was a problem hiding this comment.
Code scan: no findings.
Checked and clean, against what the review screen shows and what gets paid:
- Funding source changed: a new context starts, the old run is cancelled, and its writes are refused by
isCurrentSend. - Amount changed: the context is cleared and the run cancelled. The pay path reads
app.selectedWalletToPayFromat pay time, and the swipe stays blocked untilwalletSwitchContextis nil, so a stale preparation can't be paid. - Fee changed: the run restarts when the fee screen pops.
- Background and foreground: identical to master (no
onDisappearwithout PIN, full teardown with PIN). Re-entry gets a newpresentationIDand fresh@State. - Android's job-owned
refreshOnchainSendIfNeededalready cancels and dedupes, so there is no twin to port.
Performance-relevant (removes a duplicate preparation, visible on iOS 26+). The gate counts Selecting UTXOs with algorithm lines after a fee round trip (expected 1), head vs master, on an iOS 26 simulator, while driving coin-selection/manual-wallet-switch.xml and send/funding-source-dropdown.xml.
Device gate (partial): 9719d7c — not run yet; pending those journeys and that count. Device results will follow in a separate review.
|
@ovitrif I am resuming active repair of the failing checks on our payment-latency stack. #1054 at I will preserve your #1098 implementation and avoid a competing fix. Its view hunk applies to our stack, while the test hunk overlaps our earlier resumed-preparation synchronization. Please flag any active amendments or intended integration constraints. Once this is approved and merged, I can reconcile the overlap while preserving one task per current context, cancellation/stale-result guards, and our retained-presentation/app-lock cleanup, then validate the combined current heads. No merge is requested without the required human approvals, and I will not treat #1098 as a fix for the separate launch failure. |
|
@ben-kaufman no amendments are planned:
Your retained-presentation and app-lock guards and the single-coin-preparation assertions don't conflict with either. |
ben-kaufman
left a comment
There was a problem hiding this comment.
No material findings at 9719d7ce.
Reviewer model: gpt-6-astra
Reasoning effort: high
Lugano: needed, unblocks the required Run Tests check on approved PRs
Closes #1096
Description
.task(id:)twice for the same id, without cancelling the first run. The view now owns the switch run, keeps a live run for an unchanged context, and cancels it when the context changes or the view disappears.testPendingWalletSwitchResumesAfterTagsfailure in CI. The mock suspended only the first preparation, so the resumed one finished at once and pushed.utxoSelectionwhile the pop back from.tagwas still animating. Each failure caught two or three routes visible at once. The mock now also holds the resumed preparation, so the test waits for the review to appear before letting it continue.Neither failure comes from #799:
e7a21e8efails the class the same way as master35655ae8. It is one test,testCustomFeeWalletSwitchSurvivesFeeScreenNavigation, with three failed assertions.testPendingWalletSwitchResumesAfterTagshas failed CI since at least 15 Oct, for example https://github.com/synonymdev/bitkit-ios/actions/runs/38754121573 on master. It failed in about 17 of the 243 unit-test runs since then.Out of Scope
BitkitTests: other intermittent unit-test failures seen in CI (testAutomaticBackupDoesNotRepeatUnrelatedFailures, the UI test host's background assertion timeout).Design
N/A — no UI changes.
Preview
N/A — no user-visible changes.
QA Notes
Setup
N/A — no device testing required; the change is covered by unit tests.
Journeys
N/A — no user-visible behaviour change.
Manual Tests
N/A
Automated Checks
SendConfirmationLifecycleTests.swift— the resumed wallet switch after tags waits for the review before it continuesEvidence
SendConfirmationLifecycleTestspassed 14 runs in a row (the last 3 at9719d7ce, 11 atdb3e0d83). Before the change it failed every run, on both master and the parent of feat: add allowances for automatic paykit payments #799.SendConfirmationViewTestsandSendConfirmationSwipeTestspass.Gaps
Models used
claude-opus-5-5(reasoning:medium)claude-opus-5-5(reasoning:medium)claude-sonnet-5-5(reasoning:high)