Skip to content

Coach Builder — Onboarding Slides 6–9

Overview

The Coach Builder is not a standalone flow — it's four consecutive slides (6 through 9) inside the single 13-slide onboarding wizard. They configure the user's Coach persona, tone, focus, and check-in rhythm before the wizard reaches the goal-motivation and review slides.

For the full slide registry, the wizard's mutation contract, and the OnboardingAnswers interface, see Onboarding Flow (technical) — that page is the source of truth for the wizard as a whole. This page covers the UX and interaction detail specific to the four Coach-configuration slides.

Related docs:


The 4 Slides

Slide 6 — Persona (CoachPersonaSlide.vue)

The user selects one of six Coach personas (ionic_frontend/src/constants/coachPersonas.ts) — the persona shapes how the app communicates everywhere, not just onboarding.

Personas:

IDNameTraitsTagline
captainThe CaptainStructured · decisive"Clear eyes, steady course."
sparkThe SparkWarm · contagious"Energy is contagious — starting now."
mirrorThe MirrorReflective · non-directive"The answer is usually already in your head."
rivalThe RivalDirect · competitive"You said you wanted this."
sageThe SageSteady · long-view"Progress is a practice, not a performance."
analystThe AnalystData-led · precise"The data tells a story."

Interaction:

  • Grid view: 1 col mobile → 2 col sm → 3 col lg, role="radiogroup" / role="radio" per card
  • Tapping a card opens a full-bleed detail view (CoachPersonaDetail.vue) with persona icon, tagline, bio, and a "Choose [Name]" CTA — not an inline modal
  • Detail view back control: an arrow icon + "All Coaches" text button, which returns focus to itself when the detail view opens (aria-live="polite" announces the view change)
  • Choosing a persona fires UPDATE_COACHING_PREFERENCES_MUTATION immediately (before the wizard finishes) and auto-advances to the next slide on success
  • On mutation failure, the CTA switches to "Try again" with a retry icon and an inline role="alert" error message; the wizard does not advance until the save succeeds
  • Reassurance copy in the detail view: "You can change your Coach anytime in Settings."
  • No desktop split-panel/preview column on this slide — the detail view itself occupies the full width

State stored: answers.coachPersona (PersonaId)


Slide 7 — Tone (CoachToneSlide.vue)

The user sets the Coach's communication style along two independent axes via a 5×5 drag pad (CoachTonePad.vue): Tone (horizontal, Gentle ↔ Tough) and Depth (vertical, Brief ↔ Detailed). The Tone axis's right-hand label read "Demanding" until v4.47 wave 1 (OBJ-3018) — it now matches the resolved label and the guide, both of which already said "Tough."

Interaction:

  • role="application" pad containing a role="slider" drag dot; supports pointer drag and arrow-key nudging (Home/End jump Tone to its extremes)
  • Default position: Tone 3, Depth 3 ("Balanced · Medium")
  • A "Reset to default" button below the pad returns both axes to 3/3
  • Resolved label + explanation text (e.g. "Balanced · Medium") update live below the pad as the dot moves; a debounced aria-live region announces changes for screen readers
  • Desktop (lg: and up): two-column layout — pad on the left, sticky CoachPreview.vue on the right showing a sample message in the selected persona's voice, tinted with the persona's accent color when one is already chosen
  • Mobile: pad only, no right column

State stored: answers.coachTone (1–5), answers.coachDepth (1–5)


Slide 8 — Focus (CoachFocusSlide.vue)

The user picks what the Coach should watch — the signals it uses for check-ins and feedback.

Focus areas (ionic_frontend/src/constants/coachFocusAreas.ts):

IDLabelDescription
streaksStreaksDaily consistency, habit formation, showing up
milestonesMilestonesProgress toward your stated goals
reflectionReflectionMood, energy, and the story behind the data
accountabilityAccountabilityWhat you said you'd do, and whether you did
energyEnergyWhen you show up best, and how to protect it

