Skip to content

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

SurfaceComponentWhere it renders
Quiet Coach DockCoachDockCard.vueMobile: top of the feed; desktop: top of the right rail
Contextual Coach noteCoachContextCard.vueLeft column, when a goal is stalling
Coach tab / sidebar itemBottomTabBar.vue + SideMenu.vueMobile 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 rail

Props

PropTypeDefaultDescription
personaNamestring''Short persona name, e.g. 'Spark'. Appended after 'Your Coach ·'. Omit for a generic dock.
statusLinestring''One-line context, e.g. '12-day streak — solid.'
messagestring''The day's single observation, in the Coach's voice.
promptsstring[][]1–2 suggested prompts; clicking one emits prompt.

Events

EventPayloadDescription
openUser clicked the chat button (open blank chat).
promptstringUser clicked a suggested prompt chip; payload is the prompt text.

Design tokens

All colors use design-system tokens — no hardcoded hex:

TokenPurpose
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-coachEyebrow label and chip text
bg-coach/10, bg-coach/20Chat button states

Dark mode: the same hsl(var(--coach) / …) tokens invert with the dark-mode CSS variable layer — no separate dark overrides needed.

Storybook

bash
cd ionic_frontend && npm run storybook
# Components → Dashboard → CoachDockCard

Four stories: Default (with persona + prompts), NoPersona, Minimal (status only), Dark.

Unit tests

bash
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

vue
<!-- 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

PropTypeRequiredDescription
goalNamestringYesThe stalled goal's name.
daysSinceUpdatenumberYesDays since the goal last had activity.
personaNamestringNoShort persona name for the eyebrow; falls back to 'Coach'.

Events

EventDescription
talkOpen the Coach chat, pre-loaded with this goal's context.
logJump straight to logging progress on the goal.
dismissHide the note for this session (internal dismissed ref prevents re-render).

Unit tests

bash
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.

vue
<!-- 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" />
typescript
// 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.


Last updated: 2026-07-30 (v3.12.0: BrandFabStack retired, Coach dock surfaces documented)

Loading…