Skip to content

feat(activation): Spend-with-Peanut chooser + /dev/journey Activation Journey Explorer (TASK-20471 + 20596 tooling) - #2475

Merged
Hugo0 merged 6 commits into
devfrom
feat/spend-with-peanut-cta
Jul 24, 2026
Merged

feat(activation): Spend-with-Peanut chooser + /dev/journey Activation Journey Explorer (TASK-20471 + 20596 tooling)#2475
Hugo0 merged 6 commits into
devfrom
feat/spend-with-peanut-cta

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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-derived isActivated. This PR broadens the step to card + QR for users who actually hold card access:

  • With card access (cardInfo.hasCardAccess): step copy becomes "Spend with Peanut" ("Pay with your card or scan Pix and MercadoPago QR codes", credit-card icon) 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.
  • Without card access (including while card-info is still loading): exact previous copy and behavior — straight to the scanner, no modal, no card tease.
  • If card access is revoked while the chooser is open (card-info refetch), the chooser closes — visibility is gated on current access (CodeRabbit review fix).

Screenshots

Captured on the real /home against the local sandbox (seeded funded user, 375×667, real auth via dev cheats).

Before (no card access — unchanged) After (card access) Chooser
no card access card access chooser modal

Images live on the pr-assets-2475 orphan branch — delete it after merge.

Analytics

Two new events in analytics.consts.ts, following the existing activation_* naming:

  • activation_spend_chooser_shown — fired when the chooser opens
  • activation_spend_chooser_selected { choice: 'card' | 'qr' } — fired on option tap

Design notes / accepted trade-offs

  • The card-inclusive copy is conditional on 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 /card info fetch can never tease the card.
  • The chooser reaches a deliberately narrow population: card-access users without a card are routed to the card step (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

  1. User without card access on the outbound activation 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.
  2. User with card access (skip badge / admin grant) on the outbound step: 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. Verify activation_spend_chooser_shown / _selected in PostHog.
  3. Regression: verify/deposit steps and the provider-rejection overrides unchanged.

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.tsx with 6 cases covering: no-access (copy + straight-to-scanner), access-loading (undefined → scanner), access (copy + chooser + shown event), 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

  • New Features
    • Added a “Spend with Peanut” spend-chooser during the outbound activation step for users with card access.
    • Card-access users can pick either opening their card or starting QR scanning.
    • Introduced an “Activation Journey” developer explorer with a new journey board and supporting inspection/legend/finding views.
  • Bug Fixes
    • The spend chooser now closes automatically if card access is removed while it’s open.
  • Tests
    • Added coverage for the outbound spend-chooser flow, including analytics tracking and revocation behavior.

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:

Plus: 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 /dev page: notFound() on peanut.me.

Screenshots

Journey board (cols 1–3) Board (cols 4–6, NEW chips + degraded spec panels)
board board right
Findings strip User inspector (graceful error vs old API)
findings inspector

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)

  • Celebration dedupe (Hugo-approved, verified): the in-flow KYC terminal modal and home's WelcomeUnlockModal both said "You're unlocked" — and the flow modal never stamps 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's WelcomeUnlockModal is the single celebration (it lists what unlocked). No stamping added from the flow modal, by design.
  • Explorer finding 3 RETRACTED (prod-verified by Hugo): SHHHHH / CARD_FIRST_SWIPE / CARD_SPENT_1K all have live backend award triggers (acknowledgments/card-spend-badges.ts + routes/card/waitlist.ts; prod: 1,499 / 274 / 12 awards). The real defect was stale TODO(card-launch) comments in badge.utils.ts claiming 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.

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

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

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.

@vercel

vercel Bot commented Jul 22, 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 Jul 24, 2026 10:53am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a3bec80-badc-4e29-a77f-60f047d02d48

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Outbound Spend Selection

Layer / File(s) Summary
Spend chooser analytics contract
src/constants/analytics.consts.ts
Adds events for displaying and selecting the outbound spend chooser.
Outbound card and QR choice flow
src/components/Home/ActivationCTAs.tsx
Uses card access to change outbound CTA content and route users to the card screen or QR scanner.
Spend chooser behavior coverage
src/components/Home/__tests__/ActivationCTAs.test.tsx
Tests card-access, non-card-access, loading, analytics, navigation, and QR scanner behavior.

Activation Journey Explorer

