PR: #688: feat(record): add Blake card drawer [Story-2700] by @ankitmaheshwari-com
View PR →
Story-2700

“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

The icon

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.

The drawer

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.

The way back

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

SectionRead model fieldWhy it sits there
Header: name · Type · significancename, docType, subType, matterSignificanceType is the column that reads “—” in the register. Significance is badged 🔴 critical / 🟠 important / 🟡 peripheral / unassessed.
Side checksideCheckLeads 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.
SummarysummaryBlake's prose, with its citation trail.
Why it mattersmatterSignificance.whyThe badge asserts a level; this is the reasoning behind it.
Parties · Amounts · Date · Referencesparties, amounts, date, referencesWhat the document names. Every one is a cited claim, so every one carries its spans.
Tags · Keywordstags, keywordsThe full list the row could only show as “+5”.
FlagsisFilingInMatter, privilegeCandidatePrivilege 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

StateWhenWhat it says
ReadyA card is presentThe card, with empty fields hidden rather than padded with dashes.
Blake is reading…Status is still in the pipelineA promise: the card appears once Blake has finished.
Could not readStatus failed / needs_review / unreadableA dead end counsel must act on: upload a readable copy.
No cardPipeline finished, no card came backThe 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 internal DocumentCard has 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 primary date; the internal card's dates: CardDate[] (kind, precision) is not projected.
  • provenance — model_id, prompt_version, token cost live on the internal DocumentCard.enrichment stamp and are not projected. The collapsible provenance section is therefore not built.

Review path (Storybook)

StoryExpected proof
BlakeCardDrawer / FullEvery section renders; the amber SIDE-check banner leads with ⚠️ and the reason; cite chips appear under each claim.
BlakeCardDrawer / SidesAgreeNo alarm — a quiet “Blake agrees” instead of the amber banner.
BlakeCardDrawer / ThinCardRenders SHORT. No dashes, no empty sections — absence is information.
BlakeCardDrawer / PrivilegeCandidateCrimson candidate line with its basis, ending “Counsel decides.”
BlakeCardDrawer / BlakeIsReading · Unreadable · NoCardThe three cardless states, each worded differently.
BlakeCardDrawer / OpenAndCloseEnter 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.jsonConfig+57-2
messages/hi.jsonConfig+57-2
openapi/app-api.jsonConfig+255-2
src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsxApp route+15-0
src/app/[locale]/(workspace)/matters/actions.tsApp route+12-0
src/app/[locale]/(workspace)/matters/page.tsxApp route+1-0
src/components/documents/card/blake-card-body.tsxComponent+72-0
src/components/documents/card/blake-card-drawer.stories.tsxComponent+239-0
src/components/documents/card/blake-card-drawer.tsxComponent+128-0
src/components/documents/card/blake-card-facts.tsxComponent+93-0
src/components/documents/card/blake-card-flags.tsxComponent+60-0
src/components/documents/card/blake-card-placeholder.tsxComponent+68-0
src/components/documents/card/blake-card-section.tsxComponent+55-0
src/components/documents/card/blake-card-side-check.tsxComponent+69-0
src/components/documents/card/cited-spans.tsxComponent+58-0
src/components/documents/card/presentation.test.tsComponent+129-0
src/components/documents/card/presentation.tsComponent+123-0
src/components/documents/card/significance-badge.tsxComponent+39-0
src/components/documents/register/register-document-row.tsxComponent+2-0
src/components/documents/register/register-row-quick-actions.tsxComponent+11-1
src/components/documents/register/register-table.tsxComponent+4-0
src/components/documents/register/register-view.tsxComponent+18-1
src/components/matters/add-matter-launcher.tsxComponent+17-1
src/components/matters/live-matters-list-page.tsxComponent+4-0
src/components/matters/live-matters-wizard-payload.tsComponent+13-0
src/components/matters/matters-live-register-row.stories.tsxComponent+1-0
src/gen/client/appGetDocumentDeepReadEnvelope.tsGenerated contract+3-1
src/gen/hooks/useAppGetDocumentDeepReadEnvelope.tsGenerated contract+5-1
src/gen/hooks/useAppGetDocumentDeepReadEnvelopeSuspense.tsGenerated contract+5-1
src/gen/index.tsGenerated contract+15-0
src/gen/schemas/cardBuiltFrom.jsonGenerated contract+15-0
src/gen/schemas/cardEnrichment.jsonGenerated contract+34-0
src/gen/schemas/cardNotice.jsonGenerated contract+16-0
src/gen/schemas/cardSelfCheck.jsonGenerated contract+33-0
src/gen/schemas/citedUnionBoolNoneType.jsonGenerated contract+95-0
src/gen/schemas/documentListItem.jsonGenerated contract+236-1
src/gen/schemas/documentListResponse.jsonGenerated contract+257-1
src/gen/schemas/evidenceIndexManifest.jsonGenerated contract+257-1
src/gen/schemas/matterCreateRequest.jsonGenerated contract+13-1
src/gen/schemas/productionCardView.jsonGenerated contract+225-1
src/gen/schemas/recordBootstrapResponse.jsonGenerated contract+267-1
src/gen/types/AppGetDocumentDeepReadEnvelope.tsGenerated contract+7-0
src/gen/types/CardBuiltFrom.tsGenerated contract+26-0
src/gen/types/CardEnrichment.tsGenerated contract+33-0
src/gen/types/CardNotice.tsGenerated contract+25-0
src/gen/types/CardSelfCheck.tsGenerated contract+36-0
src/gen/types/CitedUnionBoolNoneType.tsGenerated contract+30-0
src/gen/types/MatterCreateRequest.tsGenerated contract+10-0
src/gen/types/ProductionCardView.tsGenerated contract+44-0
src/gen/types/index.tsGenerated contract+6-0
src/gen/zod/appGetDocumentDeepReadEnvelopeSchema.tsGenerated contract+10-0
src/gen/zod/cardBuiltFromSchema.tsGenerated contract+14-0
src/gen/zod/cardEnrichmentSchema.tsGenerated contract+19-0
src/gen/zod/cardNoticeSchema.tsGenerated contract+14-0
src/gen/zod/cardSelfCheckSchema.tsGenerated contract+16-0
src/gen/zod/citedUnionBoolNoneTypeSchema.tsGenerated contract+29-0
src/gen/zod/index.tsGenerated contract+6-0
src/gen/zod/matterCreateRequestSchema.tsGenerated contract+12-0
src/gen/zod/productionCardViewSchema.tsGenerated contract+33-0
src/lib/documents/register/adapter.tsLibrary+2-0
src/lib/documents/register/filters.test.tsLibrary+1-0
src/lib/documents/register/fixtures.tsLibrary+12-0
src/lib/documents/register/types.tsLibrary+9-0
src/lib/matters/optimistic.test.tsLibrary+2-0
src/lib/surface-registry/route-registry.tsLibrary+12-2
tests/unit/live-matters-wizard-payload.test.tsTest+24-1
tests/unit/matter-actions.test.tsTest+16-0