Skip to content

[code-quality-page-tabs-from-manifest] Derive the fourteen hand-maintained page tab lists from the nav manifest #6365

Description

@atomantic

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.
  • The scraper is unreachable on client-only PRs (see [code-quality-ci-plan-cross-tree-contracts] CI planner never selects the contract tests that read the other tree as text #6363), so it guards only on nightly and full-plan runs.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.jsx switch (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.

Scope: large

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    code-qualityProposed from a code-quality auditeffort:highDispatch reasoning effort: highmodel:mediumModel size: mediumplanTracked by /do:replanplanner:fable-5-1Plan authored by the fable-5-1 model

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions