diff --git a/src/components/layout/__tests__/nav-model.test.ts b/src/components/layout/__tests__/nav-model.test.ts index 84cef7aae..e37cbd032 100644 --- a/src/components/layout/__tests__/nav-model.test.ts +++ b/src/components/layout/__tests__/nav-model.test.ts @@ -160,8 +160,8 @@ describe("visibleNavDestinations module gate", () => { }); }); -describe("mobileMoreHubDestinations — the F-1 mobile invariant (N-2)", () => { - it("is exactly the visible feature destinations minus the primary slots, then the utility tail", () => { +describe("mobileMoreHubDestinations — the F-1 mobile invariant (feature-only hub, N-2)", () => { + it("is exactly the visible feature destinations minus the primary slots (no utility tail)", () => { const opts = { modules: { cycle: false } as const, }; @@ -170,9 +170,9 @@ describe("mobileMoreHubDestinations — the F-1 mobile invariant (N-2)", () => { const expectedFeatures = visibleNavDestinations(opts.modules) .map((d) => d.href) .filter((href) => !BOTTOM_NAV_PRIMARY_SLOT_HREFS.includes(href)); - const expectedTail = visibleUtilityDestinations().map((d) => d.href); - expect(hub).toEqual([...expectedFeatures, ...expectedTail]); + // Feature destinations only — the account utilities never ride the hub. + expect(hub).toEqual(expectedFeatures); }); it("never contains a primary slot (Home / Meds / Insights)", () => { @@ -184,7 +184,7 @@ describe("mobileMoreHubDestinations — the F-1 mobile invariant (N-2)", () => { } }); - it("keeps the feature destinations and the utility tail reachable in the hub", () => { + it("keeps the feature destinations reachable but excludes the account utilities", () => { const hub = mobileMoreHubDestinations({ modules: { cycle: false }, }).map((d) => d.href); @@ -192,9 +192,10 @@ describe("mobileMoreHubDestinations — the F-1 mobile invariant (N-2)", () => { expect(hub).toContain("/measurements"); expect(hub).toContain("/mood"); expect(hub).toContain("/checkups"); - // The shared utility tail rides at the end. - expect(hub).toContain("/settings/account"); - expect(hub).toContain("/notifications"); + // Settings + Notifications are account utilities — they live ONLY in the + // user/avatar menu, never duplicated into the mobile More hub. + expect(hub).not.toContain("/settings/account"); + expect(hub).not.toContain("/notifications"); }); it("gates Cycle by the module map, same as the sidebar", () => { diff --git a/src/components/layout/nav-model.ts b/src/components/layout/nav-model.ts index 6f27b106a..7dacefed9 100644 --- a/src/components/layout/nav-model.ts +++ b/src/components/layout/nav-model.ts @@ -300,26 +300,22 @@ export interface MobileMoreHubEntry { /** * The ordered "More" hub for the mobile bottom-nav: every visible feature - * destination that isn't a primary slot, in model order, followed by the - * shared utility tail (Settings, Notifications). The desktop sidebar renders - * the same feature list inline and consumes the same utility tail in its - * footer + avatar menu, so the two bars cannot drift into two hand-curated - * lists. + * destination that isn't a primary slot, in model order. Feature destinations + * only — the account utilities (Settings, Notifications) are NOT appended here. + * They live solely in the user/avatar menu (mobile top-bar dropdown; desktop + * sidebar avatar menu + footer), so surfacing them in the More hub too would + * duplicate a utility across two menus reachable from the same screen. The + * desktop sidebar renders the same feature list inline, so the two bars cannot + * drift into two hand-curated feature lists. */ export function mobileMoreHubDestinations(opts: { modules: ModuleVisibilityMap | undefined; /** Hydration gate — see `isNavDestinationVisible`. Defaults to mounted. */ mounted?: boolean; }): MobileMoreHubEntry[] { - const features = visibleNavDestinations(opts.modules, opts.mounted ?? true) + return visibleNavDestinations(opts.modules, opts.mounted ?? true) .filter((d) => !BOTTOM_NAV_PRIMARY_SLOT_HREFS.includes(d.href)) .map((d) => ({ href: d.href, tKey: d.tKey, icon: d.icon })); - const tail = visibleUtilityDestinations().map((d) => ({ - href: d.href, - tKey: d.tKey, - icon: d.icon, - })); - return [...features, ...tail]; } /**