Skip to content

URL: https://mkdocs.justinsforge.com/memory/general/reference_creatortrack_onboarding/

CreatorTrack New-User Onboarding Wizard

Built 2026-07-10 on branch feat/onboarding-wizard (plan: creatortrack-onboarding-wizard-2026-07-10). Merged to main 2026-07-11 (merge commit 5603bcf, later included in the 3306ade deploy train); LIVE on prod. Distinct from the older per-workspace guided tour (lib/onboarding.ts, /api/onboarding); the two coexist, the tour still fires after the wizard.

Flow

  1. Signup (lib/auth-credentials.ts) redirects new users to /?welcome=1&verify=1 (unchanged).
  2. The / root hop (app/(suite)/page.tsx) checks app.onboarding_responses: no row means never seen the wizard, detour to /onboarding carrying the query params. Deep links never detour; /onboarding bounces done users home, so no traps.
  3. Wizard (app/(onboarding)/onboarding/page.tsx + components/onboarding/OnboardingWizard.tsx): 7 questions, one per screen, progress bar, Skip link from Q2. Session-gated (not in proxy PUBLIC_PATHS).
  4. Complete: POST /api/onboarding/wizard upserts the row, subscribes opted-in emails to the Resend audience (best-effort, logged loud), returns 3-5 ranked apps. Final "Your starter setup" screen: cards with icon, one-line reason, early-access badge on preview apps (University), per-card deselect, "Install and continue" CTA hitting /api/onboarding/install.
  5. Skip: row stamped skipped_at, default set (tasks, calendar, knowledge) auto-installed server-side, straight into the app.

Data

app.onboarding_responses (migration 20260710T225738_...onboarding_wizard_responses.sql): user_id PK, answers jsonb, email_opt_in, completed_at, skipped_at. Per-user RLS (app.user_id GUC pattern). All pre-existing users were grandfathered with a {"grandfathered": true} skipped row so they never see the wizard. Upserts are idempotent; completed/skipped stamps are first-write-wins.

Recommendation engine

lib/onboarding-wizard.ts: weighted scoring question:answer -> appId (learn -> university, organize -> tasks/projects/calendar, clips goal -> clips, sponsor -> sales, money -> finance/business, grow -> content-research/social). Availability-filtered through the App Library chokepoints (isAppAllowedForEmail, isAppAllowedInWorkspace, owner-only gates), so allowlisted apps (Clips, Content Research) never surface to users who cannot install them; backfills from the default set to a minimum of 3. Installs reuse installSystemApp / addInstalledApp (the exact App Library path). Canned checks: scripts/smoke-onboarding-wizard.ts (npx tsx).

Email opt-in (Resend)

Audience "CreatorTrack Updates", id in ~/.forge-secrets/resend.env as RESEND_UPDATES_AUDIENCE_ID (created 2026-07-10 via API, id 4680c9f5-2303-414d-a440-c7cc7940f6b9). subscribeToUpdatesAudience() in lib/email.ts; contact upserts are idempotent. Subscribe failures never block onboarding completion.

PostHog events

onboarding_started, onboarding_question_answered (question_id, answer), onboarding_skipped (at_step), onboarding_completed (email_opt_in), onboarding_app_installed (app_id). Client capture via the existing PostHogClient mount (runtime POSTHOG_KEY gating), also mounted by app/(onboarding)/layout.tsx. Gotcha: posthog-js silently drops all events from HeadlessChrome (bot UA filter); verify with a real UA override, not vanilla Playwright.

Merge status

  • Merged to main 2026-07-11 (merge commit 5603bcf, later included in the 3306ade deploy train). Migration 20260710T225738_feat_onboarding_wizard_onboarding_wizard_responses.sql applied to prod lifeos on 2026-07-11; its grandfather insert protected every existing prod user.
  • Env: RESEND_UPDATES_AUDIENCE_ID already in ~/.forge-secrets/resend.env; prod service loads resend.env via lib/secrets.ts (it does, same file).
  • No PUBLIC_PATHS/proxy change needed.

[Claude Code]