Skip to content

fix: string badge-icon fallback + reachable restart-identity modal (release-audit follow-ups)#2219

Merged
Hugo0 merged 2 commits into
devfrom
fix/release-audit-followups
Jun 11, 2026
Merged

fix: string badge-icon fallback + reachable restart-identity modal (release-audit follow-ups)#2219
Hugo0 merged 2 commits into
devfrom
fix/release-audit-followups

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-ups from the release-audit + CodeRabbit pass on #2218 — all pre-existing (zero delta in the release), bundled here so the release PR stays clean.

  1. getBadgeIcon returns a string, always. The fallback returned PEANUTMAN_LOGO (StaticImageData, typed any by the svg module shim — so typecheck never caught it) while shareAssetLayout types iconUrl: string and ShareAssetD3 feeds it to a raw <img src>. An unknown badge code — exactly what the recurring FE-BADGES-drop incident produces — would render a broken stamp in the share asset. Now unwraps .src, with a : string annotation and a guard test pinning both branches.
  2. restart-identity reaches its own modal. UnlockedRegions' provider-rejection override only promoted fixable/blocked; the dedicated restart-identity copy + handleRestartIdentity CTA already wired inside the provider_rejection ActionModal were unreachable from this predicate — those users fell back to the generic start flow. Predicate now includes restart-identity.
  3. Lint nits from the same review: unused useRouter in the payment error boundary; alt="" on the careers mascot img.

