Skip to content

feat(home): flag-gated user-interview invite card in the carousel - #2654

Merged
jjramirezn merged 4 commits into
mainfrom
hotfix-user-interview-cta
Aug 10, 2026
Merged

feat(home): flag-gated user-interview invite card in the carousel#2654
jjramirezn merged 4 commits into
mainfrom
hotfix-user-interview-cta

Conversation

@abalinda

@abalinda abalinda commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Ask our most active users for a 15-min call with the team ("help shape the app") — one new card in the home carousel, shown only to a hand-picked cohort. The cohort lives in the PostHog flag user-interviews-invite (release condition on username), never in code: list edits and the kill switch are one click in PostHog, and no usernames ship in the JS bundle. The card opens the shared cal.com event (https://cal.com/hugo0+abalinda/dynamic) via the Capacitor-safe openExternalUrl helper and inherits the carousel's standard 7-day X-dismissal.

Temporary campaign (feature-gates doctrine: flags are scaffolding) — delete the card block, flag, and i18n keys when the interview round ends.

Task

TASK-21189 — user interviews PR (add cal./hugo+alex in carousel for quali users)

Behavior

  • Prod: card renders ONLY when the PostHog flag matches. Fail-closed — until the flag exists and flips on, this deploy is inert.
  • Previews/staging/local: nonProdBypass: true → card always visible for QA.
  • Click → user_interview_cta_clicked PostHog event, then cal.com in a new tab (web) / in-app browser (native).
  • X → standard 7-day-cooldown dismissal, same as every carousel card.
  • No viewed event: $feature_flag_called is only a rough impression proxy (fires per flag evaluation, prod only, dismissed users included). Bookings on cal.com are the campaign's real success metric; filter click insights by $host = peanut.me to exclude preview taps.

Risks / breaking changes

None. FE-only, additive, flag-gated off on prod by default. No backend, no migrations. Hotfix to main → back-merge main→dev owed after merge.

Design notes / accepted trade-offs

  • user_interview_cta_clicked also fires from previews/staging, where the card is always on via the nonProd bypass. Deliberately not gated in code: the cohort is ~25 users and we read the metric once — filter by $host = peanut.me. Bookings on cal.com are ground truth.
  • Dismissal uses the carousel's shared 7-day cooldown, so the card resurfaces weekly for targeted users while the flag is on. That is the intended nudge cadence for a short campaign; if it grates (cf. the open "home-screen modals and carousel misbehave" friction cluster), kill the flag or switch to a permanent dismissal.

Launch steps (post-merge, not in this PR)

  1. Deploy → verify inert (flag absent → card hidden).
  2. Create flag user-interviews-invite in PostHog (EU, project 138913): boolean, release condition username is one of [curated list] — Notion engaged-user list minus exclusions; cross-check spellings against prod DB first.
  3. Flip on; watch $feature_flag_called (impressions) and user_interview_cta_clicked (clicks); bookings land on cal.com.

QA

  • Local gate: prettier ✅ · tsc --noEmit ✅ · npm test 225 suites / 2886 tests ✅ (i18n parity test covers the new keys in en/es-419/pt-BR + es-AR voseo delta) · npm run build
  • /dev/home-ctas renders the new card variant with no auth or flag needed.
  • Local/preview: card leads the carousel via the nonProd bypass; click opens cal.com; X dismisses for 7 days.

Screenshots

From /dev/home-ctas (the home-CTA visual catalogue — exact CarouselCTA render, no gating):

New card In the catalogue, next to the bug-bounty card
user-interview card catalogue context

⚠️ No in-situ /home shot: the local sandbox has no FE-activated seeded user (activation = one completed spend intent), direct DB seeding is permission-blocked for agents, and POST /dev/seed-scenario currently 500s (Cannot read properties of undefined (reading 'query')). The card mounts through the same single if gate as every carousel card; any Vercel preview shows it on /home for an activated user via the nonProd bypass. Assets branch pr-assets-2654 — delete after merge.

Ask our most active users for a 15-min founder call (TASK-21189).
The cohort lives in the PostHog flag user-interviews-invite (release
condition on username), never in code - list edits and the kill
switch are one click in PostHog and no usernames ship in the bundle.
The card leads the carousel for targeted users, opens the shared
cal.com event via the Capacitor-safe helper, and inherits the
standard 7-day X-dismissal. Temporary campaign: delete the card,
flag, and i18n keys when it ends.
@vercel

vercel Bot commented Aug 10, 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 Aug 10, 2026 2:40pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a feature-flagged user-interview CTA to the home carousel. The CTA uses localized copy and mascot branding, records click analytics, and opens a shared Cal.com booking URL. A development preview is also added.

Changes

User interview CTA

Layer / File(s) Summary
Campaign contracts and localized content
src/constants/analytics.consts.ts, src/constants/general.consts.ts, src/i18n/app/messages/*.json
Adds the CTA click event, booking URL, and user-interview translations.
Feature-flagged carousel integration
src/hooks/useHomeCarouselCTAs.tsx
Adds eligibility checks, CTA generation, click tracking, external booking navigation, and feature-flag dependency handling.
CTA preview catalogue
src/app/(mobile-ui)/dev/home-ctas/page.tsx
Adds a development preview with the waving mascot asset and interview invitation copy.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HomeCarousel as Home carousel
  participant useHomeCarouselCTAs
  participant PostHog
  participant Analytics as Analytics registry
  participant CalCom as Cal.com
  HomeCarousel->>useHomeCarouselCTAs: Request carousel CTAs
  useHomeCarouselCTAs->>PostHog: Check user-interviews-invite flag
  PostHog-->>useHomeCarouselCTAs: Return campaign eligibility
  useHomeCarouselCTAs-->>HomeCarousel: Return user-interview CTA
  HomeCarousel->>useHomeCarouselCTAs: Handle CTA click
  useHomeCarouselCTAs->>Analytics: Record USER_INTERVIEW_CTA_CLICKED
  useHomeCarouselCTAs->>CalCom: Open USER_INTERVIEW_CAL_URL
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 clearly and concisely describes the flag-gated user-interview invite card added to the home carousel.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix-user-interview-cta

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

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7141.32 → 7143.02 (+1.7)
Findings: 0 net (+12 new, -12 resolved)

🆕 New findings (12)

  • critical complexity — src/hooks/useHomeCarouselCTAs.tsx — CC 69, MI 57.14, SLOC 315
  • high hotspot — src/constants/analytics.consts.ts — 43 commits, +352/-13 lines since 6 months ago
  • high hotspot — src/hooks/useHomeCarouselCTAs.tsx — 38 commits, +449/-270 lines since 6 months ago
  • high method-complexity — src/hooks/useHomeCarouselCTAs.tsx:147 — CC 30 SLOC 142
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 32.24, SLOC 184
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:82 — useHomeCarouselCTAs: MDD 168.3 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:147 — : MDD 120.6 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/useHomeCarouselCTAs.tsx:82 — useHomeCarouselCTAs: DLT 48 (calls 48 distinct functions — high context load)
  • medium complexity — src/app/(mobile-ui)/dev/home-ctas/page.tsx — CC 7, MI 56.3, SLOC 92
  • low high-dlt — src/hooks/useHomeCarouselCTAs.tsx:147 — : DLT 22 (calls 22 distinct functions — high context load)
  • low missing-return-type — src/app/(mobile-ui)/dev/home-ctas/page.tsx:172 — HomeCTAsPreviewPage: exported fn missing return type annotation
  • low missing-return-type — src/hooks/useHomeCarouselCTAs.tsx:82 — useHomeCarouselCTAs: exported fn missing return type annotation

✅ Resolved (12)

  • src/hooks/useHomeCarouselCTAs.tsx — CC 67, MI 57.4, SLOC 295
  • src/constants/analytics.consts.ts — 41 commits, +345/-12 lines since 6 months ago
  • src/hooks/useHomeCarouselCTAs.tsx — 35 commits, +407/-263 lines since 6 months ago
  • src/constants/analytics.consts.ts — CC 1, MI 32.3, SLOC 183
  • src/hooks/useHomeCarouselCTAs.tsx:78 — useHomeCarouselCTAs: MDD 150.9 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:137 — : MDD 106.1 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:78 — useHomeCarouselCTAs: DLT 44 (calls 44 distinct functions — high context load)
  • src/hooks/useHomeCarouselCTAs.tsx:137 — CC 29 SLOC 130
  • src/app/(mobile-ui)/dev/home-ctas/page.tsx — CC 7, MI 57.21, SLOC 85
  • src/hooks/useHomeCarouselCTAs.tsx:137 — : DLT 20 (calls 20 distinct functions — high context load)
  • src/app/(mobile-ui)/dev/home-ctas/page.tsx:162 — HomeCTAsPreviewPage: exported fn missing return type annotation
  • src/hooks/useHomeCarouselCTAs.tsx:78 — useHomeCarouselCTAs: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/hooks/useHomeCarouselCTAs.tsx 13.8 14.4 +0.6

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2890 ran, 0 failed, 0 skipped, 48.3s

📊 Coverage (unit)

metric %
statements 66.0%
branches 50.9%
functions 56.0%
lines 66.7%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.2s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.3s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
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/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 validateInviteCode
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
0.3s src/utils/__tests__/auth-token.test.ts › authReady does not park — hydrates the plain token without an unlock
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/utils/__tests__/auth-token.test.ts › removes the guarded item and marker and releases parked callers
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 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: 2

🤖 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/constants/general.consts.ts`:
- Line 251: Update the USER_INTERVIEW_CAL_URL constant to use the active Cal.com
15-minute event URL at hugo0/15min instead of the dead aleks-n-hugo/15min link.

In `@src/hooks/useHomeCarouselCTAs.tsx`:
- Around line 167-168: Update the openExternalUrl call in CarouselCTA to attach
a catch handler that displays a localized toast using the
userInterview.openError translation key, while preserving the existing
fire-and-forget behavior. Add userInterview.openError to every locale catalog.
🪄 Autofix

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: 2a16eb68-244e-4f5a-a737-a34318b18d49

📥 Commits

Reviewing files that changed from the base of the PR and between eabc4de and 6ebb7e5.

📒 Files selected for processing (8)
  • src/app/(mobile-ui)/dev/home-ctas/page.tsx
  • src/constants/analytics.consts.ts
  • src/constants/general.consts.ts
  • src/hooks/useHomeCarouselCTAs.tsx
  • src/i18n/app/messages/en.json
  • src/i18n/app/messages/es-419.json
  • src/i18n/app/messages/es-AR.json
  • src/i18n/app/messages/pt-BR.json

Comment thread src/constants/general.consts.ts Outdated
Comment thread src/hooks/useHomeCarouselCTAs.tsx Outdated
…rdown checklist

Applied from the automated review pass: (1) await openExternalUrl so a
native Browser.open failure surfaces in CarouselCTA's onClick catch
instead of a detached rejection; (2) the analytics comment no longer
claims flag exposures equal impressions (they fire per evaluation,
prod-only, dismissed users included) and points readers at cal.com
bookings + a $host filter; (3) the delete-together checklist now names
the dev/home-ctas preview entry so teardown removes all three copies.
The committed aleks-n-hugo/15min event 404s (CodeRabbit catch, verified).
Replaced with the hugo0+abalinda dynamic group link, which resolves and
books both hosts' mutual availability.
@abalinda
abalinda marked this pull request as ready for review August 10, 2026 14:21
@abalinda
abalinda requested a review from Hugo0 August 10, 2026 14:21
Aleks isn't a founder; only Hugo is. All four locales, the dev preview,
and the code comments now say team.
@jjramirezn
jjramirezn merged commit 11601d0 into main Aug 10, 2026
26 checks passed
jjramirezn added a commit that referenced this pull request Aug 10, 2026
ui: render the waving mascot at 44px (follow-up to #2654)
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.

2 participants