fix(nav): keep Settings and Notifications out of the mobile More hub

The mobile bottom-nav More overflow appended the shared utility tail
(Settings, Notifications) after the feature destinations. Those two are
already reachable from the user/avatar menu, so the hub duplicated them
across two menus on the same screen. Return feature destinations only;
the utilities stay in the user menu. Sidebar footer is unaffected.

Flip the three hub assertions to lock the feature-only contract.
This commit is contained in:
Marc-André Bombeck committed 2026-07-09 11:49:55 +02:00
1 parent b0ae1b54f0
commit 472a4904cd
2 files changed
+17 -20

No files matched your search

@@ -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", () => {
+8 -12
View File
@@ -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];
}
/**