PR: #607: feat(documents): Re-read and Delete — the only two document verbs [Story-2353] by @ankitmaheshwari-com
View PR →

Story-2353 · pramaan-app · 14 Jul 2026

Re-read and Delete

A lawyer now sees exactly two things they can do to a document — everywhere a document lives. Everything else the machine used to ask them to choose between is now the machine’s own problem.

What counsel used to face

A document that had been read badly offered a lawyer a diagnosis job. Was the text extracted wrong? Was it classified wrong? Were the sections cut in the wrong places? Each had its own button, in its own place, under its own name — and picking the right one required knowing how our pipeline is built. That is our knowledge, not theirs. A failed row in the register even carried a third name for the same act: “Retry”.

The ruling (founder, 2026-07-13). Counsel gets two document verbs: Re-read and Delete. The internal repair ladder — re-extract, reclassify, re-segment, re-brief — is the machine’s business. One Re-read, and the system works out which stages to run. The verb is “Re-read” and never “Repair”: a lawyer must never be left wondering whether we modify their evidence. Re-read can only mean the machine’s reading of it.

What shipped

Surface Before Now
Register row Hover cluster only; a failed row showed “Retry”. No overflow menu at all. A ⋯ menu with Re-read and Delete. “Retry” is gone — retrying the pipeline is re-reading.
Register bulk bar Four organize verbs (tag, link to proceeding, mark filed, download). The same four, then a rule, then Re-read and Delete. Six total.
Document page “Re-read against the matter” in the Journey; Download / Copy link in the strip ⋯. The strip ⋯ now carries the same two lifecycle verbs, below a separator.

Two species of verb, and why the divider matters

The bulk bar keeps its four organize actions (founder-ratified 2026-07-14). Tagging a document, linking it to a proceeding, marking it filed, downloading it — these describe a document; they never change what it is. Re-read and Delete do. The rule drawn between them in the toolbar is that distinction made visible. Reclassify (declared side, home) stays for the same reason: it is counsel telling us a fact only counsel knows, not counsel diagnosing our pipeline.

Re-read: honest, not optimistic

The re-read endpoint is asynchronous by contract — it answers 202 with a queued ticket, and the segmentation pass runs later on the registrar rail. So there is nothing to be optimistic about: nothing has changed yet. The row therefore says exactly what is true — “Re-read queued” — instead of spinning on a completion it will never be told about. The document’s JOURNEY, on the Insights lens, is where the machine’s actual progress is told.

A spinner that never resolves is a lie the UI tells on our behalf. A chip that says “queued” is the truth, and it costs nothing.

Delete: soft, guarded, and typed

Delete is a soft delete and always was, upstream: the row’s availability axis is tombstoned and the WORM bytes are retained (ADR-126 / FN-RETENTION). The register stops showing it, anything citing it can still render an honest tombstone, and an administrator can restore it. Nothing a lawyer can click destroys evidence. The confirm dialog says so in as many words — that reassurance is the copy’s whole job.

Three guards, in order

  1. Counsel only. Without matters:write both verbs still show — disabled, with the reason. A lawyer must never wonder whether Delete is missing or merely unavailable.
  2. Legal hold outranks counsel. A document on hold cannot be deleted by anyone; that is the entire point of a hold. Re-read stays available — reading a document again cannot destroy it, so a hold has no business blocking it.
  3. Type to confirm. A click-through “Are you sure?” stops nothing: the muscle memory that opened the menu also dismisses the dialog. Typing the phrase forces the eye back onto the object.

Bulk is not a bypass

Selecting twelve documents and pressing Delete runs the row guard twelve times. A document under legal hold survives the bulk exactly as it survives its own row menu, and the skip is reported — “2 documents skipped — under legal hold” — rather than silently swallowed. Partial success is stated as partial success.

The trap we did not fall into. The backend has a DELETE /matters/{id}/documents endpoint that looks like a bulk delete. It is not: it cascades the entire matter. Bulk delete here is N per-document calls, and that endpoint is never reached from a row selection.

The flow

Row ⋯ or bulk bar │ ▼ splitByGuard(verb, docs, canWrite) ← ONE guard, both surfaces │ ├── blocked ──▶ "2 skipped — under legal hold" (said out loud) │ └── allowed │ Re-read ──┤ Delete ──┐ ▼ ▼ POST …/{doc}/re-read (202) TypedConfirmDialog ("DELETE") │ │ ▼ ▼ row: "Re-read queued" rows vanish (optimistic) JOURNEY tells the rest │ DELETE …/matters/{id}/documents/{doc} │ ┌─────────────┴─────────────┐ refused accepted │ │ only those rows return router.refresh() + honest error toast (server truth lands)

The seam we are honest about

The re-read endpoint that exists today is segmentation-only: it re-cuts the document’s sections against its current extraction run. It does not yet decide depth — it will not re-extract a degraded scan, reclassify, or re-brief. The founder’s ruling requires one door that diagnoses which stages to run.

So this Story ships the vocabulary against the door that exists, and the unified repair door is filed as a pramaan-functions child ticket (pramaan-docs ADR-031). When it lands, the UI changes by exactly one line: the endpoint the Re-read verb calls. Counsel never sees the difference — which is the point of hiding the ladder behind one verb in the first place.

What to look at

File Why
src/lib/documents/register/lifecycle.ts The guards, pure and tested. One row or twelve, this is the only opinion.
src/components/documents/register/use-document-lifecycle.ts Optimistic delete + rollback; honest queued re-read; partial-success reporting.
src/components/shared/typed-confirm-dialog.tsx New primitive. Disarmed until the phrase matches; Enter cannot cheat it.
src/lib/documents/gateway.ts · …/documents/[docId]/route.ts The soft-delete door: the app-side plumbing did not exist at all before this.

Files changed

25 file(s), +1174 / -40 lines

Path Role + -
messages/en.jsonConfig+28-2
messages/hi.jsonConfig+28-2
src/app/[locale]/(workspace)/matters/[matterId]/record/doc/[documentId]/layout.tsxApp route+14-0
src/app/api/internal/matters/[matterId]/documents/[docId]/route.tsApp route+9-0
src/components/documents/register/register-bulk-bar.stories.tsxComponent+25-0
src/components/documents/register/register-bulk-bar.tsxComponent+69-8
src/components/documents/register/register-document-row.stories.tsxComponent+46-4
src/components/documents/register/register-document-row.tsxComponent+51-14
src/components/documents/register/register-row-actions.stories.tsxComponent+40-0
src/components/documents/register/register-row-actions.tsxComponent+82-0
src/components/documents/register/register-table.stories.tsxComponent+22-3
src/components/documents/register/register-table.tsxComponent+26-4
src/components/documents/register/register-view.tsxComponent+18-0
src/components/documents/register/saved-views-register.tsxComponent+65-0
src/components/documents/register/use-document-lifecycle.tsComponent+129-0
src/components/documents/viewer/document-permalink-shell.tsxComponent+66-1
src/components/documents/viewer/document-strip.tsxComponent+53-1
src/components/matters/live-matter-documents-page.tsxComponent+29-1
src/components/shared/typed-confirm-dialog.stories.tsxComponent+70-0
src/components/shared/typed-confirm-dialog.tsxComponent+109-0
src/lib/documents/gateway.tsLibrary+36-0
src/lib/documents/register/index.tsLibrary+7-0
src/lib/documents/register/lifecycle.test.tsLibrary+54-0
src/lib/documents/register/lifecycle.tsLibrary+82-0
src/lib/kubb-client.tsLibrary+16-0