Interaction:

  • Single grid, no split panel: 1 col mobile → 2 col sm → 5 col lg, role="group" over five toggle buttons
  • Multi-select: pick 1–2 areas. Selecting a first area sets it as primary; selecting a second sets it as secondary; selecting a third replaces the secondary (primary stays fixed). Deselecting the primary promotes the existing secondary into its place.
  • Selected cards are tinted with the chosen persona's accent color, when a persona has already been picked on Slide 6
  • Copy under the headline: "Two is the sweet spot. More, and the signal gets noisy."

State stored: answers.coachFocusPrimary, answers.coachFocusSecondary (both FocusAreaId)


Slide 9 — Rhythm (CoachRhythmSlide.vue)

The user chooses how often the Coach reaches out — the check-in cadence.

Frequency options (ionic_frontend/src/constants/coachFrequencies.ts):

IDLabelDescription
dailyEvery dayDaily check-in prompts and insight cards
activeWhen I'm activeOnly on days you sign in
when_neededWhen I need itOn streak breaks, overdue milestones, or inactivity
weeklyWeeklyA single Sunday planning + Friday review

Interaction:

  • CoachFrequencyCardGrid.vue: 1 col mobile → 2 col sm radio-card grid (role="radiogroup"), one persona-tinted icon tile per option, arrow-key navigation between cards
  • Default: daily pre-selected
  • The component itself renders a built-in example-notification preview below the grid (not a separate desktop-only column) — no split-panel layout on this slide

State stored: answers.coachFrequency (FrequencyId)


Design Decisions

Tone/Depth as Orthogonal Axes

Tone (Gentle ↔ Tough) and Depth (Brief ↔ Detailed) are independent. A tough coach can still be brief. The 5×5 grid lets users find their exact voice match rather than forcing a stereotype.

Focus Min 1, Max 2

Two areas balances personalization with signal clarity — fewer than 1 leaves the Coach with no focus (generic messages), more than 2 dilutes the signal back toward generic.

