Hearings tab: events, not documents
A matter's Hearings tab now opens on two read-only event streams for the matter's Main proceeding — Hearing Sessions and Orders & Directions — switched by a segmented control. The doctrine line: documents are nouns, hearings are events. Every document lives in Filings (Story-1886); Hearings holds only sessions and orders and merely references documents. There is no Exhibit Register here.
What shipped
-
New route
/matters/[matterId]/hearingsmirroring the Filings sub-route — sameLiveMatterDetailPagechrome +MatterSubnav, reachable from the matter Worknav and the subnav. The subnav's old#hearingshash anchor is now a real route. - Hearing Sessions grid: session label, type, forum/bench, date, status — a segmented subtab with a live count.
- Orders & Directions: the proceeding's orders and procedural directions, newest first, each with who issued it and when.
-
Data follows the forum pattern: RSC server-prefetch →
dehydrate→ hydrate → generated TanStack hooks. Read-only — no mutations.
Contract reality — what powers each subtab (and the gaps)
The brief was explicit: do not invent an endpoint. There
is no dedicated hearing_session / order entity in the synced
src/gen contract yet (it lands in v2). So v1 reads the closest
truthful sources and names every gap:
| Surface | Backing contract | Mapping & gap |
|---|---|---|
| Hearing Sessions | GET /app/matters/{n}/proceedings/{pid}/stages → ProceedingStageView[] |
Session = stage title; Type = stageKey token
(em-dash if absent); Date = stageDate ?? occurredAt;
Status = stage status (Active / Archived).
Gap: Forum / Bench has no backing field on
ProceedingStageView — it renders an em-dash. A
dedicated session "type" is approximated by the stable stage key.
|
| Orders & Directions | GET /app/audit/matters/{uuid}/proceedings/{pid}?category=decisions → AuditEventView[] |
No dedicated order entity exists, so Orders & Directions is the
proceeding's audit timeline scoped to the decisions
category — orders, rulings and directions are decision
events. Headline = subjectLabel ?? action; meta =
occurredAt + actorDisplayName.
Gap: first page only (limit 50, no cursor paging in
v1); a first-class order entity replaces this in v2.
|
Note the two matter ids: the stages path keys the matter by its
firm-scoped number, the audit-timeline path by its
UUID. They are different ids by contract — the page
passes numericMatterId to sessions and matter.id
to orders.
Explicitly deferred (not faked)
-
DL-08 exhibit deep-link chips — the
mockup's "Exhibits marked" column of clickable
C-1chips that jump to Filings → Exhibit Register. Deferred to v2. They need ahearing_session_idto anchor each marking to a session, which does not exist until the hearing-session entity lands. No chips are built; the column is omitted. - Forum / Bench value — surfaced as a column (it is part of the session identity) but em-dashed pending a contract field.
Out of scope
Filings / registers (#1886, already merged), Annexure Index export (#1888), and the Blake review surface (#1889). This Story is only the Hearings tab's two event subtabs.
Empty / first-run state
The real deliverable is the zero-row first run — a brand-new matter
whose main proceeding has no recorded sessions or orders yet. Both subtabs
render an explicit, honest empty panel (never a broken-looking blank), plus
loading and error states, all sharing the register chrome. Verified in
Storybook (Matters/Hearings/States) and against the real
src/gen types — imports from src/gen only;
no pnpm codegen run (off current main after
Chore-1885).
Trust boundary & surface registry
-
New BFF proxies keep the browser on
/api/internal/*: Sessions reuse the FilingsproceedingProxyHandler; Orders get a proceeding-audit proxy.toBrowserRoutemaps both new paths. Every gateway read is parsed throughassertContractagainst the generated schema (HR-21). -
page.tsxexportsagentSurface(key: "matter.hearings", agentmatter); the route registry regenerated to 30 surfaces.
Verify
- pnpm lint — 0 errors (1 pre-existing warning in
add-matter/avatar.tsx). - pnpm exec tsc --noEmit — clean.
- pnpm test — 949 passing (incl. route-registry freshness + i18n parity).
- pnpm build — green;
/matters/[matterId]/hearings+ both proxies registered. - pnpm check:routes — surface registry fresh (30 surfaces;
matter.hearingsadded). - i18n:
Hearingsnamespace +AgentSurfaces.matterHearingsin bothenandhi. No color literals introduced.
Files changed
19 file(s), +762 / -7 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +34 | -1 |
| messages/hi.json | Config | +34 | -1 |
| src/app/[locale]/(workspace)/matters/[matterId]/hearings/page.tsx | App route | +94 | -0 |
| src/app/api/internal/audit/matters/[matterId]/proceedings/[proceedingId]/route.ts | App route | +48 | -0 |
| src/app/api/internal/matters/[matterId]/proceedings/[proceedingId]/stages/route.ts | App route | +7 | -0 |
| src/components/matters/hearings/hearings-fixtures.ts | Component | +78 | -0 |
| src/components/matters/hearings/hearings-states.stories.tsx | Component | +55 | -0 |
| src/components/matters/hearings/hearings-tab.tsx | Component | +101 | -0 |
| src/components/matters/hearings/orders-list.stories.tsx | Component | +29 | -0 |
| src/components/matters/hearings/orders-list.tsx | Component | +46 | -0 |
| src/components/matters/hearings/presentation.ts | Component | +21 | -0 |
| src/components/matters/hearings/sessions-table.stories.tsx | Component | +30 | -0 |
| src/components/matters/hearings/sessions-table.tsx | Component | +77 | -0 |
| src/components/matters/live-matter-shell.tsx | Component | +6 | -0 |
| src/components/matters/matter-subnav.tsx | Component | +1 | -1 |
| src/components/matters/matter-tabs.tsx | Component | +1 | -1 |
| src/lib/hearings/gateway.ts | Library | +73 | -0 |
| src/lib/kubb-client.ts | Library | +15 | -1 |
| src/lib/surface-registry/route-registry.ts | Library | +12 | -2 |