> ## Documentation Index
> Fetch the complete documentation index at: https://docs.suchi.page/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend: document screens

> Documents, Trash, Document Detail, versions and links, Views, Calendar, the demo tour, upload, sharing, and approval cards.

Part of the [architecture guides](https://docs.suchi.page/architecture.md); see [Frontend architecture](https://docs.suchi.page/spa-architecture.md) for routing, request lifetimes, and conventions every screen follows.

## Documents, Trash, and Document Detail

Documents/Inbox derive filters, dates and pagination from the hash, without a page
cache. Links preserve route/query; filter controls remove `page`. Documents owns
a dedicated **Shared by me** button that toggles `share_link=active`; it means
unexpired, unrevoked links created by the current user, not documents merely
visible through ACLs. At narrow widths, the tag, correspondent, share,
sensitivity and date controls sit behind one screen-local disclosure; URL-derived
chips remain visible and **Clear all** removes only those controls.
The correspondent selector keeps ID and no-correspondent modes mutually
exclusive in the hash. `correspondents__isnull=true` becomes
`correspondent_missing` in the published Ask scope instead of being dropped
between the list and research requests.

The route publishes that same normalized hash context to Ask your documents
before issuing its list request. The shell clears a published context only when
its route path no longer matches; same-path query updates replace it rather than
briefly falling back to all documents.

Document result containers are noninteractive. Each has one title-labelled
document link plus sibling selection and quick-action controls; per-row filing
controls include the document title in their accessible names. A bounded footer
fits mobile. Invalid/out-of-range pages use `go(hash, { replace: true })` to sync
route state without creating Back-button loops. Verify with:
`cd ui && bun run e2e --grep 'document pagination|document controls|mobile document filters|active share links'`.

Trash uses `RuleWorkspace.svelte` inside the same 1,400-pixel content cap as
Approvals. Its card header owns the retention explanation and disabled-at-zero
**Empty trash** count action. Each row's document link covers the whole row and
highlights it on hover; the deadline and recovery controls sit above it. Like
Documents and Search rows, the retained address chip precedes the title;
the line below combines format, size, trash date, and the retained category
name. The deletion deadline is a day countdown with the date as its tooltip,
shown as an amber pill only during the final week. The empty state remains
inside the card. Trash links use `#/doc/{id}` and the same detail owner. Server
`trashed_at`, `deletes_at` and `owner_id` select read-only recovery. Detail clears
route-local date results and the access dialog's unsent person and level before
loading the next document, so its early Trash return cannot expose a live
document's accepted dates or calendar export, and one document's access choice
is never preselected on another. Trash also skips versions, similarity and
access-management reads. Restore reloads
that document; confirmed deletion returns to Trash. Actions move below the
document details on mobile. Server authorization still governs blobs.

Detail reloads only when its document ID changes or an explicit action requests
refresh. Arrival of sidebar categories must not restart reads or discard edits.
Metadata/language saves capture the document ID before the request. The save
and its eight-second Undo keep writing that captured document; local state
follows only while detail shows that same document in the same filing scope,
including after leaving and returning or a reload. An earlier save or Undo
therefore cannot change the next document's sensitivity, filing category, or
Reveal state. A failed filing-label refresh after a successful write still
reports the save and offers Undo. Access grant, change and revoke results apply
under the same rule.
Human-owned titles show a **Title pinned** line under the heading, with a pin
icon (the lock is reserved for passwords), the protection explanation on hover,
and an **Unpin** button. Rename completion updates local ownership to `human`;
unpinning sends only `title_owner: auto` and removes the line without changing
the title. Both paths use the same route-generation guard
as other detail metadata writes.
The Correspondents editor loads the existing system catalog on demand and uses
the shared type-to-select picker before sending one
`bulk_edit set_correspondent` operation for the open document. It promotes a
selected primary sender or clears sender roles with ID `0`, preserves other role
relations in local state, and rejects late responses through the same generation
guard.
The detail-toolbar **Rescan** action sends one guarded `bulk_edit
rescan_enqueue` operation for the open live document. It is hidden while the
document is waiting for its PDF password, and a caller without change
permission gets the same refusal notice as the other detail editors. It
disables during the request and while `document.processing` is true, then marks
the local document as processing and asks App to poll `/api/stats/` at once, so
repeat clicks cannot create duplicate work and the processing indicators update
without waiting for the next poll. Late responses cannot alter a different
document or filing-system scope; the shared processing bus refreshes the
completed document without resetting open editors.

Archive serial number editing is detail-owned state with the same route
generation guard. The browser accepts only decimal safe positive integers or an
empty value, then sends `archive_serial_number` in one PATCH. Server validation
and retained-value conflicts stay inline without discarding the draft. List,
ranked Search, Trash, detail, and version rows consume the server projection and
render one canonical **ASN *n*** chip; the browser does not reconstruct values
from QR payloads or folder paths.
Document Detail shows `jd_address` as the filing category address, or the
category code before system introduction, in a blue chip beside the title,
outside the rename control, using the server value without reconstructing it
from IDs. Dashboard, Documents, Search, upload/version
receipts, outgoing references, and backlinks may show the same address for
multiple rows. Collection rows render it and the ASN as aligned blue chips that
deepen with row hover or keyboard focus; grid cards pair it with the
category name in their metadata line. A single-category Documents view (`?jd=`)
or the Inbox omits it, since every row shares one address. Title links continue
to use each numeric document ID and resolved system code; address text is never
a document key.

Verify the ASN editor, projection chips, and split-upload receipt with
`cd ui && bun run e2e --grep 'archive serial numbers|split scan'`.

`DocumentDetail.svelte` owns similar-document results, version history,
replacement upload, attributed exact-revision notes, typed custom fields,
outgoing references, and backlinks for one route generation. Those reads load
concurrently and every late response checks the document/account/system
generation. Notes use optimistic screen-local create/update/delete state only
after server success. Similar documents, links, versions, and notes share one
screen-local tab card, with Notes last; Trash skips the first three reads and
shows Notes read-only. Opening another live document resets selection to the
first tab. Outgoing custom-field values and the independently paged backlink
projection remain separate state and operations. [Document detail, versions and
links](https://docs.suchi.page/document-versions-and-links.md) owns their user-facing vocabulary and
semantic contract. Populated ordinary custom fields remain in the metadata card;
unset definitions stay behind its single add-field selector.

`api.js:uploadDocumentVersion` uses private multipart assembly so `source_mtime`
and the caller's idempotency key are preserved. The screen keeps the selected
file/key after recoverable failures and clears them only after a successful
response and navigation. The server rejects stale family heads; version history
exposes upload only on the latest visible revision and an ID-free conflict tells
the user to reload after a race. Versions and backlinks page independently; late
responses check document, account, and system scope before changing state. Trash
skips these reads.

`lib/documentReferences.js` parses only exact numeric IDs or same-instance
document URLs. It rejects another origin before any lookup. Target search,
PUT/DELETE, and post-mutation detail/backlink refresh remain screen-owned; the
browser never infers visibility, currentness, or reciprocal links.

`Views.svelte` keeps a flat draft plus a separate list of currently visible
builder filters. Its empty state derives up to three category starters from
separate filing-tree areas, then fills any gaps with generic patterns; selecting
one changes only the unsaved draft. **Add a filter** changes the same ordinary
draft values. Helpers in `lib/documentFilters.js` extract one generated
`has-field:` token when reopening the editor and serialize it back into the
canonical rich query. The saved contract remains `filters.q`, so Documents
navigation, copied hashes, and Back/Forward need no second state channel.
Saved-view lists do not issue per-view document requests.
Filter vocabularies load only after the editor opens; until then, list summaries
use the saved query and generic filter labels. The editor debounces the
visibility-scoped document list endpoint for its one live match count. A count
failure stays local and never hides or broadens a saved view. Verify the surface
with the saved-view cases in `ui/tests/setup.spec.js`.

Above the single-column breakpoint, `DocumentDetail.svelte` consumes the
available content height, fixes the preview and detail sidebar to the same
bounds, and gives overflow ownership to the sidebar. Narrow layouts retain
normal document flow.

Calendar derives `month`, `start`, `days`, `date`, `view_id`, `role` and
document scope from the hash. It defaults to the browser's current local month;
`days=30` and `days=14` use rolling ranges beginning at `start` or today. Range
navigation preserves View/role, and the current-month link resets the default.
Calendar uses `RuleWorkspace.svelte` and the 1,240-pixel cap shared by Views and
Automations. Grid selection is local UI state: it highlights a day and scrolls
the adjacent grouped feed without a request.

`document_ids` opens an all-dates agenda. Exact-day requests use equal bounds
and `precision=day`; exact-day and all-dates agendas page by 500.
`lib/intelligence.js` preserves precision, so partial dates never populate day
cells. [Document dates](https://docs.suchi.page/document-dates.md) owns review/display behavior.

Calendar exports use `lib/calendarExport.js` for explicitly requested, reviewed
exact-day `.ics` files: escape text, fold UTF-8 lines, preserve stable event IDs,
add a role-based reminder alarm and disclose the exported contents. Calendar and
Document Detail share its download. This adds no subscription or authority.

Demo eligibility comes from `whoami.kind` (`demo-anon`/`demo-scratch`). Calendar
remains visible without mutation/research access; default demo Calendar opens
all dates, while explicit month/day links work. Curated `demo-corpus` entries omit
confidence/export controls. Public `/api/demo/mode` survives initial filing-state
reset but obeys session revision cancellation, allowing cookie minting and the
first-visit tour. The tour links real Search/Calendar and labels research as
private-installation only.

## Upload, sharing, and review

`UploadBox.svelte` owns the full-page picker, drop, confirmed paste and visible
queue. The shell routes top-bar, command and global-drop entry points there; no
second modal owns upload state. A batch records every file synchronously in
selection order and sends them sequentially to its captured system. Each entry
keeps one idempotency key for retry and drops its `File` after acceptance.
Rows are checked once after acceptance, then only when `processing_bus.svelte.js`
reports that App's `/api/stats/` poll saw finished work (fewer unfinished
documents, or none); the screen owns no timer. Checks run four at a time and read
processing tasks before terminal document details, so active jobs do not cause
repeated document reads. Document Detail uses the same signal to re-fetch a
document whose `processing` flag is set, merging the fresh fields without the
full load that would reset open editors. A Documents list whose query includes
`is:processing` reloads in the background on the bus's separate `finished`
counter, which advances only when the unfinished count drops, so idle polls
never reload a list. When the
accepted row is retired by splitting, the queue requests its immutable
`split_origin_id`, orders children by `split_index`, and replaces the parent
success link with links to every child, including their ASN chips. Navigation
stops receipt polling and releases paste URLs while already accepted transfers
continue for the same account.

`lib/ShareLinkForm.svelte` owns the Network/Public choice, expiry presets and
dialog-local password state for detail and bulk creation. The screen passes only
the server-projected `public_share_available` boolean; it never needs the Public
listener address. Public always has a bounded expiry. Each owning screen captures
document/account/system generation before creation and discards late feedback.
Creation displays its URL before clipboard access; `lib/clipboard.js` reports
failures so that URL stays manually selectable.

`DocumentDetail.svelte` requests active links with `status=active`, while its
collapsed History requests `status=inactive` in pages of five. Revocation uses a
confirmation that distinguishes stopping future access from retracting an
already-downloaded file. `LinkQR.svelte` lazily encodes private or existing
shared links locally with escaped SVG matrix attributes; it issues no request,
credential or new share. Document-detail full-text controls use
already-authorized `content`, retaining Reveal for sensitive text and a
selectable fallback when copying fails.

Mailbox OAuth flow and `sealed_secret_b64` handoff
state also die with the form. The latter is a server-sealed actor/system-bound
creation handoff, not a copy of the at-rest credential.

Vocabularies come from focused endpoints; local fallbacks keep forms usable without
becoming authoritative. Reversible optimistic changes restore state on failure.
Use semantic controls, visible focus, icon labels and stable responsive layouts.
Account-email controls follow the same semantic rule: native labels and inputs,
visible focus, a trapped modal, live error/status regions, and layouts with no
horizontal overflow at desktop, Pixel 7 width, or enlarged root text.

`Tasks.svelte` presents the document projection of open metadata decisions by
default, grouped by `doc_id`. The single queue is centered with a 1,400-pixel
maximum width. Each document decision uses one five-column row: fixed action,
flexible current-to-proposed change, reason chip, compact supporting-document
disclosure, and decision controls. The current value is struck, the proposed
value is emphasized, and the proposal is not repeated elsewhere in the row.
Long values wrap inside the change column without moving the remaining edges.

The user can progressively disclose a synchronized suggestion projection
grouped by field, proposed value, projected source, and automation identity; its
explanation exists only while that projection is visible. Above the responsive
breakpoint the two projections use side-by-side columns and the left rows
temporarily compact their reason and supporting-document columns. Below it they
stack document-first and the complete document context returns.
Completing an individual or sequential bulk action removes each successful task
from the shared array, so counts and both projections change without a reload.
Failed tasks retain their server error and stay disabled until refresh. Filters
apply to metadata tasks only; date review, workflow decisions, and processing
recovery remain independent sections.

Approval cards show decision context, not internal IDs; rescan document links
live under **Affected documents**. `Tasks.svelte` and `lib/intelligence.js`
consume bounded server projections: proposed/current values, reasons, source
freshness and permitted known actions. Producer/score details are disclosures,
not authority badges. Ask-derived metadata cards use the projected automation
name and fixed effect, including typed custom fields; they never need the
classifier question, answer, evidence quote, action mapping, or destination IDs.
Hidden supporters are omitted server-side; sensitive quotes require local
Reveal. Unknown actions stay read-only, stale dates cannot be accepted, and
superseded metadata reviews are canceled server-side instead of becoming a
special card state. Metadata resolution reports queued work rather than claiming
the field changed. Date mutation results clear only successful rows; failures
remain distinguishable and refreshable. [Web app](https://docs.suchi.page/web-app.md) owns remaining
interaction detail.

`Calendar.svelte` preserves accepted facts and recognizes reviewed, automatic
and previously accepted legacy values. Automatic dates are unlabeled; the other
states retain distinct chips. Automatic acceptance still requires no human
review and `policy_version=threshold-auto-v1`. Evidence and confidence remain
behind **From the document**; local ICS export remains reviewed-only and
exact-day. These screens reuse the tasks, intelligence and document APIs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.