chore: back-merge main into dev (2026-06-01 hotfixes)#2151
Conversation
Release dev → main · card-launch badges + AR/BR rail nudge + Founder Haus rename
The synthetic "You skipped the line" / card-unlock share-asset row was derived from hasCardAccess alone. Card access = holding a skip badge (OG/Devconnect/Arbiverse/Pioneer) or an admin grant; it does NOT mean the user ever entered the card flow or got a card. Result: 3,230 of 9,757 prod users (every skip-badge holder + admin grants) saw a shareable "I got my Peanut card" asset for a card they never received — only 9 users actually have a card. Gate deriveCardUnlockEntry on hasIssuedCard (>=1 Rain card row, read from the already-cached rain overview — no extra fetch). No DB change: the badges are legitimate data; the bug was purely FE gating.
…ry-gate fix(card): gate card-unlock history row on an issued card, not access
When viewing /add-money/<country>, narrow the 'is the bank channel ready' gate to that country's rail jurisdiction. Without this, a rejected rail in an unrelated jurisdiction trips blocked-rejection here and surfaces 'We couldn't unlock this — Verification issue' on a country whose own rail is fine. Triggering incident (2026-06-01): a sync run flipped 1,595 users' BANK_TRANSFER_MX BRIDGE rails to REJECTED (catalog seed artifact, not a real Bridge endorsement — Mexico is SPEI). The catalog deactivation hides those rows going forward, but this scoping fix is the broader guardrail: any future blocked rail in country X must not block country Y.
The Bridge offramp /confirm call from the FE uses fetchWithSentry's 10s
default timeout. When it fires (Sentry PEANUT-UI-QH9, Konrad 2026-06-01):
the on-chain wallet→Bridge deposit has already succeeded, but the FE
showed a generic "contact support" error + a big pink Retry button.
Clicking Retry re-runs sendMoney() and would send funds to the deposit
address a second time — real money lost.
Three fixes, in order of how badly we need each:
1. Gate the Retry button on the in-flight on-chain leg.
- WithdrawBankPage: a new submittedTxHash state is set as soon as the
wallet→Bridge tx confirms (before confirmOfframp). When set, the UI
replaces the Retry button with a Done button + an InfoCard ("your
transfer is processing, contact support if it doesn't show in 30
min"). The on-chain leg is irreversible from the FE's side; Bridge
and the BE poller will reconcile.
- BankFlowManager (claim-link bank flow): the same risk via claimLink.
handleConfirmClaim now swallows confirmOfframp errors after the
on-chain claim succeeded and routes to SUCCESS instead of bubbling
a retryable error back to ConfirmBankClaimView.
2. Bump confirmOfframp's timeout from 10s → 60s. Matches the long-running
confirm pattern in services/manteca.ts and services/rain.ts. The BE
needs more headroom than the default — investigation of why the BE
write takes >10s is tracked separately.
3. Add "timed out after" to ErrorHandler's timeout matcher list. Our own
fetchWithSentry AbortError copy ("Request to <url> timed out after
<ms>ms") didn't hit either existing matcher and fell through to the
"contact support" fallback. This is a backstop for the non-offramp
endpoints; fixes 1+2 are what actually defuses the double-pay.
Fixes Sentry PEANUT-UI-QH9.
…-scope-gate hotfix(add-money): country-scope the bank-deposit gate
…imeout-safety hotfix(offramp): prevent double-pay after Bridge confirm timeout
Both PEANUT-UI-QHB and PEANUT-UI-QHC fire as `[share-asset] save/share
failed [object Event]` — same user, both buttons, one second apart.
Root cause: when an inlined `<img>` in <ShareAssetD3 /> fails to load
(badge icon URL 404 / CORS / blocker), html-to-image's toBlob rejects
with the raw DOM ErrorEvent (see node_modules/html-to-image/lib/util.js:209,
`img.onerror = reject`). The catch block did `(err as Error).message`,
which is `undefined` on an Event, and `console.error('...', err)`
stringifies the Event as `[object Event]` — that becomes the Sentry
title and we learn nothing about which asset broke.
Changes:
- New ShareAssetCaptureError. captureShareAsset wraps the Event reject
in this Error type, extracting `target.src` so the failing image URL
rides along on `failedImages` instead of being lost.
- ShareAssetActions' two catch blocks route through a `describeShareError`
helper that handles ShareAssetCaptureError, normal Error, and raw DOM
Event (defence-in-depth for any future call path that bypasses our
wrapper).
- Both blocks now explicitly call Sentry.captureException with `feature`,
`action`, `source` tags and the structured detail as `extra` — no longer
relying on Sentry's console-error auto-capture, which stringified the
Event opaquely.
- AbortError check tightened to `instanceof Error && name === 'AbortError'`
so we don't silently swallow non-Error rejections that happen to
duck-type that field.
Next time this fires, Sentry will show e.g.:
ShareAssetCaptureError: html-to-image failed to inline image:
https://cdn.example/badges/123.png
+ extra.failedImages with the array.
PostHog `CARD_SHARE_ASSET_FAILED` events get the same structured fields
so we can dashboard the failing URLs.
…pture fix(share-asset): surface failing image URL instead of [object Event]
Brings the following main-only hotfixes into dev so feature work can build on top of them: - #2148 fix(share-asset): surface failing image URL instead of [object Event] - #2147 hotfix(offramp): prevent double-pay after Bridge confirm timeout (Konrad PEANUT-UI-QH9) - #2145 hotfix(add-money): country-scope the bank-deposit gate - #2144 fix(card): gate card-unlock history row on an issued card, not access
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 4 minutes and 46 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
Comment |
Code-analysis diffPainscore total: 5835.68 → 5838.95 (+3.27) 🆕 New findings (60)
…and 40 more. ✅ Resolved (61)
…and 41 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
Routine back-merge so feature work on `dev` builds on the hotfixes that already shipped to `main` today.
Included hotfixes
Why now
`feat/bridge-confirm-recovery-fe` (#2150) builds on #2147's `submittedTxHash` state. Targeting `main` for that PR is fine, but once it lands, the next dev → main release would conflict without this back-merge. Running this now keeps the dev branch caught up and avoids review-time surprises on subsequent dev-targeted PRs.
Conflict status
Clean merge — no conflicts. `git log dev..main` is straightforward and `dev` has not touched any of the same lines.
Test plan