Skip to content

Prod Release Sprint 149 — cross-chain withdraw · card-launch CTA · share-asset · badges (2026-06-29)#2298

Merged
Hugo0 merged 60 commits into
mainfrom
dev
Jun 29, 2026
Merged

Prod Release Sprint 149 — cross-chain withdraw · card-launch CTA · share-asset · badges (2026-06-29)#2298
Hugo0 merged 60 commits into
mainfrom
dev

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Prod Release Sprint 149 — cross-chain withdraw · card-launch CTA · share-asset · badges (2026-06-29)
Pairs with peanut-api-ts dev→main (#1075 isPublicLaunched, badges, EEA test fix). Deploy order: BE first, then FE. No DB migrations in this release.

⚠️ Launch-day release (Card public launch Mon 2026-06-29). Largest item is the re-enabled cross-chain withdraw (#2280) — see Risks.

Changes (13 PRs)

Cross-chain withdraw (headline)

Card launch

Badges

Withdraw / add-money / bank flows

Compliance / guest claims

Misc / dev

Risks

QA checklist

  • Same-chain withdraw (Arbitrum USDC) completes with correct fee shown
  • Cross-chain withdraw (e.g. → Base/Polygon/Gnosis) — quote loads, "You pay" includes fee, executes; insufficient-balance is gated
  • Withdraw token selector restricted to Rhino-supported chains/tokens (no dead options)
  • UK GBP + GB-IBAN-EUR + intra-SEPA bank withdrawals each complete
  • Add-money: no redundant second bank/method screen; amount field autofocuses (desktop)
  • Request shows spendable balance incl. card collateral; no over-spend
  • Card launch CTA shows for an eligible activated user; hidden for ineligible/has-card/dismissed
  • Card reveal failure shows the friendly message (not raw error)
  • Share-asset renders correctly (win + rejection variants)
  • Psyops badge: invite psyops awards it; detail modal opens; earn toast non-blocking
  • Guest claim with travel-rule details succeeds
  • Regression: send-link create+claim, QR pay, direct send, deposit — all unaffected
  • Nutcracker smoke (6 critical scenarios) green on staging

Deploy / monitor

  • api-ts release merged + deployed FIRST (Render)
  • FE merged → Vercel prod deploy green; peanut.me responding
  • Post-deploy 6-channel sweep: Sentry (peanut-ui) flat, Vercel runtime clean, no new error spikes vs prior hour
  • Post-release: back-merge main → dev

0xkkonrad and others added 30 commits June 23, 2026 21:08
Replace the postage-stamp layout with a force-directed sticker collage —
the pixelated card sits centred, badges are placed by a repulsion-based
solver that fills the field without heavy overlap, and a hero "I'M IN!"
burst + a peanut.me/<handle> pill frame it. The card's peanut logo and
pixel hand stay uncovered via sticker-half-inflated keep-outs.

Why: the launch "I got in" share moment needed a louder, more on-brand
asset. The old stamp framing + EDITION/tier/points/stats chrome read as
cluttered, and the prior ring layout crowded the edges at high badge
counts. The look ships via component defaults, so existing surfaces
(BadgeSkipCelebration, CardUnlockDrawer) pick it up with no caller
changes. Also exposes /dev/share-builder as a dev-only public route
(prod still 404s) for iterating on the asset.
CodeRabbit review on #2274:
- Size the username-pill keep-out to the rendered pill (peanut.me/<handle>
  widens with the handle); a fixed x0 only guarded the right edge, so a
  sticker could land on a long handle. Pill box now computed from the
  rendered geometry and passed into placeStamps; add a regression test.
- Reserve the *rotated* hero bounding box so a tilted hero sticker's corners
  aren't covered; extract shared heroTilt() used by render + keep-out.
- Correct the heroMessage/usernameStyle prop docs (undefined=default hero,
  null=none; pill defaults to white).
Users who pass the eligibility hold but lack a card-access badge hit a
flat "you don't have the required badge :(" wall. Replace it with a
shareable door rejection that doubles as a growth loop: a "not tonight,
<username>" asset (smug peanut bouncer, scarcity tally as screen copy)
plus a primary "Tweet to appeal" share that attaches the asset and tags
@joinpeanut with a random caption. The secondary "Join the waitlist
anyway" still calls joinWaitlist, flipping the state machine to the
friendly joined screen as the post-share cooldown.

CardRejectionScreen owns the join itself (mirrors the old
CardWaitlistScreen contract: joinWaitlist + posthog + loading/error), so
the /card state machine drops it straight into the not-joined slot.
Removes the now-orphaned CardWaitlistScreen. Adds /dev/rejection-builder
to iterate on copy, the door tally, and the bouncer mascot.
react/no-unknown-property flags the styled-jsx `jsx` attr; scope a
disable to keep the new file lint-clean, matching ShareAssetD3's pattern.
handleAppeal logged + Sentry-captured a capture/native-share failure but
showed the user nothing — the spinner just stopped and no tweet went
out. The appeal is the whole point of the rejection screen, so fall back
to the text-only twitter intent on any non-abort error: the @joinpeanut
tag still goes out even if html-to-image or the OS share sheet fails.
The keep-out-box approach still let stickers pile up / heavily overlap on
dense badge sets. Replace it with a force-directed relaxation: pairwise
repulsion between stickers, keep-clear ellipses around the hero + card,
and a pill keep-out with exit vectors, iterated to a stable spread. Drops
the now-unused pillKeepoutBox / PILL_RIGHT / PILL_BOTTOM exports and
updates the layout tests to assert the no-heavy-overlap invariant.
…ner-trap

Merging the force-directed engine onto the branch dropped two keep-out
refinements the earlier collage had, and a self-review found the engine
itself could still pile badges up:

- Rotated hero keep-out: reserve the *rotated* hero bounding box again
  (shared heroTilt() helper drives both the render and the keep-out, so
  they stay in lockstep) — badges no longer clip the tilted hero corners.
- Dynamic pill keep-out: size the bottom-right keep-out to the *rendered*
  username pill (pillKeepoutBox, threaded through placeStamps) instead of
  a static box, so badges clear the whole pill for long handles.
- Corner-trap: the soft edge + pill pulls could deadlock two stickers
  against the bottom-right corner (Gauss-Seidel local minimum) — across a
  7k-layout sweep the worst centre gap was 0.22×size, well into "heavy
  overlap". Add a final separation-only pass (separation + hard keep-outs
  + clamp, no edge/pill pull) so pairwise spread wins the last word; worst
  gap is now 0.75×size. Broaden the overlap test to a 160-seed sweep so
  the corner-trap regime is actually exercised.

Also: appeal share treats an unmeasured asset ref as a graceful text-only
fallback rather than a Sentry-logged error.
Some users don't want their peanut.me/<handle> on a public victory post.
Add a `hideUsername` prop to ShareAssetD3: when set, the username pill
doesn't render and its bottom-right keep-out is pushed off-canvas so the
badge collage reclaims that corner. Surfaced as a simple "Hide username"
checkbox directly beneath the asset (above the share buttons) on both
share surfaces — BadgeSkipCelebration and CardUnlockDrawer — and in the
/dev/share-builder iterator. The captured PNG honours the toggle, so a
hidden handle never leaks into the shared image.
Hiding then un-hiding the username left the pill invisible for ~2.3s: the
conditional unmount re-ran the pill's staggered entrance animation (600ms
fade + 1700ms delay) on every toggle, so it looked like the handle never
came back. Keep the pill mounted and drive the toggle with `visibility`
instead — the entrance plays once on the initial reveal, and toggling is
instant. `visibility: hidden` is also excluded from the html-to-image
capture, so a single capture-at-share-time still produces the correct PNG
(verified: with-handle vs no-handle) — no need to pre-render two images.
The win share posted one identical line ("I got my Peanut card. shhhh.")
for everyone. Add a win-caption pool (winCaptions.ts, Hugo's picks: hype
+ invite/FOMO + Devconnect callbacks + "shhhh" + anti-bank) and pick one
at random per mount in ShareAssetActions — used for both the native share
sheet and the desktop twitter intent (shareCardOnTwitter now takes the
caption), so the two paths post the same line. Drops the now-unused
shareText prop from the celebration + history-replay callers.
Self-review caught one curly apostrophe (U+2019) in the 'can't talk' line
while the other 15 use straight ASCII; normalize so the tweet text reads
consistently.
The file/caption comments promised the @joinpeanut tag was rendered into
the pixels so it survives an image-only re-post, but the asset only drew
the headline + mascot — the growth tag lived only in the share caption.
A user who re-posts just the PNG would carry no tag, silently breaking
the screen's entire referral rationale. Render the handle bottom-right.
- shareAssetLayout: doc comment said 2300/count, code uses 2700/count
- share-builder: surface hideUsername in the 'Resulting props' panel devs
  copy from (it's a headline feature, was omitted)
- ShareAssetD3: pill render now uses the shared PILL_MAX_W constant instead
  of a hardcoded 780, keeping render + keep-out estimate in lockstep
…to-access)

