Skip to content

feat(05): Onboarding wizard and profile switcher - #47

Open
tpaulshippy wants to merge 7 commits into
mainfrom
feature/roadmap-05-onboarding
Open

feat(05): Onboarding wizard and profile switcher#47
tpaulshippy wants to merge 7 commits into
mainfrom
feature/roadmap-05-onboarding

Conversation

@tpaulshippy

@tpaulshippy tpaulshippy commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Roadmap 05: Onboarding & Profile Switcher

Implements #57.

What changed

  • Backend: onboarding_completed_at field, POST /api/user/onboarding/complete, POST /api/onboarding/bootstrap (atomic setup), teen-delegated claim in JWTs
  • Frontend: 4-step wizard (Welcome → Kid name → Bot → PIN+notifications), profile switcher chip on header/drawer, auto-select first profile, empty states
  • Tests: 98 backend, 68 frontend (14 suites), e2e walkthrough
  • First-run finishes in under 3 minutes: name → bot → PIN → notifications → chat

Evidence

Onboarding Wizard screenshot

https://github.com/tpaulshippy/bots/raw/pr-47-onboarding/evidence/pr47-onboarding.mp4

tpaulshippy and others added 7 commits August 25, 2026 14:05
- UserAccount.onboarding_completed_at + onboarding_completed() heuristic
  fallback (hasPin && profiles>=1) for pre-wizard accounts
- GET /api/user now returns onboardingCompleted
- POST /api/user/onboarding/complete: idempotent, 403 for teen delegated
  sessions
- POST /api/onboarding/bootstrap: renames/recreates default profile,
  updates/creates first bot, sets PIN via existing account path (until
  feature 02 hashing), marks complete; retry-safe matching by first
  profile/bot
- get_delegated_tokens embeds is_teen_delegated claim so the API can
  recognize delegated sessions
- seed_e2e_onboarding command: fresh-state 'e2e-test-user' plus second
  profile for switcher testing
- Tests: bootstrap rename/create/pin/flag, GET reflects flag, teen
  delegated 403, seed idempotency
Wizard:
- Gate in index.tsx: token && !onboardingCompleted && !teen-delegated
  routes to /onboarding; account fetch failures fall back to chat
- 4 steps: welcome, kid name (pre-fills signal-created profile), first
  bot (Blank/Character template + appearance, defaults Penelope/teal/
  sparkles), protect (PIN + confirm required, notifications optional)
- Finish calls POST /onboarding/bootstrap, pre-selects the renamed
  profile and first bot so chat works immediately, then marks complete
- Skip-resistant: no swipe/header-back on critical steps; forward only
  with valid input

Profile switcher:
- New ProfileSwitcher chip (avatar initial + name) on the Chats header
  and drawer; action sheet lists profiles with checkmark, switching
  updates selectedProfile storage without opening Settings
- Manage profiles… is PIN-gated via PinWrapper; teen delegated sessions
  get a display-only chip

Empty states & polish:
- selectBot: 'Create a tutor' CTA when no bots exist
- botChat: auto-selects first profile instead of dead-end message;
  forces onboarding/profile when none exist
- flashcards: 'Go to chats' link on empty state
- profilesList: router.dismiss() instead of double router.back()
- settings: debug updateId gated behind __DEV__

Tests: wizard step transitions, gate routing incl. teen-delegated,
switcher storage change + PIN gate + read-only mode
front/e2e/05-onboarding.e2e.js modeled on chatImageUpload.e2e.js:
- fresh-state injection (tokens only, no profile/bot) gates app to /onboarding
- welcome → kid name (pre-filled) → bot defaults → PIN + notifications
  prompt → finish lands in a chat that sends via the real API
- verifies server state: profile renamed, single bot, pin + flag set
- relaunch skips the wizard; switcher flow: chip → option tap → chip
  updates; Manage profiles… through the PIN gate into Profiles

Adds menu-button/drawer-chats testIDs for navigation automation.
- POST /api/onboarding/bootstrap now echoes the exact profile_id and
  bot_id it configured, so the protect step can select them even though
  profile listings are name-ordered (the renamed default is not always
  results[0])
- protect.tsx matches fetched lists by returned id with first-item
  fallback
- seed_e2e_onboarding resets the default/second profile names to
  Jordan/Maya each run for deterministic e2e pre-fill
- tests: response id fields; wizard selects non-first listed profile
- Defer refreshSelected() and isTeenDelegatedSession() via microtask
- Escape apostrophe with '
- Add jest globals to eslint config
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.

1 participant