Skip to content

fix: reset the withdraw flow when a send method click enters it - #2627

Merged
jjramirezn merged 5 commits into
mainfrom
hotfix/send-flow-stale-method
Aug 6, 2026
Merged

fix: reset the withdraw flow when a send method click enters it#2627
jjramirezn merged 5 commits into
mainfrom
hotfix/send-flow-stale-method

Conversation

@abalinda

@abalinda abalinda commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Browser/device back from /withdraw?method=crypto never runs the in-app NavHeader reset, so selectedMethod survives in the app-wide WithdrawFlowContext. The next Bank click on /send then skips method selection, lands on the crypto amount step, and Continue routes to /withdraw/crypto?method=bank — the crypto flow wearing a bank marker. The mirror direction (stale bank method hijacking an Exchange or Wallet click) breaks the same way.

Fix: a fresh method click is a fresh statement of intent. SendRouterView.handleMethodClick is the only click-site that mints the ?method= marker, so call the pre-existing resetWithdrawFlow() there before navigating, in all four withdraw-bound cases (bank, exchange-or-wallet, mercadopago, pix). Plus one completeness fix the adversarial pass surfaced: resetWithdrawFlow() now also closes showCompatibilityModal, which every reset call site (home mount, crypto-page unmount, these clicks) was leaving armed.

withdraw/page.tsx is untouched: inside the withdraw flow, selectedMethod stays the routing source of truth (the 7edcf0919 invariant and its pinned test survive unmodified) — we only guarantee the context is fresh at entry.

Task

TASK-21203 — https://app.notion.com/p/3b483811757981418f7ff0efe4d33221

Verification (3-agent adversarial panel, all lenses traced to code)

  • Refute-the-fix: HOLDS on all axes. The click-handler reset flushes in the sync lane before the startTransition-wrapped route render, the provider never remounts, and the step-sync effect self-heals as a third safety net. Mirror direction traced clean.
  • Regression hunt: no real regressions. The "preserved typed amount" a reset might wipe never existed (rawTokenAmount is component-local; context amount is only written on Continue). The manteca page reads zero context fields and self-resets on mount. /home has always reset the flow on mount, so "state survives leaving the flow" was never a supported invariant, and every re-entry path guards against cleared context.
  • Test attack (empirical mutation pass): revert, reorder-past-push, and over-eager-reset mutants all caught; the initially-untested manteca one-liners got their own test after the pass flagged them.

Design notes / accepted trade-offs

  • Reset at the click site, not self-healing inside /withdraw: the within-flow back-navigations that echo ?method= (AddWithdrawCountriesList) want state preserved and are correctly untouched. Browser-forward cannot recreate a mismatch (a fresh click truncates forward history).
  • Known residual, same bug family, lower severity (follow-up, not this PR): marker-less /withdraw entries (home tile, bottom nav, KYC modal, exchange-rate widget) still resume a stale selectedMethod at the amount step. That path is internally consistent (no marker/context mismatch — Continue routes where the UI says) and matches the codebase's blessed resume semantics.
  • Known larger refactor (follow-up): the ?method= URL marker and WithdrawFlowContext remain two sources of truth — this is the third bug from that split.

Risks / breaking changes

  • FE-only, no API changes, no cross-repo deploy order.
  • Blast radius: the four send-method click paths + the one-line reset completeness fix (strictly closes a modal that should never be open at reset time).
  • Hotfix to mainback-merge debt main→dev after merge. No conflict expected: handleMethodClick is byte-identical on main and dev, and open PR fix(send): keep the send framing past the amount step #2625 does not touch SendRouter.view.tsx.

QA

  1. /sendExchange or Walletbrowser back (swipe/hardware/browser — not the in-app ) → Bank → must land on bank method selection, not the crypto amount step.
  2. Reverse: /sendBank → pick a saved account → browser backExchange or Wallet → must land on the crypto amount step cleanly.
  3. In-app back paths unchanged. Plain /withdraw (no marker) unchanged.
  4. Note: do not QA by asserting /withdraw/crypto?method=bank can never occur — it is legitimately mintable (send → bank → "show all methods" → Crypto card → Continue).

