Skip to content

Goal Builder — Onboarding Phase B

Overview

The Goal Builder is a 4-slide sequence (Life Area → Goal Name → Goal Shape → Goal Why) introduced in Phase B of the Onboarding Overhaul. It replaces the legacy GoalTypeSlide with a guided, context-rich construction sequence that produces a fully-formed first goal before the user ever reaches the goal creation form.

There is no experiment gate — these slides run for every user as slides 2–5 of the single 13-slide onboarding registry (see Onboarding Flow (technical)). The variant_c concept referenced in earlier drafts of this doc no longer exists; it was retired once the guided goal flow reached 100% rollout, and variantId / getVariantId were removed from useOnboardingFlow and every callsite. All slides sit inside the existing SlideWizardLayout chrome — no new navigation shell is required.

Related docs:

  • Onboarding Timeline — where the goal builder fits in the Day 1 arc
  • Goals — how the builder's answers pre-fill GoalCreate.vue
  • Meet Coach — the parallel configuration experience for AI Coach
  • AI Coaching Expansion — the goal-discovery quiz branch off Step 2 (GoalNameSlide.vue)

The 4-Step Flow

Step 1 — Life Area (LifeAreaSlide.vue)

The user picks the life area their first goal belongs to. Eight color-coded cards in a 2×4 grid (mobile: 2×4 scroll):

IDLabelBadge colorIcon
healthHealthEmeraldHeartPulse
wealthWealthAmberCoins
careerCareerSkyBriefcase
personal-growthPersonal growthVioletSprout
relationshipsRelationshipsRoseHeart
familyFamilyOrangeUsers
spiritualitySpiritualityPurplePlus
life-visionLife visionIndigoEye

A ninth "Something else" dashed-border card reveals a text input (aria-label="Name your area") for users who don't fit the preset areas. The custom label is passed forward alongside the id: 'custom' sentinel.

Accessibility: role="radiogroup" wrapping the grid; each card has role="radio" + aria-checked. Custom input uses aria-required="false".

Answer stored: flow.answers.lifeArea = { id, label }.


Step 2 — Goal Name (GoalNameSlide.vue)

Free-text entry for the goal's name. Shows the selected life area label as a contextual label above the input and offers 3 area-specific suggestion chips below.

Suggestion chips by area:

AreaSuggestions
healthRun my first 5K · Sleep 8 hours consistently · Cut out sugar for 90 days
wealthSave three months of expenses · Pay off my credit card · Start investing monthly
careerEarn a promotion this year · Learn one new skill a quarter · Ship a side project
personal-growthRead one book a month · Build a daily meditation habit · Journal three times a week
relationshipsSchedule a weekly date night · Call a friend every week · Be more present at dinner
familyEstablish a family dinner routine · Plan one trip together · Read bedtime stories nightly
spiritualityBuild a daily meditation practice · Spend 10 minutes in reflection each morning · Read a chapter of scripture daily
life-visionDefine my 5-year vision · Write my personal mission statement · Identify what matters most

Chips are rounded-full pills labeled "OR TRY —". Clicking a chip sets the textarea value but leaves the cursor in the textarea so the user can edit.

Accessibility: Heading gets tabindex="-1" focus on mount to announce slide to screen readers.

Answer stored: flow.answers.goalName = string.

Not sure what to set? (OBJ-1510): below the suggestion chips, a "Not sure what to set? Let Coach ask a few questions" branch button (behind goal_quiz_enabled) toggles this slide into an in-place quiz mode — the goal-discovery quiz renders inside the slide body instead of navigating away, so the wizard chrome and progress bar never change. Picking a suggestion prefills the textarea (same update:value path as a manual edit) and advances to Step 3; the quiz never creates a goal itself. Full flow/state reference: AI Coaching Expansion § Goal-discovery quiz.


Step 3 — Goal Shape & Target Date (GoalShapeSlide.vue)

The user picks from 4 goal shapes in a 2×2 grid:

IDLabelDescriptionTarget date?Default days
sprintSprint30 days, one push.Yes30
arcArcA season or a year.Yes90
habitHabitOngoing. Build a streak.No
life-listLife-listNo deadline. Someday.No

Selecting Sprint or Arc reveals a collapsible target date panel (v-show, aria-live="polite") with three preset chips ("30 days", "90 days", "1 year") and a native date input for custom dates. Selecting Habit or Life-list hides the panel and clears any stored targetDate.

The default date for Sprint is today + 30 days; for Arc, today + 90 days. Both can be overridden with the custom picker.

Answers stored: flow.answers.goalShape = ShapeId, flow.answers.targetDate = ISO date string | null.


