V1 Web PWA · Surface walkthrough

Onboarding

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections6 minread

On this page

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|edit parsed by parseOnboardingRouteMode
  • Step deep link via ?step=<stepId> parsed by parseOnboardingStep (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 by parsePublicEntrySource)
  • 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 / hydrationuseOnboarding() reads the oshun.onboarding external 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/preferences PATCH is held in flight
  • Save failure — verify error surface and retry behaviour
  • Completion — final step navigates to redirect target or resolveFirstRunEntryTarget(entrySource) fallback
  • Anonymous user — middleware should redirect to /welcome before reaching this page
  • Already-onboarded user without mode — direct /onboarding currently opens the Welcome/new-flow view; review/edit behavior is only entered via explicit ?mode=revisit or ?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: reduce and the wizard's Reduced motion accessibility toggle both set data-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 togglesOnboardingNotificationPreferences channels
  • Accessibility needsOnboardingAccessibilityNeeds toggles
  • Memory scopeOnboardingMemoryPreferences scope choices

Completion handoff#

  • Final "Finish" submit
    • Function: writes selections to BFF via patchProfilePreferences(patch: OshunPreferencesPatch) and the local store
    • Then redirects to sanitised redirectPath or resolveFirstRunEntryTarget(entrySource)

Data & contracts#

  • Reads:
    • useOnboarding() hook (state shape OnboardingWizardSelections)
    • oshunWebPreferencesStore for hydration
    • getShellNavigationDomains() 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.tsx
    • apps/oshun/web/src/lib/hooks/use-onboarding.ts
    • apps/oshun/web/src/lib/onboarding-routing.ts
    • apps/oshun/web/src/profile/preferences-sync.ts
    • apps/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 with redirect=/profile preserved
    • skips onboarding, verifies the Profile "Onboarding setup" status, re-enters /onboarding?mode=edit&redirect=/profile, completes the remaining steps, persists wizardComplete: true, and reopens /onboarding?mode=revisit
    • forces /v1/preferences to return 500 on Finish and verifies the inline data-onboarding-finish-error plus "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 persisted oshun-sync-queue IndexedDB PATCH /v1/preferences action, 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/preferences PATCH and verifies Finish remains on the summary step with the disabled "Saving..." action until the request resumes
    • proves a completed user who visits bare /onboarding without a mode lands on the Welcome/new-flow view rather than automatic revisit
    • opens anonymous /onboarding?step=memory and verifies middleware preserves the requested path in /welcome?redirect=...
    • verifies signed-in ?step=memory opens Memory (9 / 10), Back moves to Access, and an invalid step falls back to Welcome (1 / 10) when no draft exists
    • signs up from entry=welcome-nisaba-conversion with 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=home with [data-nisaba-surface]

Open questions / known gaps#

  • page.tsx does not forward searchParams to OnboardingWizard; the wizard reads URL state via useBrowserSearchParams. Verify the end-to-end of ?step, ?mode, ?redirect, ?entry flows
  • 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/preferences through the shipped service-worker Background Sync path, and replays into the BFF when connectivity returns
  • Document the first-run handoff target resolution (resolveFirstRunEntryTarget)