Skip to content

fix(card): key card flows off findActiveCard, never cards[0] + un-trappable setup modal#2334

Merged
Hugo0 merged 4 commits into
mainfrom
fix/rain-active-card-selection
Jul 2, 2026
Merged

fix(card): key card flows off findActiveCard, never cards[0] + un-trappable setup modal#2334
Hugo0 merged 4 commits into
mainfrom
fix/rain-active-card-selection

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Companion to peanutprotocol/peanut-api-ts#1107 (2026-07-02 duplicate-card incident). The "Finish setting up your card" modal — and the grant pre-flights in useGrantSessionKey / useSpendBundle / useSignSpendBundle — read cards[0] (newest row, including CANCELED ones) while the backend stored the session-key grant on a different card. With duplicate ACTIVE cards every passkey tap returned 200, the non-dismissible modal never cleared, and the error-gated "Skip for now" never rendered: a hard app lockout with zero Sentry signal.

Fixes:

  1. All five cards[0] call sites now use the existing findActiveCard(overview) helper (cardState.utils — "the one place that decides which card entry the UI operates on"). Pure reuse, no new abstraction.
  2. Un-trappable modal: a grant that resolves ok without flipping hasWithdrawApproval (the lockout shape) now reveals the "Skip for now" escape anyway, fires a console.warn, and pages Sentry via captureMessage(level: error).

Risks / breaking changes

  • Behavior change is intentionally narrow: card selection now skips CANCELED rows (strictly more correct — e.g. a canceled-newest user no longer gets a pointless extra grant prompt on first spend).
  • Safe to deploy independently of the BE PR; together they close the incident end-to-end. BE first is the natural order but not required.

QA

  • EnableAutoBalanceBanner test extended: CANCELED-newest shapes (modal hidden when the active card is granted, still shown when not), stuck-after-success reveals Skip + console.warn + Sentry capture. Existing escape-hatch regressions kept.
  • Full jest suite green locally (1646 tests, 106 suites). Typecheck clean.

…ppable setup modal

2026-07-02 dup-card incident: the setup modal read cards[0] (newest) while
the backend stored the session-key grant on a different card — every tap
'succeeded', the modal never dismissed, and the error-gated Skip never
rendered. All card flows now use the existing findActiveCard helper, and a
grant that succeeds without flipping hasWithdrawApproval reveals the escape
hatch and pages Sentry (console.warn + captureMessage).
@vercel

vercel Bot commented Jul 2, 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, Comment Jul 2, 2026 7:30pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 2, 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: 4 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 084c485b-c866-4235-9830-85255ff43718

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd3fb7 and c2fa8e5.

📒 Files selected for processing (3)
  • src/components/Home/EnableAutoBalanceBanner.tsx
  • src/components/Home/__tests__/EnableAutoBalanceBanner.test.tsx
  • src/hooks/wallet/useGrantSessionKey.ts

Walkthrough

This PR replaces first-card selection (overview.cards[0]) with findActiveCard(overview) across a dev page and several wallet hooks to determine the active card for approval/grant checks. It also adds stuck-after-success detection with Sentry reporting and an escape hatch in EnableAutoBalanceBanner, with expanded tests.

Changes

Active card selection and escape hatch

Layer / File(s) Summary
Active card selection in grant and spend hooks
src/hooks/wallet/useGrantSessionKey.ts, src/hooks/wallet/useSignSpendBundle.ts, src/hooks/wallet/useSpendBundle.ts, src/app/(mobile-ui)/dev/card-session-approve/page.tsx
Replaces overview?.cards?.[0] with findActiveCard(overview) to select the card used for grant eligibility and withdraw-approval checks.
Stuck-after-success detection and escape hatch
src/components/Home/EnableAutoBalanceBanner.tsx
Adds grantSucceeded state, computes stuckAfterSuccess when a grant succeeds but hasWithdrawApproval doesn't flip, logs a console warning and Sentry error, and expands the "Skip for now" condition to include this case.
Test coverage for active-card selection and lockout escape
src/components/Home/__tests__/EnableAutoBalanceBanner.test.tsx
Adds Sentry mock, mutable mockCards, and tests for ACTIVE-card selection over CANCELED rows and the duplicate-card lockout escape flow.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • peanutprotocol/peanut-ui#2229: Both PRs modify EnableAutoBalanceBanner.tsx and its tests around the escape-hatch modal behavior for session-key grant failures.
  • peanutprotocol/peanut-ui#2234: Both PRs modify useSpendBundle/useSignSpendBundle, changing inputs used for routing/approval decisions.

Suggested labels: enhancement

Suggested reviewers: jjramirezn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: switching card flows to findActiveCard and fixing the stuck setup modal.
Description check ✅ Passed The description matches the changeset and explains the card-selection fix, modal escape hatch, and test updates.
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.

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

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5856.19 → 5827.36 (-28.83)
Findings: +3 net (+28 new, -25 resolved)

