Skip to content

fix: prepare the wallet switch once on send review - #1098

Merged
ovitrif merged 2 commits into
masterfrom
fix/send-confirmation-lifecycle-tests
Oct 26, 2026
Merged

ovitrif merged 2 commits into
masterfrom
fix/send-confirmation-lifecycle-tests

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Lugano: needed, unblocks the required Run Tests check on approved PRs

Closes #1096

Description

  • Fixes the wallet switch on the send review running its on-chain preparation twice. When the review reappears after the fee screens, iOS 26 and later start its .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.
  • Fixes the intermittent testPendingWalletSwitchResumesAfterTags failure in CI. The mock suspended only the first preparation, so the resumed one finished at once and pushed .utxoSelection while the pop back from .tag was 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:

  • Locally, its parent e7a21e8e fails the class the same way as master 35655ae8. It is one test, testCustomFeeWalletSwitchSurvivesFeeScreenNavigation, with three failed assertions.
  • testPendingWalletSwitchResumesAfterTags has 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

  • updated SendConfirmationLifecycleTests.swift — the resumed wallet switch after tags waits for the review before it continues

Evidence

  • On an iPhone 17 simulator with iOS 27.0, SendConfirmationLifecycleTests passed 14 runs in a row (the last 3 at 9719d7ce, 11 at db3e0d83). Before the change it failed every run, on both master and the parent of feat: add allowances for automatic paykit payments #799.
  • SendConfirmationViewTests and SendConfirmationSwipeTests pass.

Gaps

  • The CI flake runs on iOS 18 (macos-15), which isn't available locally. Repeated Run Tests runs on this PR confirm it.

Models used

  • Planning/scoping: claude-opus-5-5 (reasoning: medium)
  • Implementation: claude-opus-5-5 (reasoning: medium)
  • Review: claude-sonnet-5-5 (reasoning: high)

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.
@ovitrif ovitrif self-assigned this Oct 21, 2026
@ovitrif
ovitrif requested review from a team, ben-kaufman and pwltr and removed request for a team October 21, 2026 23:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T23:14:29.436921Z 9719d7c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

The review page now owns the wallet-switch task and keeps one live run for an unchanged context.

  • Send reviews keep one wallet preparation run for each send context.

Acknowledgment: ovitrif intentionally delays the resumed mock preparation to avoid overlapping the tag pop with the next push.

Reviews (1) · Last reviewed commit: "test: check the resumed wallet switch pr..." · Reviewed by Greptile

@talosmachina talosmachina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .tag is on top confirmationContext is 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.last or the sheet's presentationID, 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 the defer, which checks runID, so a cancelled older task cannot clear a newer run.
  • Mock count drift: the second suspend() returns before finished / wasCancelled, so the first assertions on wasCancelled and preparationCount == 1 keep their meaning. suspendsResumedPreparation is 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)

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.selectedWalletToPayFrom at pay time, and the swipe stays blocked until walletSwitchContext is 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 onDisappear without PIN, full teardown with PIN). Re-entry gets a new presentationID and fresh @State.
  • Android's job-owned refreshOnchainSendIfNeeded already 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.

@ben-kaufman

Copy link
Copy Markdown
Contributor

@ovitrif I am resuming active repair of the failing checks on our payment-latency stack. #1054 at d404f925 failed its resumed-preparation expectation (Second send preparation suspended) in run 39919145514 attempt 2; this remains distinct from the earlier extra-visible-route assertion. The same attempt separately failed app launch, which I am investigating in #1110 from the result bundle.

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.

@ovitrif

ovitrif commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

@ben-kaufman no amendments are planned: 9719d7ce is the final head, green, and it waits only on a human approval. For the merge with your stack, keep these two:

  • SendConfirmationView: one live switch run per context, started from startWalletSwitch and cancelled when the context changes or the view disappears. A second start for the same context while a run is live is skipped.
  • testPendingWalletSwitchResumesAfterTags: the resumed preparation stays suspended until the review is visible again after the pop from .tag, and only then resumes. Its push must not overlap that transition. The test also asserts preparationCount == 2 after the final wait.

Your retained-presentation and app-lock guards and the single-coin-preparation assertions don't conflict with either.

@ben-kaufman ben-kaufman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No material findings at 9719d7ce.

Reviewer model: gpt-6-astra
Reasoning effort: high

@ovitrif
ovitrif merged commit b8b7f3d into master Oct 26, 2026
20 checks passed
@ovitrif
ovitrif deleted the fix/send-confirmation-lifecycle-tests branch October 26, 2026 21:45
@ovitrif ovitrif added this to the 3.1.0 milestone Oct 26, 2026
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.

bug: send confirmation lifecycle tests fail intermittently on master

4 participants