12 file(s), +383 / -195 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +3 | -2 |
| messages/hi.json | Config | +3 | -2 |
| src/app/[locale]/(workspace)/matters/[matterId]/hearings/layout.tsx | App route | +33 | -0 |
| src/app/[locale]/(workspace)/matters/[matterId]/hearings/orders/page.tsx | App route | +76 | -0 |
| src/app/[locale]/(workspace)/matters/[matterId]/hearings/page.tsx | App route | +14 | -80 |
| src/app/[locale]/(workspace)/matters/[matterId]/hearings/sessions/page.tsx | App route | +76 | -0 |
| src/components/matter-detail/matter-worknav.tsx | Component | +6 | -2 |
| src/components/matters/hearings/hearings-subtabs.tsx | Component | +61 | -0 |
| src/components/matters/hearings/hearings-tab.tsx | Component | +0 | -101 |
| src/components/matters/hearings/orders-panel.tsx | Component | +50 | -0 |
| src/components/matters/hearings/sessions-panel.tsx | Component | +43 | -0 |
| src/lib/surface-registry/route-registry.ts | Library | +18 | -8 |
The Hearings tab's two subtabs — Hearing Sessions and Orders &
Directions — are now real, deep-linkable routes:
/matters/[matterId]/hearings/sessions and
/matters/[matterId]/hearings/orders. The bare
/matters/[matterId]/hearings URL never renders naked — it
redirects to Sessions (the default). This continues EPIC-2018
(route-per-mode) on top of the fat matter layout (S1, Story-2019),
one level down: a subtab is now the URL, not useState.
hearings/sessions/page.tsx and
hearings/orders/page.tsx. Each is a thin RSC that resolves
the main proceeding, server-prefetches ONLY its own event stream
(Sessions → proceeding stages; Orders → the audit timeline scoped to
decisions) into a per-request QueryClient, and
hydrates it so the client body reads a warm cache (no first-paint
spinner; ADR-095). Splitting the streams into separate routes means each
subpage prefetches one register instead of both up front.
agentSurfaces — keys
matter.hearings.sessions and
matter.hearings.orders (presence keys
AgentSurfaces.matterHearingsSessions /
matterHearingsOrders, added in en + hi). The old bare
matter.hearings surface is retired — its page is now a pure
redirect exporting agentSurface = null. Registry
regenerated with pnpm gen:routes.
<Link> nav — a
new hearings/layout.tsx renders
HearingsSubtabs ONCE around both subpages. The old
SegmentedFilter + useState<Subtab> is
gone; the active subtab is derived from the pathname via the surface
registry (resolveSurfaceForPath), never threaded as a prop.
Right-click / ⌘-click / open-in-new-tab all work because each subtab is a
real <a href> built with buildSurfacePath.
HearingsTab deleted — its per-subtab
loading / error / empty / populated state logic moved into two small
client bodies, SessionsPanel and OrdersPanel,
each owning its own generated TanStack hook. The leaf renderers
(SessionsTable, OrdersList) are unchanged.
matter.hearings.sessions (the surviving default
surface) and stays active on both subpages, since the bare
matter.hearings key no longer exists in the registry.
A literal redirect("./sessions") resolves relative to the
request URL and would drop the locale / matter segments. The page instead
builds the target from the durable key:
redirect(localizeHref(locale, buildSurfacePath("matter.hearings.sessions", { matterId })))
— yielding /en/matters/42/hearings/sessions, keeping the
locale prefix, and obeying the repo rule to navigate by
surface_key, never a string-built raw URL.
The old SegmentedFilter showed a live count badge per subtab,
which was free when one page fetched both streams. With the split, each
subpage prefetches only its own stream (the whole point — RFD §4), so the
<Link> subtab row intentionally drops the counts rather
than double-fetching the sibling stream just to badge it.
pnpm gen:routes + pnpm check:routes — 31 surfaces, registry fresh.pnpm lint — clean (one pre-existing unrelated <img> warning).pnpm test — 1001 passed (incl. route-registry freshness).pnpm build — compiled; hearings, hearings/sessions, hearings/orders all present.