Skip to main content
Part of the architecture guides; see Frontend architecture for routing, request lifetimes, and conventions every screen follows.

Metadata, filing trees, and configuration

customFieldLabel in lib/format.js owns user-facing labels for extraction-managed definitions. Document Detail, Settings, Views, and Automations use that label while retaining the server’s stable field name in IDs, automation parameters, and query text. Document Detail puts the generic add-field selector behind Edit in its final metadata row and excludes any definition with managed_by; populated managed values remain editable. lib/TagPicker.svelte is a local existing-tag combobox for document detail and Documents selection; it filters the complete catalog by name/slug and calls a screen-owned assignment callback on Enter/click. api.js:listAllTags follows paginated tag results rather than borrowing the first-page-only Documents filter. Detail edits one document immediately and keeps a refused query; when needs-review is visible, a successful edit refreshes tags from the server because the client cannot infer classifier ownership from slugs alone. Documents snapshots selected IDs for one bulk_edit add_tag, refreshes server-backed pills, and retains the selection on partial/total failure. Both invalidate late UI updates on route or system changes. Only admins see the Manage tags link beside a document’s Tags; the Documents list stays limited to search, display and filter controls, and document screens do not mutate the catalog. Documents.svelte reuses its bounded correspondent facet for selection actions. The selection sends one bulk_edit set_correspondent request and then reloads the server-backed rows. The separate No correspondent filter means no role relation at all; it is not the same as clearing sender roles. routes/FilingTreeSettings.svelte owns the standalone Filing tree workspace: ready-made selection, the one-set-per-lane composer, reviewed category mappings, Blank confirmation, optional post-Apply Refile, and taxonomy import/export. TaxonomyImport.svelte owns file/paste reads, TOML/HuML choice, seed/remap/destination choices and preview freshness within that workspace. TOML is the initial import/export choice; HuML remains selectable. File extensions select uploaded content, while the required format header syntax selects pasted content without duplicating server parsing. Schema validation stays server-side. Late FileReader/network responses cannot replace new input, and conflicting actions are disabled in flight. Ready-made selection requires current_preset_builtin from setup state; an imported/custom tree leaves every preset card unselected even when its file ID matches a built-in. The current-tree panel remains the source of visible live structure. Preset/composer Preview renders the complete target tree and document counts for each collision. The screen proposes replacement, free same-decade remap or skip, then re-previews changed choices before Apply sends expected_state_hash. Taxonomy-file Preview renders the declared file format/version, source story, destination, user_areas, separate System/49 generated_areas, additions, preserved disabled rules and dependency skips. 00–09 is explanatory, not a row. First prefixed import offers using the original archive or preserving it under an explicit other code. Named success refreshes the catalog, selects the returned system and invalidates old requests. Taxonomy administration requires a web admin session; mobile tokens cannot use it. See Filing trees. lib/configuration.js owns the Archive Configuration section inventory. ArchiveSettings.svelte owns overview and section navigation. Settings.svelte fills the shell’s remaining dynamic viewport height, keeping Settings tabs and the build footer outside its scrolling content row. The archive frame fills that row: its right pane scrolls independently with a stable scrollbar gutter. The sidebar can scroll in shorter windows and becomes a horizontal section rail on narrow screens. App.svelte passes URL people and metadata through Settings and Archive Configuration to PeopleSettings.svelte; lib/configuration.js owns the Tags deep-link hash. Archive navigation exposes People and Metadata as separate sections. User/Group tabs update people; metadata type changes update metadata, so reload and history restore the selected panel. App.svelte owns the administrator’s filing-tree reminder state and passes it to Dashboard.svelte. Dashboard renders the first setup step itself; on other screens, SetupReminder.svelte renders it in the sidebar, and Settings renders its inline form. Failures remain visible with Retry. Choosing a preset, confirmed Blank, or an imported tree refreshes the state; visiting Filing tree alone does not dismiss it. Dashboard.svelte derives later first-document steps from the real document, Inbox, review, and processing counts; no separate onboarding state is stored. ConfigurationSection.svelte owns the remaining direct Archive Configuration forms and their required-load/retry gates. Mailbox editing delegates to EmailAccounts.svelte; both automation entry points link to the existing Automations.svelte editor. ConfigurationSection.svelte owns the model dialog and separate drafts for Local model, Hosted model, and Account subscription. The subscription panel marks the feature Experimental above its Provider control and composes connection state, model catalog, permission to send document text, the retained confidence slider, and the connection-test result. Provider ID openai_chatgpt is explicit server state; the browser never infers subscription mode from a service URL. Device authorization exposes only its code and verification URL, polls through subscriptionLoginAction(provider, action), and cancels timers on disposal or provider changes. getSubscriptionModels(provider) loads the catalog after connection, provider selection, and saved-state reload; saveSubscriptionModel(provider, model) persists the preference without activating inference. Selection falls back from the current available model to the saved preference and then the first catalog entry. Generation checks discard late login, catalog, and test results. Connecting still requires explicit egress acknowledgement, a successful test, and Enable model; tokens remain server-side. Verify with bun run e2e --grep 'Account subscription'. Classification uses the same Suggestions and Ask your documents cards in both shells. The Suggestion application radio group defaults to Apply when confident and saves independently through Save application mode, using a standalone auto_apply PATCH. Review first disables automatic application. Local thresholds and research depth use disclosure controls; model connection and activation live in the model dialog. Save matching options calls saveArchiveMatchingSettings with only the three archive_* fields in a standalone PATCH. It preserves the saved model and unsaved connection form without checking account connectivity or reloading inference. Verify disconnected-account behavior with bun run e2e --grep 'local matching saves after disconnect'. Model activation, local matching and research settings retain their own saves; one save must not overwrite another form’s saved or unsaved state. Automatic-only threshold sliders are disabled, not cleared, in review mode. The connection test validates a synthetic response without changing application mode. PeopleSettings.svelte owns the separate People and Metadata destinations. People contains Users and Groups; Metadata selects among tags, correspondents, and custom fields. FilingTreeSettings.svelte owns presets, tree construction, and taxonomy files. ArchiveSettings.svelte places FolderLayoutsSettings.svelte first under Advanced; its editor calls the server preview rather than duplicating template semantics in JavaScript. ConfigurationSection.svelte reuses the archive-settings load/save shell and existing radio-card controls for Scan processing. It reads and writes the effective mode, QR prefix, and DPI through /api/admin/settings/scan, hides inactive label details, and disables fields named in the response’s managed object. ArchiveSettings.svelte projects the same endpoint into the Advanced overview status. Marker grammar, deployment precedence, and legacy-token collision checks remain server-owned. Automations.svelte retains action parameter schemas, reuses the existing metadata, user-directory, and filing-system-membership APIs, and presents a list-first workspace with a mutually exclusive local editor state. Compact rows derive one-line trigger/action summaries from the loaded schema and facets; custom rules PATCH their numeric order after drag or keyboard movement, while filing-tree rules remain read-only in a collapsed section. Empty state patterns seed ordinary drafts rather than introducing a separate persistence contract. The sentence builder reveals only filters explicitly added to each trigger and keeps JSON editing as the complete fallback. Named/type-aware controls cover folder layouts, eligible owners, and custom fields. The builder also consumes the server’s bounded ask capabilities to edit one question, closed answer choices, and per-action answer branches; enabling that branch clears transient trigger filters and limits action/custom-field pickers to the advertised safe subset. The browser does not infer model authority, action mappings, limits, or destination validity. IDs remain an internal persisted action contract. RuleWorkspace.svelte, RuleRow.svelte, RuleEmpty.svelte, and RuleSection.svelte own the shared list/editor shell, compact sentence row, starter-pattern state, and progressive builder sections used by Views and Automations. They own layout and responsive behavior only; each route retains its API calls, draft semantics, validation, permissions, and navigation. UserCreateForm.svelte owns user creation in People and submits no hidden member capabilities for admins. Self-disable checks use session.user.user_id; the API owns self-disable, last-admin and capability normalization safeguards. Verify shared configuration with: cd ui && bun run e2e --grep 'archive setup|filing-tree|refreshes intake owners|failed configuration reads'. PeopleSettings.svelte alone creates, renames and deletes catalog tags. It loads every tag page, keeps a searched multi-selection across filters, and prunes selected IDs after a successful refresh. Single-row and bounded bulk deletion share one native ConfirmDialog and api.js:deleteTags; cancel sends no request, failures keep rows/selection for retry, and a late response cannot replace a different metadata type or filing system. The server’s admin/session-only, transactional /api/tags/ DELETE enforces ownership and child re-rooting; UI selection is not an authorization boundary. Verify with cd ui && bun run e2e --grep 'document tag|bulk tag|tag management|settings tag'. AccountSettings.svelte sends only display_name for profile saves and derives email controls exclusively from whoami.email_change_mode, never authn_by. Password mode opens the focused native EmailChangeDialog.svelte; OIDC mode uses a native same-origin POST form to /oidc/email-change; disabled/demo mode is read-only and exposes no mutation request path. AccountSettings.svelte shows TwoFactorSettings.svelte only for a row-backed local-auth browser identity, including profile-only/no-filing-system mode. OIDC and demo identities expose no factor routes or controls. The component owns status loading, focused lifecycle dialogs, the explicitly otpauth QR, manual secret copy, and one-time recovery-code copy/download/acknowledgement. Enable, regenerate, and disable use named api.js operations that invalidate older session requests only after the server rotates the cookie. Passwords, factor codes, enrollment seed/token, and recovery codes are erased on failure, close, account change, and destroy. It shows the enable response’s active_api_tokens note beside the new codes and a warning when two or fewer recovery codes remain. lib/twoFactorErrors.js turns too_many_attempts (with the Retry-After wait that api.js attaches to ApiError.retryAfter), two_factor_locked, and two_factor_required into wording shared by sign-in and every factor dialog. PeopleSettings.svelte renders the server’s two_factor_enabled user flag. Administrators can reset another enabled user through AdminTwoFactorResetDialog.svelte, which reauthenticates the acting administrator and clears both inputs on failure or exit. The browser updates the row only after the server confirms the audited reset.