Layer / File(s) Summary
Journey contracts and catalog
src/app/(mobile-ui)/dev/journey/journeyTypes.ts, src/app/(mobile-ui)/dev/journey/journeyData.ts, src/app/(mobile-ui)/dev/journey/__tests__/journeyData.test.ts
Defines journey data types, funnel states, in-app surfaces, findings, and consistency checks.
Live journey specification board
src/app/(mobile-ui)/dev/journey/useJourneySpec.ts, src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx, src/app/(mobile-ui)/dev/journey/EmailCard.tsx, src/app/(mobile-ui)/dev/journey/SurfaceCard.tsx, src/app/(mobile-ui)/dev/journey/RulesLegend.tsx
Fetches the sandbox journey specification and renders funnel columns with in-app, email, push, and rules content.
Explorer page and user inspection
src/app/(mobile-ui)/dev/journey/page.tsx, src/app/(mobile-ui)/dev/journey/UserInspector.tsx, src/app/(mobile-ui)/dev/journey/FindingsStrip.tsx, src/app/(mobile-ui)/dev/page.tsx
Composes the explorer page, exposes user lifecycle inspection and findings, and adds the tool to the Dev Tools index.

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
Loading

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: the Spend-with-Peanut chooser and the /dev/journey Activation Journey Explorer.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/spend-with-peanut-cta

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

@notion-workspace

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6231.08 → 6231.08 (0)
Findings: 0 net (+0 new, -0 resolved)

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2052 ran, 0 failed, 0 skipped, 32.2s

📊 Coverage (unit)

metric %
statements 60.1%
branches 43.5%
functions 49.0%
lines 60.4%
⏱ 10 slowest test cases
time test
3.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.1s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/utils/__tests__/demo-balance.test.ts › starts at the full balance on a fresh install and stamps a timestamp
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.1s src/utils/__tests__/demo-balance.test.ts › debits and floors at zero
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@Hugo0

Hugo0 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 74f02f5 and 4371a23.

📒 Files selected for processing (3)
  • src/components/Home/ActivationCTAs.tsx
  • src/components/Home/__tests__/ActivationCTAs.test.tsx
  • src/constants/analytics.consts.ts

Comment thread src/components/Home/ActivationCTAs.tsx
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.
@Hugo0

Hugo0 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.
@Hugo0 Hugo0 changed the title feat(activation): Spend with Peanut — card + QR chooser on first-payment step (TASK-20471) feat(activation): Spend-with-Peanut chooser + /dev/journey Activation Journey Explorer (TASK-20471 + 20596 tooling) Jul 24, 2026
@Hugo0

Hugo0 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app/(mobile-ui)/dev/journey/EmailCard.tsx (1)

3-16: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Live spec.emailPreviewBase is fetched but never used to build preview links. EmailCard hardcodes the preview path against JOURNEY_API_BASE, and JourneyBoard never threads the spec's own emailPreviewBase down 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 optional previewBase prop, fall back to the current hardcoded path only when it's absent, and wrap step.type in encodeURIComponent.
  • src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx#L49-L67: pass previewBase={spec?.emailPreviewBase} to each EmailCard usage (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

📥 Commits

Reviewing files that changed from the base of the PR and between 93b79c8 and 3af09a5.

📒 Files selected for processing (12)
  • src/app/(mobile-ui)/dev/journey/EmailCard.tsx
  • src/app/(mobile-ui)/dev/journey/FindingsStrip.tsx
  • src/app/(mobile-ui)/dev/journey/JourneyBoard.tsx
  • src/app/(mobile-ui)/dev/journey/RulesLegend.tsx
  • src/app/(mobile-ui)/dev/journey/SurfaceCard.tsx
  • src/app/(mobile-ui)/dev/journey/UserInspector.tsx
  • src/app/(mobile-ui)/dev/journey/__tests__/journeyData.test.ts
  • src/app/(mobile-ui)/dev/journey/journeyData.ts
  • src/app/(mobile-ui)/dev/journey/journeyTypes.ts
  • src/app/(mobile-ui)/dev/journey/page.tsx
  • src/app/(mobile-ui)/dev/journey/useJourneySpec.ts
  • src/app/(mobile-ui)/dev/page.tsx

Comment thread src/app/(mobile-ui)/dev/journey/useJourneySpec.ts
… 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.
@Hugo0

Hugo0 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…rmed 200s degrade gracefully instead of crashing
@Hugo0
Hugo0 marked this pull request as ready for review July 24, 2026 10:43
…-cta

# Conflicts:
#	src/app/(mobile-ui)/dev/page.tsx
@Hugo0
Hugo0 merged commit 5f6590f into dev Jul 24, 2026
4 of 5 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.

1 participant