feat(nav): give the Coach a single primary nav home

The Coach is reachable from seven scattered entry points — a floating
button, the hero CTA, suggested-prompt chips, every metric empty state,
a per-metric icon, the conversations drawer — but it had no entry in the
nav. Power users found it instantly; a new user could miss the
differentiator entirely behind a 56px floating circle.

Add the Coach to the shared destination model as one labeled, primary
home, consistent with every other destination across desktop and mobile.
The existing entry points stay; this adds the front door they lacked.
This commit is contained in:
Marc-André Bombeck committed 2026-06-15 00:06:28 +02:00
1 parent c90459a7bd
commit 9bedd6b4f8
9 files changed
+47 -13

No files matched your search

+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Kontoeinstellungen",
"mood": "Stimmung",
"workouts": "Workouts",
"coach": "Coach",
"skipToContent": "Zum Inhalt springen",
"sidebar": "Sidebar",
"mainNavigation": "Hauptnavigation",
+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Account settings",
"mood": "Mood",
"workouts": "Workouts",
"coach": "Coach",
"skipToContent": "Skip to content",
"sidebar": "Sidebar",
"mainNavigation": "Main navigation",
+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Ajustes de cuenta",
"mood": "Estado de ánimo",
"workouts": "Entrenamientos",
"coach": "Coach",
"skipToContent": "Saltar al contenido",
"sidebar": "Barra lateral",
"mainNavigation": "Navegación principal",
+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Paramètres du compte",
"mood": "Humeur",
"workouts": "Entraînements",
"coach": "Coach",
"skipToContent": "Aller au contenu",
"sidebar": "Barre latérale",
"mainNavigation": "Navigation principale",
+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Impostazioni account",
"mood": "Umore",
"workouts": "Allenamenti",
"coach": "Coach",
"skipToContent": "Vai al contenuto",
"sidebar": "Barra laterale",
"mainNavigation": "Navigazione principale",
+1
View File
@@ -228,6 +228,7 @@
"accountSettings": "Ustawienia konta",
"mood": "Nastrój",
"workouts": "Treningi",
"coach": "Coach",
"skipToContent": "Przejdź do treści",
"sidebar": "Pasek boczny",
"mainNavigation": "Nawigacja główna",
@@ -16,9 +16,18 @@ import {
} from "../nav-model";
describe("nav-model destination list", () => {
it("lists Workouts as a first-class destination on both surfaces", () => {
it("lists Workouts and Coach as first-class destinations", () => {
const hrefs = NAV_DESTINATIONS.map((d) => d.href);
expect(hrefs).toContain("/insights/workouts");
expect(hrefs).toContain("/insights/coach");
});
it("gives the Coach exactly one nav home (F-3)", () => {
const coach = NAV_DESTINATIONS.filter(
(d) => d.href === "/insights/coach",
);
expect(coach).toHaveLength(1);
expect(coach[0]!.tKey).toBe("nav.coach");
});
it("orders the core spine: dashboard → measurements → mood → medications", () => {
@@ -57,6 +66,13 @@ describe("isNavDestinationActive most-specific resolution", () => {
expect(isNavDestinationActive("/", "/measurements")).toBe(false);
});
it("does not light up Insights when on its Coach sibling", () => {
expect(isNavDestinationActive("/insights/coach", "/insights/coach")).toBe(
true,
);
expect(isNavDestinationActive("/insights", "/insights/coach")).toBe(false);
});
it("does not light up Insights when on its Workouts sibling", () => {
expect(
isNavDestinationActive("/insights/workouts", "/insights/workouts"),
@@ -131,24 +131,25 @@ describe("<SidebarNav> targets deprecation (v1.8.6)", () => {
});
});
describe("<SidebarNav> unified destination model (v1.17.1 F-1)", () => {
it("surfaces Workouts as a first-class sidebar destination", () => {
// Pre-unify the sidebar hid Workouts entirely while the mobile bar
// promoted it. Both now render the one shared model, so both carry it.
describe("<SidebarNav> unified destination model (v1.17.1 F-1 / F-3)", () => {
it("surfaces Workouts and the Coach as first-class sidebar destinations", () => {
// Pre-unify the sidebar hid Workouts entirely and had no Coach home,
// while the mobile bar promoted Workouts and still missed Coach. Both
// now render the one shared model, so both carry both destinations.
const html = render();
expect(html).toContain('href="/insights/workouts"');
expect(html).toContain('href="/insights/coach"');
expect(html).toContain("Workouts");
expect(html).toContain("Coach");
});
it("marks Workouts active without also marking Insights active", () => {
const html = render({ pathname: "/insights/workouts" });
// The Workouts link carries aria-current="page"; the Insights link,
// its less-specific sibling, must not (most-specific resolution).
const workouts = html.match(
/<a[^>]*href="\/insights\/workouts"[^>]*>/,
);
it("marks Coach active without also marking Insights active", () => {
const html = render({ pathname: "/insights/coach" });
// The Coach link carries aria-current="page"; the Insights link, its
// less-specific sibling, must not (most-specific resolution).
const coach = html.match(/<a[^>]*href="\/insights\/coach"[^>]*>/);
const insights = html.match(/<a[^>]*href="\/insights"[^>]*>/);
expect(workouts?.[0]).toMatch(/aria-current="page"/);
expect(coach?.[0]).toMatch(/aria-current="page"/);
expect(insights?.[0]).not.toMatch(/aria-current="page"/);
});
});
+11
View File
@@ -5,6 +5,7 @@ import {
FlaskConical,
Home,
Lightbulb,
MessagesSquare,
Pill,
Trophy,
Waves,
@@ -87,6 +88,16 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
icon: Lightbulb,
tourId: "nav-insights",
},
// v1.17.1 (F-3) — the Coach finally gets a single labeled nav home. It
// was reachable from seven scattered entry points (FAB, hero CTA, empty
// states, per-metric icons …) but nowhere in the nav, so a new user
// could miss the differentiator entirely. The other entry points stay.
{
href: "/insights/coach",
tKey: "nav.coach",
icon: MessagesSquare,
tourId: "nav-coach",
},
{
href: "/achievements",
tKey: "nav.achievements",