Skip to main content
Part of the architecture guides; see Frontend architecture 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 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 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 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.