Skip to content

feat(shhhhh): polish card share asset for Twitter (peanut blue, hero card, clean year)#2196

Open
Hugo0 wants to merge 6 commits into
chore/sync-main-dev-0606from
feat/shhhhh-visual-polish
Open

feat(shhhhh): polish card share asset for Twitter (peanut blue, hero card, clean year)#2196
Hugo0 wants to merge 6 commits into
chore/sync-main-dev-0606from
feat/shhhhh-visual-polish

Conversation

@Hugo0
Copy link
Copy Markdown
Contributor

@Hugo0 Hugo0 commented Jun 7, 2026

What

Visual polish of the card share asset (ShareAssetD3) per the Waitlist Feedback doc, optimised for how it reads on a Twitter timeline.

Feedback Change
"BG violates brandbook / doesn't pop" #efe4ff lavender → #90A8ED peanut blue (reused from the prod LP businessBgColor / --background-color, as one DRY const)
"card too small / whitespace huge / should fill the space" card is the hero: CARD_W 620→760 (52%→63% of canvas), centred
"the ''' look buggy" stamp year '252025 (the leading apostrophe read as a stray tick)
"badges too small" low badge-count stamps bumped for thumbnail legibility (high counts unchanged — the non-overlap invariant + jitter leaves no headroom)

The /shhhhh LP hero uses the same PixelatedCardFace, so its scale was compensated (0.645→0.526) to keep its on-screen footprint unchanged by the shared CARD_W bump.

Gate (local)

  • prettier --check clean · typecheck exit 0 · npm test = 1306 passed / 76 suites
  • shareAssetLayout.test.ts non-overlap + canvas-bounds invariants stay green (24/24)

Notes

  • Stacks on chore: sync main into dev (2026-06-06) #2190 (the main→dev back-merge) — based on chore/sync-main-dev-0606 so the diff is just these 3 files; GitHub auto-retargets to dev once chore: sync main into dev (2026-06-06) #2190 merges.
  • Follow-ups (not in this PR): the /shhhhh desktop hero card bleeds slightly off the right (pre-existing rotation overflow) — left for a deliberate hero-framing pass; and the SINCE OCT '25 stat keeps its apostrophe (reads as a normal date, unlike the stamp tick).

… card, clean year

Addresses the Waitlist Feedback doc (share-asset half):
- BG #efe4ff lavender → #90A8ED peanut blue (reused from the prod LP
  businessBgColor / --background-color) so the asset pops on a timeline; the
  lavender was off-brandbook and washed out.
- Card is the hero now: CARD_W 620→760 (~63% of canvas), centred — was ~52%
  and read as "card too small / whitespace huge".
- Stamp year drops the leading apostrophe ('25 → 2025) — the tick read as a
  buggy stray mark on the stamp.
- Low badge-count stamps bumped for thumbnail legibility; high counts unchanged
  (the circumscribing-circle non-overlap invariant + jitter leaves no headroom).
- LP hero scale compensated 0.645→0.526 so its on-screen footprint is unchanged
  by the shared CARD_W bump (hero left as-is; its framing is a separate follow-up).

Layout non-overlap + bounds tests stay green.
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jun 7, 2026 6:55pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

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: 0552b88f-09ad-42b3-9495-ef0f1c5839a6

📥 Commits

Reviewing files that changed from the base of the PR and between 49a4fcd and d756fe7.

📒 Files selected for processing (1)
  • src/app/shhhhh/ShhhhhLandingPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/shhhhh/ShhhhhLandingPage.tsx

Walkthrough

Brand-color and layout updates for shared card assets; add persistent skip-celebration field and POST API; retire the synthetic card-unlock activity entry when seen; update HomeHistory truncation, tests, and the Shhhhh landing page to implement new waitlist/skip CTA flows and hook renames.

Changes

Share Asset Card Styling and Layout

Layer / File(s) Summary
Share asset brand color system
src/components/Card/share-asset/ShareAssetD3.tsx
Introduces ASSET_BG constant with peanut blue (#90A8ED) and uses it for the main asset background and the --stamp-bg CSS variable, replacing #efe4ff.
Card layout and stamp sizing configuration
src/components/Card/share-asset/shareAssetLayout.ts
Increases CARD_W (620 → 760), recalculates CARD_H, centers CARD_LEFT/CARD_TOP, and replaces STAMP_SIZE_BY_COUNT with new sizes for badge counts 1–10.
Stamp year formatting
src/components/Card/share-asset/shareAssetLayout.ts
Change stamp year rendering to full four-digit year when badge.earnedAt is present.

Card skip-celebration lifecycle and activity feed; Shhhhh waitlist

Layer / File(s) Summary
Card service API and response shape
src/services/card.ts
Add `cardWaitlistSkipCelebrationSeenAt: string
deriveCardUnlockEntry gating and tests
src/components/Card/cardUnlock.types.ts, src/components/Card/__tests__/cardUnlock.types.test.ts, src/components/Card/__tests__/cardState.utils.test.ts
Add optional celebrationSeenAt input to deriveCardUnlockEntry and early-return null when set; add unit test for this case and extend cardInfo test helper to accept cardWaitlistSkipCelebrationSeenAt.
HomeHistory & mobile UI wiring
src/components/Home/HomeHistory.tsx, src/app/(mobile-ui)/card/page.tsx
Pass cardWaitlistSkipCelebrationSeenAt into deriveCardUnlockEntry, stop preserving the synthetic card-unlock row when truncating recent entries, and make markSkipCelebrationSeen() perform a best-effort cardApi.markCelebrationSeen() call (log errors on failure).
Shhhhh landing waitlist & CTA flow
src/app/shhhhh/ShhhhhLandingPage.tsx
Replace prior early-access/card prefetch with waitlist-centric flow: read /shhhhh?campaign=skip, implement joinWaitlist() and joined-state, detect joinWaitlistAfterSignup cookie for post-signup continuation, award skip badge via invitesApi for skip campaign, and conditionally render WaitlistJoined and the sticky CTA.
Hook rename and imports
src/hooks/useCardInfo.ts, src/app/(mobile-ui)/home/page.tsx, src/components/Profile/index.tsx, src/hooks/useHomeCarouselCTAs.tsx
Rename useCardPioneerInfouseCardInfo and update callers/imports to use the new hook name.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 accurately reflects the main changes: visual polish to the card share asset with three specific improvements (peanut blue background, larger hero card, clean year formatting).
Description check ✅ Passed The description comprehensively relates to the changeset, explaining the visual polish context, specific color/layout/formatting changes, and local testing validation.
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

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

deriveCardUnlockEntry derived the row from permanent inputs (skip badge /
cardAccessGrantedAt) with no seen-gate, and HomeHistory force-kept it past the
5-item cap → it never left the feed (Hugo: "always there, buggy").

- deriveCardUnlockEntry now takes celebrationSeenAt and returns null once set.
- HomeHistory passes cardInfo.cardWaitlistSkipCelebrationSeenAt + no longer pins
  the row (it ages out like any other entry).
- card/page.tsx stamps the BE column (cardApi.markCelebrationSeen) when the
  celebration is acknowledged, alongside the localStorage gate.
- CardInfoResponse + cardApi.markCelebrationSeen added; regression test asserts
  the row clears once celebrationSeenAt is set.

Pairs with peanut-api-ts #990 (exposes + writes the column).
Pure rename — the hook returns generic /card info (waitlist state + gates),
nothing Pioneer-specific. Renames the hook + its 4 call sites. No behaviour change.
…p Pass

Per Hugo (2026-06-07): visiting /shhhhh is NOT a bypass. The plain "Try the
door" + "or join the waitlist" now join the waitlist directly (cardApi.joinWaitlist)
and show an inline "✓ You're on the waitlist · #N" confirmation — no
flowEarlyAccess stamp, no /card detour, no badge. (Signed-out → signup with a
redirect back to /shhhhh + a cookie that finishes the join on return.)

Only /shhhhh?campaign=skip — the Skip Pass link — awards the WAITLIST_SKIP badge
(same contract as /invite?campaign=skip, via useZeroDev's post-signup campaignTag
branch) → the user is in. Pairs with the BE gate (peanut-api-ts #990): pre-launch
only Skip Pass holders get a card; everyone else waits.

The /card outer-gate (flowEarlyAccess) is no longer stamped from the bare door —
the waitlist screen lives behind that gate, so a direct inline join is the only
way to honour "no flowEarlyAccess grant". Removes the now-unused prefetch.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/shhhhh/ShhhhhLandingPage.tsx`:
- Around line 214-220: The invitesApi.awardBadge method returns an object with a
success property instead of throwing an error on failure. In the try block
containing the awardBadge call, you need to check the result of awaiting
invitesApi.awardBadge(SKIP_CAMPAIGN) and verify that the returned object has
success: true before proceeding with the posthog.capture call for
ANALYTICS_EVENTS.INVITE_ACCEPTED and the subsequent router.push to /home. If the
success property is false, handle the failure appropriately in the catch block
or add conditional logic to prevent tracking and routing when the badge award
fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90713730-ab3a-4c23-8fa1-36876e12546e

📥 Commits

Reviewing files that changed from the base of the PR and between 019e7df and 49a4fcd.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/home/page.tsx
  • src/app/shhhhh/ShhhhhLandingPage.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Profile/index.tsx
  • src/hooks/useCardInfo.ts
  • src/hooks/useHomeCarouselCTAs.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Home/HomeHistory.tsx

Comment on lines +214 to +220
await invitesApi.awardBadge(SKIP_CAMPAIGN)
posthog.capture(ANALYTICS_EVENTS.INVITE_ACCEPTED, { campaign_tag: SKIP_CAMPAIGN })
await fetchUser()
} catch (err) {
console.error('[shhhhh] awardBadge(skip) failed:', err)
}
router.push('/home')
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check awardBadge result before tracking success and routing.

Line 214 awaits invitesApi.awardBadge(...), but the API returns { success: false } for non-OK responses instead of throwing. The current flow still tracks INVITE_ACCEPTED (Line 215) and routes to /home (Line 220), which can silently drop the skip-badge award.

💡 Suggested fix
-            try {
-                await invitesApi.awardBadge(SKIP_CAMPAIGN)
-                posthog.capture(ANALYTICS_EVENTS.INVITE_ACCEPTED, { campaign_tag: SKIP_CAMPAIGN })
-                await fetchUser()
-            } catch (err) {
-                console.error('[shhhhh] awardBadge(skip) failed:', err)
-            }
-            router.push('/home')
+            try {
+                const award = await invitesApi.awardBadge(SKIP_CAMPAIGN)
+                if (!award.success) {
+                    console.error('[shhhhh] awardBadge(skip) failed: unsuccessful response')
+                    return
+                }
+                posthog.capture(ANALYTICS_EVENTS.INVITE_ACCEPTED, { campaign_tag: SKIP_CAMPAIGN })
+                await fetchUser()
+                router.push('/home')
+            } catch (err) {
+                console.error('[shhhhh] awardBadge(skip) failed:', err)
+            }
             return

Based on learnings from src/services/invites.ts, invitesApi.awardBadge returns { success: false } for non-OK responses instead of throwing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/shhhhh/ShhhhhLandingPage.tsx` around lines 214 - 220, The
invitesApi.awardBadge method returns an object with a success property instead
of throwing an error on failure. In the try block containing the awardBadge
call, you need to check the result of awaiting
invitesApi.awardBadge(SKIP_CAMPAIGN) and verify that the returned object has
success: true before proceeding with the posthog.capture call for
ANALYTICS_EVENTS.INVITE_ACCEPTED and the subsequent router.push to /home. If the
success property is false, handle the failure appropriately in the catch block
or add conditional logic to prevent tracking and routing when the badge award
fails.

…erender)

useSearchParams() bailed /shhhhh out of static generation → prerender error
in next build (Vercel red). Read ?campaign=skip from window.location.search in
a mount effect instead, keeping the marketing page statically prerendered.
/code-review: isSkipCampaign was useState+useEffect but only used in handleCTA,
not render — needless state + a theoretical mount-race on the gating path. Read
it inline in the handler instead (still client-only, no prerender impact).
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