fix(claim): stop claim page crashing on links with no events relation - #2893
Conversation
The API stopped selecting the SendLink `events` relation post-ledger-collapse, so `claimLinkData.events` is now always undefined. Claim.tsx dereferenced `events[0]` with the optional chain on the wrong side of the index, throwing "Cannot read properties of undefined (reading '0')" inside a render-phase useMemo — which Next.js surfaces as "Application error: a client-side exception has occurred" (Sentry PEANUT-UI-SJ0). Guard the array itself and mark `events` optional on the SendLink type so the compiler catches the next orphaned access.
… claim page GET /send-links/:pubKey answers a cache hit (30s TTL) with the raw Prisma row, which carries `intents` instead of the `claim` + `events` shape `sanitizeSendLink` projects on the DB path. The existing CANCELLED test never caught PEANUT-UI-SJ0 because its fixture hard-codes `events: []`, and `[][0]?.timestamp` is safe where `undefined[0]` throws. Add a case built from the cache-hit shape. It gates on the drawer effect rather than the rendered view: the view settles before the transaction memo runs, so asserting on it alone passes even against the crashing code.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Code-analysis diffPainscore total: 7107.2 → 7107.44 (+0.24) 🆕 New findings (12)
✅ Resolved (12)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
Clean: the guard safely handles omitted events, the type now matches both API response shapes, and the regression test exercises the previously crashing receipt path.
Checked clean
- Correctness: verified that a cancelled raw-cache payload without claim or events no longer throws while deriving receipt data.
- API contract: confirmed database responses project events while the cache path can return the raw shape without that field.
- Security: no authorization, trust-boundary, secret-handling, injection, or workflow-permission behavior changed.
- Adversarial: reconstructed the missing-events render failure and confirmed all remaining claim and event reads on this path are guarded.
- Slop: found no duplicate abstraction, dead code, misleading behavior, or architecture drift in the scoped change.
- CI: exact-head unit, typecheck, eslint, format, analyze, and aggregate ci-success checks passed.
Second opinion by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only, so treat its findings as advice.
Exact head: 7e3ee6dc9ce6 · Context: repo, engineering
Re-targets #2889 (merged to
main) and #2892 ontodev.devnever received the fix — the crashing line is still live there:Without this, the next
dev→mainrelease re-introduces the crash. Carries both the fix and its regression test, sincedevhas neither.The bug
Opening a cancelled send link showed "Application error: a client-side exception has occurred" —
TypeError: Cannot read properties of undefined (reading '0')thrown inside a render-phaseuseMemo(Sentry PEANUT-UI-SJ0).The optional chain sits on the wrong side of the subscript:
events[0]?.timestampstill throws wheneventsitself is missing.Why
eventsgoes missingGET /send-links/:pubKeyhas two response paths. The DB path returnssanitizeSendLink(sendLink), which projectsintentsinto the legacyclaim+eventsshape. The cache path (30s TTL) returns the raw Prisma row, which has neither. So the crash needed a cache hit on a cancelled link — which is why it was intermittent.The backend half is peanutprotocol/peanut-api-ts#1473 (also re-targeted to
dev). This PR is still worth landing on its own: the guard is correct regardless of which shape the API sends.Changes
eventsoptional on theSendLinktype. It was declared required, which is exactly why the compiler never flagged the dereference — and why the existing test fixture couldn't be corrected.The test
devalready had the right test — "CANCELLED link shows ClaimedView", rendering the realClaimcomponent. It passed anyway because the fixture hard-codesevents: [], and[][0]?.timestampis safe whereundefined[0]throws.The new case builds the cache-hit shape by stripping
claimandevents. It gates on the receipt rather thanClaimedView:claimLinkDatais populated by an async effect that awaitsfetchTokenDetails, so the view settles before the memo runs — asserting onClaimedViewalone passes even against the crashing code.Note this gate differs from #2892's, which used the transaction-details drawer. That hook doesn't exist on
dev; here the same memo feedsselectedTransactionand the receipt instead.Verified on this base
origin/dev'sClaim.tsx: fails withTypeError: Cannot read properties of undefined (reading '0').tsc --noEmitreports only the pre-existingsrc/utils/tw.ts(58,5)error, which is present on cleanorigin/dev.The default fixture keeps
events: []so the projected shape stays covered too.