The Documents Register slice navigator
A real matter does not have six tags. It has eighty — "Financial · Vega",
"Affidavit · Acme", "Procedural · Orion" — and thirty filing types, and a
fistful of proceedings. The slice navigator is the left rail a lawyer
uses to cut the register down to one of them: click
"Financial" and the table shrinks to the financial evidence, with its
count and total weight on the row. This Story makes that rail survive the
eighty-tag case without becoming a wall of text — and pins it to the
canonical mockup (matter-detail-documents-AGENT5-v2).
🛰️ Satellite — why a navigator and not just a filter box
The stakes: the register is the lawyer's filing cabinet, and the one question they ask it all day is "show me only this kind." A bare text filter answers that only if they already know the exact value to type. The navigator answers the better question — "what kinds are even in here, and how much of each?" — by listing the values with their counts and sizes, biggest first. That is the difference between searching and browsing; a filing cabinet needs both.
The naive build dumps every value into a <ul>. It
works for the six-tag demo and detonates at eighty: the rail
becomes a 600px scroll of near-duplicates, the table is shoved below the
fold, and the one tag the lawyer wants is lost in the noise. The fix is
not "make the list prettier" — it is to accept that the value space is
unbounded and design three compounding escape hatches for it.
✈️ Airplane — three affordances, one job
Think of the rail as a card catalog drawer. You never riffle all eighty cards. You either know the heading and jump to it, or you read the front few and pull the tab for "everything else". The navigator gives the drawer three handles, and they stack:
| Affordance | The pressure it relieves |
|---|---|
| Count-sort | The values that matter most (most documents) float to the top, so the useful six are usually the visible six. |
| Search | When the lawyer knows the heading ("Vega"), they type it and the long tail collapses to the matches — the cap steps out of the way. |
| Cap + "Show all N" | The default view shows six and names the rest (Show all 84); the tail is one click away, never in the face. |
| Scroll | When all 84 are expanded, they live in a fixed-height scroller — the table never gets pushed off-screen. |
The engine already does the hard half. Story-1510 (DOCS-VIEWENGINE) emits
a SliceModel — the values, each with a corpus-wide count and
total byte size, already count-sorted with the "missing value" sentinels
last. This Story is the presentation of that model: it adds no
counting logic, only the header, the search, the cap, and the scroll.
🔬 Microscope — the parts that earn their place
Why a header that names the field in crimson mono
The same rail slices by tag, by document type, by filing type, by status —
whatever the View popover (a later Story) points it at. Without a label,
the lawyer cannot tell "Financial" (a tag) from "Evidence" (a document
type) when both could appear. The Slice by · Tag header,
with the field in the crimson monospace the mockup reserves for
machine-chosen axes, says which question this list answers. The
field name is the localized column label, so it is correct in Hindi too.
Why count is plain and size is a pill
A row carries two numbers that mean different things: how many
documents (a tally) and how heavy (a magnitude). Rendering both
as identical grey text makes the eye stumble. The mockup distinguishes
them — count as a right-aligned mono tally, size as a rounded pill — and
we mirror that exactly. Both are gated by the shared field-sum
mode (count / size / both /
none), the same toggle that drives the footer, so the rail
and the footer never disagree about what a number means.
Why the cap turns itself off during search
Capping is for browsing, not for searching. If a lawyer has typed "Vega" and there are nine matches, hiding three behind "Show all" would be hostile — they already told us what they want. So the cap applies only when the search box is empty; a query shows every match, and an empty result set says No matches rather than a blank rail.
// the whole cap rule, in three lines
const capped = !showAll && !q && filtered.length > CAP;
const visible = capped ? filtered.slice(0, CAP) : filtered;
const expandable = !q && filtered.length > CAP;
Why a scroller and not a virtual list
Eighty rows is not ten thousand. A windowing library (react-window) would
add a dependency and a measurement dance to save paint cost the browser
does not feel at this size — and the mockup itself just sets
max-height and lets the browser scroll. We do the same:
the expanded list lives in a fixed-height, overflow-scrolled column. It
stays smooth at the 84-tag fixture, and the table below it never moves.
If a future corpus makes this the bottleneck, windowing is a
drop-in — but we do not pay for it speculatively.
SCALE_TAG_DOCUMENTS, 84 tags;
SCALE_FILING_DOCUMENTS, 32 filing types) are the
scale case, and the stories render the rail against them. The count-sort,
the cap, the "Show all 84", the scroll — all verified at the volume the
PRD names, not at the volume that is easy.
What shipped
| File | Change |
|---|---|
register-slice-navigator.tsx | Rebuilt to mockup parity: Slice by header, count-as-tally + size-as-pill gated by field-sum mode, search-aware cap, fixed-height scroller, crimson selected state, ARIA-labelled nav + aria-pressed rows. |
register-slice-navigator.stories.tsx | Stories per state: tags, tags-at-scale (84), filing-types-at-scale (32), status, value-selected, the four field-sum modes, empty corpus. |
fixtures.ts | Deterministic SCALE_TAG_DOCUMENTS / SCALE_FILING_DOCUMENTS generators — the 80-tag / 30-filing-type corpora. |
messages/{en,hi}.json | DocumentsRegister.slice.sliceBy in both locales. |
Mode-1 honoured: AI-derived fields (tags, document type) stay nullable and
fall into their localized sentinel buckets ("Untagged", "Not classified")
until EPIC-009 fills them. Tokens only — no hex/rgb literals; selected =
bg-brand-primary-tint / text-brand-primary.
One-sentence recall
The slice navigator turns the engine's value model into a browsable card-catalog drawer that count-sorts, searches, caps, and scrolls — so eighty tags read as easily as six.
Files changed
6 file(s), +226 / -83 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +1 | -0 |
| messages/hi.json | Config | +1 | -0 |
| src/components/documents/register/register-slice-navigator.stories.tsx | Component | +31 | -18 |
| src/components/documents/register/register-slice-navigator.tsx | Component | +115 | -64 |
| src/components/documents/register/register-view.tsx | Component | +1 | -1 |
| src/lib/documents/register/fixtures.ts | Library | +77 | -0 |