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”.
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.
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
- Counsel only. Without
matters:writeboth verbs still show — disabled, with the reason. A lawyer must never wonder whether Delete is missing or merely unavailable. - 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.
- 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.
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
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.json | Config | +28 | -2 |
| messages/hi.json | Config | +28 | -2 |
| src/app/[locale]/(workspace)/matters/[matterId]/record/doc/[documentId]/layout.tsx | App route | +14 | -0 |
| src/app/api/internal/matters/[matterId]/documents/[docId]/route.ts | App route | +9 | -0 |
| src/components/documents/register/register-bulk-bar.stories.tsx | Component | +25 | -0 |
| src/components/documents/register/register-bulk-bar.tsx | Component | +69 | -8 |
| src/components/documents/register/register-document-row.stories.tsx | Component | +46 | -4 |
| src/components/documents/register/register-document-row.tsx | Component | +51 | -14 |
| src/components/documents/register/register-row-actions.stories.tsx | Component | +40 | -0 |
| src/components/documents/register/register-row-actions.tsx | Component | +82 | -0 |
| src/components/documents/register/register-table.stories.tsx | Component | +22 | -3 |
| src/components/documents/register/register-table.tsx | Component | +26 | -4 |
| src/components/documents/register/register-view.tsx | Component | +18 | -0 |
| src/components/documents/register/saved-views-register.tsx | Component | +65 | -0 |
| src/components/documents/register/use-document-lifecycle.ts | Component | +129 | -0 |
| src/components/documents/viewer/document-permalink-shell.tsx | Component | +66 | -1 |
| src/components/documents/viewer/document-strip.tsx | Component | +53 | -1 |
| src/components/matters/live-matter-documents-page.tsx | Component | +29 | -1 |
| src/components/shared/typed-confirm-dialog.stories.tsx | Component | +70 | -0 |
| src/components/shared/typed-confirm-dialog.tsx | Component | +109 | -0 |
| src/lib/documents/gateway.ts | Library | +36 | -0 |
| src/lib/documents/register/index.ts | Library | +7 | -0 |
| src/lib/documents/register/lifecycle.test.ts | Library | +54 | -0 |
| src/lib/documents/register/lifecycle.ts | Library | +82 | -0 |
| src/lib/kubb-client.ts | Library | +16 | -0 |