Three navigations, one matter
The matter page had drifted: a stale 7-tab subnav, a 10-icon right rail
that tried to own every verb, and an Inbox crammed into a 44px strip
where a daily work queue cannot live. This Story rebuilds the matter
page's chrome to the locked IA of 2026-06-20 — three
proper-named navigations, each with one job: the
Matter Ribbon (know + reach), the
Matter Worknav (choose surface + scope), and the
Matter Action Rail (persistent tools). No chrome
duplication. It is the foundation every Work Mode body builds on. The
Worknav follows the Option-B layer order (Triage →
Think → Evidence → Act), and the document Work Mode is renamed
Record — the Evidentiary Record — with its route moved
to /matters/[id]/record.
🛰️ Satellite — why a matter page needs three navigations, not one tab strip
A litigator's matter is three different questions at once, and a single tab strip answers none of them well. "What matter am I in and how urgent is it?" is identity — it changes nothing. "What facet of the work am I reading, and through which proceeding?" is surface + scope — it changes what's on screen. "What fast action do I fire?" is a verb — it opens a tool. Fold all three into one row and every control fights for the same space; the Inbox — the assigned counsel's actual daily queue of agent proposals, routed emails, and unassigned docs — gets exiled to a rail icon with a tiny badge, where it can never have the list + preview + filters it needs.
The naive fix is "add more tabs / more rail icons." That is exactly the drift this Story reverses: it is how you end up with a 10-icon rail nobody can scan and an Inbox that is structurally too small to use. The locked answer is separation by role: Ribbon = know + reach, Worknav = choose surface + scope, Action Rail = persistent tools. Each region owns one verb, so nothing duplicates and the Inbox gets the full body it always needed.
✈️ Airplane — the one enum that runs the Worknav
The Inbox is the trickiest call. It is not a Work Mode (those are facets of the matter's substance — Brief, Documents, Proof…); it is a queue of "what needs me," a different category entirely. But it also must not be a rail panel, a notification drawer, or an agent-panel subview — those are all too small and break the single-source rule. So it becomes a Triage Mode: leftmost in the Worknav, behind a divider, carrying the pending count badge — modeled as one more value in a single active-mode enum.
WorknavMode = "inbox" | "brief" | "strategy" | "record"
| "chronology" | "proof" | "filings" | "hearings"
Option-B layers (visible separators between each):
Inbox ┃ Brief·Strategy ┃ Record·Chronology·Proof ┃ Filings·Hearings
Triage ┃ Think ┃ Evidence ┃ Act
The one analogy that holds all the way down: the Proceeding Lens is a camera, and each mode tells it how to behave. On Triage (Inbox) the camera is off — the view is always matter-wide, so the lens chip is hidden (a live scope chip that does nothing is a lie); per-item proceeding chips carry scope instead. On Switch modes (Brief / Strategy) the camera swaps lenses — you get the proceeding-specific brief, a distinct artifact, not a filtered one. On Narrow modes (Documents…Hearings) the camera zooms — same body, filtered to the scoped proceeding, with a crimson scope strip and a one-click reset.
🔬 Microscope — the seams that make it real
Region per file (storyable in isolation)
The header splits into matter-ribbon.tsx (row 1) and
matter-worknav.tsx (row 2), composed by
matter-detail-header.tsx. WHY split: each
navigation is a region with its own locked lexicon and its own states, so
each gets its own Storybook file and stays small — the header is now a
thin sticky wrapper, not a 150-line do-everything client.
The Worknav enum + lens taxonomy (presentation, not the client)
presentation.ts exposes WORKNAV_MODES (the
ordered list, each tagged triage or work, divider
after Inbox) and LENS_TYPE (the triage/switch/narrow
map). WHY in presentation: AGENTS.md keeps mode order, class
tags, and lens behavior out of the client file — the Worknav just renders
the map and decides "is the lens hidden?" from LENS_TYPE[activeMode] === "triage".
The lens chip's three scope treatments
When shown (Switch + Narrow), the lens chip renders one of three token-backed treatments: main (default Main-arbitration scope, plain), scoped (a named proceeding — crimson border-left + crimson dot), wide (explicit Matter-wide aggregate — dashed). WHY crimson only for scoped: crimson is a status hue (design system), so it marks the one state that narrows what counsel sees — a proceeding filter is active and must be visible at a glance.
The Action Rail drops to four
RailIconId collapses from ten ids to
intake | notes | audit | settings — Inbox left for the
Worknav, and the redundant mark-filed / log-call / mira / marginalia /
parties verbs go with it. Intake is the polymorphic Add (the
+); admin is renamed settings and
still opens the Matter administration panel. WHY four: the
rail owns only always-available matter tools; mode-local actions
(Inbox triage chips, Proof open-links) live inside the active work surface,
not the rail.
Record ≠ documents — rename the container, not the contents
The document Work Mode becomes Record (tab label) and the
page is the Evidentiary Record (heading / empty state).
The route moves /matters/[id]/documents →
/matters/[id]/record (old path deleted, no back-compat
redirect — proxy.ts localizes bare paths and there are no
external links). The Worknav Record mode navigates there; internal links
(revalidate, business-action route template, page-help map, gateway route
tags, the legacy sub-nav) all repoint. WHY only the container:
the items inside the Record are documents — so the
documents DB table, the document_type catalog, the
register's "Document Type" column, and every backend /app/.../documents
path and S3 key stay exactly as they are. Record is the shelf; documents are
the books.
/record route and does not rework it. Other Work Modes render a
placeholder body. The landing rule reads role + pending count — neither is
in the payload yet, so the shell defaults to Brief (the
documented safe landing) with an inbox count of 0; wiring real role/pending
data is the follow-up. The /record route still wears the legacy
matter chrome (sub-nav) under Story-1527's mount; unifying it under this
locked-IA chrome is a tracked follow-up.
What shipped
| Acceptance criterion | Where | |
|---|---|---|
| Matter Ribbon — 5 locked elements + practice area | matter-ribbon.tsx (crumb · title · practice area · Urgency Pill · ⌘K search · Agent Capsule) | done |
| Matter Worknav — 8-mode enum, Option-B layers (Inbox ┃ Brief·Strategy ┃ Record·Chronology·Proof ┃ Filings·Hearings) with visible separators | matter-worknav.tsx + WORKNAV_LAYERS | done |
Record rename + route move /documents→/record (no redirect); links repointed; backend untouched | route dir record/, WorkMode record, matter-subnav, gateway/route-template/page-help/revalidate; "Evidentiary Record" heading + empty state | done |
| Single active-mode enum (Inbox is a route mode, not a panel) | WorknavMode in types.ts | done |
| Proceeding Lens — hidden on Inbox; main / scoped / wide treatments | MatterWorknav lensScope + LENS_TYPE | done |
| Action Rail = 4 icons (Intake · Notes · Audit · Settings) | rail-icon-strip.tsx, RailIconId, matter-right-rail.tsx | done |
| Lens taxonomy wired (Triage / Switch / Narrow) | LENS_TYPE map drives lens-hidden + chip variants | done |
| Storybook per region (Ribbon · Worknav · Rail, 4 icons) | matter-ribbon.stories, matter-worknav.stories, rail-icon-strip.stories, matter-detail-header.stories | done |
| i18n (en + hi) · tokens · a11y | MatterDetail.* both locales; semantic tokens only; ARIA-labelled tabs / rail buttons | done |
| Evidentiary Record register content | owned by Story-1527 (merged) — mounted at /record; not reworked here | out of scope |
Verify: pnpm lint (0 errors), tsc --noEmit ✓,
pnpm test (matter-detail suites pass),
pnpm storybook:check ✓ (drift + build), i18n en/hi parity ✓,
color-literal scan clean.
Files changed
27 file(s), +629 / -276 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +10 | -11 |
| messages/hi.json | Config | +10 | -11 |
| src/app/[locale]/(workspace)/matters/[matterId]/{documents => record}/loading.tsx | App route | +0 | -0 |
| src/app/[locale]/(workspace)/matters/[matterId]/{documents => record}/page.tsx | App route | +0 | -0 |
| src/app/[locale]/(workspace)/matters/actions.ts | App route | +1 | -1 |
| src/components/documents/register/saved-views-register.stories.tsx | Component | +1 | -1 |
| src/components/documents/register/saved-views-register.tsx | Component | +1 | -1 |
| src/components/matter-detail/matter-detail-header.stories.tsx | Component | +27 | -17 |
| src/components/matter-detail/matter-detail-header.tsx | Component | +38 | -124 |
| src/components/matter-detail/matter-ribbon.stories.tsx | Component | +47 | -0 |
| src/components/matter-detail/matter-ribbon.tsx | Component | +91 | -0 |
| src/components/matter-detail/matter-right-rail.stories.tsx | Component | +1 | -3 |
| src/components/matter-detail/matter-right-rail.tsx | Component | +8 | -10 |
| src/components/matter-detail/matter-worknav.stories.tsx | Component | +61 | -0 |
| src/components/matter-detail/matter-worknav.tsx | Component | +118 | -0 |
| src/components/matter-detail/presentation.ts | Component | +60 | -7 |
| src/components/matter-detail/rail-icon-strip.stories.tsx | Component | +29 | -0 |
| src/components/matter-detail/rail-icon-strip.tsx | Component | +18 | -41 |
| src/components/matter-detail/types.ts | Component | +47 | -20 |
| src/components/matters/live-matter-documents-page.tsx | Component | +3 | -0 |
| src/components/matters/live-matter-shell.tsx | Component | +43 | -16 |
| src/components/matters/matter-subnav.tsx | Component | +6 | -4 |
| src/lib/kb/page-help-map.ts | Library | +1 | -1 |
| src/lib/matters/business-actions.ts | Library | +1 | -1 |
| src/lib/matters/gateway.ts | Library | +4 | -4 |
| tests/unit/lib/business-action-log.test.ts | Test | +2 | -2 |
| tests/unit/matter-documents-route.test.tsx | Test | +1 | -1 |