odysseus/static/js
Léo d0bf771f9d
perf(static): vendor KaTeX and Mermaid, and load them on first use (#5994)
* fix(static): vendor KaTeX and Mermaid instead of loading them from a CDN

index.html pulled katex.min.{js,css} and mermaid.min.js from cdn.jsdelivr.net on
every page load. For self-hosted software that is three problems at once: an
air-gapped or offline install renders no math and no diagrams at all, every
session announces its IP, User-Agent and Referer to a third party, and the "runs
on your own hardware" promise quietly isn't true.

static/lib/ already vendors highlight.js, docx, xlsx, mammoth, html2pdf and
qrcode, so the CDN usage was an inconsistency rather than a policy. Vendoring
also pins Mermaid, which was floating on the `11` tag, to 11.16.1.

Behaviour is unchanged: both libraries still load eagerly from <head>, just from
this machine.

- KaTeX goes in its own directory because its stylesheet resolves fonts with a
  relative url(fonts/...), so the vendored CSS needs no rewrite. Only the .woff2
  variants ship, matching static/fonts/, since a browser that supports woff2
  never requests the .woff/.ttf alternatives the stylesheet also lists.
- The service worker precaches KaTeX and its fonts so offline math is typeset
  rather than falling back to system glyphs, and CACHE_NAME is bumped. Mermaid
  is left to the existing cache-first rule: at 3.5 MB, precaching it would mean
  re-downloading it on every cache bump for a library most sessions never touch.
- Licence texts travel with the bundles in licenses/, following the convention
  the repo already uses for OpenDyslexic and DeepResearch.
- .gitattributes turns the whitespace check off for static/lib/ so `git diff
  --check` passes without stripping bytes from the published npm artifacts,
  which would desync them from upstream.

* perf(markdown): load KaTeX and Mermaid on first use, not on every page load

Both libraries loaded eagerly from <head>, costing every session ~985 KB on the
wire (929 KB of that Mermaid) even though most chats contain neither a formula
nor a diagram. Measured on a cold profile via the Resource Timing API: JS bytes
per page load drop from 3,102,141 to 2,098,634, a saving of 1,003,507 bytes, and
third-party requests per load go from 3 to 0.

markdown.js now fetches each library the first time one is actually needed:

- renderMermaid() checks for an unprocessed mermaid fence before touching the
  network, and re-queries the DOM after the load so a diagram replaced mid-stream
  still renders.
- mdToHtml() is synchronous, so when KaTeX is not in yet it banks the math source
  in an inert placeholder and schedules a flush that loads the library and swaps
  the placeholders in. Once KaTeX is loaded it typesets inline exactly as before,
  so callers that never call a render helper still get their math.

Both loaders memoise the promise rather than the module, so concurrent callers
share one fetch and a double trigger cannot start two loads; a failed load clears
the memo so the next formula retries instead of being poisoned for the session.
The flush is scheduled with setTimeout rather than requestAnimationFrame, which
is throttled to a stop in a background tab and never fires at all in a headless
browser, so math would have sat as plain source text until the tab was focused.

If neither library ever loads, math degrades to readable source text and diagrams
to their fence contents, rather than to nothing.

* fix(markdown): unescape &amp; last so math entities survive intact

The math pass unescaped &amp; before &lt; and &gt;. mdToHtml escapes the source
first, so a literal "&lt;" typed inside a formula arrives here as "&amp;lt;",
turns back into "&lt;" on the ampersand pass, and is then eaten by the very next
one. Typing $a &lt; b$ rendered as "a < b" instead of the literal text.

The code-block pass in the same function already unescapes &amp; last; only the
math paths were the outlier, in all four of the copies this branch consolidated
into pushMath(). Reordering to match makes them consistent and clears the
js/double-escaping alert CodeQL raised on this PR.

Math containing a genuinely typed "<" is unaffected, which is why this went
unnoticed for so long. Covered by a regression test asserting both cases.

* fix(markdown): decode entity-spelled math in one pass

mdToHtml escapes the source before the math pass, so a typed "<" reaches
the delimiters as "&lt;" and a typed "&lt;" reaches them as "&amp;lt;".
KaTeX has no entity syntax and reads the leftover "&" as an alignment
marker, so "$a &lt; b$" rendered as a red .katex-error instead of a
formula, on both the inline and the deferred path.

Chained replaces cannot fix it in either order: unescaping "&amp;" first
lets the next pass eat the "&lt;" it just wrote, and unescaping it last
leaves the entity spelling for KaTeX to choke on. One alternation,
longest form first, decodes every spelling and never rescans its own
output.

The tests now drive the vendored KaTeX build rather than a renderer that
echoes its input, which is why the old assertion looked correct.

* fix(document): typeset deferred math before the PDF export

exportAsPdf() renders the document into a detached container and hands
it straight to html2pdf. On a page where KaTeX has not loaded yet,
mdToHtml() returns pending placeholders and schedules a flush scoped to
document, which never reaches a node that was never attached, so the
PDF printed raw formula source.

Render the container's own math first. renderMath() returns immediately
without fetching anything when there is nothing pending, so a document
with no formulas still exports without pulling KaTeX.
2026-08-16 22:43:12 +01:00
..
calendar fix(security): escape backslashes in calendar bg-image CSS url() (#4712) 2026-06-22 21:17:52 +02:00
color fix: theme color parsing breaks on #rgb shorthand hex (#1213) 2026-06-03 00:30:03 +09:00
compare Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
editor Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
emailLibrary fix(email): route summaries through shared LLM adapter (#5841) 2026-08-08 23:06:41 +02:00
markdown Ignore non-string markdown table rows (#1648) 2026-06-03 14:17:02 +09:00
model fix: model cost/info matches first substring key (gpt-4o-mini billed as gpt-4o) (#1439) 2026-06-04 03:05:37 +01:00
research Show thumbnails on past research cards 2026-06-30 08:00:01 +00:00
settings refactor(settings): add registry-backed navigation and finder (#6040) 2026-08-16 02:48:19 +01:00
util fix: monthly schedule label shows 21th/22th/31th (ordinal suffix for days >20) (#1577) 2026-06-03 08:57:47 +09:00
a11y.js Improve accessibility across core flows (#86) 2026-06-01 22:04:00 +02:00
admin.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
appConfig.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
assistant.js refactor(tools): move model-interaction tools to the agent_tools registry (#4445) 2026-06-18 05:56:37 +00:00
calendar.js Merge dev into main for testing 2026-06-28 14:07:23 +00:00
censor.js Ignore censor preference storage errors (#1652) 2026-06-03 14:16:55 +09:00
chat.js refactor(static): load the image editor on first use (#6074) 2026-08-16 17:54:24 +01:00
chatModelProvenance.js refactor(model-routing): centralize explicit foreground fallback policy (#6020) 2026-08-14 08:10:30 +01:00
chatRenderer.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
chatStream.js fix(agent): harden approval lifecycle 2026-08-15 06:52:44 +00:00
chatStreamErrors.js refactor(model-routing): centralize explicit foreground fallback policy (#6020) 2026-08-14 08:10:30 +01:00
codeRunner.js Fix/windows llama cpp serve and test upstream (#2669) 2026-06-05 14:53:33 +02:00
colorPicker.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
composerArrowUpRecall.js fix(chat): stop ArrowUp from eating an unsent multi-line prompt (#5875) 2026-08-07 19:34:50 +02:00
cookbook-deps-recipes.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbook-diagnosis.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbook-hwfit.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbook.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbookDownload.js Checkpoint Odysseus local update 2026-07-07 00:50:07 +00:00
cookbookPorts.js fix(cookbook): only block model launch on real port collisions (#4760) 2026-06-24 19:44:09 +02:00
cookbookProgressSignal.js Don't falsely declare a dependency build stale (#1568) (#1768) 2026-06-03 13:23:35 +09:00
cookbookRunning.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
cookbookSchedule.js Cookbook UI: Ollama browser, advanced serve fold, API tokens form, diagnosis toolbar, polish 2026-06-09 09:46:19 +09:00
cookbookServe.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
document.js perf(static): vendor KaTeX and Mermaid, and load them on first use (#5994) 2026-08-16 22:43:12 +01:00
documentLibrary.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
dragSort.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
emailInbox.js fix(agent): harden approval lifecycle 2026-08-15 06:52:44 +00:00
emailLibrary.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
emailShared.js Polish mobile UI and editor workflows 2026-06-27 13:05:44 +00:00
emojiPicker.js fix(modal): keep body-portaled dropdowns above their tool modal at any stack depth (#4720) (#4724) 2026-06-23 10:24:31 +02:00
emojiShortcodes.js Render emoji shortcodes as icons in chat (#345) (#629) 2026-06-05 02:28:42 +02:00
escMenuStack.js fix: make transient dropdown/popup menus close on Escape 2026-06-01 14:23:22 -04:00
fileHandler.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
gallery.js refactor(static): load the image editor on first use (#6074) 2026-08-16 17:54:24 +01:00
galleryEditor.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
group.js fix: group selection drop-downs recreation and repopulation logic (#3424) 2026-06-26 13:35:25 +01:00
init.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
keyboard-shortcuts.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
langIcons.js fix: langIcon throws on an explicit null opts argument (#1740) 2026-06-03 13:29:21 +09:00
liveThinkingThrottle.js perf(chat): batch live thinking rendering and bound timer updates (#5931) 2026-08-10 20:11:48 +01:00
markdown.js perf(static): vendor KaTeX and Mermaid, and load them on first use (#5994) 2026-08-16 22:43:12 +01:00
memory.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
modalManager.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
modalSnap.js Merge remote-tracking branch 'origin/dev' into test-main-dev-merge-20260615 2026-06-15 21:20:15 +09:00
modelPicker.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
models.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
modelSort.js Ignore invalid model sort inputs (#1653) 2026-06-03 14:16:52 +09:00
MODULE_SUMMARY.md perf(chat): batch live thinking rendering and bound timer updates (#5931) 2026-08-10 20:11:48 +01:00
notes.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
package.json Fix test suite: ESM module loading and stub isolation (#844) 2026-06-02 11:29:29 +09:00
panels.js refactor(static): load the image editor on first use (#6074) 2026-08-16 17:54:24 +01:00
platform.js Ignore AltGr keystrokes in Ctrl+Alt keyboard shortcuts (#825) 2026-06-02 11:12:54 +09:00
presets.js fix: group selection drop-downs recreation and repopulation logic (#3424) 2026-06-26 13:35:25 +01:00
providerDeviceFlow.js feat: add ChatGPT Subscription provider (#2876) 2026-06-08 10:19:18 +02:00
providers.js feat(discovery): detect llama.cpp servers and label local providers (#4729) 2026-06-23 23:39:56 +02:00
rag.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
researchSynapse.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
search-chat.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
search.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
section-management.js Fix Models section collapse dead pause and missing animation 2026-06-01 16:53:46 +02:00
sessions.js fix(agent): harden approval lifecycle 2026-08-15 06:52:44 +00:00
settings.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
sidebar-layout.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
signature.js Constrain signature uploads to PNG data (#2844) 2026-06-05 13:17:43 +02:00
skills.js fix(agent): close approval continuation gaps 2026-08-15 06:14:37 +00:00
slashAutocomplete.js feat: add ChatGPT Subscription provider (#2876) 2026-06-08 10:19:18 +02:00
slashCommands.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
spinner.js fix(ui): stop the whirlpool spinner animating when it is never attached (#5990) 2026-08-12 00:25:03 +01:00
startupShell.js perf(ui): stop session loading from blocking shell (#5927) 2026-08-10 19:37:21 +01:00
storage.js feat(a11y): add a Text size control and an OpenDyslexic font option (#4210) 2026-06-22 13:53:46 +02:00
streamingRenderer.js fix(chat): stop code-block button flicker during streaming (#3023) 2026-06-06 04:08:54 -06:00
streamingSegmenter.js fix(chat): stop code-block button flicker during streaming (#3023) 2026-06-06 04:08:54 -06:00
tasks.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
theme.js feat(a11y): add a Text size control and an OpenDyslexic font option (#4210) 2026-06-22 13:53:46 +02:00
tileManager.js fix(ui): restore all-edge modal snap zones (#2260) 2026-06-15 12:36:34 +02:00
toolWindowZOrder.js fix(modal): keep body-portaled dropdowns above their tool modal at any stack depth (#4720) (#4724) 2026-06-23 10:24:31 +02:00
tourAutoplay.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
tourHints.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
tts-ai.js perf(frontend): share one cached fetch for /api/auth/settings and /api/tools (#5997) 2026-08-16 21:03:05 +01:00
ui.js Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
ui_visibility.js fix(sidebar): keep minimized icon rail in sync with per-tab visibility (#5987) 2026-08-12 01:28:30 +01:00
voiceRecorder.js Odysseus v1.0 2026-05-31 23:58:26 +09:00
windowDrag.js fix(windowDrag): disable duplicate top-edge fullscreen snap (#3495) 2026-06-15 16:10:40 +09:00
windowResize.js Make tool windows resizable by dragging edges or corners 2026-06-01 19:49:23 +02:00
workspace.js feat(agent): confine agent file/shell tools to a selectable workspace (#3665) 2026-06-11 18:17:54 +02:00