Account and sign-in

The /app CSP admits only self plus the provider-discovered authorization endpoint’s HTTP(S) origin for form submissions, because that POST redirects to the provider. Non-SPA responses keep form-action 'self'. The password dialog owns new-email, exact confirmation, and current-password validation. It traps focus, focuses the first field, closes with Escape, exposes inline errors with role=alert, and disables fields/close while the request is busy. Password memory is cleared on invalid input, mismatch, server failure, close, account change, destroy, and before the successful state transition. When the server answers two_factor_required, the dialog keeps the password and reveals a code field for the retry. api.js:changeMyEmail advances the session/request generation only after a successful rotation response, so pending pre-change GETs cannot repopulate the new account scope; failure leaves unrelated requests valid. Login.svelte performs local sign-in in two requests only when the first correct-password response carries two_factor_required. It retains the password in component memory for that second request, accepts an authenticator or recovery code, submits a six-digit code as soon as it is entered, preserves the generic credential failure surface, and clears both values on account edit, success, navigation, or destroy. The server-rendered /login form shows the two_factor_code field only after a two_factor_required or two_factor_invalid redirect and never persists it. After success, the screen reloads /api/whoami, waits for the principal/scope reset tick, and uses the shell’s separate account notification channel. That channel is deliberately outside the filing-scoped subtree, so Sign-in email changed survives account/system reset. Both account and ordinary transient notices use an atomic polite role=status; actionable failures remain persistent inside their owning screen. Scoped token, pairing, mailbox, and vault feedback continues through the ordinary notifier. App.svelte allowlists only identity_bound, email_changed, and email_checked from the OIDC callback’s account_notice query parameter. It renders fixed local copy, removes that one parameter with history.replaceState, and preserves every other query parameter and the hash. Unknown values are removed without rendering provider-controlled text. Demo kinds receive a read-only profile without token, vault, mailbox or pairing controls or requests.

Mobile pairing and tokens

The mobile pairing prompt loads only after its button is selected; it keeps the single-use secret in component memory, clears expired QR/link displays, and discards it on navigation, account change or system switch. When the original session remains available, unused and late-created codes are cancelled against their captured system. Generation and clipboard writes check the original account and system; late responses never display in another cabinet’s prompt. AccountSettings.svelte owns one active-token list and renders the current account’s mobile_pairing credentials in Mobile app through a shared row snippet; manual and legacy credentials remain in API tokens. Token reads refresh every two seconds only while the prompt is open, immediately on close, and on window focus. Reads are bounded, abortable, ordered, and checked against their original account+system generation; either change clears the list, and unmount stops polling. Revocation reloads the list so an earlier poll cannot restore a disconnected app.