“Blake read this” — the card drawer
Blake produces a rich production card per document. The Record register is a table, and a table has room for Tags and Side and nothing else — Type reads “—”, and significance, the SIDE check, the summary and the parties have no column at all. The card now gets a surface of its own: a right drawer over the register, so counsel reads what Blake concluded without losing their place in the list.
How counsel reaches it
A ScanText quick action on every Record row, beside Preview and Download. Offered even before the card lands — telling counsel Blake is still reading is part of the drawer's job.
Slides in from the right over the table under AnimatePresence, dimming the register behind it. Esc peels it first, then the peek, then the cursor.
Close returns counsel to exactly the row they were on — the cursor moved to it when the drawer opened.
What the drawer shows, in reading order
| Section | Read model field | Why it sits there |
|---|---|---|
| Header: name · Type · significance | name, docType, subType, matterSignificance | Type is the column that reads “—” in the register. Significance is badged 🔴 critical / 🟠 important / 🟡 peripheral / unassessed. |
| Side check | sideCheck | Leads the drawer: it is the one part of the card that can be wrong in a way that costs counsel. A mismatch renders amber with ⚠️ and the reason; agreement renders quiet. |
| Summary | summary | Blake's prose, with its citation trail. |
| Why it matters | matterSignificance.why | The badge asserts a level; this is the reasoning behind it. |
| Parties · Amounts · Date · References | parties, amounts, date, references | What the document names. Every one is a cited claim, so every one carries its spans. |
| Tags · Keywords | tags, keywords | The full list the row could only show as “+5”. |
| Flags | isFilingInMatter, privilegeCandidate | Privilege is worded as a candidate — Blake raising a hand, never the membrane's verdict. The copy says “Counsel decides” out loud. |
Citations deep-link to the page
Every cited claim renders Blake's own citeLabel chips underneath it. Each chip
is a link to matter.record.doc?page=N — the Original lens, turned to the exact
page the span was quoted from, through the shared buildDocumentPageHref seam
(Story-2435) that Insights already uses. The verbatim quote rides on the title, so the
passage is readable before the jump. A card claim without its source is an assertion;
counsel is one click from the passage it came from.
The honest states
| State | When | What it says |
|---|---|---|
| Ready | A card is present | The card, with empty fields hidden rather than padded with dashes. |
| Blake is reading… | Status is still in the pipeline | A promise: the card appears once Blake has finished. |
| Could not read | Status failed / needs_review / unreadable | A dead end counsel must act on: upload a readable copy. |
| No card | Pipeline finished, no card came back | The distinction worth a third state — dressing this up as “still processing” would leave counsel waiting for a card that is never coming. |
Read-model gaps — NOT re-derived in the app
Three fields the Story names are not on the app-facing
ProductionCardView. Per the Story's own instruction they are
reported rather than reconstructed here — a functions follow-up owns them:
key_assertions— absent entirely. The internalDocumentCardhas no assertions field on the app projection either. Deep-linking is therefore delivered on every OTHER cited claim (summary, parties, amounts, date, references, tags), which the contract does carry with full spans.dates[]— the projection carries one primarydate; the internal card'sdates: CardDate[](kind, precision) is not projected.- provenance —
model_id,prompt_version, token cost live on the internalDocumentCard.enrichmentstamp and are not projected. The collapsible provenance section is therefore not built.
Review path (Storybook)
| Story | Expected proof |
|---|---|
| BlakeCardDrawer / Full | Every section renders; the amber SIDE-check banner leads with ⚠️ and the reason; cite chips appear under each claim. |
| BlakeCardDrawer / SidesAgree | No alarm — a quiet “Blake agrees” instead of the amber banner. |
| BlakeCardDrawer / ThinCard | Renders SHORT. No dashes, no empty sections — absence is information. |
| BlakeCardDrawer / PrivilegeCandidate | Crimson candidate line with its basis, ending “Counsel decides.” |
| BlakeCardDrawer / BlakeIsReading · Unreadable · NoCard | The three cardless states, each worded differently. |
| BlakeCardDrawer / OpenAndClose | Enter AND exit replayable — the motion doctrine's required interactive story. |
Verification
pnpm lint 0 errors · pnpm tsc --noEmit clean ·
pnpm test 231 files / 1461 tests passing (15 new, covering the card-state
machine, the SIDE-mismatch rule, minor-unit amount scaling, and span deep-links) ·
pnpm build compiled successfully.
Live staging confirmation needs a real carded document on the Record. The Storybook states above are component proof, not the system boundary — per the repo's own “done = system boundary” doctrine the issue stays in Review until a real carded document has been opened in the drawer on staging.
Files changed
67 file(s), +3524 / -21 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +57 | -2 |
| messages/hi.json | Config | +57 | -2 |
| openapi/app-api.json | Config | +255 | -2 |
| src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx | App route | +15 | -0 |
| src/app/[locale]/(workspace)/matters/actions.ts | App route | +12 | -0 |
| src/app/[locale]/(workspace)/matters/page.tsx | App route | +1 | -0 |
| src/components/documents/card/blake-card-body.tsx | Component | +72 | -0 |
| src/components/documents/card/blake-card-drawer.stories.tsx | Component | +239 | -0 |
| src/components/documents/card/blake-card-drawer.tsx | Component | +128 | -0 |
| src/components/documents/card/blake-card-facts.tsx | Component | +93 | -0 |
| src/components/documents/card/blake-card-flags.tsx | Component | +60 | -0 |
| src/components/documents/card/blake-card-placeholder.tsx | Component | +68 | -0 |
| src/components/documents/card/blake-card-section.tsx | Component | +55 | -0 |
| src/components/documents/card/blake-card-side-check.tsx | Component | +69 | -0 |
| src/components/documents/card/cited-spans.tsx | Component | +58 | -0 |
| src/components/documents/card/presentation.test.ts | Component | +129 | -0 |
| src/components/documents/card/presentation.ts | Component | +123 | -0 |
| src/components/documents/card/significance-badge.tsx | Component | +39 | -0 |
| src/components/documents/register/register-document-row.tsx | Component | +2 | -0 |
| src/components/documents/register/register-row-quick-actions.tsx | Component | +11 | -1 |
| src/components/documents/register/register-table.tsx | Component | +4 | -0 |
| src/components/documents/register/register-view.tsx | Component | +18 | -1 |
| src/components/matters/add-matter-launcher.tsx | Component | +17 | -1 |
| src/components/matters/live-matters-list-page.tsx | Component | +4 | -0 |
| src/components/matters/live-matters-wizard-payload.ts | Component | +13 | -0 |
| src/components/matters/matters-live-register-row.stories.tsx | Component | +1 | -0 |
| src/gen/client/appGetDocumentDeepReadEnvelope.ts | Generated contract | +3 | -1 |
| src/gen/hooks/useAppGetDocumentDeepReadEnvelope.ts | Generated contract | +5 | -1 |
| src/gen/hooks/useAppGetDocumentDeepReadEnvelopeSuspense.ts | Generated contract | +5 | -1 |
| src/gen/index.ts | Generated contract | +15 | -0 |
| src/gen/schemas/cardBuiltFrom.json | Generated contract | +15 | -0 |
| src/gen/schemas/cardEnrichment.json | Generated contract | +34 | -0 |
| src/gen/schemas/cardNotice.json | Generated contract | +16 | -0 |
| src/gen/schemas/cardSelfCheck.json | Generated contract | +33 | -0 |
| src/gen/schemas/citedUnionBoolNoneType.json | Generated contract | +95 | -0 |
| src/gen/schemas/documentListItem.json | Generated contract | +236 | -1 |
| src/gen/schemas/documentListResponse.json | Generated contract | +257 | -1 |
| src/gen/schemas/evidenceIndexManifest.json | Generated contract | +257 | -1 |
| src/gen/schemas/matterCreateRequest.json | Generated contract | +13 | -1 |
| src/gen/schemas/productionCardView.json | Generated contract | +225 | -1 |
| src/gen/schemas/recordBootstrapResponse.json | Generated contract | +267 | -1 |
| src/gen/types/AppGetDocumentDeepReadEnvelope.ts | Generated contract | +7 | -0 |
| src/gen/types/CardBuiltFrom.ts | Generated contract | +26 | -0 |
| src/gen/types/CardEnrichment.ts | Generated contract | +33 | -0 |
| src/gen/types/CardNotice.ts | Generated contract | +25 | -0 |
| src/gen/types/CardSelfCheck.ts | Generated contract | +36 | -0 |
| src/gen/types/CitedUnionBoolNoneType.ts | Generated contract | +30 | -0 |
| src/gen/types/MatterCreateRequest.ts | Generated contract | +10 | -0 |
| src/gen/types/ProductionCardView.ts | Generated contract | +44 | -0 |
| src/gen/types/index.ts | Generated contract | +6 | -0 |
| src/gen/zod/appGetDocumentDeepReadEnvelopeSchema.ts | Generated contract | +10 | -0 |
| src/gen/zod/cardBuiltFromSchema.ts | Generated contract | +14 | -0 |
| src/gen/zod/cardEnrichmentSchema.ts | Generated contract | +19 | -0 |
| src/gen/zod/cardNoticeSchema.ts | Generated contract | +14 | -0 |
| src/gen/zod/cardSelfCheckSchema.ts | Generated contract | +16 | -0 |
| src/gen/zod/citedUnionBoolNoneTypeSchema.ts | Generated contract | +29 | -0 |
| src/gen/zod/index.ts | Generated contract | +6 | -0 |
| src/gen/zod/matterCreateRequestSchema.ts | Generated contract | +12 | -0 |
| src/gen/zod/productionCardViewSchema.ts | Generated contract | +33 | -0 |
| src/lib/documents/register/adapter.ts | Library | +2 | -0 |
| src/lib/documents/register/filters.test.ts | Library | +1 | -0 |
| src/lib/documents/register/fixtures.ts | Library | +12 | -0 |
| src/lib/documents/register/types.ts | Library | +9 | -0 |
| src/lib/matters/optimistic.test.ts | Library | +2 | -0 |
| src/lib/surface-registry/route-registry.ts | Library | +12 | -2 |
| tests/unit/live-matters-wizard-payload.test.ts | Test | +24 | -1 |
| tests/unit/matter-actions.test.ts | Test | +16 | -0 |