The 'Tweet to appeal' CTA tweeted but left no in-app record — the appealer
wasn't on the waitlist, so the manual-grant workflow ('we let them in by
hand') had nothing to release from. Now appeal fires joinWaitlist() in
parallel with the share: drops them into the userId-keyed queue and flips
to the friendly cooldown after sharing. Joining is NOT access — release
stays manual (admin grant / grant-card-access), so no one gets auto-access.
PostHog tags the join source:'appeal' so appealers are distinguishable from
quiet 'Join anyway' joiners. onJoined() is deferred to finally so the
unmount can't race captureShareAsset's read of captureRef.
… handle

The @joinpeanut tag belongs in the share text, not the pixels. Revert the
baked-in handle on the rejection asset (keep the image clean) and instead
ensure every shareable caption carries the handle: the rejection captions
already did, so add @joinpeanut to all 16 win captions. Now both win and
rejection shares tag the brand via the tweet text on every post.
… fees

Cross-chain withdraw was disabled after a customer paid ~$4 in fees on a
~$5 mainnet withdraw while the confirm screen showed the fee struck through
as 'Sponsored by Peanut!'. The Rhino fee (destination gas + 0.07%) is real
and user-paid; only the kernel execution gas is actually sponsored.

- Confirm screen now shows the honest network fee + a 'You pay' total
  (amount + fee), separating the user-paid bridge fee from the sponsored
  kernel gas. Fixes the misleading 'Recipient receives' tooltip.
