Skip to content

fix(claim): stop claim page crashing on links with no events relation - #2889

Merged
innolope-dev merged 1 commit into
mainfrom
fix/claim-cancelled-events-crash
Aug 31, 2026
Merged

fix(claim): stop claim page crashing on links with no events relation#2889
innolope-dev merged 1 commit into
mainfrom
fix/claim-cancelled-events-crash

Conversation

@innolope-dev

@innolope-dev innolope-dev commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Problem

Opening a send link on production shows "Application error: a client-side exception has occurred" — the page never renders.

Sentry: PEANUT-UI-SJ0TypeError: Cannot read properties of undefined (reading '0') at src/components/Claim/Claim.tsx:184, on release 85079d7e (current main, from the dev→main merge #2879). 2 users so far, plus the same crash in the native env.

Cause

peanut-api-ts dropped the events relation from SEND_LINK_SELECT ("Post-ledger-collapse: SendLink no longer has claim/claimAttempts/events relations"), so GET /send-links/:id no longer returns an events field at all.

Claim.tsx still dereferenced it:

cancelledDate: status === 'cancelled' ? new Date(claimLinkData.events[0]?.timestamp) : undefined,

The optional chain sits on the wrong side of the index — events[0]?.timestamp still throws when events itself is undefined. (3b6055bc7 patched this same line but placed the ?. after the subscript.)

It throws inside a useMemo during render, so React unwinds the tree and Next.js paints the generic client-exception screen rather than anything actionable.

Note the trigger is the status === 'cancelled' branch — the ternary short-circuits for other statuses, so this hits cancelled links.

Fix

  • Guard the array itself, not the element.
  • Mark events optional on the SendLink type. It was declared required, which is exactly why the compiler never flagged the dereference.

Scope check

Swept every remaining access to the removed relations. Claim.tsx:169 (events?.find), useClaimSuccessPolling.ts:28 (events?.[…]) and all five claim?. sites were already guarded — line 184 was the only unguarded one. tsc --noEmit is clean with events now optional, which confirms nothing else relied on it being present.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed cancelled date handling so dates are only displayed for cancelled transactions.
    • Improved compatibility with responses where transaction event details are unavailable.

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

vercel Bot commented Aug 31, 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 31, 2026 3:50pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7753.18 → 7753.44 (+0.26)
Findings: 0 net (+12 new, -12 resolved)

🆕 New findings (12)

  • critical complexity — src/components/Claim/Claim.tsx — CC 101, MI 53.52, SLOC 404
  • medium high-mdd — src/components/Claim/Claim.tsx:51 — Claim: MDD 105.9 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Claim.tsx:124 — : MDD 26.8 (uses across many lines from declarations)
  • medium method-complexity — src/components/Claim/Claim.tsx:124 — CC 16 SLOC 79
  • medium react-effect-derives-state — src/components/Claim/Claim.tsx:414 — small useEffect that only sets state from deps
  • medium react-effect-derives-state — src/components/Claim/Claim.tsx:420 — useEffect with empty deps + setState — derived state anti-pattern
  • medium react-effect-derives-state — src/components/Claim/Claim.tsx:440 — small useEffect that only sets state from deps
  • low high-dlt — src/components/Claim/Claim.tsx:280 — : DLT 16 (calls 16 distinct functions — high context load)
  • low high-dlt — src/components/Claim/Claim.tsx:284 — processLink: DLT 15 (calls 15 distinct functions — high context load)
  • low high-mdd — src/components/Claim/Claim.tsx:280 — : MDD 10.5 (uses across many lines from declarations)
  • low enum-usage — src/services/services.types.ts:384 — enum EInviteType — prefer as const object literals
  • low enum-usage — src/services/services.types.ts:443 — enum PointsAction — prefer as const object literals

✅ Resolved (12)

  • src/components/Claim/Claim.tsx — CC 100, MI 53.54, SLOC 404
  • src/components/Claim/Claim.tsx:51 — Claim: MDD 104.8 (uses across many lines from declarations)
  • src/components/Claim/Claim.tsx:124 — : MDD 25.9 (uses across many lines from declarations)
  • src/components/Claim/Claim.tsx:124 — CC 15 SLOC 79
  • src/components/Claim/Claim.tsx:411 — small useEffect that only sets state from deps
  • src/components/Claim/Claim.tsx:417 — useEffect with empty deps + setState — derived state anti-pattern
  • src/components/Claim/Claim.tsx:437 — small useEffect that only sets state from deps
  • src/components/Claim/Claim.tsx:277 — : DLT 16 (calls 16 distinct functions — high context load)
  • src/components/Claim/Claim.tsx:281 — processLink: DLT 15 (calls 15 distinct functions — high context load)
  • src/components/Claim/Claim.tsx:277 — : MDD 10.5 (uses across many lines from declarations)
  • src/services/services.types.ts:383 — enum EInviteType — prefer as const object literals
  • src/services/services.types.ts:442 — enum PointsAction — prefer as const object literals

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 4233 ran, 0 failed, 0 skipped, 1.3m

📊 Coverage (unit)

metric %
statements 71.5%
branches 56.4%
functions 62.5%
lines 72.5%
⏱ 10 slowest test cases
time test
4.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.3s src/app/receipt/[entryId]/pdf/__tests__/receipt-pdf-render.test.ts › produces a real, non-trivial PDF document
1.0s src/hooks/query/__tests__/user.test.tsx › does NOT clear a token that rotated mid-request (stale 401 racing a fresh login)
0.9s src/utils/__tests__/crisp.test.ts › settles, and hands back a usable plugin, against a real-shaped plugin proxy
0.6s src/utils/__tests__/crisp.test.ts › configures once across repeated support opens
0.5s src/utils/__tests__/crisp.test.ts › resets the native session on logout once support has been opened
0.5s src/utils/__tests__/crisp.test.ts › retries configuration on the next open after a failure
0.5s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.4s src/utils/__tests__/auth-token.test.ts › authReady does not park — hydrates the plain token without an unlock
0.4s src/utils/__tests__/auth-token.test.ts › ignores the guarded marker and falls back to the plain token
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4daabaf4-2fd7-477c-a542-c303efe9324c

📥 Commits

Reviewing files that changed from the base of the PR and between 85079d7 and 94bbdcc.

📒 Files selected for processing (2)
  • src/components/Claim/Claim.tsx
  • src/services/services.types.ts

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


📝 Walkthrough

Walkthrough

The change makes SendLink.events optional because the API may omit the relation. Claim now reads the first event timestamp only when the transaction status is cancelled.

Changes

SendLink event handling

Layer / File(s) Summary
Optional events and cancelled date handling
src/services/services.types.ts, src/components/Claim/Claim.tsx
SendLink.events is now optional. cancelledDate reads the first event timestamp only for cancelled transactions.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Merge Risk: ⚪ Minimal · up to 94bbd

This change prevents cancelled send-link pages from crashing when the events relation is absent and updates the type to allow that response shape. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: 0xkkonrad

🚥 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: preventing the claim page from crashing when the API omits the events relation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
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.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2 files.

✨ 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 fix/claim-cancelled-events-crash

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

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

Clean. The fix guards the optional events array before indexing, preserves the cancellation timestamp when present, and aligns the SendLink type with the runtime wire shape. No actionable correctness, security, adversarial, or slop issues found.

Checked clean

  • Verified the exact head SHA, supplied base SHA, and merge base; the diff is limited to Claim.tsx and the SendLink type.
  • Exercised the cancelled-link logic mentally with events omitted, empty, and populated; all paths now render without changing the populated timestamp behavior.
  • Swept every remaining SendLink events access and confirmed each already guards an omitted array.
  • Checked the backend send-link projection and prior claim-receipt history to confirm that optional frontend handling is compatible with the wire shape.
  • Reviewed trust boundaries, authorization, secret exposure, mutation behavior, duplication, naming, and architecture drift; this patch introduces none.
  • Exact-head typecheck, ESLint, format, unit, E2E, CodeQL JavaScript analysis, and JavaScript/TypeScript code scanning 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: 94bbdccf67bb · Context: repo, mono

@innolope-dev
innolope-dev merged commit c020f6b into main Aug 31, 2026
27 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