Step 4 — Goal Why (GoalWhySlide.vue)

An optional textarea asking "Why this one?" The placeholder is Because… (italic). Users can skip this step — the CTA is enabled even with an empty textarea.

Heading: "Why this one?" with the emphasis word styled text-primary italic.

Accessibility: Textarea has aria-label="Your motivation" + aria-required="false".

Answer stored: flow.answers.goalWhy = string | null.


Registry Wiring (placeholderSlides.ts)

The four slides are registered unconditionally, in order, alongside every other onboarding slide — there is no variantId concept or per-slide skip flag gating them:

ts
export const placeholderSlides: readonly OnboardingSlide[] = [
  { id: 'start',       component: WelcomeIntro,   /* ... */ },
  { id: 'life-area',   component: LifeAreaSlide,  nextLabel: 'Continue', hideWrapper: true },
  { id: 'goal-name',   component: GoalNameSlide,  nextLabel: 'Continue', hideWrapper: true },
  { id: 'goal-shape',  component: GoalShapeSlide, nextLabel: 'Continue', hideWrapper: true },
  { id: 'goal-why',    component: GoalWhySlide,   nextLabel: 'Continue', hideWrapper: true },
  // ...coach-builder, goal-motivation-snapshot, source, review, done
]

See Onboarding Flow (technical) — Slide Registry for the full, current registry listing.


Handoff to Goal Creation

There is no query-param redirect keyed on a variant_c flag. DoneSlide.vue submits directly via a mutation, and the path depends only on whether the user named a goal:

  • goalName present — calls the atomic COMPLETE_ONBOARDING_AND_CREATE_GOAL_MUTATION (goalName, goalCategoryId, onboardingAnswers), which both stores onboarding state and creates the goal server-side in one transaction (Onboarding::CompleteOnboardingAndCreateGoal). Routes to /dashboard?source=wizard on success.
  • goalName absent — calls STORE_ONBOARDING_STATE_MUTATION only (no goal created), then routes to /goals/create?source=wizard&coachPersona=...&category=... so the user finishes goal creation on that page.

goalCategoryId for the mutation resolves from flow.answers.lifeArea.id (set by LifeAreaSlide). See Onboarding Flow (technical) — Backend: CompleteOnboardingAndCreateGoal Mutation for the full GraphQL signature and interaction details.


OnboardingAnswers Interface

Five new optional fields were added (ionic_frontend/src/composables/useOnboardingFlow.ts):

ts
interface OnboardingAnswers {
  // ... existing fields
  lifeArea?:   { id: string; label: string } | null
  goalName?:   string | null
  goalShape?:  'sprint' | 'arc' | 'habit' | 'life-list' | null
  targetDate?: string | null   // ISO 8601 date string
  goalWhy?:    string | null
}

Accessibility Summary

FeatureImplementation
Card gridsrole="radiogroup"role="radio" + aria-checked
Target date panelaria-live="polite" — announces when it appears
Custom area inputaria-label="Name your area" + aria-required="false"
Goal why textareaaria-label="Your motivation" + aria-required="false"
Reduced motionAll transitions wrapped in motion-reduce:transition-none motion-reduce:transform-none
Tap targetsmin-h-[80px] (life area, 2-col), min-h-[44px] (shape), chips use py-2.5 — all ≥44px
Focus on mountEach slide tabindex="-1"-focuses its heading so the screen reader announces the new step

Key Files

FilePurpose
ionic_frontend/src/views/welcome/slides/LifeAreaSlide.vueStep 1 — life area picker
ionic_frontend/src/views/welcome/slides/GoalNameSlide.vueStep 2 — free-text goal name + suggestion chips
ionic_frontend/src/views/welcome/slides/GoalShapeSlide.vueStep 3 — shape selector + conditional target date panel
ionic_frontend/src/views/welcome/slides/GoalWhySlide.vueStep 4 — optional motivation textarea
ionic_frontend/src/views/welcome/slides/DoneSlide.vueSubmits via CompleteOnboardingAndCreateGoal or StoreOnboardingState + redirects to /goals/create
ionic_frontend/src/views/GoalCreate.vueManual goal-creation form (reached when no goal name was given in the wizard)
ionic_frontend/src/views/welcome/placeholderSlides.tsFull 13-slide registry — no variant gating
ionic_frontend/src/composables/useOnboardingFlow.tsOnboardingAnswers interface + shared flow state
ionic_frontend/tests/unit/views/welcome/slides/Unit tests for all 4 slides + DoneSlide handoff
.planning/phases/goal-builder/UI-SPEC.mdDesi's design contract for this phase

Last updated: 2026-07-19

Loading…