- Disproportionate-fee hard-stop (cross-chain-fee.utils): blocks the CTA when
  the fee exceeds 5% of the amount. Flat mainnet gas makes small mainnet
  withdrawals fail this — exactly the case that got the feature disabled.
- Thread the quote economics (feeUsd/payAmount/receiveAmount) from preview
  into provision so the backend can persist + ledger them.
- Re-enable: flip disableXchainWithdraw only; TokenSelector restricts withdraw
  to USDC/USDT across Rhino chains. Non-stable bridge path stays out (its fee
  accounting is unfixed); claim / cross-chain pay-request stay disabled.
The reveal error path piped the raw thrown Error.message straight to the
card face — CardFace renders the error string verbatim. For any real API
failure that message is the backend's raw error (e.g. an upstream Rain 500
body), because rainRequest throws `new Error(err.error)`. The existing
'Failed to load card details' fallback only covered non-Error throws,
which never happen on this path, so users saw internal error text on the
card instead of a usable message.

Always render a friendly, actionable message; keep the raw text on the
PostHog CARD_PAN_FAILED event so diagnostics aren't lost.
…ut of analytics

CodeRabbit: forwarding the full backend error message to PostHog could push
raw upstream/provider detail (Rain 500 body, correlationId) into client
analytics. Cap it to a 120-char slice — enough to segment failures — since the
complete sanitized detail is already in Sentry via fetchWithSentry.
…-error

fix(card): friendly message when card reveal fails
… block)

Per the cross-chain withdraw decision (keep mainnet + L2, stable + eth; fix is
honesty not removing features):

- Remove the stablecoin-only TokenSelector restriction — ETH/non-stable
  destinations are allowed again (they route through the existing swaps path).
- Turn the 5% fee hard-stop into a non-blocking heads-up: the CTA is no longer
  disabled on a high fee ratio; instead a muted note flags when the fee is a
  large share of the amount. The fee is shown honestly and the user decides,
  same as fiat withdrawals.

Note: the SDA (stablecoin) path books the fee on the ledger; the swaps (ETH)
path shows the fee honestly pre-sign but doesn't yet book a ledger entry —
that's a follow-up (the swaps settlement is a separate path, kept as-is).
Per CodeRabbit — the comment said 'USDC/USDT only' but withdrawals now allow
non-stable (ETH) destinations via the swaps path too. Reword to match.
…add up

Per CodeRabbit — the 'Network fee' row rounds to 2 decimals but 'You pay' summed
the full-precision fee, so they could disagree by a cent. Use the same rounded
fee value in the total.
The Squid-era selector exposed chains/tokens Rhino rejects — e.g. USDC on
Scroll → '400 SCROLL is disabled' on preview. Add a curated
RHINO_WITHDRAW_SUPPORTED_TOKENS_BY_CHAIN map (from Rhino's live SDA + bridge
config) and, in withdraw mode, filter the network list, popular buttons, and
per-chain token list to it. Drops Scroll entirely and native gas tokens Rhino
can't bridge (POL on Polygon, xDAI on Gnosis); keeps USDC/USDT everywhere
supported plus ETH (arb/eth/op) and BNB. EVM-only, matches the current
selectable chain set.
Arbitrum's chain icon pointed at an arbiscan.io-hosted SVG that blocks
hotlinking, so next/image failed and the token selector fell back to initials
('AO'). Add a local-asset override map and prefer the bundled arbitrum.svg.
Now visible because cross-chain withdraw re-enables the full network selector.
evmChainIdToRhinoName returned our display names (POLYGON, BNB), but Rhino's
API expects MATIC_POS and BINANCE — so Polygon/BNB withdrawals 400'd with
'Invalid chain'. The map conflated display ChainName with Rhino's API name;
they coincide for most chains but not these two. Emit the correct Rhino names
(per peanut-api-ts CHAINS_CONFIG) and decouple the return type from the
display-name union. Fixes withdraw + claim cross-chain to those chains.
…ulating

The 'Recipient receives', 'Network fee', and 'You pay' rows depend on the Rhino
quote; while it's being fetched they were hidden (rendered only once the value
existed), so the confirm screen looked empty with just a disabled CTA. Render
them during isCalculating with PaymentInfoRow's existing loading prop so they
show a spinner, then the real values. Same-chain (no quote) is unaffected.
The non-blocking fee heads-up reused ErrorAlert with muted styling; switch to
the InfoCard component (variant=info) so it reads as an informational note, not
an error.
0xkkonrad and others added 9 commits June 29, 2026 00:06
* feat(badges): add Psyops Division badge art + invite mapping

Adds psyops_division.svg (winged agency-seal mascot from the badge-draft
skill), the BADGES entry (icon + copy + displayName), and the
psyops -> PSYOPS_DIVISION invite-code campaign mapping. Pairs with the
peanut-api-ts registry PR; campaign-maps.test.ts guard passes.

* feat(badges): replace Card Pioneer with Founding Pioneer badge

The grandfathered CARD_PIONEER code stays (it gates card access + cashback),
so we keep the backend code and only repoint the FE asset/copy/name to the
new Founding Pioneer (Flag Lander) — existing holders re-render the new badge.
Also wires FOUNDING_PIONEER as a new invite-activated community badge for the
early crew (invite code "founding").
On top of the toast core (523b046): adds the missing BadgeEarnToast component test (the /home gate, single-vs-coalesced label, detail-modal-vs-/badges tap), excludes BETA_TESTER (every signup earns it -> noise, not a moment), and updates the detection tests. 23 badge tests green, typecheck clean.
… fixes)

Addresses the 3 fire-once bugs from the high-effort code review: (1) cold-start re-fire - seen is now derived synchronously via useMemo, not a useState+useEffect that lagged the async user load by one render; (2) staggered drop - per-batch toast id (not a fixed id) so a 2nd badge earned within the window surfaces instead of being marked-seen-but-never-shown; (3) private-mode nag - in-memory fallback when localStorage writes throw so the toast does not re-fire every /home visit. Also dismiss on nav away from /home; compute getBadgeIcon once. +3 tests; 26 badge tests green, typecheck + eslint clean.
… union (festa/cardalpha + psyops/founding)

Secret-scan pre-commit hook flagged 0x-hex in dev's supported-chains.ts / withdraw-crypto files — verified public contract addresses + bytes32 config already in dev, no private-key material. False positive.
feat(badges): detail modal (2x image + tappable drawer) + non-blocking earn toast (TASK-19791)
- gate visible on overview loading + dismissed!==null so dismissed users
  and card-holders don't flash the banner / fire a premature VIEWED event
- disableHaptics on the inner Button (Bruddle Button self-triggers haptic;
  handleTryDoor already fires one) to avoid a double buzz
feat(card): home card-launch CTA (fat /shhhhh-tone splash on launch day)
…collage

feat(card): redesign launch share asset as a sticker collage
feat(dev): /dev/home-ctas preview for every home CTA
@vercel

vercel Bot commented Jun 29, 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 29, 2026 10:13am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 29, 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: 3 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: a4e3a203-827d-4c5e-8aad-e9282614faad

📥 Commits

Reviewing files that changed from the base of the PR and between ece9cb5 and efbb2bd.

⛔ Files ignored due to path filters (3)
  • public/badges/founding_pioneer.svg is excluded by !**/*.svg
  • public/badges/peanut-pioneer.png is excluded by !**/*.png
  • public/badges/psyops_division.svg is excluded by !**/*.svg
📒 Files selected for processing (72)
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/dev/home-ctas/page.tsx
  • src/app/(mobile-ui)/dev/page.tsx
  • src/app/(mobile-ui)/dev/rejection-builder/page.tsx
  • src/app/(mobile-ui)/dev/share-builder/page.tsx
  • src/app/(mobile-ui)/home/page.tsx
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx
  • src/app/ClientProviders.tsx
  • src/app/actions/supported-chains.ts
  • src/app/shhhhh/ShhhhhLandingPage.tsx
  • src/components/AddMoney/views/CryptoDeposit.view.tsx
  • src/components/Badges/BadgeDetailModal.tsx
  • src/components/Badges/BadgeEarnToast.tsx
  • src/components/Badges/BadgeStatusDrawer.tsx
  • src/components/Badges/__tests__/BadgeEarnToast.test.tsx
  • src/components/Badges/__tests__/badgeCelebration.utils.test.ts
  • src/components/Badges/__tests__/useBadgeEarnToast.test.ts
  • src/components/Badges/badge.utils.ts
  • src/components/Badges/badgeCelebration.utils.ts
  • src/components/Badges/index.tsx
  • src/components/Badges/useBadgeEarnToast.ts
  • src/components/Card/AddCardEntryScreen.tsx
  • src/components/Card/BadgeSkipCelebration.tsx
  • src/components/Card/CardEligibilityCheckScreen.tsx
  • src/components/Card/CardRejectionScreen.tsx
  • src/components/Card/CardUnlockDrawer.tsx
  • src/components/Card/CardWaitlistScreen.tsx
  • src/components/Card/__tests__/cardState.utils.test.ts
  • src/components/Card/share-asset/PixelatedCardFace.tsx
  • src/components/Card/share-asset/RejectionAssetD3.tsx
  • src/components/Card/share-asset/ScaledRejectionAsset.tsx
  • src/components/Card/share-asset/ShareAssetActions.tsx
  • src/components/Card/share-asset/ShareAssetD3.tsx
  • src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts
  • src/components/Card/share-asset/rejectionCaptions.ts
  • src/components/Card/share-asset/share.utils.ts
  • src/components/Card/share-asset/shareAsset.types.ts
  • src/components/Card/share-asset/shareAssetLayout.ts
  • src/components/Card/share-asset/winCaptions.ts
  • src/components/Global/ExchangeRateWidget/index.tsx
  • src/components/Global/TokenSelector/TokenSelector.consts.ts
  • src/components/Global/TokenSelector/TokenSelector.tsx
  • src/components/Home/ActivationCTAs.tsx
  • src/components/Home/CardLaunchCTA/CardLaunchCTABanner.tsx
  • src/components/Home/CardLaunchCTA/__tests__/cardLaunchCTA.utils.test.ts
  • src/components/Home/CardLaunchCTA/cardLaunchCTA.utils.ts
  • src/components/Home/CardLaunchCTA/index.tsx
  • src/components/Invites/campaign-maps.ts
  • src/components/Marketing/ArticleBackNav.tsx
  • src/components/Profile/components/ProfileMenuItem.tsx
  • src/components/Profile/index.tsx
  • src/components/Send/views/SendRouter.view.tsx
  • src/components/Slider/index.tsx
  • src/components/TransactionDetails/transactionTransformer.ts
  • src/components/Withdraw/views/Confirm.withdraw.view.tsx
  • src/config/underMaintenance.config.ts
  • src/constants/analytics.consts.ts
  • src/constants/general.consts.ts
  • src/constants/rhino.consts.ts
  • src/constants/routes.ts
  • src/constants/token-details.json
  • src/features/payments/shared/hooks/useCrossChainTransfer.ts
  • src/hooks/__tests__/useCardReveal.test.ts
  • src/hooks/useActivationStatus.ts
  • src/hooks/useCardInfo.ts
  • src/hooks/useCardReveal.ts
  • src/services/card.ts
  • src/services/rhino-sda.ts
  • src/styles/globals.css
  • src/utils/cross-chain-fee.utils.test.ts
  • src/utils/cross-chain-fee.utils.ts
  • src/utils/history.utils.ts

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

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5751.17 → 5846.48 (+95.31)
Findings: +34 net (+165 new, -131 resolved)

🆕 New findings (165)

  • critical complexity — src/components/Global/TokenSelector/TokenSelector.tsx — CC 125, MI 61.01, SLOC 327
  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 95, MI 57.44, SLOC 376
  • critical complexity — src/components/TransactionDetails/transactionTransformer.ts — CC 93, MI 44.41, SLOC 270
  • critical complexity — src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 76, MI 53.34, SLOC 329
  • critical complexity — src/components/Card/share-asset/shareAssetLayout.ts — CC 55, MI 47.56, SLOC 258
  • critical complexity — src/app/(mobile-ui)/dev/share-builder/page.tsx — CC 51, MI 68.07, SLOC 199
  • high complexity — src/app/shhhhh/ShhhhhLandingPage.tsx — CC 45, MI 57.64, SLOC 255
  • high complexity — src/components/Withdraw/views/Confirm.withdraw.view.tsx — CC 40, MI 62.27, SLOC 42
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 36 commits, +1049/-455 lines since 6 months ago
  • high complexity — src/components/Card/share-asset/ShareAssetD3.tsx — CC 36, MI 49.87, SLOC 226
  • high complexity — src/components/Home/ActivationCTAs.tsx — CC 34, MI 57.96, SLOC 130
  • high hotspot — src/app/(mobile-ui)/home/page.tsx — 32 commits, +154/-138 lines since 6 months ago
  • high hotspot — src/components/TransactionDetails/transactionTransformer.ts — 32 commits, +860/-760 lines since 6 months ago
  • high method-complexity — src/components/Global/TokenSelector/TokenSelector.tsx:63 — CC 31 SLOC 92
  • high method-complexity — src/components/TransactionDetails/transactionTransformer.ts:450 — mapTransactionDataForDrawer CC 31 SLOC 133
  • high complexity — src/features/payments/shared/hooks/useCrossChainTransfer.ts — CC 31, MI 45.16, SLOC 320
  • high method-complexity — src/utils/history.utils.ts:325 — completeHistoryEntry CC 31 SLOC 114
  • high hotspot — src/components/Badges/badge.utils.ts — 30 commits, +317/-102 lines since 6 months ago
  • high complexity — src/components/Card/share-asset/PixelatedCardFace.tsx — CC 30, MI 57.63, SLOC 154
  • high complexity — src/components/Card/share-asset/RejectionAssetD3.tsx — CC 12, MI 47.24, SLOC 59

…and 145 more.

✅ Resolved (131)

  • src/components/Global/TokenSelector/TokenSelector.tsx — CC 115, MI 61.25, SLOC 308
  • src/app/(mobile-ui)/card/page.tsx — CC 95, MI 57.45, SLOC 376
  • src/components/TransactionDetails/transactionTransformer.ts — CC 92, MI 44.6, SLOC 268
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 70, MI 52.77, SLOC 317
  • src/app/(mobile-ui)/dev/share-builder/page.tsx — CC 45, MI 67.49, SLOC 185
  • src/app/shhhhh/ShhhhhLandingPage.tsx — CC 38, MI 55.43, SLOC 244
  • src/app/(mobile-ui)/card/page.tsx — 35 commits, +1036/-450 lines since 6 months ago
  • src/components/Card/share-asset/ShareAssetD3.tsx — CC 34, MI 52.34, SLOC 215
  • src/components/Card/share-asset/shareAssetLayout.ts — CC 33, MI 44.28, SLOC 340
  • src/app/(mobile-ui)/home/page.tsx — 31 commits, +148/-138 lines since 6 months ago
  • src/components/Home/ActivationCTAs.tsx — CC 31, MI 56.24, SLOC 123
  • src/components/TransactionDetails/transactionTransformer.ts — 31 commits, +852/-759 lines since 6 months ago
  • src/features/payments/shared/hooks/useCrossChainTransfer.ts — CC 31, MI 45.29, SLOC 317
  • src/utils/history.utils.ts:321 — completeHistoryEntry CC 31 SLOC 114
  • src/components/Global/TokenSelector/TokenSelector.tsx:62 — CC 30 SLOC 88
  • src/components/TransactionDetails/transactionTransformer.ts:450 — mapTransactionDataForDrawer CC 30 SLOC 131
  • src/components/Badges/badge.utils.ts — CC 10, MI 47.87, SLOC 137
  • src/constants/analytics.consts.ts — CC 1, MI 34.96, SLOC 146
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:39 — WithdrawCryptoPage is 485 lines — split it
  • src/app/shhhhh/ShhhhhLandingPage.tsx:162 — ShhhhhLandingPage is 429 lines — split it

…and 111 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Card/share-asset/RejectionAssetD3.tsx 0.0 14.3 +14.3
src/components/Card/CardRejectionScreen.tsx 0.0 8.6 +8.6
src/components/Badges/BadgeEarnToast.tsx 0.0 7.7 +7.7
src/components/Home/CardLaunchCTA/index.tsx 0.0 7.5 +7.5
src/components/Card/share-asset/ScaledRejectionAsset.tsx 0.0 6.8 +6.8
src/components/Badges/badgeCelebration.utils.ts 0.0 5.4 +5.4
src/components/Badges/useBadgeEarnToast.ts 0.0 5.3 +5.3
src/app/(mobile-ui)/dev/home-ctas/page.tsx 0.0 5.3 +5.3
src/components/Home/CardLaunchCTA/CardLaunchCTABanner.tsx 0.0 5.1 +5.1
src/utils/cross-chain-fee.utils.ts 0.0 4.2 +4.2
src/app/(mobile-ui)/dev/rejection-builder/page.tsx 0.0 4.1 +4.1
src/components/Home/CardLaunchCTA/cardLaunchCTA.utils.ts 0.0 3.7 +3.7
src/components/Card/share-asset/winCaptions.ts 0.0 3.6 +3.6
src/components/Card/share-asset/rejectionCaptions.ts 0.0 3.4 +3.4
src/components/Badges/BadgeDetailModal.tsx 0.0 2.8 +2.8
src/components/Withdraw/views/Confirm.withdraw.view.tsx 7.3 9.1 +1.7
src/components/Card/share-asset/shareAssetLayout.ts 11.6 12.6 +1.1
src/components/Global/TokenSelector/TokenSelector.tsx 10.6 11.6 +1.0
src/components/Global/TokenSelector/TokenSelector.consts.ts 4.9 5.7 +0.8
src/app/(mobile-ui)/dev/page.tsx 8.6 9.3 +0.7

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1598 ran, 0 failed, 0 skipped, 23.1s

📊 Coverage (unit)

metric %
statements 54.4%
branches 37.0%
functions 42.0%
lines 54.3%
⏱ 10 slowest test cases
time test
3.6s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s 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/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Perk claimed shows shake class + go home button
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Perk claim in progress shows disabled button + progress
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
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 ETH address in lowercase
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle unresolvable ENS name
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

Hugo0 added 3 commits June 29, 2026 02:16
Unify the activation-funnel card step to render the #2295 /shhhhh-tone CardLaunchCTABanner so non-activated card-eligible users get the same mysterious CTA as the activated launch splash (keeps the /card routing + Maybe-later dismiss). Fix Visa merchant count to 150M+ (was 40m in ActivationCTAs, 140M in AddCardEntryScreen) to match /shhhhh. Add reliable keyless Arb-Sepolia RPC fallbacks (publicnode + drpc) ahead of the 503-prone official endpoint so harness smart-wallet deploys stop failing on transient 503s. Sandbox-relevant testnet RPC entry; prod wallet chain (arbitrum mainnet) untouched.
…itlist only on re-visit

Bare /shhhhh door no longer joins the waitlist inline. Post-launch it routes to /card, where the press-and-hold eligibility -> Berghain 'not tonight' rejection IS the join moment (matches the home launch CTA). Reads BE waitlist state on mount so a RETURNING joined user sees the inline 'on the waitlist #N' pill instead of the door. Signed-out door -> signup -> /card. Drops the joinWaitlistAfterSignup cookie entirely (kills the stale-cookie auto-join surprise). Pre-launch (no access, /card 404s) still joins inline.
A returning joined user can tap the inline 'on the waitlist #N' confirmation to re-open /card (their waitlist-joined status) instead of it being a dead pill.
…aunchCTA

Konrad's call: max the launch buzz on Twitter, hold the in-app nudge and
convert the existing base on our own schedule. The mysterious card CTA only
ever reaches existing users today (funnel card step gates on a card-access
badge; home splash gates on isActivated), so muting both paths delays it for
everyone who'd see it. New users keep the normal verify → deposit → outbound
funnel, and /card stays reachable (door, waitlist pill, direct link).

Default true (off) now; flip to false ~2-3 days post-launch to start in-app
conversion.
Hugo0 added 2 commits June 29, 2026 03:05
… explainer

Konrad's call: ship the in-app nudge now (not delayed), but rework it so it
actually converts. Copy is now just the whisper — title 'shhh' + 'Tap to find
out if you're in' + 'Try the door →'. Routes to /shhhhh (the explainer landing)
instead of /card directly: the bare card flow doesn't explain anything, so we
funnel through the landing which walks users into the canonical flow.

disableCardLaunchCTA flips to false (CTA live) and stays as a kill-switch.
On mobile the rotated PixelatedCardFace in the hero column sat practically
flush against the marquee below — the rotated card's bounding box overflows
its layout box downward and ate the section's bottom padding. Add pb-16 on
mobile (reset md:pb-0 — desktop is a 2-col layout, no collision). Launch-day
polish, direct to dev.
@Hugo0 Hugo0 merged commit cfc9955 into main Jun 29, 2026
22 of 25 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.

5 participants