Skip to content

v4.28 — Mobile Settings & Coaching Refresh

Below the lg breakpoint, /settings becomes a hub of priority tiles that push into sub-pages, and Coaching becomes a one-at-a-time accordion — replacing the icon-only tab strip that used to overflow-scroll on a 390px screen.

Shipped. All five phases merged into the integration branch gsd/v4.28-mobile-settings-coaching, and that branch has been promoted to master via PR #2047 (merge 14f30c9b9, OBJ-2089). The design_system/css/tokens.css conflict between this branch's Phase 1 work and master's independently-shipped OBJ-2044 accent-accessible token was resolved by Codi's reconciliation merge (671d2f008) ahead of promotion. Master containment was independently re-verified by content (not file count) — see the milestone audit.

Summary

/settings was a single 939-line views/Settings.vue with an 8-item, icon-only tab strip that overflow-scrolled on a 390px viewport — on mobile, users saw eight unlabelled glyphs and had to guess. Every panel was stacked into one route, so the mobile experience was a horizontally-scrolling icon rail over one long vertical scroll. Josh produced a Design Canvas package with two chosen directions — Settings 1b ("priority tiles") and Coaching 2a ("accordion") — and this milestone rebuilds the mobile experience from them.

/settings below lg now renders a hub: a profile hero card (avatar, name, rank + XP), a 2×2 grid of priority tiles (Coaching / Notifications / Privacy & data / Supporter), a quick-toggles card, and a quiet "everything else" list (App & appearance / Help & feedback / Account). Tapping a tile or row pushes a sub-page built on a shared row grammar (toggle / nav / action / field / segmented / grid / swatches / chips / persona / plan). Coaching — today's personalization tab — becomes a coach-identity card plus four accordions that open one at a time: How Sage works, Voice & focus, Personality type, Goal style. No new routes were added; every sub-page is a ?tab=<slug> state of the same /settings route, and the slug map is additive-only — every legacy ?tab= value (profile, security, privacy, personalization, supporter, feedback, tours, notifications) still resolves. Desktop (lg and up) is byte-for-byte untouched throughout, verified diffable every phase via a CSS-only hidden lg:block / lg:hidden dual-layout pattern rather than a JS media query or component fork.

Almost every control in the mocks was already wired to a real composable or component — this was a re-layout, not a rebuild. Two controls had no backing and ship visibly disabled with an honest affordance and a filed backlog ticket: the Settings search pill (OBJ-2009) and two of four streak metaphor options, Sprout/Mountain/Waves (OBJ-2037; Stones and supporter-gated Flame are real). Zero backend or GraphQL changes; no PostHog flag, per Josh's explicit direct-to-master call.

Goal

Rebuild the mobile Settings and Coaching surfaces from Josh's Design Canvas package. /settings becomes a hub of priority tiles that push sub-pages; the icon-only tab strip is retired on mobile only. Coaching (today's personalization tab) becomes a one-at-a-time accordion. Desktop (lg and up) is untouched, all existing ?tab= deep links keep resolving, and no backend changes ship.

Scope — What Shipped

