You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Slice audited: the nav manifest and the per-page tab registries it is checked against. Audit date: 2026-09-06.
Problem
server/lib/navManifest.js already declares every tab destination (path, label, section) for the tabbed pages. Fourteen pages declare the same tabs again as a local array: client/src/components/brain/constants.js:7, cos/constants.js:23, digital-twin/constants.js:42, meatspace/constants.js:15, client/src/pages/Calendar.jsx:22, Goals.jsx:15, Insights.jsx:24, Privacy.jsx:20, Messages.jsx:21, Wiki.jsx:16, MediaGen.jsx:7, Music.jsx:21, Sharing.jsx:50, SystemHealthPage.jsx:57.
The only thing holding the two registries together is server/lib/navManifest.test.js:363-407, a drift test that regex-scrapes each page's source — TABBED_PAGES (37-72) with four source-shape parsers (extractConstArrayBlock 80, extractSwitchCases 94, extractConstIds 110, extractTabPaths 117) plus nestedIdSources — and tells a human to copy the missing entry across. It compares paths only.
Settings and Models already left this pattern: their headers derive the tab list from the manifest through tabId and getSectionNavTabs (navManifest.js:350, client/src/components/ui/SectionTabsHeader.jsx:14-19, ModelsTabsHeader.jsx:25). That landed in 3c8799a ("fix: align model child navigation with manifest") after the two registries had diverged for real — Providers and Usage moved sections in the manifest and the header list did not follow.
Impact
The divergence already happened once (3c8799a) and nothing structural prevents the next one.
Today 17 of the 106 tabs checked show a different label in the page than the sidebar and ⌘K show for the same path — e.g. /system-resources/queues "Queues" vs "Active Queues", /music/generate "Generate" vs "Music Designer", /digital-twin/legacy "Legacy" vs "Legacy Bundle" — with nothing declaring which are intentional short forms, because the scraper never looks at labels.
Every new tab is a three-file edit (page array, manifest entry, and a TABBED_PAGES row or a new parser kind when the page shape is new); the scraper grew in 43ac3e9, aa0a3bb, a7c30fd and 3c8799a.
Extend the mechanism Settings and Models already use to the remaining pages, so the manifest is the one registry and pages hold presentation only:
In navManifest.js, add tabGroup: '<page>' ('brain', 'goals', …) beside the existing tabId on each page-tab entry, and an optional tabLabel for the page-local short label. Decision: keep the 17 short labels as tabLabel; the manifest label remains the ⌘K/sidebar name.
Add getPageNavTabs(group) returning { id, label, to, feature } in declaration order (pages order tabs deliberately — Overview first, Settings last — unlike the alphabetical section headers). Validate at module load that tabId is unique within a group, as the section check already does.
Each page keeps a presentation map keyed by tab id (icon, component, description — the NAV_PRESENTATION pattern in Layout.jsx:165-169) and builds its TABS by merging it with getPageNavTabs, throwing at module load on a tab with no presentation entry. Client code imports the server leaf directly, as the two headers already do.
Delete the ids / links parsers, nestedIdSources and their TABBED_PAGES rows from navManifest.test.js; replace them with one assertion per group that every manifest tab has a presentation entry, placed in the client suite next to each page (client tests can import server/lib/navManifest.js).
Out of scope: the POST switch (tab) dispatch (kind: 'switch') and the Settings.jsxswitch (activeTab) parity (navManifest.test.js:409-435) stay as they are; getSectionNavTabs and the App.jsx route scan are untouched.
Acceptance criteria
None of the 14 pages above declares a tab { id, label } pair; each derives TABS from getPageNavTabs plus a local presentation map.
navManifest.test.js has no ids / links parsers and no nestedIdSources; TABBED_PAGES lists only the POST switch page.
Adding a manifest entry with a tabGroup but no page presentation fails that page's client test at module load, naming the tab id.
The 17 differing labels render unchanged in the page tab bars (via tabLabel) and unchanged in the sidebar and ⌘K (via label).
cd client && npm test and cd server && npx vitest run lib/navManifest.test.js pass.
Slice audited: the nav manifest and the per-page tab registries it is checked against. Audit date: 2026-09-06.
Problem
server/lib/navManifest.jsalready declares every tab destination (path, label, section) for the tabbed pages. Fourteen pages declare the same tabs again as a local array:client/src/components/brain/constants.js:7,cos/constants.js:23,digital-twin/constants.js:42,meatspace/constants.js:15,client/src/pages/Calendar.jsx:22,Goals.jsx:15,Insights.jsx:24,Privacy.jsx:20,Messages.jsx:21,Wiki.jsx:16,MediaGen.jsx:7,Music.jsx:21,Sharing.jsx:50,SystemHealthPage.jsx:57.The only thing holding the two registries together is
server/lib/navManifest.test.js:363-407, a drift test that regex-scrapes each page's source —TABBED_PAGES(37-72) with four source-shape parsers (extractConstArrayBlock80,extractSwitchCases94,extractConstIds110,extractTabPaths117) plusnestedIdSources— and tells a human to copy the missing entry across. It compares paths only.Settings and Models already left this pattern: their headers derive the tab list from the manifest through
tabIdandgetSectionNavTabs(navManifest.js:350,client/src/components/ui/SectionTabsHeader.jsx:14-19,ModelsTabsHeader.jsx:25). That landed in 3c8799a ("fix: align model child navigation with manifest") after the two registries had diverged for real — Providers and Usage moved sections in the manifest and the header list did not follow.Impact
/system-resources/queues"Queues" vs "Active Queues",/music/generate"Generate" vs "Music Designer",/digital-twin/legacy"Legacy" vs "Legacy Bundle" — with nothing declaring which are intentional short forms, because the scraper never looks at labels.TABBED_PAGESrow or a new parser kind when the page shape is new); the scraper grew in 43ac3e9, aa0a3bb, a7c30fd and 3c8799a.Fix
Extend the mechanism Settings and Models already use to the remaining pages, so the manifest is the one registry and pages hold presentation only:
navManifest.js, addtabGroup: '<page>'('brain','goals', …) beside the existingtabIdon each page-tab entry, and an optionaltabLabelfor the page-local short label. Decision: keep the 17 short labels astabLabel; the manifestlabelremains the ⌘K/sidebar name.getPageNavTabs(group)returning{ id, label, to, feature }in declaration order (pages order tabs deliberately — Overview first, Settings last — unlike the alphabetical section headers). Validate at module load thattabIdis unique within a group, as the section check already does.NAV_PRESENTATIONpattern inLayout.jsx:165-169) and builds itsTABSby merging it withgetPageNavTabs, throwing at module load on a tab with no presentation entry. Client code imports the server leaf directly, as the two headers already do.ids/linksparsers,nestedIdSourcesand theirTABBED_PAGESrows fromnavManifest.test.js; replace them with one assertion per group that every manifest tab has a presentation entry, placed in the client suite next to each page (client tests can importserver/lib/navManifest.js).Out of scope: the POST
switch (tab)dispatch (kind: 'switch') and theSettings.jsxswitch (activeTab)parity (navManifest.test.js:409-435) stay as they are;getSectionNavTabsand the App.jsx route scan are untouched.Acceptance criteria
{ id, label }pair; each derivesTABSfromgetPageNavTabsplus a local presentation map.navManifest.test.jshas noids/linksparsers and nonestedIdSources;TABBED_PAGESlists only the POSTswitchpage.tabGroupbut no page presentation fails that page's client test at module load, naming the tab id.tabLabel) and unchanged in the sidebar and ⌘K (vialabel).cd client && npm testandcd server && npx vitest run lib/navManifest.test.jspass.Scope: large