Context. surface customer · domain onboarding-public · route /onboarding · auth signed-in · source apps/oshun/web/src/app/onboarding/page.tsx
Last walked. 2026-06-27 targeted route-mode walk — real signup/signin against dev BFF, resume/edit/revisit lifecycle, anonymous deep-link redirect preservation, direct ?step=memory, invalid-step fallback, remote preferences-sync failure handling, skipped-to-complete profile return, and no-redirect Nisaba first-run handoff verified in Playwright. Evidence: apps/oshun/web/e2e/onboarding-lifecycle.spec.ts
Purpose#
First-run setup wizard. Walks a freshly signed-in user through choosing domains,
shaping guidance lanes, deciding teaching/AI preferences, and agreeing to
notification + accessibility + memory defaults. Renders a single multi-step
component (OnboardingWizard) backed by the useOnboarding() hook and
oshunWebPreferencesStore.
Entry points#
- Post-sign-up redirect from
/welcome(mode=signup success) - Resume-banner CTA on
/(OnboardingResumeBanner"Continue onboarding") - "Revisit onboarding" link from
/profile(verify) - Mode hints via search params:
?mode=resume|revisit|editparsed byparseOnboardingRouteMode - Step deep link via
?step=<stepId>parsed byparseOnboardingStep(valid step ids:welcome,goals,domains,interests,routine,guide,notifications,accessibility,memory,complete) - Redirect target via
?redirect=<path>(sanitised) — used to bounce back into a specific surface on completion - Entry source via
?entry=<source>(parsed byparsePublicEntrySource) - Direct URL / bookmark — requires signed-in session
Layout regions#
page.tsx is a one-liner:
export default function OnboardingPage() { return <OnboardingWizard /> }.
The wizard composition lives in
apps/oshun/web/src/components/onboarding/OnboardingWizard.tsx and is rendered
in the Lilith manuscript register (cream paper, terracotta accent). It
implements its own step progression internally — page.tsx does not pass any
step or mode props.
- Wizard frame: Lilith eyebrow + serif title + step progress indicator (10 named steps: Welcome, Goals, Domains, Interests, Rhythm, Guide, Alerts, Access, Memory, Ready)
- Step body: per-step composition (domain picker tiles, interest chips, goal cards, routine timing picker, guide preference, notification toggles, accessibility needs, memory scope toggles)
- Footer nav: Back / Continue (Next) buttons, with Continue acting as Submit on the final step
States#
- Loading / hydration —
useOnboarding()reads theoshun.onboardingexternal store synchronously on mount; there is no separate loading skeleton. Resume, edit, revisit, and direct step Playwright cases verify hydrated state before interaction. - First step (Welcome) — entry copy and "Begin" CTA
- Each of the 10 steps — Welcome, Goals, Domains, Interests, Routine (Rhythm), Guide, Notifications (Alerts), Accessibility (Access), Memory, Complete (Ready)
- Resume mode (
?mode=resume) — wizard jumps to the user's last incomplete step - Revisit mode (
?mode=revisit) — wizard surfaces a "review and change" UX over a completed setup - Edit mode (
?mode=edit) — wizard mounts at a specific step with the existing selection pre-populated - Step deep link (
?step=memory) — wizard mounts directly at that step - Validation gate per required step — Goals, Domains, and Routine keep Continue disabled until at least one valid selection is present; optional Interest, Guide, Notifications, Accessibility, and Memory steps can continue with defaults.
- Save in flight — final Continue / Save switches to disabled
"Saving..." while the real
/v1/preferencesPATCH is held in flight - Save failure — verify error surface and retry behaviour
- Completion — final step navigates to
redirecttarget orresolveFirstRunEntryTarget(entrySource)fallback - Anonymous user — middleware should redirect to
/welcomebefore reaching this page - Already-onboarded user without mode — direct
/onboardingcurrently opens the Welcome/new-flow view; review/edit behavior is only entered via explicit?mode=revisitor?mode=edit - Offline — Finish saves locally, queues the real preferences PATCH through the service-worker Background Sync path, and replays into the BFF when connectivity returns
- Reduced motion — browser
prefers-reduced-motion: reduceand the wizard's Reduced motion accessibility toggle both setdata-onboarding-motion="reduced"and suppress animated wizard chrome
Interactions#
Interactions all live inside OnboardingWizard and are step-dependent. Verify
against OnboardingWizard.tsx during the walk. Selected step elements observed
in the wizard source:
Step navigation#
- "Back" (button,
ArrowLeft) — moves to previous step; disabled on first step - "Continue" / "Begin" / "Finish" (button, primary,
ArrowRight) — advances; submits on final step
Step-specific elements (sample, verify on walk)#
- Goal cards (
OnboardingGoalId) — multi-select with check indicator - Domain tiles (
OshunDomainId, six options) — multi-select with accent - Interest chips (
OnboardingInterestId) — multi-select; each chip carries a recommended-domains hint - Routine timing picker (
OnboardingRoutineTimingId) — daypart preference (morning / afternoon / evening etc.) - Guide preferences — assistant persona, teacher tone, content tone
(verify across
OshunAssistantPersonaPreference,OshunAssistantTeacherPreference,OshunAssistantContentTonePreference) - Notification toggles —
OnboardingNotificationPreferenceschannels - Accessibility needs —
OnboardingAccessibilityNeedstoggles - Memory scope —
OnboardingMemoryPreferencesscope choices
Completion handoff#
- Final "Finish" submit
- Function: writes selections to BFF via
patchProfilePreferences(patch: OshunPreferencesPatch)and the local store - Then redirects to sanitised
redirectPathorresolveFirstRunEntryTarget(entrySource)
- Function: writes selections to BFF via
Data & contracts#
- Reads:
useOnboarding()hook (state shapeOnboardingWizardSelections)oshunWebPreferencesStorefor hydrationgetShellNavigationDomains()for the six-domain list
- Writes:
patchProfilePreferences({...}: OshunPreferencesPatch)(from@/profile/preferences-sync) — fans out to the BFF preferences endpoint and the local store
- Realtime: none
- Caching: client-side via
oshunWebPreferencesStore - Auth/role check: signed-in; middleware enforces
- Metadata:
title: 'Get Started',description: 'Set up your OSHUN shell — choose your domains, shape your guidance lanes, and decide how evidence-backed study should show up.',robots: { index: false, follow: false }
Cross-references#
- Shell auth:
shell/04-auth-session.md - Home resume banner:
../02-home-discovery/home.md(OnboardingResumeBanner) - Welcome / signup:
../00-public/welcome.md - Component sources:
apps/oshun/web/src/components/onboarding/OnboardingWizard.tsxapps/oshun/web/src/lib/hooks/use-onboarding.tsapps/oshun/web/src/lib/onboarding-routing.tsapps/oshun/web/src/profile/preferences-sync.tsapps/oshun/web/src/profile/store.ts
E2E coverage#
apps/oshun/web/e2e/onboarding-lifecycle.spec.ts- signs up through the real welcome/auth/BFF path, abandons at Rhythm, proves
Home resume CTA returns to
/onboarding?mode=resume, and proves a returning sign-in resumes the saved draft withredirect=/profilepreserved - skips onboarding, verifies the Profile "Onboarding setup" status, re-enters
/onboarding?mode=edit&redirect=/profile, completes the remaining steps, persistswizardComplete: true, and reopens/onboarding?mode=revisit - forces
/v1/preferencesto return 500 on Finish and verifies the inlinedata-onboarding-finish-errorplus "Continue without syncing" path without losing local choices - forces browser-offline Finish with the real service worker active, verifies
data-onboarding-finish-queued, inspects the persistedoshun-sync-queueIndexedDBPATCH /v1/preferencesaction, dispatches Background Sync, reads the BFF preference record back, and asserts the queue drains - verifies required-step gating on Goals, Domains, and Routine; confirms the optional steps allow defaults; verifies browser and in-wizard reduced-motion paths suppress animation
- delays the real
/v1/preferencesPATCH and verifies Finish remains on the summary step with the disabled "Saving..." action until the request resumes - proves a completed user who visits bare
/onboardingwithout a mode lands on the Welcome/new-flow view rather than automatic revisit - opens anonymous
/onboarding?step=memoryand verifies middleware preserves the requested path in/welcome?redirect=... - verifies signed-in
?step=memoryopens Memory (9 / 10), Back moves to Access, and an invalidstepfalls back to Welcome (1 / 10) when no draft exists - signs up from
entry=welcome-nisaba-conversionwith no redirect, selects deeper-study + primary-text-study, verifies the Ready first-run card says "Open Nisaba first", clicks it, and lands on live/domains/nisaba?origin=homewith[data-nisaba-surface]
- signs up through the real welcome/auth/BFF path, abandons at Rhythm, proves
Home resume CTA returns to
Open questions / known gaps#
-
page.tsxdoes not forwardsearchParamstoOnboardingWizard; the wizard reads URL state viauseBrowserSearchParams. Verify the end-to-end of?step,?mode,?redirect,?entryflows - Document each step's required vs optional selections: required = Goals, Domains, Routine; optional/defaulted = Interests, Guide, Notifications, Accessibility, Memory
- Confirm save semantics: per-step draft save to
oshun.onboarding; final submit writes the local preference store and then syncs to/v1/preferences - Confirm offline behaviour: Finish saves locally, queues
PATCH /v1/preferencesthrough the shipped service-worker Background Sync path, and replays into the BFF when connectivity returns - Document the first-run handoff target resolution
(
resolveFirstRunEntryTarget)