---
title: "Onboarding"
description: "Rationale, questions, mapping to /v1/onboarding and the plan."
source: "design/onboarding.md"
---

> Part of [Brand and design](/design/llms.txt). Every page: [llms.txt](/llms.txt).

# Onboarding

Screens: `screens/onboarding/*` (welcome + 6 steps, each with default and selected states), then `screens/diagnostic/diag-intro`. All copy is in [`copy.md`](/design/copy/index.md) under *Onboarding*, in en and zh-Hans.

## Rationale

1. **Get to the real thing fast.** Onboarding exists to feed the diagnostic and the plan, so it asks only what changes the product: practice language, goal, scenario (+ optional topics), self-level, weekly days, reminder time. Six taps, one question per screen, under 60 seconds.
2. **Every answer has a visible consequence.** The practice language sets the diagnostic article. The goal and scenario pick articles and advice phrasing. Weekly days pre-fill the paywall slider and calendar. Reminder time drives the single daily notification. Self-level is only a prior; the diagnostic overrides it.
3. **Honesty up front.** The welcome screen states under the CTA that third-party AI processes recordings and never trains on them (D6). The diagnostic intro states it is a real assessment and free. The mic sheet explains before the system prompt.
4. **No account wall.** An anonymous server account already exists (`POST /auth/anonymous`). Sign in with Apple is offered later, in Profile, once there is something to lose.
5. **Language is two separate things (D13).** The *UI language* follows the device (overridable in Settings). The *practice language* is what you read and speak in. The language step defaults to the device locale and says where the UI language is changed, so a Chinese-UI user can practise English and the other way round.

## Steps, copy and mapping

| # | Screen | Question (en / zh-Hans) | Options → `OnboardingAnswers` value | Default | Used for |
|---|---|---|---|---|---|
| 0 | welcome | "Ten minutes a day. Say complex things clearly." / 每天十分钟，把复杂的事讲清楚。 | — | — | Legal + AI disclosure |
| 1 | language | "Which language do you want to practise?" / 你想练习哪种语言？ | English → `practice_language: en` · 中文（简体） → `zh-Hans` (options from `config.supported_practice_languages`, shown as endonyms) | **device locale, pre-selected** | Diagnostic article language; `settings.practice_languages = [x]`; daily quest / library filter |
| 2 | goal | "What do you most want to improve?" / 你最想提升哪一点？ | Explain clearly at work → `report_clearly` · Remember what I read → `remember_more` · Create content → `create_content` · Be more concise → `speak_concisely` | none (CTA disabled) | Plan copy emphasis; quick-pick subtitles; candidate one-actions |
| 3 | scenario | "Where do you need to speak most?" / 你常在哪里需要表达？ | Meetings and updates → `work_meeting` · Study and exams → `study` · Video and podcasts → `creator` · Everyday conversations → `social`; + optional topic chips → `preferred_topics[]` | none | Article selection (topics); example sentences in the scenario's register |
| 4 | self-level | "How do you speak today?" / 你现在讲得怎么样？ | `beginner` · `sometimes_clear` · `mostly_clear` | none | Prior for the first article's difficulty if the diagnostic is unreliable; copy tone |
| 5 | commitment | "How many days a week?" / 每周想练几天？ + "When should we remind you?" / 什么时候提醒你？ | 3 / 5 / 7 → `weekly_days`; wheel → `reminder_time` (HH:mm local) | 5 · 08:30 | Paywall slider default (CCR-7), plan calendar practice days, reminder schedule |
| 6 | notification-priming | "One reminder a day" / 每天提醒你一次 | Turn on reminders → iOS prompt → `PUT /me/push-token`; Not now → `reminders_enabled: false` | — | D10: max 1 notification/day |

On step 6, either button sends `POST /v1/onboarding` with every answer. The response returns `{diagnostic_session, article}`. The article is in `practice_language` and is prefetched so the diagnostic read screen opens instantly.

## How answers feed the plan

```
diagnostic assessment (in practice_language)
  └─ weakest dimension (lowest score; ties → structure > key_info > efficiency > fluency)
       └─ target_micro_skill = diagnostic one_action.micro_skill_id
            └─ Plan.days[1..14].micro_skill_id   (server, D9)
                 ├─ days 1, 2, 4, 7, 11, 14 = target skill (7 = weekly review, 14 = re-test vs baseline)
                 └─ other days rotate supporting skills, biased by goal:
                      report_clearly / speak_concisely → three_points, cut_fillers, drop_repeats
                      remember_more                    → keep_constraints, name_the_claim
                      create_content                   → one_example, name_the_claim
weekly_days → which plan days are practice days in the paywall calendar (pattern in paywall.md);
              the plan never "fails" a missed day — status becomes `missed`, nothing to make up
reminder_time → local notification schedule (engagement.md)
preferred_topics + practice_languages → daily quest article selection (highest unlocked tier, unseen first)
```

## Edge cases

- **Device locale isn't supported** (e.g. ja): UI falls back to `en`, and the language step pre-selects `en`.
- **User changes the practice language later:** the plan stays (micro-skills are language-independent). New quests come in the new language. Ability is per user (D13), and growth can filter by language.
- **Diagnostic unreliable twice:** offer "Skip for now" → plan built from self-level (prior) with `baseline_overall = null`; the paywall hides the projection. This is a backend follow-up and is listed in Known gaps.
- **Kill during onboarding:** answers persist locally; relaunch resumes at the last unanswered step (`me.onboarding.status = not_started`).
- **Notification permission previously denied at OS level:** step 6 shows "Turn on reminders" → opens Settings instead of the prompt.
