The Record view: real columns, readability status & privilege-off-Record
The matter's evidentiary Record already rendered a rich, sortable
register (real columns, saved views, slicer). This story reconciles it
to the rest of the document-import substrate: it teaches the status
column the new terminal unreadable state, adds the
import-completeness honesty line so nothing is silently
missing, and reflects that privileged documents live
off the Record — they appear only as a completeness drop
reason, never as rows.
What changed
unreadable status
The processing/readability pill learns unreadable
(stored, but no extractable text — OCR/encrypted/corrupt). A
terminal state needing human action (re-upload a readable copy), so
it shares the amber attention palette with needs_review,
distinct from the red system failed. Localized in
en + hi; the pill keeps its raw-token
fallback for any future status.
ImportCompletenessSummary renders the matter's
falsifiable import ledger (functions Story-1855,
MatterImportCompletenessResponse):
accounted / in-flight / dropped against
expected. Green when the V-LEDGER identity holds
(isComplete), amber while imports settle, red on a true
residual (silent loss). Live via the generated
useAppGetMatterImportCompleteness hook.
Privileged documents (privilege_flag) are gated out
server-side (consistent with the privilege-marking story #1854), so
they never arrive as rows. The Record reflects that by surfacing the
excluded count as a privileged drop reason in the
completeness ledger — visible, not silent.
The completeness ledger follows the canonical pattern:
record/page.tsx best-effort prefetches it server-side
and seeds the query cache; HydrationBoundary hydrates it
warm; the client reads via the generated
useAppGetMatterImportCompleteness hook through a new
/api/internal/…/imports/completeness BFF proxy
(HR-21/HR-22). A ledger blip never 404s the Record.
Contract status (verified against merged main)
-
Completeness — WIRED. functions
Story-1855 (#496) shipped
GET /app/matters/{id}/documents/imports/completeness→MatterImportCompletenessResponse. The OpenAPI mirror is synced andpnpm codegengenerated the hook + Zod schema; the gateway parses it throughassertContractat the trust boundary (HR-21). -
unreadable— FORWARD-READY, backend does not emit it yet. The mergedDocumentListItem.processingStatusenum is stillpending→…→indexed, failed / needs_review— #496 did not addunreadable. BecauseprocessingStatusis a free string and the pill keeps a raw-token fallback, theunreadabletone + i18n light up the moment the backend starts emitting it (no further app change). No live data drives it today; this is honest UI support, not a claim that the status ships. -
First-run honesty. The container renders
nothing while the query is pending/errored or
expectedCount === 0— a brand-new matter shows no banner until there is an actual import to reconcile (AGENTS.md “done = system boundary”: verified against the real, possibly empty ledger, not a fixture).
Where it lives
openapi/app-api.json+openapi/permission-map.json+src/gen/**— mirror synced from functions main (#496) +pnpm codegen: the completeness hook, client, types, Zod.src/lib/documents/gateway.ts—getImportCompleteness(): one read,assertContractonMatterImportCompletenessResponse(HR-21).src/app/api/internal/matters/[matterId]/documents/imports/completeness/route.ts— the browser→server BFF proxy the hook resolves onto.src/lib/kubb-client.ts—toBrowserRoutemapping for the completeness path.src/components/documents/register/import-completeness-summary.tsx— presentational line over the wire DTO (privileged reason via the lexicon).src/components/documents/register/import-completeness-container.tsx— reads the hook, renders nothing on empty/pending/no-imports.src/components/documents/register/presentation.ts—unreadablestatus tone;completenessResidual/completenessTone; banner + drop-chip token classes.src/components/matters/live-matter-documents-page.tsx— mounts the container above the register.src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx— best-effort prefetch + cache seed;agentSurfacerefresh.messages/en.json+messages/hi.json—status.unreadable+ thecompleteness.*namespace (reasons + item states).src/lib/documents/register/fixtures.ts—UNREADABLE_DOCUMENT/SAMPLE_DOCUMENTS_WITH_UNREADABLE+ wire-shaped sample ledgers (kept out of the shared engine corpus so test counts stay stable).tests/unit/import-completeness-contract.test.ts— full-shape parse + gateway no-store + drift-rejection (HR-21).
Observability
No new instrumentation (pramaan-docs ADR-104 / ADR-065). The summary is
presentational and the bootstrap read already traverses
src/lib/gateway.ts, which mints/forwards
traceparent + x-pramaan-correlation-id —
the trace propagates for free. No OTel SDK, spans, or ad-hoc headers
added on this surface.
Verify
pnpm lint pnpm test pnpm build
- Storybook
DocumentStatusPill→ Unreadable + AllStatuses;RegisterView→ WithUnreadableAndFailedRows (both terminal rows visible);ImportCompletenessSummary→ WithGap (red) / AllAccounted (green) / InFlight (amber) / FullyExplained. - Empty first-run
RegisterView→ EmptyRegister renders the real zero state; with no ledger the completeness line does not mount. - i18n
status.unreadable+completeness.*present in bothenandhi. - Tokens no color literals; amber/red/green from semantic tokens only.
- Surface registry
pnpm gen:routesregenerated; the record surface description names columns + readability + completeness + privilege-off-Record.
Files changed
16 file(s), +523 / -5 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +19 | -0 |
| messages/hi.json | Config | +19 | -0 |
| src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx | App route | +23 | -2 |
| src/app/api/internal/matters/[matterId]/documents/imports/completeness/route.ts | App route | +20 | -0 |
| src/components/documents/register/document-status-pill.stories.tsx | Component | +2 | -0 |
| src/components/documents/register/import-completeness-container.tsx | Component | +31 | -0 |
| src/components/documents/register/import-completeness-summary.stories.tsx | Component | +59 | -0 |
| src/components/documents/register/import-completeness-summary.tsx | Component | +101 | -0 |
| src/components/documents/register/presentation.ts | Component | +44 | -0 |
| src/components/documents/register/register-view.stories.tsx | Component | +16 | -1 |
| src/components/matters/live-matter-documents-page.tsx | Component | +2 | -0 |
| src/lib/documents/gateway.ts | Library | +23 | -0 |
| src/lib/documents/register/fixtures.ts | Library | +76 | -0 |
| src/lib/kubb-client.ts | Library | +12 | -0 |
| src/lib/surface-registry/route-registry.ts | Library | +2 | -2 |
| tests/unit/import-completeness-contract.test.ts | Test | +74 | -0 |