All five phases are complete and promoted to master (PR #2047) — the items below are live for mobile users.

  • New mobile Settings hub (views/settings/SettingsHub.vue) — profile hero, disabled search pill, 2×2 priority tile grid, quick toggles, quiet "everything else" list, sign-out footer.
  • New sub-page shell + row grammar (views/settings/SettingsSubPage.vue, components/settings/rows/SettingsGroup.vue plus 10 row primitives: toggle, nav, action, field, segmented, grid, swatches, chips, persona, plan) and an additive ?tab= slug map (views/settings/slugMap.ts).
  • Four restyled sub-pages (Phase 2): Profile, Account, Privacy & data, Notifications — reusing existing wired components (PrivacyControlsCard.vue, DataExportCard.vue, ReminderPreferences.vue, WeeklyDigestSettingsSection.vue) via the CSS-only dual-layout pattern.
  • Three restyled/relocated sub-pages (Phase 3): App & appearance (new destination for themes, dashboard mode, and the widget row — moved off Supporter per Josh's Gate 1 ruling), Supporter (now entitlements-only, links across to appearance), Help & feedback.
  • New Coaching accordion (Phase 4): CoachingAccordion.vue + CoachingAccordionSection.vue on ?tab=coaching, aliasing personalization — coach identity card, glance chips, four one-at-a-time accordions built from existing components (CoachTonePad.vue, CoachFrequencyCardGrid.vue, CoachFocusList.vue, CoachPersonaSummary.vue/CoachPersonaDetail.vue, EnneagramResultCard.vue/EnneagramRadarChart.vue, GoalMotivationSnapshotCard.vue).
  • Pinned sub-page header, no push/pop slide (Phase 5): the header moved out of the <Transition> subtree and crossfades in place (160ms, prefers-reduced-motion-aware); the translateX push/pop Phase 1 shipped was retired entirely once the motion ruling landed.
  • a11y sweep (Phase 5): real accessible names on hub tiles/rows, accordion ARIA, focus order + focus return on hub↔sub-page navigation, 44px minimum targets, inert="false" fixed in two files (ReminderPreferences.vue, WeeklyDigestSettingsSection.vue), dl/dt/dd nesting fixed, a real blocking a11y test (tests/unit/views/settings/a11y.spec.ts) replacing a Cypress spec that could never fail the build.
  • Two AA design tokens (--accent-accessible, --gradient-brand-deep) added to design_system/css/tokens.css and consumed throughout — this is the same token whose independent master landing (OBJ-2044) conflicted with this branch during promotion, resolved by Codi's reconciliation merge (671d2f008).
  • Docs: docs/features/settings-mobile-ia.md (this milestone's IA + slug map reference), plus root and in-app CHANGELOG.md entries.

Phases

PhaseNameStatusPlansHighlights
1Settings hub shell (1b) + sub-page navigation + ?tab= mapShipped1Hub, sub-page shell, 10 row-grammar primitives, additive slug map; resolved the themes-location conflict; 2 new AA tokens
2Sub-pages A — Profile, Account, Privacy & data, NotificationsShipped1CSS-only dual-layout pattern established (desktop diff nil by construction); Privacy value pill; 1 axe violation caught + fixed
3Sub-pages B — App & appearance, Supporter, Help & feedbackShipped1Physically executed the themes/dashboard-mode/widget-row move to App & appearance; native Supporter gate traced to zero web pricing; 2-real/3-disabled streak metaphor grid
4Coaching sub-page — accordion + four panel bodiesShipped1Two new components, chrome prop to suppress duplicate card shells inside the accordion; 190/190 tests green
5Polish & close — a11y, tests, docs, route-drift, disabled affordancesShipped1Pinned-header motion ruling (retired push/pop transition); a11y sweep; blocking a11y test replacing a non-enforcing gate; docs

Key Decisions

  • Hub + sub-pages over a redesigned tab strip/settings stays one route; every sub-page is a ?tab=<slug> state, additive-only over the legacy values. Avoids a second navigation model and keeps every existing deep link (/feedback, /checkout/cancel, /settings/blocked, /coach, /enneagram-assessment, /coaching/type) resolving untouched.
  • CSS-only dual layout, not a JS media query or component fork — each shared component carries both hidden lg:block (today's desktop markup, verbatim) and lg:hidden (new row grammar) in one template. This makes the desktop diff nil by construction, not by discipline — Roy byte-verified the Profile panel identical (112/112 lines) after Phase 2, and it held through Phase 3, the one phase that moves controls between panels rather than restyling them in place.
  • Themes, dashboard mode, and the widget row move to App & appearance; Supporter keeps entitlements only — ruled by Josh at Phase 1's Gate 1 to resolve a genuine conflict in the design package (SettingsDetail.dc.html put themes under App & appearance; today they lived under Supporter). Phase 3 performed the physical move; Phase 4 built no theme picker as a result.
  • The pinned sub-page header retires the push/pop transition rather than amending it — deferred through Phases 1–3 as a design call, not a code difficulty. Desi's Phase 5 ruling: pin the header (160ms crossfade, no slide); with a static header, a sliding body reads as two competing motions, so the translateX transition Phase 1 shipped was deleted, not patched.
  • Two controls with no backing ship visibly disabled, never hidden or faked — the Settings search pill (nothing exists behind it; OBJ-2009) and two of four streak metaphor options (Sprout/Mountain/Waves — useStreakMetaphor.ts only supports stones/flame; OBJ-2037). Both per Josh's explicit ruling on OBJ-1993.
  • A non-enforcing a11y gate was replaced with a real one — the original a11y acceptance criterion rested on a Cypress spec with skipFailures: true in a non-required CI job, so it could not fail the build under any circumstances (the same family as this repo's documented "continue-on-error is an absent gate" gotcha). Roy's Phase 5 Round 1 review caught it; Codi replaced it with a Vitest spec inside the required a11y job.
  • The milestone ships on an integration branch and promotes to master as a separate, explicitly-tracked step — per this repo's stranded-milestone-branch gotcha, code-complete-on-a-branch and shipped-to-users are different claims. Orion filed OBJ-2089 rather than letting the milestone read done everywhere while master carried none of it.

Requirements Coverage

This milestone has no standalone REQUIREMENTS.md — it originated from Josh's Design Canvas package rather than a requirements-driven roadmap, the same shape as v1.0/v1.3/v1.4's pre-audit-discipline gaps noted on the Project History index. A hand-authored milestone audit (passed) exists, cross-checked against the milestone ROADMAP's own per-phase Delivered / Deviations from plan / Known gaps carried forward sections (linked below) and a live source-code verification pass.

Outcomes

Mobile users get a Settings home that says what each destination is instead of eight unlabelled icons, and a Coaching surface that opens one topic at a time instead of one long scroll. Every existing deep link, redirect, and ?tab= value keeps working. Desktop users see no change at all. Two honestly-disabled affordances (search, three streak metaphors) each carry a backlog ticket instead of a silent gap.

Tech Debt

  • (carried past milestone close) OBJ-2076 — the export-state live authenticated walk (does in-flight DataExportCard state survive a hub↔sub-page navigation) is machine-proven by SettingsPanelPersistence.spec.ts but has never been walked by a human with real backend access; five agents across three phases hit a missing-credentials wall before Phase 5 hit a more specific one (secret_key_base not provisioned to agent checkouts).
  • (phase 5, deferred) OBJ-2071EnneagramResultCard's dominant-type pill measures 2.62:1 against a solid bg-accent backdrop; named owner Desi, explicitly re-deferred, not part of this milestone's scope.
  • (phase 1, backlog) OBJ-2009 — the Settings search pill has no backing implementation; ships visibly disabled.
  • (phase 3, backlog) OBJ-2037 — the streak metaphor picker ships two of four options; Sprout/Mountain/Waves are visibly disabled pending useStreakMetaphor.ts support.
  • (phase 4, unfiled) GoalMotivationSnapshotCard.stories.ts renders a hand-authored StubCard mock rather than the real component — pre-existing drift, found while verifying an unrelated a11y fix, still needs a ticket filed.
  • (milestone close, done) OBJ-2089 — integration branch promoted to master via PR #2047 (merge 14f30c9b9). The design_system/css/tokens.css conflict between this branch's Phase 1 tokens and master's independently-shipped OBJ-2044 accent-accessible work was resolved by Codi's reconciliation merge (671d2f008) ahead of promotion.
  • e4c8ab97f — Phase 1: Settings hub shell, sub-page shell, row-grammar primitives (PR #1958)
  • ab64b14df — Phase 2: Sub-pages A row grammar (PR #1972)
  • 425d78b5d — Phase 3: Sub-pages B — App & appearance, Supporter, Help & feedback (PR #1992)
  • a244f0222 — Phase 4: Coaching accordion mobile branch (PR #2006)
  • 17cf2c40e — Phase 5: pin sub-page header, retire translateX push/pop transition (PR #2027)

Last updated: 2026-08-03

Loading…