odysseus/specs/frontend.md
RaresKeY 7026cf40b5
Some checks failed
CI / Focused test guidance (report-only) (push) Has been cancelled
CI / Python syntax (compileall) (push) Has been cancelled
CI / JS syntax (node --check) (push) Has been cancelled
CI / Python tests (pytest) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
ci / docker publish / build (arm64) (push) Has been cancelled
ci / docker publish / build (amd64) (push) Has been cancelled
ci / docker publish / merge manifest + tag (push) Has been cancelled
docs: bootstrap specs ground truth (#5794)
* docs(specs): restore bootstrap after dev rewrite

* docs(specs): remove runtime inventory snapshot

* docs(specs): reconcile current dev truth

* docs(specs): document scheduled task actions as an owner-attribution source

Owner Attribution covered cookie, bearer-token and internal-loopback
requests. Scheduled task actions are a fourth source and behave
differently: _execute_action passes owner=task.owner off the stored
ScheduledTask row, so no request and no resolved principal are in
flight, and route-level require_user() never runs.

Webhook triggers are the sharp case. They are unauthenticated by
design with the token as the only credential and execute under the
stored task.owner.

Paths cite routes/task/task_routes.py, the canonical location after
the task subpackage move (#6081); routes/task_routes.py on current dev
is the backward-compat shim.

* docs(specs): add chained tasks to the trigger list, refresh dev stamp

Review feedback from RaresKeY on the previous commit.

"Every trigger path" was too broad: success-chained tasks are another
path into _execute_action. Added them with their own citation, and
noted that chaining additionally requires the target task to share
task.owner and rejects cycles, which is stricter than the trigger-side
checks. Softened the lead-in to "these trigger paths".

Line 56 still pointed at routes/task_routes.py for webhook credential
validation. That path is the backward-compat shim on current dev after
the task subpackage move (#6081); repointed to the canonical
routes/task/task_routes.py.

Stamp moved to dev@2a6b09b. Inspection backing that bump was scoped:
every file path cited in this spec was mechanically checked to resolve
on 2a6b09b, and every file:line in the Owner Attribution additions was
read against it. Behavioral claims elsewhere in the file were not
re-audited.

* docs(specs): correct SECURE_COOKIES description to match current behavior

Third of the stale details RaresKeY enumerated. The cookie section
described SECURE_COOKIES as purely opt-in, which stopped being true.

_secure_cookie() (routes/auth_routes.py:89) treats an explicit true or
false as authoritative and derives the Secure attribute from the
request otherwise, including when the variable is unset and when
docker-compose injects it present-but-empty. Either the connection
scheme or the first X-Forwarded-Proto hop being https is enough.

* docs(specs): refresh current dev truth

---------

Co-authored-by: StressTestor <212606152+StressTestor@users.noreply.github.com>
2026-08-25 14:18:44 +02:00

14 KiB

Frontend

Last updated: dev@e71f8ce | 2026-08-25

Scope

This spec covers the current browser app in:

  • static serving and SPA routes in app.py;
  • CSP/security headers in core/middleware.py;
  • static/index.html;
  • static/login.html;
  • static/app.js;
  • static/style.css;
  • static/js/*.js and static/js/*/*.js;
  • vendor libraries under static/lib/*;
  • custom fonts and static assets under static/fonts/*;
  • static/sw.js and static/manifest.json;
  • frontend-oriented tests in tests/*_js.py, tests/*.mjs, tests/bombadil-spec.ts, static DOM/CSS/source-shape tests, and app/static tests such as tests/test_app_static_mime.py.

/backgrounds currently targets static/backgrounds.html; if that route remains, the file must exist or the route should be removed.

static/manifest.json and static/index.html reference PWA icon files under static/icons/; the current 192px, 512px, and maskable icon files exist and should stay aligned with those references.

Current Call Sites Include

  • static/index.html script tags and modulepreloads;
  • static/sw.js PRECACHE;
  • app-owned SPA deep links for notes, calendar, cookbook, email, memory, gallery, tasks, and library;
  • /login and app-owned static/HTML routes;
  • /api/activity/heartbeat browser visibility pings used by the foreground activity gate;
  • static/app.js route opener/sidebar/tool-window wiring;
  • frontend JS helper tests and static HTML/CSS/source-shape regressions;
  • CDN dependencies, local vendor libraries, service worker, and PWA manifest.

Runtime Shape

The frontend is a raw static SPA served by FastAPI. There is no Vite, React, TypeScript, bundler, or generated build output.

app.py owns:

  • stable .js/.mjs MIME registration;
  • the /static mount;
  • no-cache headers for .js, .css, and .html static source files;
  • nonce-injected SPA/login HTML serving;
  • SPA deep-link routes.

static/index.html owns the DOM shell and script loading order. It loads browser ES modules directly. Current boot order includes nonce-bearing inline boot scripts, self-hosted highlight.js, modulepreloads, ordered module script tags, static/app.js, static/js/init.js, static/js/a11y.js, workspace/chat helpers, provider device-flow helpers, and service-worker registration. KaTeX and Mermaid are vendored under static/lib and injected only on first real math/diagram use rather than loading in the initial HTML.

The two first-paint Fira Code faces are preloaded so the shell does not wait for later CSS discovery. static/js/startupShell.js lets the visible shell initialize before session loading completes; session/transcript hydration is deferred and coordinated by static/js/sessions.js plus history/session routes rather than blocking first paint.

Exact script URL identity matters. Versioned script tags, unversioned imports, and service-worker precache entries must stay aligned. static/sw.js deliberately separates first-paint PRECACHE from lazy PANEL_PRECACHE; the latter currently contains the image-editor module graph so an editor never opened online can still open offline. KaTeX scripts/styles/fonts are also precached. Current service-worker coverage is not a generated full module-graph manifest, so changes still need direct verification.

Security Policy

core/middleware.py owns CSP and security headers. app.py injects the per-request nonce into served HTML. New inline scripts or external scripts/styles/images/media must fit the CSP contract or explicitly update it.

/static/* is public/auth-exempt. Frontend privilege gates are display-only; backend routes enforce authorization.

XSS/DOM policy:

  • prefer DOM construction, textContent, and shared escaping helpers;
  • Markdown raw HTML preservation must remain constrained through sanitizer helpers;
  • remote email body_html must pass through the email-library sanitizer before insertion;
  • Mermaid, code-runner iframe srcdoc, visual reports, remote media, and scattered innerHTML templates require explicit review.
  • Visual report Markdown HTML is server-rendered and should be treated as security-sensitive alongside frontend entry points and remote media.

Storage/secrets policy:

  • localStorage/sessionStorage are for preferences, UI state, offline caches, and user-switch sentinels;
  • static/js/init.js owns user-switch storage cleanup;
  • raw API tokens, provider keys, HF tokens, and other credentials must not be persisted in browser storage unless a feature documents masking/stripping and backend storage ownership.

Service Worker And PWA

static/sw.js owns PWA cache behavior:

  • API and non-GET requests are bypassed;
  • root navigation uses stale-while-revalidate;
  • JS/CSS use network-first behavior;
  • other static assets use cache-first with background refresh;
  • CACHE_NAME bumps and PRECACHE updates must accompany cache policy or shell asset changes.

static/manifest.json owns default PWA metadata. Route-specific manifests can be generated as Blob URLs when supported. Current default icon references must match real files under static/icons/.

KaTeX and Mermaid are self-hosted and lazy-loaded through memoized, retry-after-failure promises in static/js/markdown.js; math placeholders preserve source until KaTeX arrives, detached PDF export renders its own container, and Mermaid fetches only when a diagram exists. Pyodide remains a jsDelivr-loaded optional runtime, so offline/PWA behavior is not fully self-contained.

Module Ownership

Current major frontend areas include:

  • chat, stream handling, rendering, sessions, markdown, uploads, voice recorder, TTS, and keyboard shortcuts;
  • models, provider setup, pure model-key matching helpers, model picker, presets, search, RAG, settings, and admin;
  • settings shell modules under static/js/settings/: registry metadata, navigation, finder search, lifecycle/docking, DOM helpers, and persisted sidebar collapse/resize behavior;
  • compare modules under static/js/compare/, including sanitized popup/search/image handling;
  • document editor/library in static/js/document.js and static/js/documentLibrary.js;
  • image editor integration in static/js/galleryEditor.js plus leaves under static/js/editor/;
  • gallery, email inbox/library, calendar, research panel/jobs/synapse, notes/tasks, assistant, memory/skills, Cookbook/HW Fit, workspace picker, provider device flow, composer ArrowUp recall, theme, modal/window utilities, storage, and accessibility helpers.

Coordinator ownership:

  • static/app.js owns late orchestration, global fetch 401 redirects, sidebar/tool route wiring, and many window.* compatibility bridges;
  • static/js/init.js owns post-load cleanup, user-switch storage wipe, and cosmetic privilege gates;
  • static/js/storage.js owns shared key constants and safe JSON helpers;
  • feature modules own feature state where possible.

static/js/appConfig.js owns one invalidatable promise cache for GET /api/auth/settings and GET /api/tools, including one-shot login-page settings prefetch, retry after rejected fetches, and explicit invalidation after settings/tool writes. Consumers treat resolved objects as read-only. static/js/panels.js owns memoized first-use panel imports; its current registry contains the image editor, shares in-flight imports, and evicts failed imports so a later online retry can succeed.

static/js/MODULE_SUMMARY.md is a refreshed ownership/navigation map for the no-build frontend. The current static/js/ tree, static/app.js, static/index.html, and executable behavior remain the authority when the summary drifts.

Current small frontend helper contracts include static/js/model/matchKey.js for longest-substring model info/pricing matches, static/js/models.js for in-flight /api/models request sharing, static/js/providerDeviceFlow.js for Copilot/ChatGPT Subscription device-flow polling UI, static/js/composerArrowUpRecall.js for prompt recall from an empty composer, static/js/fileHandler.js for capped pending-file state and collapsed attachment-chip display, static/js/streamingSegmenter.js for incremental markdown/code-fence segmentation, static/js/emojiShortcodes.js for shortcode replacement, static/js/documentLibrary.js for keeping document counters/language chips in sync after archive/delete, static/js/keyboard-shortcuts.js for rejecting empty or non-string persisted keybinds before combo parsing, static/js/modalSnap.js for reusable desktop modal edge docking, static/js/toolWindowZOrder.js for shared portal/window z-index allocation, and static/js/emailShared.js for common email UI helpers.

Recent browser behavior contracts include mobile chat Enter inserting newlines while desktop Enter submits; ArrowUp recall only consuming a truly empty composer with the caret at the top, not an unsent multiline prompt; queued prompts preserving mobile behavior; regenerate-from-here versus resend; AI-message delete confirmation; native document tool results opening/updating the editor; and exact tool-approval cards that expose the sealed action/effects/workspace/document identity and submit only opaque task-scope/chat-session-scope/deny decisions without writing synthetic composer text. Chat rendering hides leaked tool JSON/document fences, no longer strips the ordinary word “assistant,” and batches live-thinking DOM updates with bounded timers. Markdown editing/restoration preserves extracted code/math blocks verbatim, including replacement-string $& and $$ text and triple-backtick fences. Session URL hashes are restored, minimized sidebar icon state follows per-tab visibility, detached terminal dots remain centered, and spinner animation starts only when attached.

The Settings finder and navigation are registry-backed, hide admin-only destinations from non-admin users, lazy-load admin panels, and keep the registry synchronized with DOM panels. Email OAuth connect preserves SMTP security and reopens the settings surface; unread message opens use one authoritative backend read/mark-seen request with stale-response guards; email-library prewarm is idle-only, single-flight, bounded to the initial page, and cancelled around visible foreground work.

UI Policy

  • New code must run as browser ES modules without a build step.
  • Reuse existing CSS variables, modal/window patterns, icon style, storage helpers, and route conventions.
  • Custom font handling includes bundled OpenDyslexic assets plus user-supplied fonts exposed through /api/fonts/custom; font and text-size settings must stay coordinated between settings UI, theme helpers, and CSS variables.
  • Avoid relying on stale module summaries.
  • API shape changes must update the owning JS module and tests.
  • Add behavior to large coordinators such as static/app.js, static/js/chat.js, static/js/document.js, or static/js/settings.js only when it matches their existing wiring ownership.

Degraded And Platform Behavior

  • Server no-cache applies to .js, .css, and .html source files, not every static asset.
  • Service-worker cache changes can affect frontend behavior even when source files revalidate.
  • Mobile behavior uses separate CSS/media/hover/safe-area/100dvh handling and JS layout code; check it directly.
  • Browser APIs such as service workers, Blob route manifests, Web Speech, getUserMedia, visual viewport, and storage can be absent or restricted.
  • Local libraries and CDN globals degrade differently; document, markdown, math, diagrams, and code runner flows should handle missing globals where possible.
  • localStorage migrations and cross-user cleanup are part of compatibility.

Testing Coverage

Existing frontend coverage is a mix of Node-executed helper tests, .mjs tests, static DOM/CSS/source-shape tests, browser exploration specs, and app/static tests. Many tests are useful source-shape regressions but do not replace browser/module-graph execution.

Recent focused coverage includes model-key matching under Node, document-library counters, chat resend/delete/mobile Enter/ArrowUp, scoped approval continuation and compare routing, route provenance, live-thinking throttling, startup shell/history hydration, shared app-config caching/invalidation, settings registry/navigation/finder/lifecycle, lazy panel loading/offline editor precache, vendored lazy KaTeX/Mermaid rendering, email read dedup/prewarm, Markdown restoration, malformed keybinds, currency-safe inline math, notes/calendar/modal/manifest/admin-log behavior, Markdown XSS helpers, and CardDAV unchanged-password handling.

Missing coverage includes:

  • SPA route/static auth and no-cache headers;
  • CSP header contents and nonce injection for / and /login;
  • service-worker API/non-GET bypass and cache strategy;
  • service-worker precache versus index.html script/module tags, including query strings;
  • ongoing manifest/icon reference drift;
  • module graph/load-order validation;
  • degraded vendor-library/browser API behavior, including Pyodide's remaining CDN path.

Current Gaps

  • static/style.css and large coordinators remain high-risk owners: static/js/document.js, static/js/settings.js, static/js/chat.js, and static/app.js.
  • There is no build-time type checking, module graph validation, script-order validation, or service-worker precache validation.
  • Frontend state is mostly module/global/localStorage driven, so cross-session and cross-user behavior needs explicit care.
  • window.* compatibility bridges remain widespread.
  • PWA/static-serving behavior may deserve a separate spec if service worker, manifests, route-specific icons, and cache policy keep growing.
  • A static asset/route manifest regression should verify files referenced by index.html, manifest.json, sw.js, and app-owned HTML routes actually exist.