feat(nav): fix the sidebar order of the clinical and insight spine

Below the dashboard/measurements/mood/cycle head, the entries now follow one
fixed sequence: Medications, Vorsorge, Labs, Illness, Insights, Coach,
Achievements. A disabled module drops out; the rest keep this order.
This commit is contained in:
Marc-André Bombeck committed 2026-06-21 13:58:43 +02:00
1 parent 8d1c90b8b6
commit de14238535
3 files changed
+48 -38

No files matched your search

@@ -50,9 +50,13 @@ describe("nav-model destination list", () => {
expect(hrefs).toContain("/vorsorge");
const vorsorge = NAV_DESTINATIONS.find((d) => d.href === "/vorsorge");
expect(vorsorge?.tKey).toBe("nav.vorsorge");
// Peer to Labs: sits in the clinical spine, before Insights.
expect(hrefs.indexOf("/labs")).toBeLessThan(hrefs.indexOf("/vorsorge"));
expect(hrefs.indexOf("/vorsorge")).toBeLessThan(hrefs.indexOf("/insights"));
// v1.19.1 (S4) — the fixed spine is Medications → Vorsorge → Labs →
// Illness → Insights, so Vorsorge sits after Medications and before Labs.
expect(hrefs.indexOf("/medications")).toBeLessThan(
hrefs.indexOf("/vorsorge"),
);
expect(hrefs.indexOf("/vorsorge")).toBeLessThan(hrefs.indexOf("/labs"));
expect(hrefs.indexOf("/labs")).toBeLessThan(hrefs.indexOf("/insights"));
});
it("does not list Recovery as a left-nav destination (it is an Insights pill)", () => {
@@ -99,15 +103,17 @@ describe("nav-model utility tail (N-1 — one shared list)", () => {
});
describe("visibleNavDestinations module gate", () => {
it("includes Cycle (between Medications and Insights) only when its module is enabled", () => {
it("includes Cycle (between Mood and Medications) only when its module is enabled", () => {
// v1.18.0 — cycle is `requiresModule: "cycle"` and reads the delegated
// `cycle` key from the same resolved module map every other gate uses.
// v1.19.1 (S4) — cycle sits in the head block, after Mood and before the
// fixed Medications → … → Achievements spine.
const off = visibleNavDestinations({ cycle: false }).map((d) => d.href);
const on = visibleNavDestinations({ cycle: true }).map((d) => d.href);
expect(off).not.toContain("/cycle");
expect(on).toContain("/cycle");
expect(on.indexOf("/medications")).toBeLessThan(on.indexOf("/cycle"));
expect(on.indexOf("/cycle")).toBeLessThan(on.indexOf("/insights"));
expect(on.indexOf("/mood")).toBeLessThan(on.indexOf("/cycle"));
expect(on.indexOf("/cycle")).toBeLessThan(on.indexOf("/medications"));
});
it("drops a module-gated entry (mood / labs / coach / achievements / insights / medications) when its module is disabled", () => {
@@ -109,17 +109,18 @@ describe("<SidebarNav> cycle entry gate (v1.15.0 / v1.18.0 module map)", () => {
expect(html).not.toContain('href="/cycle"');
});
it("places the Cycle entry between Medications and Insights when enabled", () => {
it("places the Cycle entry between Mood and Medications when enabled", () => {
const html = render({ modules: { cycle: true } });
const med = html.indexOf('href="/medications"');
const mood = html.indexOf('href="/mood"');
const cycle = html.indexOf('href="/cycle"');
const insights = html.indexOf('href="/insights"');
expect(med).toBeGreaterThan(-1);
const med = html.indexOf('href="/medications"');
expect(mood).toBeGreaterThan(-1);
expect(cycle).toBeGreaterThan(-1);
expect(insights).toBeGreaterThan(-1);
// Cycle sits after Medications and before Insights in document order.
expect(med).toBeLessThan(cycle);
expect(cycle).toBeLessThan(insights);
expect(med).toBeGreaterThan(-1);
// v1.19.1 (S4) — Cycle sits after Mood and before Medications in document
// order; Medications opens the fixed clinical/insight spine below it.
expect(mood).toBeLessThan(cycle);
expect(cycle).toBeLessThan(med);
});
});
+27 -24
View File
@@ -63,6 +63,10 @@ export interface NavDestination {
* (after Medications) and is filtered out when the account gate is off;
* the order is otherwise identical for both surfaces.
*/
// v1.19.1 (S4) — the clinical/insight spine below the dashboard/measurements/
// mood/cycle head follows one fixed sequence, each entry directly below the
// previous: Medications → Vorsorge → Labs → Illness → Insights → Coach →
// Achievements. A disabled module simply drops out; the rest keep this order.
export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
{ href: "/", tKey: "nav.dashboard", icon: Home, tourId: "nav-dashboard" },
{
@@ -78,6 +82,13 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
tourId: "nav-mood",
requiresModule: "mood",
},
{
href: "/cycle",
tKey: "nav.cycle",
icon: Droplets,
tourId: "nav-cycle",
requiresModule: "cycle",
},
{
href: "/medications",
tKey: "nav.medications",
@@ -87,12 +98,23 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
// module; the nav entry now drops when the account turns the module off.
requiresModule: "medications",
},
// v1.17.1 — Vorsorge (preventive-care) gets a top-level nav home in the
// clinical spine. It is a first-class tracking surface ("wann muss ich was
// wo machen"), not pure configuration, so it belongs in the model both bars
// render — not buried three taps deep under Settings → Reminders. The
// Reminders hub still links to it; this is the direct front door.
{
href: "/cycle",
tKey: "nav.cycle",
icon: Droplets,
tourId: "nav-cycle",
requiresModule: "cycle",
href: "/vorsorge",
tKey: "nav.vorsorge",
icon: Stethoscope,
tourId: "nav-vorsorge",
// v1.18.1 — deliberately NOT module-gated (no `requiresModule`). Unlike
// labs / illness / cycle (opt-in clinical-spine verticals born off by
// default), preventive-care reminders are a CORE surface available to
// every account from birth: a reminder can target core vitals
// (weight / BP / pulse) that are never behind a module toggle, and a
// free-text "Großes Blutbild" reminder belongs to no module at all.
// Gating the entry would orphan reminders the user can still create.
},
{
href: "/labs",
@@ -112,25 +134,6 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
tourId: "nav-illness",
requiresModule: "illness",
},
// v1.17.1 — Vorsorge (preventive-care) gets a top-level nav home in the
// clinical spine, peer to Labs and Recovery. It is a first-class tracking
// surface ("wann muss ich was wo machen"), not pure configuration, so it
// belongs in the model both bars render — not buried three taps deep under
// Settings → Reminders. The Reminders hub still links to it; this is the
// direct front door so the customer knows where it lives.
{
href: "/vorsorge",
tKey: "nav.vorsorge",
icon: Stethoscope,
tourId: "nav-vorsorge",
// v1.18.1 — deliberately NOT module-gated (no `requiresModule`). Unlike
// labs / illness / cycle (opt-in clinical-spine verticals born off by
// default), preventive-care reminders are a CORE surface available to
// every account from birth: a reminder can target core vitals
// (weight / BP / pulse) that are never behind a module toggle, and a
// free-text "Großes Blutbild" reminder belongs to no module at all.
// Gating the entry would orphan reminders the user can still create.
},
// v1.18.0 — Workouts and Recovery both left the left-nav: each already
// surfaces as an Insights tab-strip pill (`/insights/workouts` gated on
// a workout row, `/insights/recovery` always present), so neither is a