Grid↔Detail Transition Removed (PR #922)

The 200ms opacity fade between the persona grid and the detail view (<Transition mode="out-in">) was removed in PR #922. The toggle is now a plain v-if — no animation runs for any user, reduced-motion or otherwise.

Why: Under reducedMotion:reduce (enforced by the Playwright smoke suite via playwright.config.ts), Vue's out-in sequencing defers the entering vnode to the afterLeave callback. When stale ion-router-outlet pages carry will-change from a prior slide-fade-enter-active class, afterLeave never fires — showDetail flips but the detail block never renders, permanently hiding the "Choose" button for all test browsers. Removing the wrapper fixes the test deterministically. See PR #922 for the full investigation history.


Data & State Management

There is no UpdateOnboardingAnswers mutation and no User.onboarding_answers column — those do not exist in the codebase. Coach answers live client-side, keyed into the shared reactive answers map held by useOnboardingFlow.ts (an untyped reactive<Record<string, unknown>>), typed against the OnboardingAnswers interface declared in placeholderSlides.ts — see Onboarding Flow § OnboardingAnswers Interface for the full shape.

Persona selection fires UPDATE_COACHING_PREFERENCES_MUTATION immediately so the choice is written without waiting for the wizard to finish. The remaining Coach answers (tone, depth, focus, frequency) are persisted server-side at the end of the wizard, alongside the rest of the onboarding answers:

  • CompleteOnboardingAndCreateGoal (rails_api/app/interactions/onboarding/complete_onboarding_and_create_goal.rb) — atomic path when the user named a goal. Stores onboarding state and creates the goal in one transaction.
  • StoreOnboardingState (rails_api/app/interactions/user_identity/store_onboarding_state.rb) — used when no goal name was given; persists the onboarding blob only, and the Done slide routes to /goals/create instead.

Both write onboarding data as a JSON blob into UserDetail.data['onboarding'] (via deep_merge, preserving sibling keys) — there is no dedicated onboarding_answers column on User. See Onboarding Flow § Backend Mutation for the full mutation signature and transaction details.

These values configure:

  • Coach initialization (Coach component receives persona, tone, depth, focus, frequency)
  • Check-in prompts (personalized to persona + focus)
  • Notification delivery (scheduled via coachFrequency)
  • Progress messaging (Coach reflects selected tone/depth in all feedback)

Accessibility

All four slides follow WCAG AA standards:

  • Keyboard navigation: Tab through all interactive elements; Enter/Space to select; arrow keys drive the tone pad and the rhythm card grid
  • Screen readers: role="radiogroup"/role="radio" on the persona and rhythm selectors, role="group" on the focus grid, role="application" + role="slider" on the tone pad with a debounced aria-live value announcement
  • Color contrast: All text pairs meet ≥4.5:1 ratio (AA standard)
  • Touch targets: All interactive elements ≥44px tall
  • Motion: The persona grid↔detail toggle was de-animated in PR #922 (see Design Decisions) — no animated transition runs for any user on that surface. Other animated surfaces (slide entry, CoachPreview updates) respect prefers-reduced-motion independently.
  • Focus management: Logical tab order; the persona detail view's back button receives focus when the detail view opens

Validation & Error Handling

SlideValidation RuleError MessageCTA Behavior
PersonaPersona required; save must succeed(mutation failure) "Couldn't save your Coach. Check your connection and try again."Advance blocked until UPDATE_COACHING_PREFERENCES_MUTATION resolves
ToneAlways valid (has default)(none)Always enabled
FocusAt least one area selected"Pick at least one focus area."Disabled if none selected
RhythmAlways valid (has default)(none)Always enabled

  • SlideWizardLayout.vue — wizard chrome (progress bar, back/continue buttons, footer) shared across all 13 slides
  • CoachPersonaDetail.vue — full-bleed persona bio view (icon, tagline, bio, Choose CTA)
  • CoachPersonaIcon.vue — bespoke per-persona icon
  • CoachPreview.vue — right-column sample-message card used on the Tone slide only
  • CoachTonePad.vue — 5×5 interactive drag pad
  • CoachFrequencyCardGrid.vue — rhythm selector grid with the built-in example-notification preview

Migration from PersonaSlide

The legacy PersonaSlide.vue (a 4-option Coach/Mentor/Ally/Captain picker) is not in the slide registry and is not imported anywhere in the active flow — see Onboarding Flow § Key Files for its orphaned status alongside GoalTypeSlide.vue.


Rollout & Monitoring

There is no variant_c or any other experiment gate — the Coach Builder slides run for 100% of new signups as part of the single onboarding registry. See Onboarding Flow § A/B Variant History for the retirement log of the flags that once gated earlier phases of the onboarding rewrite.

Ongoing monitoring: Sentry errors (persona save failures logged as breadcrumbs), Coach mutation success rate (UPDATE_COACHING_PREFERENCES_MUTATION, CompleteOnboardingAndCreateGoal), and user feedback in Support.


Testing & QA

See Onboarding Flow § Test Coverage for the full suite list (Vitest unit/integration/a11y, RSpec interaction + GraphQL). Coach-specific flows worth exercising:

  1. Persona selection: grid → detail switch (instant, no animation); detail → grid return via "All Coaches"; "Choose [Name]" saves and advances; retry path on mutation failure
  2. Tone dragging: pad responds to drag and arrow keys; labels update live; Reset returns to 3/3
  3. Focus selection: can't proceed without ≥1 area; selecting a 3rd area replaces the secondary (not the primary)
  4. Rhythm selection: default daily pre-selected; example notification renders
  5. Reduced motion / dark mode / keyboard-only across all four slides

See Also

Last updated: 2026-07-08

Loading…