Screenshots: ⚠️ NONE — the change is which pre-existing screen appears after a click given abandoned state; both screens are unchanged pixel-wise. Manual repro steps above are the visual evidence.

Summary by CodeRabbit

  • Bug Fixes
    • Withdrawal flow state is now reset before starting bank, crypto, Mercado Pago, or Pix transfers.
    • Resetting the withdrawal flow now also closes any open compatibility modal.
    • Contact navigation remains unaffected.

Browser back from /withdraw?method=crypto never runs the in-app NavHeader
reset, so selectedMethod survives in the app-wide WithdrawFlowContext. The
next Bank click then skips method selection, lands on the crypto amount
step, and Continue routes to /withdraw/crypto?method=bank — the crypto flow
wearing a bank marker. The mirror direction (stale bank method hijacking an
Exchange or Wallet click) breaks the same way.

A fresh method click is a fresh statement of intent, and SendRouterView's
handleMethodClick is the only place the ?method= marker is minted — so
clear abandoned flow state there, with the existing resetWithdrawFlow(),
before navigating. Inside /withdraw, selectedMethod stays the routing
source of truth (7edcf09's invariant is untouched).
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 6, 2026 2:09pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2a8128a9-5ba0-4e8b-8306-bf6dbffa5589

📥 Commits

Reviewing files that changed from the base of the PR and between 1aee6e7 and 8605967.

📒 Files selected for processing (1)
  • src/context/__tests__/WithdrawFlowContext.test.tsx
📝 Walkthrough

Walkthrough

Send method selection now resets withdrawal flow state before navigating to bank, crypto, Mercado Pago, or Pix methods. The reset also closes the compatibility modal. Contacts navigation does not reset withdrawal state. Tests verify the behavior and call ordering.

Changes

Send withdrawal flow reset

Layer / File(s) Summary
Withdrawal reset integration
src/components/Send/views/SendRouter.view.tsx, src/context/WithdrawFlowContext.tsx
SendRouterView calls resetWithdrawFlow before withdrawal-method navigation. resetWithdrawFlow clears the compatibility modal state.
Reset behavior validation
src/components/Send/__tests__/send-states.test.tsx
Tests verify reset calls, reset-before-navigation ordering, method-specific routes, and unchanged contacts behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels: enhancement

Suggested reviewers: kushagrasarathe

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: resetting the withdraw flow when a send method starts it.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/send-flow-stale-method

Comment @coderabbitai help to get the list of available commands.

@abalinda

abalinda commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6345.36 → 6346 (+0.64)
Findings: +1 net (+12 new, -11 resolved)

🆕 New findings (12)

  • high complexity — src/components/Send/views/SendRouter.view.tsx — CC 48, MI 60.08, SLOC 174
  • medium high-mdd — src/components/Send/views/SendRouter.view.tsx:40 — SendRouterView: MDD 64.1 (uses across many lines from declarations)
  • medium high-mdd — src/context/WithdrawFlowContext.tsx:77 — WithdrawFlowContextProvider: MDD 63.0 (uses across many lines from declarations)
  • medium complexity — src/context/WithdrawFlowContext.tsx — CC 6, MI 51.36, SLOC 111
  • low high-dlt — src/components/Send/views/SendRouter.view.tsx:40 — SendRouterView: DLT 24 (calls 24 distinct functions — high context load)
  • low high-mdd — src/components/Send/views/SendRouter.view.tsx:276 — : MDD 18.3 (uses across many lines from declarations)
  • low structural-dup — components/Send/views/Contacts.view.tsx:57 — 17 duplicate lines / 60 tokens with components/Send/views/SendRouter.view.tsx:102
  • low high-mdd — src/components/Send/views/SendRouter.view.tsx:161 — : MDD 17.3 (uses across many lines from declarations)
  • low high-dlt — src/context/WithdrawFlowContext.tsx:77 — WithdrawFlowContextProvider: DLT 17 (calls 17 distinct functions — high context load)
  • low high-mdd — src/components/Send/views/SendRouter.view.tsx:204 — : MDD 13.0 (uses across many lines from declarations)
  • low high-mdd — src/components/Send/views/SendRouter.view.tsx:124 — handleMethodClick: MDD 11.3 (uses across many lines from declarations)
  • low missing-return-type — src/components/Send/views/SendRouter.view.tsx:40 — SendRouterView: exported fn missing return type annotation

✅ Resolved (11)

  • src/components/Send/views/SendRouter.view.tsx — CC 48, MI 60.54, SLOC 167
  • src/components/Send/views/SendRouter.view.tsx:39 — SendRouterView: MDD 61.8 (uses across many lines from declarations)
  • src/context/WithdrawFlowContext.tsx:77 — WithdrawFlowContextProvider: MDD 61.5 (uses across many lines from declarations)
  • src/context/WithdrawFlowContext.tsx — CC 6, MI 51.47, SLOC 110
  • src/components/Send/views/SendRouter.view.tsx:39 — SendRouterView: DLT 22 (calls 22 distinct functions — high context load)
  • src/components/Send/views/SendRouter.view.tsx:267 — : MDD 18.3 (uses across many lines from declarations)
  • components/Send/views/Contacts.view.tsx:57 — 17 duplicate lines / 60 tokens with components/Send/views/SendRouter.view.tsx:100
  • src/components/Send/views/SendRouter.view.tsx:152 — : MDD 17.3 (uses across many lines from declarations)
  • src/context/WithdrawFlowContext.tsx:77 — WithdrawFlowContextProvider: DLT 16 (calls 16 distinct functions — high context load)
  • src/components/Send/views/SendRouter.view.tsx:195 — : MDD 13.0 (uses across many lines from declarations)
  • src/components/Send/views/SendRouter.view.tsx:39 — SendRouterView: exported fn missing return type annotation

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2358 ran, 0 failed, 0 skipped, 40.9s

📊 Coverage (unit)

metric %
statements 63.1%
branches 46.9%
functions 52.9%
lines 63.6%
⏱ 10 slowest test cases
time test
3.8s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.2s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/utils/__tests__/demo-balance.test.ts › resetDemoBalance refills and restarts the TTL window
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.2s src/utils/__tests__/demo-balance.test.ts › auto-refills a wallet older than the TTL on cold start
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
src/components/Send/__tests__/send-states.test.tsx (1)

325-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover every withdrawal method and verify call order.

This block checks reset-before-navigation ordering only for bank. The crypto test checks both calls but not their order, and no test covers mercadopago or pix, which have separate branches in src/components/Send/views/SendRouter.view.tsx. Add parameterized cases for all four withdrawal methods with the expected route and the same invocationCallOrder assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Send/__tests__/send-states.test.tsx` around lines 325 - 355,
Expand the withdrawal navigation tests around the existing bank and
exchange-or-wallet cases into parameterized coverage for bank, crypto,
mercadopago, and pix, using each action card’s expected route. For every case,
assert resetWithdrawFlow is called once, router push receives the expected
route, and resetWithdrawFlow occurs before navigation; preserve the Peanut
contacts test without reset behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/Send/__tests__/send-states.test.tsx`:
- Around line 325-355: Expand the withdrawal navigation tests around the
existing bank and exchange-or-wallet cases into parameterized coverage for bank,
crypto, mercadopago, and pix, using each action card’s expected route. For every
case, assert resetWithdrawFlow is called once, router push receives the expected
route, and resetWithdrawFlow occurs before navigation; preserve the Peanut
contacts test without reset behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e50a357-c304-4e4d-970f-84d6e06033a7

📥 Commits

Reviewing files that changed from the base of the PR and between fb4cd2c and 5e14652.

📒 Files selected for processing (2)
  • src/components/Send/__tests__/send-states.test.tsx
  • src/components/Send/views/SendRouter.view.tsx

Adversarial mutation pass showed a partial revert of the mercadopago/pix
reset calls shipped undetected (the harness only rendered bank + exchange
cards), and the exchange test survived a reset-after-push reorder. Close
both holes.
Adversarial pass found the reset misses showCompatibilityModal: browser-back
with the modal open leaves it armed, so the next /withdraw/crypto entry pops
it immediately over a null withdrawData. Pre-existing gap in every reset call
site (home mount, crypto unmount); this PR leans on the function harder, so
make it actually reset the flow.
Closes the CodeRabbit nitpick: every withdraw-bound method now pins both the
reset call and its ordering against navigation.
@abalinda

abalinda commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

The single nitpick (method coverage + call order) is addressed: 7e73e9f added the Mercado Pago / Pix reset test + exchange ordering, 1aee6e7 added ordering assertions to both manteca clicks. Every withdraw-bound method now pins reset + route + reset-before-push ordering; the contacts test pins no-reset. Also since your review: 8e5347a makes resetWithdrawFlow close showCompatibilityModal (adversarial-pass finding).

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@abalinda I will perform a full review of the current pull request state. I will verify the new test coverage, reset-before-navigation order, and showCompatibilityModal reset behavior.

⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 11 minutes.

@abalinda

abalinda commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai do the review now bro, 11 minutes passed

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@abalinda I will perform a full review of the current pull request state.

🐇🔍

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
src/components/Send/__tests__/send-states.test.tsx (1)

172-176: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add provider-level coverage for showCompatibilityModal closure in resetWithdrawFlow.

The mock at line 172 replaces useWithdrawFlow, so the route tests verify only mock calls and order. They cannot detect a regression in resetWithdrawFlow internals, including the setShowCompatibilityModal(false) call on line 106 of WithdrawFlowContext.tsx. The existing AddWithdrawRouterView.test.tsx uses the real context provider but does not test the reset logic. Add a provider-level test that opens the modal (by simulating the charge-setup flow that calls setShowCompatibilityModal(true) at line 258 of crypto/page.tsx), invokes resetWithdrawFlow, and asserts that the modal closes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Send/__tests__/send-states.test.tsx` around lines 172 - 176,
Add a provider-level test using the real WithdrawFlowContext provider rather
than the mocked useWithdrawFlow in send-states.test.tsx. Simulate the
charge-setup flow that triggers setShowCompatibilityModal(true), invoke
resetWithdrawFlow, and assert that the compatibility modal closes, covering the
reset logic in WithdrawFlowContext and its showCompatibilityModal state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/Send/__tests__/send-states.test.tsx`:
- Around line 172-176: Add a provider-level test using the real
WithdrawFlowContext provider rather than the mocked useWithdrawFlow in
send-states.test.tsx. Simulate the charge-setup flow that triggers
setShowCompatibilityModal(true), invoke resetWithdrawFlow, and assert that the
compatibility modal closes, covering the reset logic in WithdrawFlowContext and
its showCompatibilityModal state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a97c04c-1d6f-49b0-92fd-463e2b20be97

📥 Commits

Reviewing files that changed from the base of the PR and between fb4cd2c and 1aee6e7.

📒 Files selected for processing (3)
  • src/components/Send/__tests__/send-states.test.tsx
  • src/components/Send/views/SendRouter.view.tsx
  • src/context/WithdrawFlowContext.tsx

Both send-states and withdraw-states mock the context, so nothing verified
the actual reset semantics — the CodeRabbit r2 nitpick and the adversarial
panel's accepted gap. One probe test against the real provider pins every
cleared field, including the compatibility-modal closure this PR added.
@abalinda

abalinda commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

r2 nitpick addressed in the latest commit: src/context/__tests__/WithdrawFlowContext.test.tsx mounts the real provider, arms the flow state (method, amounts, compatibility modal), invokes resetWithdrawFlow(), and asserts every field clears — including showCompatibilityModal.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@abalinda I will review the latest commit. I will verify the real-provider test and the complete reset behavior of resetWithdrawFlow().

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@abalinda
abalinda marked this pull request as ready for review August 6, 2026 14:15
@jjramirezn
jjramirezn merged commit eb0e956 into main Aug 6, 2026
26 of 28 checks passed
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.

2 participants