Repository navigation
Review Workspace
The screen where a document becomes a set of decisions: what is on it, how each control behaves, and how the client keeps its state.
Canonical sources: docs/editor.md (components) · docs/client-state.md (store and hooks) · DESIGN.md (visual language).
Companion pages: Search and Rules · Hush Assistant · Export and Anonymisation.
| Route | What it is |
|---|---|
/ |
Landing page and the upload panel: drag and drop, the preset chooser (with its disclaimer), the retention window. One file opens its workspace; several become a batch. |
/documents |
This session's documents, grouped by batch (groupByBatch): cards with review progress, retention, status, retry and delete; limits (rate gauges and daily quotas) and usage (tokens, time, cost). Polls every 4 s only while something is processing. |
/workspace/:id |
The editor: the processing screen while the document works, then review. |
/batches/:id |
The batch: every document's status and counts, Retry, Review links, carried decisions (batch rules with their reach, removable), and the batch download button. Polls every 4 s while anything is processing. |
/restore |
Upload an exported file and its vault, and get the original back. See Export and Anonymisation. |
/about |
What the project is. |
Providers ThemeProvider (forced dark) · StoreProvider · TooltipProvider · Toaster
Workspace the orchestrator: status routing, stream, refresh, shortcuts
├─ WorkspaceHeader filename, kind, "looked for <preset>", batch chip + prev/next,
│ retention, status, batch download, Export
├─ PageNavigator thumbnail rail: real renders with accepted redactions drawn on,
│ search-hit badges
├─ DocumentCanvas format dispatch
│ ├─ PdfViewer rendered page + RedactionLayer + SearchBoxes
│ ├─ DocxViewer paginated runs
│ ├─ EmlViewer foldable headers / bodies / attachments
│ ├─ TextViewer txt · rtf · pptx
│ ├─ ImageCanvas pixels + OCR words + regions + SearchBoxes
│ └─ SpreadsheetGrid sheets, rows, columns, cells (hidden ones marked)
├─ SearchBar the find strip (Ctrl/⌘+F, /)
├─ RedactionInspector tabs: Redactions | Rules (MobileInspector: a bottom sheet)
├─ EditorToolbar select / redact tools, search, Hush, shortcuts, zoom, undo/redo
├─ ExportDialog method, style, metadata, labels, variants → report, vault
├─ RuleDialog pattern, scope, live preview
├─ HushPanel the assistant (right rail, or a sheet on narrow screens)
├─ ShortcutSheet every key binding (?)
└─ LiveAnnouncer screen-reader live regions: review position, search position
-
Still processing: the
ProcessingScreen(status pill and progress from the SSE stream). Closing the tab does not cancel anything. - Failed before extraction: the failure screen, with Retry only if the failure code is retryable.
-
Failed after extraction (e.g.
rule-too-broad): the editor opens with aFailureNoticebanner. Manual redaction is still real, and an empty suggestion list must never read as "nothing to redact". -
An expanded mailbox:
ExpandedNoticeinstead of an editor, offering Download redacted mailbox (that one upload, rebuilt as.mbox). - When the stream reports
readyorfailed, the workspace refreshes once per status.
"looked for Names and contact details". An empty inspector reads as "nothing to redact" unless you know the search was narrowed. See Redaction Presets.
The editor asks for the outline first (GET /content?view=outline: kind, metadata, sheets, page numbers), then the page on screen, then its neighbours. Rail thumbnails request their page when they come within 400 px of view. At most 24 pages stay in memory, least recently used first out. A 1,992-page text file opens after ~9 KB instead of 17 MB. See Streaming IO.
| Look | Meaning |
|---|---|
| Dashed red | a suggestion: proposed, not decided |
| Solid black | accepted: this is what the export will produce |
| Red underline | the selected redaction |
| Grey / dimmed | rejected ("kept in file") |
| Amber | a search hit, never a decision; stronger amber with an edge is the current hit |
| Red + rule badge | created by a rule |
The chrome is forced dark so the white document surface stays the strongest element on screen, and black overlays show exactly what the export will look like.
In text-flow viewers (DOCX, TXT, RTF, PPTX, EML), highlights use the CSS Custom Highlight API over the text the viewer already drew, so a redaction covers exactly its characters and not the whole line it sits on. (Before 1.9.0, a line holding one redacted ID was drawn fully black, which overstated what the export removes.) Browsers without the API fall back to marking the whole line. In geometry viewers (PDF, image, OCR), boxes come from boxesForRedaction, the same function the exporter uses: what is drawn is a promise about the exported file.
| Gesture | Result |
|---|---|
| Click a word | Redacts the token under the pointer (wordAt): letters and digits joined by the punctuation identifiers, emails and phone numbers use, so EMP-10007, jane.doe1@example.com, Doe. Grouped values (a card number, a spaced IBAN, a phone number) are taken whole; a name is taken a word at a time. On PDFs, the character comes from measured glyph positions. |
| Click inside an existing redaction | Selects it |
| Drag across text | Redacts exactly the selection, across lines if need be, with whitespace trimmed at either end |
| Drag a rectangle (redact tool) on a PDF or image | A region redaction, if larger than MIN_DRAG_PX
|
| Click a cell, row or column header | A cell / row / column redaction |
| Enter / Space on a focused span | Redacts the span, or selects the redaction already in it |
Touch drawing of regions is not supported yet (#45).
-
EML: sections fold. Headers are open; of the text/plain and HTML bodies, one is open (HTML by default, since it is what the recipient saw); attachments are folded, with a count. A section holding undecided suggestions opens regardless of the default, because a default the code chose must never be the reason something went unread. HTML bodies are drawn from the markdown the extractor produced, and no message content ever reaches an
href,srcor other fetchable attribute, so a tracking pixel cannot phone home from a reviewer's screen. - Spreadsheets: CSV and TSV are reviewed in the same grid as workbooks. Hidden and very-hidden sheets get a banner and a tab marker, with counts of hidden rows and columns. A sensitive column arrives as one column suggestion.
- Images: OCR words are hit targets; face regions arrive as whole-head boxes; blur and pixelate are export styles, not canvas states.
Right rail on wide screens (xl), bottom sheet on narrow ones. Two tabs: Redactions and Rules (see Search and Rules).
-
Occurrence groups. Suggestions are grouped by
category|normalizedText(selectOccurrenceGroups), so seventeen "John Smith"s are one row and one decision. - Filters: source (All / AI / Manual / Rules), category chips (top 8).
- Per-group actions: Redact this (the first occurrence) · Redact all N · Everywhere (a literal document rule) · Whole batch (a literal batch rule, offered only when the batch has more than one document) · Ignore (reject every occurrence).
- Accept all / Reject all act on every filtered suggestion, not just the visible ones. Accept all leaves ignored suggestions alone (fixed in 1.5.0), and ignored rows are marked and dimmed.
-
Method per suggestion (
mask,pseudonymize,tokenize,encrypt), limited to what the category and the target allow. See Export and Anonymisation. - Confidence is a band (high / medium / low) plus a percentage: a hint for triage, never a verdict.
Press ? for the shortcut sheet. It is rendered from SHORTCUTS in lib/editor/shortcuts.ts, the only list of bindings (toolbar tooltips read from it too), so this wiki does not copy it. Named ones worth knowing: Ctrl/⌘+F or / search, Ctrl/⌘+K Hush, Esc closes search or returns to the select tool, Ctrl/⌘+Z undo.
Two rules hold for every binding (tests/shortcuts.test.ts): nothing fires while you are typing in a field, and nothing destructive is bound to a bare key. Accept and reject act on the current selection only, and undo is one keystroke away.
- Redactions on the canvas are focusable buttons with descriptive labels and
aria-pressed. Per-word hit targets are out of the tab order, because a page of prose as hundreds of tab stops is worse than none. The inspector list is the complete keyboard path to every suggestion. -
LiveAnnouncerdescribes the whole position, not the last change: "7 of 12 suggestions reviewed, 4 accepted, 5 left to review". Search has its own region ("7 of 132 matches"). - Zoom: out / in, a log-scale slider (100% in the middle; a native range input for keyboards and screen readers), a percentage that resets to 100%, fit width and fit page. Fit modes survive a resize.
- Download and Report are real
<a>links styled as buttons, so link semantics and middle-click still work. - Still open: a screen-reader pass by someone who uses one daily, and a colour-contrast audit (#42).
- RetentionControl offers only windows that would actually push the expiry out, computed from creation and capped at 72 h. A document at the ceiling has no options, and the limit is stated rather than discovered by trying.
-
Usage (
/api/documents/:id/usage, and the session total on/documents) shows tokens, duration and calls, plus a currency figure only when prices are configured. An invented price is worse than no price. The panel also reports a degraded model pass and why. -
LimitsCard shows rate limits (pace, refilling within a minute) and daily quotas (volume, per UTC day) side by side, because hitting either produces the same refusal. On a self-hosted install it says there are no quotas instead of drawing empty bars.
/api/limitsdeliberately does not spend from the bucket it reports on.
A Redux Toolkit store, created lazily per browser session (makeStore() in StoreProvider, never a module singleton, so SSR and hydration cannot share one), with seven slices:
| Slice | Owns |
|---|---|
document |
the DocumentSummary, the outline, and at most PAGE_CACHE_LIMIT = 24 pages |
redactions |
redaction entities and ids, selection, filters, and undo/redo |
editor |
viewport: page, zoom, tool, fit mode, active sheet, grid selection |
processing |
run status, progress, event log, suggestion count |
ui |
export dialog, shortcut sheet, mobile sheet, page panel |
rules |
every rule in scope for the open document |
search |
the query, options, and hits, each keyed by the question it answers |
Undo/redo covers decisions only (page turns and zoom are view, not decision). It keeps up to 50 snapshots of entities and ids, materialised with Immer's current() before copying. Spreading the draft directly once made undo restore the state it was meant to replace, and a test now catches that. Since 1.5.0 an undo saves only the redactions it changed: before, pressing Ctrl+Z in a tab holding stale decisions could overwrite every decision on the document. Server reloads (redactionsReplaced) and streamed suggestions are not undoable, while removing a rule is.
Main hooks: use-redactions (accept, reject, create, rules, undo, redo, all persisted through /api/documents/:id/redactions), use-processing-stream (SSE with indexed resume), use-normalized-document (outline and pages), use-search, use-rules, use-learned-shapes (Hush's offers), use-batch-export, use-retry-document, use-shortcuts.
Anonify: AI proposes, the application applies, and only what a person accepted is removed.
Repository · Report a bug · Request a feature · Security policy · Apache-2.0
This wiki describes v1.10.0 and summarises and links the in-repo documentation. Where the two disagree, the files under docs/, CHANGELOG.md and SECURITY.md are canonical.
Getting going
Using it
- Supported Formats
- Review Workspace
- Search and Rules
- Hush Assistant
- Redaction Presets
- Export and Anonymisation
- Batches and Mailboxes
How it works
- Architecture
- Processing Workflow
- Redaction Engine
- Storage and Encryption
- Streaming IO
- Data Model
- API Reference
- Security Model
When it breaks
Contributing
Repository · Issues · Releases · Changelog