v1.8 — Onboarding Wizard Overhaul
Replace the modal-based onboarding wizard with a full-page, persona-aware, slide-driven flow that lifts activation, hits a ≤60s time-to-first-goal budget, and ships as a reusable surface — without breaking any existing user.
Summary
The pre-v1.8 onboarding was a single hardcoded modal: every new user saw the same five slides, completion was tracked client-side via a localStorage flag, and the wizard could not branch by persona, source, or stated goal type. Activation analytics were anecdotal because there was no funnel to measure.
v1.8 rebuilt the experience as a full-page route at /welcome/:slideId, refactored the slide list into a configurable SlideRegistry<TState> consumed by a generic SlideWizardLayout.vue, moved completion state to a server-authoritative UserDetail.data['onboarding'] JSONB blob updated through a StoreOnboardingState mutation, and instrumented every slide step as a UserAction plus PostHog event so activation lift can be measured against the pre-v1.8 baseline.
The framework was deliberately separated from the onboarding surface so it could be reused. Phase 34 proved that out by shipping a profile-completion mini-wizard at /profile/complete that consumes SlideWizardLayout verbatim.
The milestone shipped in 6 phases, 19 plans, and 44 tasks — all 32 requirements satisfied, 10/10 cross-phase contracts verified, every phase nyquist_compliant: true. Two phases carry human_needed flags for explicit post-deploy manual checks (live PostHog ramp validation, physical-device a11y testing) — tracked in HUMAN-UAT files, not as gaps.
Goal
Transform the existing modal-based OnboardingWizard into a modern, full-page, persona-aware onboarding flow that measurably lifts activation, hits a ≤60s time-to-first-goal budget, and ships as a reusable slide-driven framework — without breaking the experience for any of the existing user base.
Scope — What Shipped
Server-authoritative onboarding state (Phase 32 — SAFE-* / SYNC-*)
- Nested
UserDetail.data['onboarding']JSONB schema (status, current_slide, persona, goal_type, source attribution). StoreOnboardingStatemutation with deep-merge semantics; status enum strictly limited tocomplete | skip.- Idempotent backfill inside
SyncUserretroactively marks every pre-v1.8 user complete on next sign-in. - SYNC-03 regression suite proves: double-call returns identical payloads; produces exactly one
UserAction.first_sign_inrow; backfills at most once; concurrentClerkUserSyncconverges to a singleUserrow viaRecordNotUniquerescue.
Apollo + first-run gate (Phase 32 — SYNC-04 / SYNC-05)
UserFragmentextended to include onboarding status; status flows into the Apollo cache.- Fail-closed PostHog flag wrapper at
src/lib/featureFlags.ts— unknown flags default tofalse. - New
useOnboardingGatecomposable is the single source of truth for/welcomegating. - Router guard invokes
useOnboardingGatevia a Phase-33-ready stub so the gate is ready when the new flow lands.
Legacy bridge (Phase 32)
- The pre-v1.8
OnboardingWizard.vuemodal now atomically commits skip/complete to the server throughStoreOnboardingState, fires fire-and-forget progress on every slide advance, and resumes mid-flow users from cachedonboardingStatus.progress.currentSlideon mount. - Backed by 12 green Vitest examples (6 exit + 6 resume); flips Phase 32's
nyquist_compliant: true.
Full-page route shell (Phase 33)
/welcome/:slideIdroute underIonPagefull-page chrome with progress bar, back, skip, next.- Module-scoped
useOnboardingFlowcomposable holds in-flight answers across browser navigation. - Phase 32 router-guard flip redirects first-run users to
WelcomeSlide. - Placeholder slide registry (welcome / name / preview / done) and SFCs with exact UI-SPEC copy + class strings + a11y markup;
DoneSlidecommits completion atomically via the Phase 32 mutation with non-blocking failure handling. - Two integration test surfaces —
welcomeFlow.integration+welcomeGate— assert the route shell + gate contract.
Reusable slide framework (Phase 34)
SlideDefinition<TState>types, three pure resolver functions with fail-closed hook wrapping and cycle detection, and a reactiveuseSlideFramework(registry, state, currentSlideIdRef)composable. Reactive validation gating + resolver delegation, 35 passing unit tests.- Phase 33's
WelcomeLayout.vuechrome extracted into a generic, framework-drivenSlideWizardLayout.vue; originalWelcomeLayoutrefactored to a ~105-LOC onboarding-specific wrapper that supplies only theStoreOnboardingStateskip mutation and terminal-advance delegation. placeholderSlides.tsretyped asSlideRegistry<OnboardingAnswers>with explicitvalidate: () => truehooks.- Profile-completion mini-wizard at
/profile/completeconsumesSlideWizardLayoutverbatim — second-surface proof of reusability.
Persona-aware branching (Phase 35)
- Six Coach personas extracted to a typed constant; persona-tuned copy map for post-persona slides; defensive URL/referrer attribution parser.
PersonaSlide(2x3 picker that commits viaUPDATE_COACHING_PREFERENCES_MUTATION).GoalTypeSlide(GraphQL category picker + explicit skip).SourceSlide(4-option self-report; auto-skipped when URL attribution exists).DoneSliderewritten to render persona-tuned closing copy viagetPersonaCopy.WelcomeLayout'sonMountedcallscaptureSourceAttributionand seedsflow.answers.sourceAttributiononce per session.- 7 integration tests cover PATH-01 through PATH-04 end-to-end.
Post-wizard activation + funnel analytics (Phase 36)
- Pre-v1.8
GettingStartedCard(225 lines of inline goal-create form) replaced with a lightweight glass-card "next moves" tile chooser surfacing 3–5 persona-tuned activation paths. recordUserAction(action: String!, metadata: JSON)GraphQL mutation extended to back the newwizard_*enum values.wizard_started,wizard_slide_viewed,wizard_slide_advanced,wizard_slide_skipped,wizard_completed,wizard_abandonedevents instrumented through bothUserActionand PostHog.
Visual polish + accessibility (Phase 37)
prefers-reduced-motionsupport extracted into a shared module.- Glassmorphism, design tokens, Outfit/Inter, motion aligned with the current design system.
- Dark/light parity verified at all iPhone viewports.
aria-liveannouncers on slide transitions; focus traps on modal-style skip confirmations; 44px tap targets across the flow.
Phases
| Phase | Name | Status | Plans | Highlights |
|---|---|---|---|---|
| 32 | Safety rails + first-run plumbing | Shipped | 5 | Server-authoritative onboarding state, idempotent backfill, fail-closed flag wrapper, legacy modal bridge |
| 33 | Full-page route shell | Shipped | 3 | /welcome/:slideId, useOnboardingFlow, placeholder registry, integration test surfaces |
| 34 | Slide-driven framework | Shipped | 3 | SlideDefinition<TState> + SlideWizardLayout + profile-completion proof of reuse |
| 35 | Persona-aware branching paths | Shipped | 2 | Persona/goal-type/source slides, attribution parser, 7-test PATH suite |
| 36 | Post-wizard activation + funnel analytics | Shipped | 3 | Tile chooser replaces inline form, wizard_* UserActions + PostHog events |
| 37 | Visual polish + accessibility | Shipped | 3 | Reduced-motion, design-system alignment, dark/light parity, a11y |
Key Decisions
- Server-authoritative onboarding status. Pre-v1.8 used
localStorage; multi-device users could re-trigger the wizard. Moving status toUserDetail.data['onboarding']makes the gate honest across devices and reload survives. - Status enum strictly
complete | skip.StoreOnboardingStaterefuses any other status. Prevents drift where the UI invents intermediate states the gate doesn't understand. - Slide framework is surface-agnostic.
SlideDefinition<TState>is generic; the onboarding registry is one of many. Phase 34's profile-completion wizard validates that the abstraction is real, not aspirational. - Funnel events fire from both
UserActionand PostHog.UserActionsurvives PostHog outages and lets gamification badges trigger off the same signal. - Safety phases first (Phase 32 before any UI). SAFE + SYNC ship before slide work so every subsequent slide lives behind a feature flag and cannot regress existing users.
- POLISH deferred to Phase 37. Polishing every slide once content has stabilized beats polishing-then-re-polishing as branching paths land.
Requirements Coverage
32 / 32 requirements satisfied; 10/10 cross-phase contracts verified; all phases nyquist_compliant: true.
| Category | Count | Status |
|---|---|---|
| SAFE-* | 4 | All satisfied |
| SYNC-* | 5 | All satisfied |
| PAGE-* | 4 | All satisfied |
| FRAMEWORK-* | 5 | All satisfied |
| PATH-* | 4 | All satisfied |
| METRIC-* | 6 | All satisfied (live PostHog ramp = HUMAN-UAT) |
| POLISH-* | 4 | All satisfied (physical-device a11y = HUMAN-UAT) |
Outcomes
- New users land on
/welcome/welcomewith a persona-aware branching path, and existing users are silently backfilled tocompleteon next sign-in. - The slide framework powers two surfaces today (onboarding + profile-completion) and is ready to power any future multi-step flow.
- Activation funnel is measurable end-to-end in PostHog;
wizard_*events plus the post-wizard tile-chooser conversions are the lift signal v1.8 was scoped against. - The pre-v1.8 modal flow is removed entirely; Apollo cache + router guard are now the single source of truth for first-run.
Tech Debt
- (Phase 32) Pitfall #2 uniqueness gap documented as a pending
xitwith Option B escape-hatch reference — surfaces only under simultaneous duplicate sign-ins, mitigation is theRecordNotUniquerescue. - (Phase 36) Live PostHog activation-lift validation flagged
human_needed; numeric lift can only be measured after a real ramp window post-deploy. - (Phase 37) Physical-device a11y verification flagged
human_needed; emulator-only coverage is insufficient for VoiceOver / TalkBack rotor-navigation edge cases.
Related Artifacts
- Roadmap: v1.8-ROADMAP.md
- Requirements: v1.8-REQUIREMENTS.md
- Milestone audit: v1.8-MILESTONE-AUDIT.md
- Phase artifacts: .planning/milestones/v1.8-phases/
- Merge PR: #331
Related Commits
825b909b— v1.8 — Onboarding Wizard Overhaul (full-page /welcome flow + persona-aware branching + funnel analytics) (#331)0c5463e0— fix(auth+onboarding): clear stale fallback token on logout; mark onboarding complete reactivelyea884f5d— test(welcome): add setOnboardingCompletedAt to useClerkSync mock
Last updated: 2026-05-22