Onboarding Tonic internal

Flow

The iOS onboarding flow, drawn as two parallel cohort paths. Hover any step for its source-of-truth identifier, the data it collects, and any flag or condition that controls it.

Hand-maintained. This graph mirrors Packages/OnboardingFeatureSupport/Sources/OnboardingFeatureSupport/OnboardingFlowState.swift. If you change a branch in Swift, update src/lib/onboardingFlow.ts too. Phase 6 (later) replaces this with a generated JSON definition shared by both sides.
always taken flag-gated screen affirmation terminal / entry flag-gated step

About to start

journeyStatus = .aboutToStart · exploring GLP-1

if about_to_start if ≠ maintain if about_to_start if about_to_start if trackingStyleQuestion if completionScreenForExplorers if maintain if flag off id: welcome kind: entry First-launch carousel. No telemetry yet — onboarding.started fires on signIn entry. Welcome id: signIn kind: screen Apple / Google. Emits onboarding.sign_in_started, sign_in_succeeded, signed_up, or sign_in_failed. collects: userId Sign in id: journeyStatus kind: screen The fork: aboutToStart vs alreadyOnGlp1. Decides which lane the user runs. collects: journeyStatus: about_to_start | already_on_glp1 Journey status id: journeyAffirmation kind: affirmation Soft acknowledgement of the journey-status answer. No data collected. Journey affirmation id: genderSelection kind: screen Male / Female / Other. collects: gender Gender id: birthdayPicker kind: screen Default lands on 30 years ago. collects: birthDate Birthday id: height kind: screen Imperial wheel, stored as cm. collects: heightCm Height id: weight kind: screen Imperial wheel, stored as kg. collects: weightKg Weight id: weightGoal kind: screen Lose / Maintain / Gain. Maintain skips goalWeight + weightPace. collects: weightGoal Weight goal id: goalWeight kind: screen Only shown when weightGoal != .maintain. collects: goalWeightKg shown when: weightGoal ≠ maintain Goal weight (weightGoal ≠ maintain) id: weightPace kind: screen Only shown when weightGoal != .maintain. collects: weeklyPaceKg shown when: weightGoal ≠ maintain Weight pace (weightGoal ≠ maintain) id: weightGoalAffirmation kind: affirmation Soft acknowledgement of the weight-goal answer. Goal affirmation id: activityLevel kind: screen Sedentary → Very active. collects: activityLevel Activity level id: notificationsPermission kind: screen Permission prompt. Emits permission_prompted + permission_result. Notifications permission id: healthKitBenefits kind: screen Read-access permission prompt + benefits screen. HealthKit benefits id: trackingStyle kind: screen Only shown when FeatureFlags.trackingStyleQuestion is on. collects: trackingStyle gated by: trackingStyleQuestion Tracking style (flag: trackingStyleQuestion) id: trackingAffirmation kind: affirmation Only shown when FeatureFlags.trackingStyleQuestion is on. gated by: trackingStyleQuestion Tracking affirmation (flag: trackingStyleQuestion) id: planPreview kind: terminal Calorie + macro reveal. For explorers (aboutToStart) this is also the silent finalize point — see completionScreenForExplorers. Plan preview id: completion kind: terminal Trophy screen. Explorers skip this by default — gated on completionScreenForExplorers. gated by: completionScreenForExplorers Completion (flag: completionScreenForExplorers)

Already on GLP-1

journeyStatus = .alreadyOnGlp1 · on medication

if already_on_glp1 if weekly | biweekly if ≠ maintain if trackingStyleQuestion if daily | custom | not sure if maintain if flag off id: welcome kind: entry First-launch carousel. No telemetry yet — onboarding.started fires on signIn entry. Welcome id: signIn kind: screen Apple / Google. Emits onboarding.sign_in_started, sign_in_succeeded, signed_up, or sign_in_failed. collects: userId Sign in id: journeyStatus kind: screen The fork: aboutToStart vs alreadyOnGlp1. Decides which lane the user runs. collects: journeyStatus: about_to_start | already_on_glp1 Journey status id: journeyAffirmation kind: affirmation Soft acknowledgement of the journey-status answer. No data collected. Journey affirmation id: medicationSelection kind: screen Zepbound / Mounjaro / Ozempic / Wegovy / Trulicity / compounded / other. collects: selectedMedication Medication id: doseSelection kind: screen Dose list is derived from the medication's active ingredient. collects: selectedDose Dose id: frequencySelection kind: screen Daily / weekly / biweekly / custom / not sure. Weekly+biweekly trigger injectionDaySelection. collects: injectionFrequency Frequency id: notificationsPermission kind: screen Permission prompt. Emits permission_prompted + permission_result. Notifications permission id: injectionDaySelection kind: screen Only shown for weekly or biweekly frequency. collects: injectionWeekdaySelected shown when: frequency = weekly | biweekly Injection day (frequency = weekly | biweekly) id: scheduleTime kind: screen Time-of-day for the reminder. Defaults to 09:00. collects: medicationTime Schedule time id: lastInjectionDate kind: screen Anchor for the next-dose countdown. collects: lastInjectionDate Last injection id: genderSelection kind: screen Male / Female / Other. collects: gender Gender id: birthdayPicker kind: screen Default lands on 30 years ago. collects: birthDate Birthday id: height kind: screen Imperial wheel, stored as cm. collects: heightCm Height id: weight kind: screen Imperial wheel, stored as kg. collects: weightKg Weight id: weightGoal kind: screen Lose / Maintain / Gain. Maintain skips goalWeight + weightPace. collects: weightGoal Weight goal id: goalWeight kind: screen Only shown when weightGoal != .maintain. collects: goalWeightKg shown when: weightGoal ≠ maintain Goal weight (weightGoal ≠ maintain) id: weightPace kind: screen Only shown when weightGoal != .maintain. collects: weeklyPaceKg shown when: weightGoal ≠ maintain Weight pace (weightGoal ≠ maintain) id: weightGoalAffirmation kind: affirmation Soft acknowledgement of the weight-goal answer. Goal affirmation id: activityLevel kind: screen Sedentary → Very active. collects: activityLevel Activity level id: healthKitBenefits kind: screen Read-access permission prompt + benefits screen. HealthKit benefits id: trackingStyle kind: screen Only shown when FeatureFlags.trackingStyleQuestion is on. collects: trackingStyle gated by: trackingStyleQuestion Tracking style (flag: trackingStyleQuestion) id: trackingAffirmation kind: affirmation Only shown when FeatureFlags.trackingStyleQuestion is on. gated by: trackingStyleQuestion Tracking affirmation (flag: trackingStyleQuestion) id: planPreview kind: terminal Calorie + macro reveal. For explorers (aboutToStart) this is also the silent finalize point — see completionScreenForExplorers. Plan preview id: completion kind: terminal Trophy screen. Explorers skip this by default — gated on completionScreenForExplorers. Completion

Flags in this graph

  • trackingStyleQuestion Packages/AppLogic/Sources/AppLogic/Services/FeatureFlags.swift

    Gates the trackingStyle + trackingAffirmation steps for both cohorts.

  • completionScreenForExplorers Packages/AppLogic/Sources/AppLogic/Services/FeatureFlags.swift

    Gates the .completion trophy for explorers (about_to_start). When off, explorers end on .planPreview and finalize silently.

Sources

Coming next