Test changes: new badge.utils.test.ts; shareAssetLayout.test.ts gets a StaticImageData-shaped mascot mock (jest's svg stub flattens imports to a bare string, so the fallback path needs the real shape).

Declined CodeRabbit findings (from #2218, for the record)

  • claim_external sign flip — misread semantics: incoming claims take the receive/+ branch; claim_external is the claim-to-external-wallet path.
  • Case-insensitive WebAuthn text match — widens the matcher toward WebKit's getUserMedia denial message (same phrase); current casing matches the observed iOS variant.
  • Merchant-name acronym handling, stale ?method= param, repeated-@ invite strip — valid minors, out of scope here; noted in TODO.

Risks / breaking changes

None — FE-only, no contract changes, no migrations. Blast radius: badge icon fallback (share asset + badge lists), one KYC modal predicate, an error boundary, the careers page.

QA

  • getBadgeIcon('NOT_A_REAL_BADGE') → string URL (unit-tested).
  • Profile → Unlocked regions: a user whose provider rejection is restart-identity now gets the "Verify with a different document" modal instead of the generic flow.
  • typecheck 0 errors · jest 85/85 suites (1425 pass) · build green locally.

/code-review findings + disposition (second commit)

  • Applied: predicate → state !== 'happy' (enumeration is the bug class that caused the original miss); jest asset stub → StaticImageData shape at the moduleNameMapper level (src/utils/__mocks__/static-image.ts), deleting both per-file mascot mocks — .src-reading code no longer tests against fiction.
  • Key discovery: POST /users/identity/restart and the country_not_supported reason exist only in open api PR fixed withdraw status not resetting after withdraw #914 — the restart-identity state is unreachable on today's backend, so the predicate change is inert until fixed withdraw status not resetting after withdraw #914 merges, then becomes live with a working CTA. No 404 exposure.
  • Confirmed follow-up (post-fixed withdraw status not resetting after withdraw #914): isSumsubApproved here is an any-rail-enabled proxy (useCapabilities.ts:184), so a user whose only rails are blocked Manteca country_not_supported ones never reaches this modal — the gate should read the provider-agnostic identityVerification signal instead. In TODO.
  • Noted, pre-existing (not this PR): the provider-rejection override can mask the processing variant (already true for fixable/blocked); restart-identity call sites run with regionIntent=undefined (shared pattern, BE pins the level); 4 surfaces hand-roll the same rejection-state ternary cascade (shared deriveRejectionModalProps follow-up in TODO).
  • jest-transform-stub stays in devDependencies: removing it forces a lockfile re-resolve that moves Sentry onto different otel peers — deferred to a deps PR.

…e restart-identity modal

getBadgeIcon fell back to PEANUTMAN_LOGO (StaticImageData, typed `any` by the
svg shim) while shareAssetLayout types iconUrl as string and ShareAssetD3 feeds
it to a raw <img src> — an unknown badge code (the recurring FE-BADGES-drop
incident) would render a broken stamp. Unwrap .src and pin the contract with a
test.

UnlockedRegions' provider-rejection override only promoted fixable/blocked, so
the restart-identity copy + handleRestartIdentity CTA already wired in the
modal were unreachable from this predicate — those users fell back to the
generic start flow. Include restart-identity.

Plus two lint nits from the #2218 review: unused useRouter in the payment error
boundary, missing alt on the careers mascot.
@vercel

vercel Bot commented Jun 11, 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 Jun 11, 2026 12:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 11, 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1f6e5db-a895-411e-9883-f84d0dfb18e6

📥 Commits

Reviewing files that changed from the base of the PR and between 85483d5 and 7474430.

📒 Files selected for processing (4)
  • package.json
  • src/components/Badges/__tests__/badge.utils.test.ts
  • src/components/Profile/views/UnlockedRegions.view.tsx
  • src/utils/__mocks__/static-image.ts
✅ Files skipped from review due to trivial changes (1)
  • src/utils/mocks/static-image.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Profile/views/UnlockedRegions.view.tsx

Walkthrough

This PR contains four isolated improvements: error recovery logging in the PaymentError component, badge icon return-type normalization with corresponding test coverage and Jest mock updates, provider rejection state routing expansion in UnlockedRegions, and an accessibility fix adding an alt attribute to an image element.

Changes

UI and UX refinements

Layer / File(s) Summary
Error recovery and logging in PaymentError
src/app/[...recipient]/error.tsx
PaymentError now imports modal context and utility functions, removes unused router import, and adds a useEffect that logs errors and calls recoverFromChunkError when the error prop changes.
Badge icon string return type with tests and Jest mocking
src/components/Badges/badge.utils.ts, src/components/Badges/__tests__/badge.utils.test.ts, package.json, src/utils/__mocks__/static-image.ts
getBadgeIcon return type is now explicitly string and returns PEANUTMAN_LOGO.src instead of the object itself. Test file covers known badge codes, unknown codes, and undefined fallback behavior. Jest moduleNameMapper is updated to use a custom static image mock that exports a StaticImageData-shaped stub for test consumers.
Provider rejection state routing in UnlockedRegions
src/components/Profile/views/UnlockedRegions.view.tsx
Modal routing condition changes from checking specific provider rejection states ('fixable' or 'blocked') to treating any state other than 'happy' (including 'restart-identity') as provider rejection.
Image alt attribute in Careers component
src/components/Jobs/index.tsx
Mascot image element now includes an explicit empty alt="" attribute for accessibility.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • jjramirezn
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main changes: a string badge-icon fallback fix and reachable restart-identity modal, matching the primary objectives in the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the rationale, implementation, risks, QA, and follow-ups for each change.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5815.92 → 5819.26 (+3.34)
Findings: 0 net (+8 new, -8 resolved)

🆕 New findings (8)

  • critical complexity — src/components/Profile/views/UnlockedRegions.view.tsx — CC 55, MI 62.46, SLOC 150
  • medium high-mdd — src/components/Profile/views/UnlockedRegions.view.tsx:60 — UnlockedRegions: MDD 72.8 (uses across many lines from declarations)
  • medium method-complexity — src/components/Profile/views/UnlockedRegions.view.tsx:60 — CC 23 SLOC 92
  • low structural-dup — src/components/Profile/views/UnlockedRegions.view.tsx:300 — 16 duplicate lines / 83 tokens with src/features/limits/views/LimitsPageView.tsx:146
  • low structural-dup — src/components/Profile/views/UnlockedRegions.view.tsx:301 — 16 duplicate lines / 73 tokens with src/features/limits/views/LimitsPageView.tsx:189
  • low high-mdd — src/components/Profile/views/UnlockedRegions.view.tsx:301 — : MDD 10.6 (uses across many lines from declarations)
  • low missing-return-type — src/app/[...recipient]/error.tsx:9 — PaymentError: exported fn missing return type annotation
  • info unused-dep — package.json:149 — unused devDependency: jest-transform-stub

✅ Resolved (8)

  • src/components/Profile/views/UnlockedRegions.view.tsx — CC 56, MI 62.4, SLOC 150
  • src/components/Profile/views/UnlockedRegions.view.tsx:60 — UnlockedRegions: MDD 71.0 (uses across many lines from declarations)
  • src/components/Profile/views/UnlockedRegions.view.tsx:60 — CC 24 SLOC 92
  • src/components/Profile/views/UnlockedRegions.view.tsx:297 — 16 duplicate lines / 83 tokens with src/features/limits/views/LimitsPageView.tsx:146
  • src/components/Profile/views/UnlockedRegions.view.tsx:298 — 16 duplicate lines / 73 tokens with src/features/limits/views/LimitsPageView.tsx:189
  • src/components/Profile/views/UnlockedRegions.view.tsx:298 — : MDD 10.6 (uses across many lines from declarations)
  • src/app/[...recipient]/error.tsx:10 — PaymentError: exported fn missing return type annotation
  • src/components/Badges/badge.utils.ts:198 — getBadgeIcon: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/__mocks__/static-image.ts 0.0 2.9 +2.9

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1429 ran, 0 failed, 0 skipped, 20.3s

📊 Coverage (unit)

metric %
statements 51.4%
branches 33.6%
functions 39.3%
lines 51.4%
⏱ 10 slowest test cases
time test
0.6s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Request/__tests__/request-states.test.tsx › renders request form with nav header, action card, QR code, amount input, and create button
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (missing 0x prefix)
0.1s src/components/Request/__tests__/request-states.test.tsx › API failure shows error message and toast
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
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 maximum length (17 digits) US account
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

…st asset stub

/code-review pass on this PR converged on two structural fixes:

The predicate now reads state !== 'happy' instead of enumerating the three
non-happy members — the enumeration pattern is how restart-identity got missed
in the first place, and a future fourth state would have repeated it.

The jest asset stub (jest-transform-stub) flattened image imports to a bare
string while Next yields StaticImageData — tests of .src-reading code ran
against fiction, which is exactly how the original object-into-string fallback
shipped unnoticed. Point the mapper at a {src,width,height} stub and drop both
per-file mascot mocks this PR had added. jest-transform-stub stays in
devDependencies for now: removing it forces a pnpm lockfile re-resolve that
drags Sentry onto different otel peers — that cleanup belongs in a deps PR.
@Hugo0 Hugo0 marked this pull request as ready for review June 11, 2026 12:19
@Hugo0 Hugo0 merged commit 3ab1534 into dev Jun 11, 2026
20 of 22 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