ID-118 {118.3} TECH — Zone-grouped platform navigation implementation plan
ID-118 {118.3} TECH — Platform navigation information architecture
Section titled “ID-118 {118.3} TECH — Platform navigation information architecture”Kind: TECH ({118.3}) — translates the ratified {118.2} PRODUCT (30 BIs) into an
implementation plan across the three navigation surfaces. Feeds {118.4} PLAN decomposition
and the id-135 {135.10} /search entry.
Authored: 09/07/2026 · Task Planner (fresh dispatch — not the {118.2} author, by design).
Inputs read in full: PRODUCT.md (this dir); IA-MAP-s454.md ({118.1} RESEARCH);
decision register DR-041/DR-040/DR-038/DR-034/DR-025/DR-013/DR-021.
Controlling ruling: DR-041 (three-zone IA). Blocking OQs answered: BI-8 = reserved slot;
BI-21 Procurement = all authenticated now, RBAC later. This spec honours both; does not reopen.
Empirical-verification note. This spec cites internal Canonical routes/components and the existing
@/components/ui/dropdown-menuRadix wrapper (verified below by direct read of its exports) — no new external-library symbols are introduced. The pre-ratification import-and-call check (shared-discipline.md§Empirical verification) is therefore N/A.
Context
Section titled “Context”The change re-organises the platform’s flat top-level nav into the DR-041 three-zone IA
(Applications | Knowledge | Governance + utilities), surfacing /reference as the Knowledge-zone
“External sources” entry and providing the Knowledge slot for id-135’s /search ({135.10}).
Behaviour is fully specified in PRODUCT.md (BI-1..BI-30); this section grounds the plan in the
code that must change.
Code-intelligence orientation (verbatim — cited per the Planner code-intel binding rule; guide
refs .gitnexus/CLAUDE.md, .ast-dataflow/CLAUDE.md).
gitnexus_query({query: 'navigation shell reference IA zone grouped nav command palette', repo: 'canonical'})→processes: []for the nav surfaces themselves (nav is presentational, not a traced execution flow). Definitions surfaced the mutation targets + harness:Function:components/shell/site-header.tsx:SiteHeader(startLine 80, endLine 242, moduleUi);Function:components/shell/command-palette.tsx:CommandPalette(22–347, moduleShell) +runCommand(74–78); harnessFunction:e2e/helpers/responsive.ts:getVisibleNavLinks(125–141, moduleCluster_118) andnavigateViaHeader(30–48); the separate OKF-bundle nav surfaceFunction:components/okf/bundle-nav.tsx:BundleNav(114–180) — out of scope (per-bundle side-nav, not top-level IA).gitnexus_impact({target: 'SiteHeader', direction: 'upstream', repo: 'canonical'})→ risk LOW, impactedCount 2. d=1Function:components/shell/auth-aware-chrome.tsx:AuthAwareChrome(sole caller,CALLS); d=2Function:app/layout.tsx:RootLayout. Single affected moduleShell.gitnexus_impact({target: 'CommandPalette', direction: 'upstream', repo: 'canonical'})→ risk LOW, impactedCount 1. d=1Function:app/layout.tsx:RootLayout(mounted globally,CALLS).
Load-bearing findings (confirmed by direct read):
- No shared nav registry.
SiteHeaderhardcodes a flatNAV_LINKSarray (components/shell/site-header.tsx:34–77) feeding both the desktop bar (map at 112–136) and the mobile drawer (map at 191–216).CommandPalettehand-duplicates a separateCommand.Group heading="Navigation"list (command-palette.tsx:130–300) that is already drifted — it still listsWorkspaces(150–159) andBids → /procurement(160–169), and lacks Search/Answers/External sources/Coverage/Intelligence/Activity. This is the core reason BI-18/BI-19 (lockstep) exist: any membership/label change is today a three-place manual edit that has already fallen out of sync. - Current
NAV_LINKSmembership (site-header.tsx:34–77): Search/search(requiresEdit:false, already added flat by {135.10}), Q&A Library/library(false), Coverage/coverage(true), Workspaces/workspaces(false), Change Reports/change-reports(false), Intelligence/intelligence(true), Review/review(true). Plus Settings icon, persistentSearchBar(compact), and an explicit mobile “Home”. - Gating model is a single boolean. Nav items carry only
requiresEdit, resolved againstuseUserRole()→{ canEdit, canAdmin }(hooks/use-user-role.ts). Provenance (BI-11, admin-only) is not expressible in the current shape — it exists today only inside the command palette behindcanAdmin(command-palette.tsx:190,288–297). The config must grow a three-value visibility axis. - Active-state machinery is
pathname === href || pathname?.startsWith(href + '/')witharia-current="page"on the active leaf (site-header.tsx:114–120, 193–199). BI-23/BI-24 reuse this. - Disclosure primitive is already in the tree.
components/ui/dropdown-menu.tsx(Radix wrapper) exportsDropdownMenu / DropdownMenuTrigger / DropdownMenuContent / DropdownMenuItem(verified by read: exports block at line 230).accordion.tsxandpopover.tsxare also available. cmdk (^1.1.1) backs the palette. - Route reality (verified
find app -name page.tsx): every zone destination exists —/search /library /reference /procurement /intelligence /review /coverage /change-reports /activity /provenance— except an OKF index/landing: only/okf/[bundleId]is routable, no/okfindex. This is exactly the BI-8 reserved-slot trigger./workspacesdoes still exist as a route (app/workspaces/page.tsx) — see the BI-10↔BI-28 decision below. There is no/browseroute and no/item/[id]route (see Risk R4). - Stale e2e nav test (owned by this Task, routed via {118.2} journal / DR-021).
e2e/tests/auth.spec.ts:196–230(“navigation header is visible with all expected links”) assertsBrowse(204),Q&A Library(206),Coverage(208),Workspaces(209) links.Browsenever existed in the currentNAV_LINKS(it holdsSearch), so line 204 already fails at--workers=2; the whole assertion set is stale against the target IA and is rewritten here (BI-16/BI-17/BI-28).
Governing rulings respected (not re-litigated): DR-041 (the three-zone frame), DR-038 (“Workspaces”/“Bids” retired as labels; instances live under their application), DR-025 (“Evidence” is backend-only, never user-facing), DR-034 (Coverage entry surfaces only the surviving template-completion + governance coverage), DR-040 (Streamdown — informational; nav renders no markdown).
Proposed changes
Section titled “Proposed changes”C0 — New single source of truth: components/shell/nav-config.ts (the central decision)
Section titled “C0 — New single source of truth: components/shell/nav-config.ts (the central decision)”The one architectural move that makes BI-18/BI-19 structurally true rather than manually
maintained: extract the zone model into one typed module both consumers import. Co-located in
components/shell/ beside its two consumers; direct file import (@/components/shell/nav-config), no
barrel (CLAUDE.md convention). It is data + pure helpers only — no JSX, no 'use client'.
import type { LucideIcon } from 'lucide-react';
export type NavVisibility = 'all' | 'edit' | 'admin'; // replaces requiresEdit booleanexport interface NavEntry { href: string; label: string; // the BI-17 ratified user-facing string, single-sourced icon: LucideIcon; visibility: NavVisibility; keywords?: string; // command-palette search terms (cmdk value=) reserved?: boolean; // BI-8: defined in the IA but NOT rendered until its route lands}export type NavZoneId = 'applications' | 'knowledge' | 'governance';export interface NavZone { id: NavZoneId; header: 'Applications' | 'Knowledge' | 'Governance'; // BI-2 exact strings entries: readonly NavEntry[];}export const NAV_ZONES: readonly NavZone[] = [...] as const;
// Shared helpers so all three surfaces agree (BI-23/BI-24, BI-20/BI-21):export function isEntryVisible(v: NavVisibility, r: { canEdit: boolean; canAdmin: boolean }): boolean;export function isEntryActive(href: string, pathname: string | null): boolean; // === || startsWith(+ '/')export function isZoneActive(zone: NavZone, pathname: string | null): boolean; // any member activeRatified membership encoded once (order per BI-4/BI-9/BI-11; labels per BI-17; gating per BI-20/BI-21):
| Zone | Entry (label) | href | visibility | reserved | icon (suggested) |
|---|---|---|---|---|---|
| Applications | Procurement | /procurement | all | — | Briefcase |
| Applications | Intelligence | /intelligence | edit | — | Newspaper |
| Knowledge | Search | /search | all | — | Search |
| Knowledge | Answers | /library | all | — | Library |
| Knowledge | External sources | /reference | all | — | Link2 |
| Knowledge | Concepts | /okf | all | yes | Waypoints |
| Governance | Review | /review | edit | — | ShieldCheck |
| Governance | Coverage | /coverage | edit | — | BarChart3 |
| Governance | Change reports | /change-reports | all | — | FileBarChart |
| Governance | Activity | /activity | all | — | Activity |
| Governance | Provenance | /provenance | admin | — | History |
Notes on the encoded values (each traceable to a BI or ruling):
- Procurement
all, Intelligenceedit— deliberate asymmetry ratified by BI-21 (Procurement OQ answered: all authenticated now, RBAC later; Intelligence keeps its existingrequiresEdit:true). - Change reports
all— preserves the currentrequiresEdit:false(BI-21 “no silent audience change”). Label lowercases the “r” per BI-17 (“Change reports”). - Provenance
admin— the new axis value; today only reachable via the palette’scanAdminblock. - Activity
all—/activityis homeless today (no current gate); all-authenticated is ratified (OQ-T1, S457 owner ruling — as encoded, no behavioural change). - Concepts
reserved:true— BI-8:/okfhas no landing route, so the entry is defined in the IA and this config but not rendered by any surface until id-132/id-138 ships the landing (owner ratified reserved-slot). Icon/label pre-declared so enabling it later is a one-linereservedflip. - Utilities (Home via
BrandLogo, the persistentSearchBar, Settings) are not inNAV_ZONES— they stay bespoke per surface (BI-12/BI-13/BI-14).
C1 — components/shell/site-header.tsx desktop bar → zone disclosures (BI-1/2/3, 22, 23/24/25, 27)
Section titled “C1 — components/shell/site-header.tsx desktop bar → zone disclosures (BI-1/2/3, 22, 23/24/25, 27)”Replace the flat NAV_LINKS.map (112–136) with a map over NAV_ZONES, each zone rendered as a
DropdownMenu from @/components/ui/dropdown-menu:
DropdownMenuTriggershows the zoneheaderstring (BI-2) and carries the BI-24 active affordance (a non-colour signal — weight/underline — whenisZoneActive(zone, pathname)), witharia-expandedsupplied by Radix (BI-25).DropdownMenuContentlists the zone’s visible, non-reserved entries; eachDropdownMenuItem asChildwraps a Next<Link>carryingaria-current="page"whenisEntryActive(BI-23). Radix supplies arrow-key traversal,Escape-to-close-with-focus-return, and outside-click/blur close (BI-25) — this is why the existing wrapper is chosen over hand-rolled disclosure.- Filter with
isEntryVisible(entry.visibility, { canEdit, canAdmin })(BI-20/BI-21); skipentry.reserved(BI-8). - All chrome uses Warm Meridian semantic tokens only, WCAG AA, visible focus ring, UK English (BI-27) —
reuse the existing token classes already in this file (
text-foreground/text-muted-foreground/bg-accent), no raw Tailwind colour.
Trade-off (recorded). DropdownMenu vs Radix NavigationMenu vs hand-rolled disclosure:
NavigationMenu is semantically closest to a menubar but is not yet wrapped in components/ui/
(new dependency surface + new a11y wiring). Hand-rolling re-implements exactly the keyboard/focus/aria
machinery BI-25 enumerates. DropdownMenu is already wrapped, already used elsewhere, and satisfies
every BI-25 clause out of the box — chosen. Consequence for the harness: Radix marks the leaf
<a> as role="menuitem", not link, so navigateViaHeader’s desktop getByRole('link') path must
open the owning zone disclosure first, then select the leaf by menuitem/accessible-name (see C5).
C2 — components/shell/site-header.tsx mobile drawer → labelled zone sections (BI-26, 27)
Section titled “C2 — components/shell/site-header.tsx mobile drawer → labelled zone sections (BI-26, 27)”The Sheet drawer (164–240) keeps its explicit “Home” at top (BI-12) and Settings + Sign-out at foot
(BI-14/BI-26). Replace the flat NAV_LINKS.map (191–216) with, per zone, a non-interactive section
heading (header string) followed by its visible, non-reserved members as the existing full-width
<Link> rows (no disclosure needed on mobile — sections are always-expanded, matching the
settings-sidebar section pattern). Same isEntryVisible / isEntryActive / reserved handling;
selecting a row closes the drawer and navigates (existing onClick={() => setMobileMenuOpen(false)}).
C3 — components/shell/command-palette.tsx → three zone groups, de-drifted (BI-18/19, 16/17, 10)
Section titled “C3 — components/shell/command-palette.tsx → three zone groups, de-drifted (BI-18/19, 16/17, 10)”Replace the single Command.Group heading="Navigation" (130–300) with three Command.Groups
(heading="Applications" | "Knowledge" | "Governance", BI-2) generated from NAV_ZONES, each item a
Command.Item routing via runCommand(() => router.push(entry.href)) with value={entry.keywords ?? entry.label}. Apply the same visibility filter (canEdit/canAdmin — the palette already reads
canAdmin; add canEdit from useUserRole) and skip reserved. Net effect (BI-19): the palette
gains Search / Answers / External sources / Coverage / Intelligence / Activity; loses
Workspaces (150–159); Bids → /procurement (160–169) becomes Procurement under Applications
(BI-18). Keep Home, the theme/keyboard Actions group, and the admin Settings › … fan-out
(190–298) as-is except moving Provenance under the Governance group. Every palette destination now
equals its site-header counterpart (BI-18).
C4 — Icons (BI-27)
Section titled “C4 — Icons (BI-27)”Add the lucide imports the config needs that aren’t already imported: Link2 (External sources),
Waypoints (Concepts — declared even though reserved, so the flip is one line), Activity,
History (Provenance). Briefcase is freed by the Workspaces removal and reused for Procurement.
Icons are suggestions, not load-bearing — the executor may pick a clearer lucide glyph provided it is
distinct from siblings and imported in both consuming surfaces.
C5 — e2e nav harness + stale test (BI-28, and the {118.2}-journal-routed stale test)
Section titled “C5 — e2e nav harness + stale test (BI-28, and the {118.2}-journal-routed stale test)”e2e/tests/auth.spec.ts:196–230— rewrite the assertion set to the target IA: assert the three zone headers (Applications/Knowledge/Governance) and that each retained destination is reachable (Search, Answers, External sources, Procurement, Intelligence, Review, Coverage, Change reports, Activity) for an editor, with viewer-role variants honouring BI-20 (all Knowledge entries visible) and BI-21 (edit/admin entries hidden appropriately). Remove theBrowse/Workspaces/Q&A Libraryliterals (BI-16/BI-17). This is the DR-021-routed stale test; it lands in the same wave.e2e/helpers/responsive.ts:navigateViaHeader(30–48) — desktop path must open the target leaf’s zone disclosure before clicking (C1 makes leavesrole="menuitem"). Add a zone argument or resolve the zone from a leaf→zone lookup exported bynav-config. 2 call sites in 2 specs (1 each) —qa-library.spec.ts:598(Answers, Knowledge) andgovernance-review.spec.ts:261(Review, Governance) — update them.getVisibleNavLinks(125–141, used only by the rewritten auth.spec) needs no signature change but its callers assert the new labels.searchFromHeader(98–118) — the persistent search box is BI-13 “unchanged” and out of id-118 scope; do not touch it here. Its stale mobile/browseexpectation (Risk R4) is an id-135 search-box concern, flagged not fixed.
C6 — Existing component tests (lockstep with C1–C3)
Section titled “C6 — Existing component tests (lockstep with C1–C3)”__tests__/components/site-header.test.tsx and __tests__/components/command-palette.test.tsx assert
the old labels (Q&A Library, Workspaces, Bids, Change Reports) and flat structure; update them
in the same commit(s) as C1–C3 to the zone structure and BI-17 labels, using the existing
vi.hoisted() mockUserRole pattern to exercise BI-20/BI-21 role variants. Keep the {135.10}
/search assertions (site-header.test.tsx:274–299) green — Search simply moves into the Knowledge
zone. Respect the __tests__/docs/ antipattern guards (behaviour-first titles, no state-token coupling).
C7 — Retire the /workspaces launcher route outright (S457 owner ruling — production-code deletion)
Section titled “C7 — Retire the /workspaces launcher route outright (S457 owner ruling — production-code deletion)”OQ-T2 is ratified but supersedes the earlier de-link-only recommendation: the owner chose full retirement of the legacy launcher (“cheapest now — app not live, no bookmarks exist, any data found is deletable across all four DBs”). Its function is fully superseded by the Applications zone. Three parts:
- (a) De-link everywhere — four sites, not three. Beyond the three nav surfaces (C1/C2 drop the
/workspacesNAV_LINKSentry; C3 drops the paletteWorkspacesitem), a fourth linker exists:app/not-found.tsx:34(href="/workspaces"). Repoint it to/(or remove the row) so the 404 page does not link a deleted route. (Confirmed by grep: exactly these four source linkers;/workspacesis not inproxy.ts/lib/routes.ts.) - (b) Delete the route tree + the launcher-private hook + all their tests. Remove
app/workspaces/entirely (page.tsx,workspaces-content.tsx,error.tsx,loading.tsx), the launcher-private hookhooks/workspaces/use-application-types.ts, and their tests (__tests__/app/workspaces-page.test.tsx,__tests__/app/workspaces/workspaces-content.test.tsx,__tests__/app/workspaces/workspaces-launcher.test.tsx,__tests__/app/workspaces-boundaries.test.tsx,__tests__/hooks/workspaces/use-application-types.test.ts) plus the launcher e2ee2e/tests/workspaces.spec.ts. Deletion boundary verified — the hook is launcher-private, NOT shared.app/api/application-types/route.tsonly names the hook in a prose comment (line 10) and defines its ownApplicationTypeRowSchemaZod schema — it does not import from it; a repo-wide sweep finds realimport … from '@/hooks/workspaces/use-application-types'statements ONLY in the two deleted launcher pages and the hook’s own test.gitnexus_impactupstream (09/07/2026, index @409fdb2 — current for these unchanged files):useLauncherTypes→ LOW, sole callersWorkspacesContent→WorkspacesPage(both inside the deleted tree, moduleWorkspaces, no processes);useApplicationTypes→ impactedCount 0. Deleting the launcher pages without the hook would therefore strand it as orphaned dead code. Re-rungitnexus_impacton the deleted symbols pre-edit andgitnexus_detect_changespre-commit (production-code deletion). - (c) Data-cleanup audit across all four DBs — but scoped precisely. The launcher owns no private
data table:
page.tsxonly readsworkspaces(row counts) +application_types(grid), and writes nothing. So the audit’s expected finding is zero launcher-private rows to purge across the four DBs (Platform prod/staging + client prod/staging). The owner’s blanket “any data found is deletable” authorisation is recorded and applies to any genuinely-orphaned launcher artefact the audit surfaces. Explicitly NOT in this step: dropping/emptying theworkspacestable itself — it is the shared live store still read by the Intelligence app (/intelligence/[workspaceId]) and procurement instances; its rework is the DR-038 activity-carried-ids migration owned by ID-130, not this nav Task. OQ-T3 RESOLVED (S457 owner ruling): launcher orphans only — the sharedworkspaces-table purge is out of scope (a separate DR-038/ID-130-coordinated migration), exactly the conservative scope this step encodes.
What this spec deliberately does NOT change
Section titled “What this spec deliberately does NOT change”Per PRODUCT Non-goals and the decision register: proxy.ts publicRoutes (BI-30 — no entry is public);
what /library renders (id-71 family); the workspaces-table → activity-carried-ids data-model
migration for live Intelligence/procurement instances (DR-038/ID-130 — the /workspaces launcher
route is deleted here per C7, but the shared workspaces table and its live readers are not
touched); the OKF editing surface (id-132/138); /item/new* retirement (backlog). No RPC/seam change;
no schema/migration; no sb()/tryQuery() surface (nav is presentational).
BI → change mapping (one-to-one, for Checker verification)
Section titled “BI → change mapping (one-to-one, for Checker verification)”| BI | PRODUCT invariant (short) | Proposed change |
|---|---|---|
| BI-1 | Three zones + utilities, nothing outside | C0 NAV_ZONES; C1/C2 render only zones + bespoke utilities |
| BI-2 | Exact zone header strings on every grouping surface | C0 header union type; C1 trigger, C2 section heading, C3 group heading |
| BI-3 | Membership closed, no route in two zones | C0 single-sourced membership (a route appears once) |
| BI-4 | Knowledge = Search/Answers/External sources/Concepts, no content_items surface | C0 Knowledge rows; no /browse//item/new present |
| BI-5 | External sources → /reference, id-111 UI home | C0 row /reference; C1/C2/C3 render it |
| BI-6 | Search → /search, id-135 Surface A, requiresEdit:false | C0 Search all; regroups the existing flat {135.10} entry into Knowledge |
| BI-7 | Answers → /library as-is (reserved slot) | C0 Answers row; no change to /library content |
| BI-8 | Concepts landing-conditional (reserved) | C0 reserved:true; C1/C2/C3 skip reserved entries |
| BI-9 | Applications = live application_types (Procurement, Intelligence) | C0 Applications rows; extension seam = new row only |
| BI-10 | ”Workspaces” retired as a label everywhere | C1/C2 remove /workspaces entry; C3 removes palette Workspaces; C7 retires the route outright (S457) — de-link (incl. not-found.tsx) + delete app/workspaces/ + data-audit |
| BI-11 | Governance = Review/Coverage/Change reports/Activity/Provenance(admin) | C0 Governance rows incl. Provenance admin |
| BI-12 | Home via logo (all) + explicit mobile Home | C2 keeps mobile Home; logo unchanged |
| BI-13 | Persistent search box unchanged | No change (explicitly excluded in C5) |
| BI-14 | Settings a utility outside zones | Unchanged (Settings icon + mobile foot entry) |
| BI-15 | /documents/[id] + /admin/* no top-level slot | C0 omits them (contextual-only) |
| BI-16 | No Browse/Workspaces/Bids/Evidence labels | C0 label set; C1/C2/C3 apply it; C5/C6 drop the literals |
| BI-17 | Exact ratified label set incl. “External sources” | C0 label strings single-sourced |
| BI-18 | Lockstep across the three surfaces | C0 shared config imported by C1/C2/C3 — structurally enforced |
| BI-19 | Palette de-drift completeness (gains Knowledge entries) | C3 generates groups from NAV_ZONES |
| BI-20 | Knowledge zone role-uniform (all) | C0 Knowledge visibility all; isEntryVisible |
| BI-21 | No silent audience change elsewhere | C0 preserves edit/admin gating; Procurement all per OQ |
| BI-22 | Full discovery set (desktop, mobile, Cmd+K) | C1/C2/C3 cover all three; box + logo utilities remain |
| BI-23 | Leaf active-state + aria-current | C0 isEntryActive; C1/C2 apply on leaf |
| BI-24 | Grouped-zone active affordance on header | C0 isZoneActive; C1 trigger affordance (non-colour) |
| BI-25 | Grouped-menu keyboard + a11y | C1 Radix DropdownMenu supplies aria-expanded/arrow/Escape/outside-click |
| BI-26 | Mobile drawer three labelled sections | C2 always-expanded sections |
| BI-27 | Warm Meridian tokens, WCAG AA, non-colour signal, UK English | C1/C2/C3 semantic tokens + focus ring + weight/underline |
| BI-28 | Destinations preserved (harness resolves each) | C0 retains every destination except the deliberately-retired /workspaces launcher (C7, S457 owner ruling); C5 harness rewrite |
| BI-29 | Corpus surfaces unchanged in content | No corpus route/page body touched — nav only (the /workspaces launcher deletion in C7 is not a corpus surface) |
| BI-30 | Authentication unchanged (publicRoutes untouched) | Explicitly excluded |
Testing and validation
Section titled “Testing and validation”Vitest (bun run test, never bun test), behaviour-first per test-philosophy.md. Component tests
own BI verification at the unit level; e2e owns the cross-surface + role reachability.
- BI-1/2/3/4/9/11 (structure + membership) —
site-header.test.tsx: render, assert the three zone headers present and each zone’s members (by label) rendered under it; assert nocontent_items-era label appears.command-palette.test.tsx: assert threeheadinggroups with matching members. - BI-5/6/7 (Knowledge destinations) — assert External sources →
/reference, Search →/search, Answers →/library(href on the leaf<a>), all three surfaces. - BI-8 (Concepts reserved) — assert no “Concepts” entry renders while
/okflanding is absent (reserved:true); add a unit test onnav-configproving areservedentry is filtered by the render predicate (guards the future flip). - BI-10/16/17 (labels/de-drift) — assert
Workspaces/Bids/Browse/Q&A Library/Evidenceare absent as labels on all three surfaces; assertExternal sources,Answers,Change reports,Procurementpresent. (This subsumes the rewrittenauth.spec.tsassertion.) - BI-18/19 (lockstep) — a
nav-configunit test is the anchor: both consumers import the sameNAV_ZONES, so a table-driven test asserting every zone member’s{label, href, zone}gives the Checker a single lockstep proof; palette test asserts each destination equals the config href. - BI-20/21 (role-gating) — drive
mockUserRolevariants (viewer / editor / admin): viewer sees all Knowledge entries + Change reports + Procurement + Activity, but not Review/Coverage/Intelligence (edit) or Provenance (admin); admin additionally sees Provenance. Assert on both header and palette. - BI-23/24 (active-state) — set
mockPathnameto/reference/abc, assertaria-current="page"on External sources and the Knowledge zone trigger shows its active affordance;/documents/xyz→ no top-levelaria-current(BI-15);/search→ Search active (keep existing {135.10} test green). - BI-25 (keyboard a11y) —
command-palette.test.tsxalready exercises focus-trap/Escape; add a header disclosure test: Enter/Space opens (aria-expandedtoggles), arrow moves between menuitems, Escape closes and returns focus to the trigger. (Radix-backed, but assert the contract holds.) - BI-27 (non-colour signal + contrast + tokens) — assert the active leaf and active zone trigger
carry a non-colour marker in addition to
aria-current: the active<a>/trigger class list includes the weight/underline token (e.g.font-semibold) so meaning is not colour-only, and the focus ring is present on:focus-visible. Contrast (WCAG AA) is checked by an axe/pa11y scan of the header (desktop bar + open disclosure + mobile drawer) in the e2e pass; the semantic-token usage (no raw Tailwind colour) is additionally guarded by the__tests__/docs/token-coupling check. - BI-26 (mobile drawer) — open the Sheet (existing pattern), assert three section headings with members beneath, Home at top, Settings + Sign-out at foot; selecting a row closes the drawer.
- BI-28 (non-regression reachability) —
bun run test:e2enav specs: the rewrittenauth.spec.tsplus the updatednavigateViaHeaderpath (open zone → click leaf) resolving Answers and Review; every retained destination reachable. - BI-29/30 — assert-by-omission: no route/page/
publicRoutesfile in the diff; a browser pass (agent-browser, editor + viewer) confirming/reference /search /library /procurement /intelligencerender unchanged and each is reachable from the new nav. Capture screenshots (desktop + mobile). - Full regression gate —
bun run testgreen after the wave (nav tests + the__tests__/docs/antipattern guards).
Risks and mitigations
Section titled “Risks and mitigations”- R1 — Three-surface drift re-opens (the exact problem this Task fixes). Mitigation: C0 makes
NAV_ZONESthe single source; the BI-18 table-drivennav-configtest fails if any consumer diverges. Do not let an executor re-inline a per-surface list “for now”. - R2 — Harness breakage from
role="menuitem". Radix DropdownMenu leaves are menuitems, not links;navigateViaHeaderand any spec usinggetByRole('link', {name})against the header break silently (test-time, not prod). Mitigation: C5 updates the helper + its 2 call sites in the same wave; land header + harness together (never split across waves) or e2e goes red. - R3 — BI-10 (retire Workspaces) vs BI-28 (preserve every destination): RESOLVED (OQ-T2, S457 owner
ruling). The owner chose full retirement — delete the
/workspaceslauncher route outright (superseding the earlier de-link-only recommendation), because the app is not live, no bookmarks exist, and any orphaned data is deletable across all four DBs. BI-28 now carries an explicit carve-out for the one deliberately-retired launcher; every other destination is preserved. Implementation + the precise data scope (route deleted, sharedworkspacestable not touched) is C7 / slice 6. OQ-T3 RESOLVED (S457): launcher orphans only — the sharedworkspaces-table purge is out of scope (a separate DR-038/ID-130 migration, not this Task). - R4 — Adjacent stale search-box target.
components/browse/search-bar.tsxroutes queries to/browse?q=and results to/item/[id](171/183/197/212), neither of which is a live route; PRODUCT BI-29 also names/browseas an “unchanged” corpus surface that does not exist. Mitigation: out of id-118 scope (BI-13 search box unchanged); flag to the Orchestrator/Curator as an id-135 search-box / backlog item, not fixed here. Noting it prevents an executor “fixing” the box mid-nav-wave. - R5 — Activity audience: RESOLVED (OQ-T1, S457 owner ruling) = all-authenticated, as encoded. No
residual risk; the
allvalue stands.
Decision-register check
Section titled “Decision-register check”No new binding ruling is warranted: DR-041 already records the three-zone IA; DR-038 the Workspaces
label retirement; BI-8/BI-21 OQ answers are in the {118.1}/{118.2} journals; OQ-T1 (Activity =
all-authenticated) and OQ-T2 (full /workspaces route retirement) are S457 owner rulings folded into
this spec (C7, R3, R5). These are task-scoped decisions implementing the existing DR-038 direction,
not new cross-cutting rulings — no DR-intent. The R4 search-box drift is a Curator finding (curated as
ID-135.23). OQ-T3 is Resolved (S457 owner ruling): launcher orphans only; the shared workspaces
table is out of scope (DR-038/ID-130) — the conservative scope this spec already encodes.
Decomposition-readiness ({118.4} PLAN candidate slices)
Section titled “Decomposition-readiness ({118.4} PLAN candidate slices)”Sibling-only dependencies (all within Task 118). The config module is the spine; the three consumer surfaces then fan out and CAN parallelise (disjoint files), but each consumer + its test move together, and the header + harness must land in the same wave (R2). Candidate slices:
nav-config.ts+ its unit test (C0) — theNAV_ZONESdata, the three helpers, and the table-driven lockstep/reserved/visibility tests. Depends on: none. Blocks all others.- Site-header desktop zone disclosures (C1) —
DropdownMenuper zone, active affordance, a11y; updatesite-header.test.tsxdesktop assertions. Depends on: (1). - Site-header mobile drawer sections (C2) — labelled zone sections; mobile assertions in
site-header.test.tsx. Depends on: (1). Parallel with (2) but same file — group (2)+(3) into one executor to avoid asite-header.tsxconflict. - Command-palette zone groups + de-drift (C3) +
command-palette.test.tsx. Depends on: (1). Parallel with (2)+(3) — disjoint file. - e2e harness + stale-test rewrite (C5) —
navigateViaHeaderzone-aware update + its 2 call sites (1 each:qa-library.spec.ts:598,governance-review.spec.ts:261) +auth.spec.ts:196rewrite. Depends on: (2)+(3) (needs the shipped desktop disclosure DOM). - Retire the
/workspaceslauncher route (C7) — repoint/remove the 4th linker (app/not-found.tsx:34); delete theapp/workspaces/tree + the launcher-private hookhooks/workspaces/use-application-types.ts+ all 5 unit tests (4 launcher + the hook’s own) + the launcher e2eworkspaces.spec.ts; run the four-DB data-audit (expected finding: zero launcher-private rows). Production-code deletion — dispatch with an independent-checker note (blast-radius review +gitnexus_detect_changes, not self-verified). Depends on: (2)+(3) (its de-link overlaps the site-header/palette edits — same-file coordination) — group thenot-found.tsx/nav de-link portion into slices (2)+(3)/(4); theapp/workspaces/deletion + audit is the independently-checkable remainder. (Optional icons slice C4 folds into (2)/(4); no own subtask.)
Effort re-estimate for the Orchestrator: the pre-reframe effort_estimate “0.5–1d” is understated.
This is a config-module extraction + net-new desktop a11y disclosure machinery + three coordinated
surfaces + a harness/test rewrite (config module, both shell components, their unit tests, the e2e
harness + specs) + the C7 launcher-route retirement (production-code deletion + four-DB data-audit).
Realistic 2–3d; the {118.2}–{118.4} pre-reframe impl subtasks (single-link add) are superseded and
must be replaced by slices 1–6 above.