🆕 New findings (28)

  • high complexity — src/components/Home/EnableAutoBalanceBanner.tsx — CC 30, MI 61.21, SLOC 61
  • high complexity — src/hooks/wallet/useSignSpendBundle.ts — CC 13, MI 38.73, SLOC 180
  • medium high-mdd — src/hooks/wallet/useSpendBundle.ts:155 — useSpendBundle: MDD 69.6 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSpendBundle.ts:170 — : MDD 60.6 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSignSpendBundle.ts:102 — useSignSpendBundle: MDD 55.4 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSignSpendBundle.ts:110 — : MDD 51.5 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useGrantSessionKey.ts:84 — useGrantSessionKey: MDD 33.3 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/wallet/useGrantSessionKey.ts:84 — useGrantSessionKey: DLT 31 (calls 31 distinct functions — high context load)
  • medium high-dlt — src/hooks/wallet/useSpendBundle.ts:155 — useSpendBundle: DLT 30 (calls 30 distinct functions — high context load)
  • medium high-mdd — src/components/Home/EnableAutoBalanceBanner.tsx:40 — EnableAutoBalanceBanner: MDD 28.6 (uses across many lines from declarations)
  • medium structural-dup — hooks/wallet/useSignSpendBundle.ts:275 — 22 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:357
  • medium structural-dup — hooks/wallet/useSignSpendBundle.ts:198 — 21 duplicate lines / 106 tokens with hooks/wallet/useSignSpendBundle.ts:253
  • medium structural-dup — hooks/wallet/useSpendBundle.ts:241 — 21 duplicate lines / 109 tokens with hooks/wallet/useSpendBundle.ts:324
  • medium complexity — src/hooks/wallet/useGrantSessionKey.ts — CC 21, MI 52.09, SLOC 191
  • medium structural-dup — hooks/wallet/useSignSpendBundle.ts:201 — 20 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:244
  • medium method-complexity — src/components/Home/EnableAutoBalanceBanner.tsx:40 — EnableAutoBalanceBanner CC 19 SLOC 41
  • medium complexity — src/app/(mobile-ui)/dev/card-session-approve/page.tsx — CC 10, MI 63.1, SLOC 23
  • low high-dlt — src/hooks/wallet/useSignSpendBundle.ts:102 — useSignSpendBundle: DLT 22 (calls 22 distinct functions — high context load)
  • low high-dlt — src/hooks/wallet/useSpendBundle.ts:170 — : DLT 22 (calls 22 distinct functions — high context load)
  • low high-mdd — src/app/(mobile-ui)/dev/card-session-approve/page.tsx:17 — CardSessionApprovePage: MDD 19.9 (uses across many lines from declarations)

…and 8 more.

✅ Resolved (25)

  • src/hooks/wallet/useSignSpendBundle.ts — CC 13, MI 38.79, SLOC 179
  • src/hooks/wallet/useSpendBundle.ts:154 — useSpendBundle: MDD 69.6 (uses across many lines from declarations)
  • src/hooks/wallet/useSpendBundle.ts:169 — : MDD 60.6 (uses across many lines from declarations)
  • src/hooks/wallet/useSignSpendBundle.ts:101 — useSignSpendBundle: MDD 55.4 (uses across many lines from declarations)
  • src/hooks/wallet/useSignSpendBundle.ts:109 — : MDD 51.5 (uses across many lines from declarations)
  • src/hooks/wallet/useGrantSessionKey.ts:79 — useGrantSessionKey: MDD 33.1 (uses across many lines from declarations)
  • src/hooks/wallet/useGrantSessionKey.ts:79 — useGrantSessionKey: DLT 30 (calls 30 distinct functions — high context load)
  • hooks/wallet/useSignSpendBundle.ts:274 — 22 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:356
  • hooks/wallet/useSignSpendBundle.ts:197 — 21 duplicate lines / 106 tokens with hooks/wallet/useSignSpendBundle.ts:252
  • hooks/wallet/useSpendBundle.ts:240 — 21 duplicate lines / 109 tokens with hooks/wallet/useSpendBundle.ts:323
  • src/components/Home/EnableAutoBalanceBanner.tsx:26 — EnableAutoBalanceBanner: MDD 20.5 (uses across many lines from declarations)
  • src/hooks/wallet/useGrantSessionKey.ts — CC 21, MI 52.29, SLOC 188
  • hooks/wallet/useSignSpendBundle.ts:200 — 20 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:243
  • src/app/(mobile-ui)/dev/card-session-approve/page.tsx — CC 10, MI 63.54, SLOC 22
  • src/hooks/wallet/useSpendBundle.ts:154 — useSpendBundle: DLT 29 (calls 29 distinct functions — high context load)
  • src/hooks/wallet/useSignSpendBundle.ts:101 — useSignSpendBundle: DLT 21 (calls 21 distinct functions — high context load)
  • src/hooks/wallet/useSpendBundle.ts:169 — : DLT 21 (calls 21 distinct functions — high context load)
  • src/app/(mobile-ui)/dev/card-session-approve/page.tsx:16 — CardSessionApprovePage: MDD 19.9 (uses across many lines from declarations)
  • src/hooks/wallet/useGrantSessionKey.ts:91 — : MDD 14.7 (uses across many lines from declarations)
  • src/hooks/wallet/useSignSpendBundle.ts:109 — : DLT 15 (calls 15 distinct functions — high context load)

…and 5 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Home/EnableAutoBalanceBanner.tsx 5.6 7.9 +2.3

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1653 ran, 0 failed, 0 skipped, 26.3s

📊 Coverage (unit)

metric %
statements 54.7%
branches 37.7%
functions 42.6%
lines 54.6%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.6s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.5s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid UK IBAN with spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Manteca PIX form ready shows merchant card + amount input + pay button
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle minimum length (6 digits) US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@Hugo0

Hugo0 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 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.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 2, 2026
@Hugo0

Hugo0 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 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.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/Home/__tests__/EnableAutoBalanceBanner.test.tsx (1)

89-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

LGTM! Good coverage for the two documented regressions.

Consider adding a follow-up test once the grantSucceeded staleness issue is fixed: render with card A, succeed a grant, then swap mockCards to a fresh card B needing setup, and assert the escape hatch/Sentry message do not fire until a grant is actually attempted for card B.

🤖 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/Home/__tests__/EnableAutoBalanceBanner.test.tsx` around lines
89 - 127, The current tests in EnableAutoBalanceBanner cover the documented
regressions, but they do not guard against stale grantSucceeded state leaking
across card changes. Add a follow-up test in EnableAutoBalanceBanner.test.tsx
that renders with one ACTIVE card, completes a successful grant, then updates
mockCards to a fresh card that still needs setup and verifies the modal behavior
resets correctly without showing the escape hatch or calling Sentry until a new
grant is attempted. Use the existing EnableAutoBalanceBanner, mockGrant, and
Sentry.captureMessage assertions to locate and extend the coverage.
🤖 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.

Inline comments:
In `@src/components/Home/EnableAutoBalanceBanner.tsx`:
- Around line 40-42: The success flag in EnableAutoBalanceBanner is not scoped
to the active card, so it can stay true across card swaps and falsely trigger
the lockout path. Update the grant-success tracking in EnableAutoBalanceBanner
to remember the specific card identity used for the successful attempt (based on
the card id from findActiveCard/overview), and reset or ignore it when that id
changes. Make sure stuckAfterSuccess only becomes true when the current active
card matches the one that was actually granted, so Sentry.captureMessage and the
“Skip for now” state only happen for a real duplicate-card case.

---

Nitpick comments:
In `@src/components/Home/__tests__/EnableAutoBalanceBanner.test.tsx`:
- Around line 89-127: The current tests in EnableAutoBalanceBanner cover the
documented regressions, but they do not guard against stale grantSucceeded state
leaking across card changes. Add a follow-up test in
EnableAutoBalanceBanner.test.tsx that renders with one ACTIVE card, completes a
successful grant, then updates mockCards to a fresh card that still needs setup
and verifies the modal behavior resets correctly without showing the escape
hatch or calling Sentry until a new grant is attempted. Use the existing
EnableAutoBalanceBanner, mockGrant, and Sentry.captureMessage assertions to
locate and extend the coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2f1c252-ba13-4200-bfdb-072e396845f1

📥 Commits

Reviewing files that changed from the base of the PR and between 456a52a and 7fd3fb7.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/dev/card-session-approve/page.tsx
  • src/components/Home/EnableAutoBalanceBanner.tsx
  • src/components/Home/__tests__/EnableAutoBalanceBanner.test.tsx
  • src/hooks/wallet/useGrantSessionKey.ts
  • src/hooks/wallet/useSignSpendBundle.ts
  • src/hooks/wallet/useSpendBundle.ts

Comment thread src/components/Home/EnableAutoBalanceBanner.tsx Outdated
…bit) — a re-issued card must not inherit an old card's grant success
Hugo0 added 2 commits July 2, 2026 12:20
- overviewFresh: a grant whose overview refetch failed is STALE, not stuck —
  don't false-page Sentry on flaky connections (grant() now reports it)
- per-card Skip dismissal: skipping stuck card A must not suppress card B's
  legitimate prompt in the same session
- stuck state now explains itself (error copy + Try again CTA) instead of
  happy-path text with an unexplained Skip
- Sentry warn dedupe uses a Set — alternating active cards (A→B→A) no longer
  re-page for an already-warned card
…-review findings 4+5)

A hard failure on card A no longer leaks 'Try again' copy and the skip
escape into a re-issued card B's first prompt — same stale-signal class
as the grantSucceededFor fix, applied to the hook's un-scoped error.
@Hugo0 Hugo0 marked this pull request as ready for review July 2, 2026 19:36
@Hugo0 Hugo0 merged commit 1a4b994 into main Jul 2, 2026
22 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant