feat(activation): Spend-with-Peanut chooser + /dev/journey Activation Journey Explorer (TASK-20471 + 20596 tooling) - #2475
Conversation
The home activation 'outbound' step was QR-only (straight to the scanner) while card spend counts as activation too and the card is the main attraction. Card-access users now see 'Spend with Peanut' copy and a card/QR chooser (card -> /card, QR -> existing scanner). Users without card access keep the exact previous copy and behavior so a gated card is never teased. Chooser exposure and choice are tracked for funnel analysis.
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
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:
📝 WalkthroughWalkthroughChangesOutbound Spend Selection
Activation Journey Explorer
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Developer
participant JourneyExplorerPage
participant useJourneySpec
participant JourneySpecAPI
participant JourneyBoard
participant UserInspector
participant JourneyInspectAPI
Developer->>JourneyExplorerPage: open /dev/journey
JourneyExplorerPage->>useJourneySpec: fetch journey specification
useJourneySpec->>JourneySpecAPI: GET /__dev/journey-spec
JourneySpecAPI-->>JourneyBoard: return stages, rules, and reminders
Developer->>UserInspector: submit userId
UserInspector->>JourneyInspectAPI: GET /__dev/journey-inspect
JourneyInspectAPI-->>UserInspector: return due state and history
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code-analysis diffPainscore total: 6231.08 → 6231.08 (0) |
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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/components/Home/ActivationCTAs.tsx`:
- Line 145: Update the showSpendChooser state and its rendering in
ActivationCTAs so the chooser is hidden whenever current hasCardAccess is false,
even if stale state remains true. Add an effect or equivalent state
synchronization to clear showSpendChooser when access is revoked, and add a
regression test covering access refresh from true to false.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a9b95e3f-034f-474c-b4d8-7422f1394d8f
📒 Files selected for processing (3)
src/components/Home/ActivationCTAs.tsxsrc/components/Home/__tests__/ActivationCTAs.test.tsxsrc/constants/analytics.consts.ts
CodeRabbit: a card-info refetch can flip hasCardAccess to false while the chooser modal is open, leaving a no-access user one tap from a gated card. Gate the modal's visibility on current access and clear the stale open state; regression test covers the revocation path.
|
@coderabbitai review |
✅ Action performedReview finished.
|
One internal page answering "what does a user in state X actually experience?" — per funnel state, every in-app activation surface (verbatim copy + gating condition + source file, transcribed from the journey UI inventory so drift is traceable) next to every lifecycle email/push, fetched LIVE from the sandbox API's __dev/journey-spec (api PR #1234) so the board always shows the real machine. Degrades honestly when the running API predates the endpoint. Includes the inventory's 7 product-issue findings as collapsible warning cards and a live per-user nudge inspector.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/app/(mobile-ui)/dev/journey/EmailCard.tsx (1)
3-16: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winLive
spec.emailPreviewBaseis fetched but never used to build preview links.EmailCardhardcodes the preview path againstJOURNEY_API_BASE, andJourneyBoardnever threads the spec's ownemailPreviewBasedown to it — undermining the stated goal of always reflecting the live machine, not a hand-copied path.
src/app/(mobile-ui)/dev/journey/EmailCard.tsx#L3-L16: accept an optionalpreviewBaseprop, fall back to the current hardcoded path only when it's absent, and wrapstep.typeinencodeURIComponent.src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx#L49-L67: passpreviewBase={spec?.emailPreviewBase}to eachEmailCardusage (welcome step and per-stage steps).🤖 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/`(mobile-ui)/dev/journey/EmailCard.tsx around lines 3 - 16, Update src/app/(mobile-ui)/dev/journey/EmailCard.tsx lines 3-16 to accept an optional previewBase prop, use it when provided, retain the existing JOURNEY_API_BASE fallback otherwise, and encode step.type with encodeURIComponent when building the preview URL. Update both welcome and per-stage EmailCard usages in src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx lines 49-67 to pass previewBase={spec?.emailPreviewBase}.
🤖 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/`(mobile-ui)/dev/journey/useJourneySpec.ts:
- Around line 25-33: Expand the response validation in the promise handler of
useJourneySpec to require the fields consumed by JourneyBoard and related
components: stages, pushReminders, rules, and welcome, including appropriate
array/object shapes. Reject malformed responses before setSpec(data) so the
existing catch path sets the “spec unavailable” error instead of allowing
render-time exceptions.
---
Nitpick comments:
In `@src/app/`(mobile-ui)/dev/journey/EmailCard.tsx:
- Around line 3-16: Update src/app/(mobile-ui)/dev/journey/EmailCard.tsx lines
3-16 to accept an optional previewBase prop, use it when provided, retain the
existing JOURNEY_API_BASE fallback otherwise, and encode step.type with
encodeURIComponent when building the preview URL. Update both welcome and
per-stage EmailCard usages in src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx
lines 49-67 to pass previewBase={spec?.emailPreviewBase}.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7c79a48b-ead9-4269-813f-13af75869ff9
📒 Files selected for processing (12)
src/app/(mobile-ui)/dev/journey/EmailCard.tsxsrc/app/(mobile-ui)/dev/journey/FindingsStrip.tsxsrc/app/(mobile-ui)/dev/journey/JourneyBoard.tsxsrc/app/(mobile-ui)/dev/journey/RulesLegend.tsxsrc/app/(mobile-ui)/dev/journey/SurfaceCard.tsxsrc/app/(mobile-ui)/dev/journey/UserInspector.tsxsrc/app/(mobile-ui)/dev/journey/__tests__/journeyData.test.tssrc/app/(mobile-ui)/dev/journey/journeyData.tssrc/app/(mobile-ui)/dev/journey/journeyTypes.tssrc/app/(mobile-ui)/dev/journey/page.tsxsrc/app/(mobile-ui)/dev/journey/useJourneySpec.tssrc/app/(mobile-ui)/dev/page.tsx
… finding Two Hugo-reviewed corrections. (1) The in-flow KYC terminal modal and home's WelcomeUnlockModal both celebrated "You're unlocked" — the flow modal never stamps activationCelebratedAt (by design: home's rich modal lists what unlocked), so users completing Sumsub in-flow got the celebration twice. Neutralize the in-flow terminal to "All set" so home owns THE celebration. (2) Explorer finding 3 was wrong: SHHHHH / CARD_FIRST_SWIPE / CARD_SPENT_1K all have live API award triggers (prod: 1,499/274/12 awards); the stale TODO(card-launch) comments in badge.utils.ts that misled the audit are deleted, the finding now records the retraction, and the inference-only finding on the unreachable card-override branch is flagged for runtime verification.
|
@coderabbitai review |
✅ Action performedReview finished.
|
…rmed 200s degrade gracefully instead of crashing
…-cta # Conflicts: # src/app/(mobile-ui)/dev/page.tsx
Summary
TASK-20471 — the home activation step "Make your first payment" (
outbound) was QR-only: its CTA jumped straight to the QR scanner, while card spend is the main attraction and counts as activation per the ledger-derivedisActivated. This PR broadens the step to card + QR for users who actually hold card access:cardInfo.hasCardAccess): step copy becomes "Spend with Peanut" ("Pay with your card or scan Pix and MercadoPago QR codes",credit-cardicon) and the CTA opens a small chooser (ActionModal, reused) with two options: Pay with your card →/card, Scan a QR code → the existing QR scanner.Screenshots
Captured on the real
/homeagainst the local sandbox (seededfundeduser, 375×667, real auth via dev cheats).Images live on the
pr-assets-2475orphan branch — delete it after merge.Analytics
Two new events in
analytics.consts.ts, following the existingactivation_*naming:activation_spend_chooser_shown— fired when the chooser opensactivation_spend_chooser_selected{ choice: 'card' | 'qr' }— fired on option tapDesign notes / accepted trade-offs
hasCardAccess, not shown to everyone — mentioning the card to a no-access user is itself a tease, so they keep the old QR-only copy verbatim.hasCardAccess === undefined(loading) deliberately collapses to the no-access path: a slow/cardinfo fetch can never tease the card.cardstep (CardLaunchCTABanner) first, so the chooser mostly serves users who already hold a card, dismissed the card nudge, or when the card launch CTA is muted. Expect modest event volume.Risks
None expected — FE-only, no backend or contract changes. The new branch is gated on
hasCardAccess, so non-card users (the vast majority of the funnel) take the identical code path as before.QA steps
outboundactivation step: home card reads "Make your first payment / Start paying to Pix and MercadoPago QR codes"; tapping Start Spending opens the QR scanner directly. No modal.outboundstep: card reads "Spend with Peanut / Pay with your card or scan Pix and MercadoPago QR codes"; tapping Start Spending opens the chooser. Pay with your card navigates to/card; Scan a QR code opens the scanner. Verifyactivation_spend_chooser_shown/_selectedin PostHog.Out of scope (deliberate)
The task's second lever — an always-visible card tile on home for card holders — is NOT in this PR. Today the home carousel only shows a card tile to users without card access ("Get your Peanut Card" waitlist acquisition in
useHomeCarouselCTAs); there is no persistent "spend with your card" tile for holders. Follow-up candidate.Tests
Extended
src/components/Home/__tests__/ActivationCTAs.test.tsxwith 6 cases covering: no-access (copy + straight-to-scanner), access-loading (undefined→ scanner), access (copy + chooser +shownevent), chooser→card (/card+ event payload), chooser→QR (scanner + event payload), and access-revoked-while-open (chooser closes). Full suite green; typecheck clean.Summary by CodeRabbit
Journey Explorer
Second half of this PR:
/dev/journey— Activation Journey Explorer (internal tool, TASK-20596 tooling). Per funnel state (no-access → access-pre-KYC → KYC'd-no-card → application-in-flight → card-active-unfunded → funded-no-spend → spent/graduated), one board column stacks everything a user experiences there:journeyData.ts, transcribed from the journey UI inventory; the feat(activation): Spend-with-Peanut chooser + /dev/journey Activation Journey Explorer (TASK-20471 + 20596 tooling) #2475 chooser is marked NEW in this PR)GET /__dev/journey-spec(api PR upgraded zerodev #1234): subject, "day N stuck" timing, links to rendered__dev/email-previewspec.pushRemindersPlus: the machine's global rules as a legend strip (governor / holdout / send window / caps), the inventory's 7 product-issue findings as collapsible warning cards, and a live user inspector (
__dev/journey-inspect) showing the currently-due nudge + lifecycle email history. Degrades honestly when the running API predates PR #1234 (the state screenshotted below — the sandbox API is on pre-#1234 code). Gated like every/devpage: notFound() on peanut.me.Screenshots
Tests
src/app/(mobile-ui)/dev/journey/__tests__/journeyData.test.ts— shape/coverage checks on the pure data: 7 states in order, every surface state-mapped + source-file-annotated, unique ids, every column populated, NEW-in-PR markers present, the 5 lifecycle stages each mapped to exactly one column, 7 findings annotated. Fetch glue deliberately untested (dev-only tool).Changelog — corrections (541db32)
activationCelebratedAt, so in-flow completers saw both. The in-flow terminal is now a neutral "All set / Your account is ready to go. / Continue"; home'sWelcomeUnlockModalis the single celebration (it lists what unlocked). No stamping added from the flow modal, by design.acknowledgments/card-spend-badges.ts+routes/card/waitlist.ts; prod: 1,499 / 274 / 12 awards). The real defect was staleTODO(card-launch)comments inbadge.utils.tsclaiming otherwise — deleted (comment-only), and the Explorer finding now records the correction (severity: low). The inference-only finding on the unreachable card-override branch is flagged as needing runtime verification before any deletion.