Coach surfaces on the dashboard
BrandFabStack.vue — the floating Coach + Goal button pair — was retired in v3.12.0 (PR #1057). The Coach is now a permanent, scrollable presence on the dashboard; the Goal FAB was removed entirely. This page documents the three surfaces that replaced the FAB stack.
Overview
| Surface | Component | Where it renders |
|---|---|---|
| Quiet Coach Dock | CoachDockCard.vue | Mobile: top of the feed; desktop: top of the right rail |
| Contextual Coach note | CoachContextCard.vue | Left column, when a goal is stalling |
| Coach tab / sidebar item | BottomTabBar.vue + SideMenu.vue | Mobile bottom bar; desktop sidebar — always coach-purple |
The Trail mark (CoachTrailMark.vue) is the shared glyph used across all three surfaces.
CoachDockCard
Path: ionic_frontend/src/components/dashboard/CoachDockCard.vue
The calm, always-present Coach entry point. Never floats — it scrolls with the page.
Layout
Dashboard.vue (active branch)
├── <CoachDockCard class="lg:hidden" …> ← mobile: above the two-column grid
└── <div class="hidden lg:flex …"> ← right rail
└── <CoachDockCard …> ← desktop: top of right railProps
| Prop | Type | Default | Description |
|---|---|---|---|
personaName | string | '' | Short persona name, e.g. 'Spark'. Appended after 'Your Coach ·'. Omit for a generic dock. |
statusLine | string | '' | One-line context, e.g. '12-day streak — solid.' |
message | string | '' | The day's single observation, in the Coach's voice. |
prompts | string[] | [] | 1–2 suggested prompts; clicking one emits prompt. |
Events
| Event | Payload | Description |
|---|---|---|
open | — | User clicked the chat button (open blank chat). |
prompt | string | User clicked a suggested prompt chip; payload is the prompt text. |
Design tokens
All colors use design-system tokens — no hardcoded hex:
| Token | Purpose |
|---|---|
var(--gradient-coach) | Persona chip background |
var(--shadow-coach) | Persona chip depth shadow |
hsl(var(--coach) / 0.10) | Card background (light gradient start) |
hsl(var(--coach-2) / 0.05) | Card background (gradient end) |
hsl(var(--coach) / 0.25) | Card border |
text-coach | Eyebrow label and chip text |
bg-coach/10, bg-coach/20 | Chat button states |
Dark mode: the same hsl(var(--coach) / …) tokens invert with the dark-mode CSS variable layer — no separate dark overrides needed.
Storybook
cd ionic_frontend && npm run storybook
# Components → Dashboard → CoachDockCardFour stories: Default (with persona + prompts), NoPersona, Minimal (status only), Dark.
Unit tests
npm run test:unit -- --run -t "CoachDockCard"Coverage: renders persona name / generic fallback, status + message, prompt chips, open and prompt emit, aria-hidden on the chip, role="group" labelling.
CoachContextCard
Path: ionic_frontend/src/components/dashboard/CoachContextCard.vue
The Coach gets "contextually loud" only when something needs attention. Occupies the same slot StaleGoalAlert used to occupy (v-if="urgentGoal" in Dashboard.vue), but speaks in the Coach's voice — one specific observation, a primary CTA ("Talk it through"), and a quick action ("Log progress").
Trigger
<!-- Dashboard.vue — left column -->
<CoachContextCard
v-if="urgentGoal"
:goal-name="urgentGoal.name"
:days-since-update="urgentGoal.daysSince"
:persona-name="coachPersonaName"
@talk="talkThroughUrgentGoal"
@log="openGoalUpdate(urgentGoal?.id ?? null)"
/>StaleGoalAlert.vue is no longer in the component tree anywhere — CoachContextCard fully replaced it. The file (and its story/spec) is still on disk as dead code, not deleted as of this writing.
Props
| Prop | Type | Required | Description |
|---|---|---|---|
goalName | string | Yes | The stalled goal's name. |
daysSinceUpdate | number | Yes | Days since the goal last had activity. |
personaName | string | No | Short persona name for the eyebrow; falls back to 'Coach'. |
Events
| Event | Description |
|---|---|
talk | Open the Coach chat, pre-loaded with this goal's context. |
log | Jump straight to logging progress on the goal. |
dismiss | Hide the note for this session (internal dismissed ref prevents re-render). |
Unit tests
npm run test:unit -- --run -t "CoachContextCard"Coverage: renders goal name and days, singular/plural "day", persona eyebrow, talk / log / dismiss emits, dismiss hides the card.
Coach tab and sidebar item
Paths:
ionic_frontend/src/components/BottomTabBar.vue(mobile)ionic_frontend/src/components/SideMenu.vue(desktop)
The Coach nav item always wears coach-purple and the Trail mark regardless of active state, so it reads as a distinct, persistent presence. A nudge dot appears over the icon when there is an unacknowledged ai_check_in notification.
<!-- BottomTabBar.vue — Coach tab definition -->
{ title: 'Coach', url: '/coach', coach: true }
<!-- Tab icon logic -->
<CoachTrailMark v-if="tab.coach" class="tab-icon" />
<span v-if="tab.coach && hasCoachNudge" class="nudge-dot" />// Nudge dot gate (identical pattern in BottomTabBar and SideMenu)
const hasCoachNudge = computed(() =>
notifications.value.some((n) => n.kind === 'ai_check_in' && !n.acknowledged)
)The .tab-btn.coach style rule pins color to hsl(var(--coach)) for both inactive and active states (overriding the default --primary-interactive active colour).
CoachTrailMark
Path: ionic_frontend/src/components/icons/coach/CoachTrailMark.vue
The dedicated Coach glyph: two filled "day" dots climbing to a hollow "goal" ring. It replaces the generic lucide/Sparkles icon wherever the Coach is represented generically — the Coach tab, the sidebar item, the AiCoachModal header, chat message avatars, and the dashboard dock and context card.
Lucide-compatible spec: 24×24 grid, stroke-width: 2, stroke-linecap: round, fill="none", stroke="currentColor". Size with a class (class="w-6 h-6"); color comes from currentColor.
Per-persona CoachPersonaIcon marks are unaffected — they retain their bespoke art where they already render.
Goal FAB
The Goal FAB (orange gradient, lucide/Plus, routes to /goals/create) was removed from the app shell along with BrandFabStack.vue. Goal creation is accessible via the Goals tab and in-line creation flows. No replacement FAB was introduced.
Related docs
docs/features/dashboard.md— Dashboard layout and responsive griddocs/product/onboarding-flow.md— Why the Coach tab is always visibletailwind.config.js— Design token mappings
Last updated: 2026-07-30 (v3.12.0: BrandFabStack retired, Coach dock surfaces documented)