Vendored front-end assets

The web front end serves all third-party JavaScript locally so it runs fully offline (no CDN calls) and under a strict script-src 'self' CSP. Each file is pinned by version and integrity hash and served from /vendor by Caddy.

To refresh a pin, re-run the fetch below, update the version + SRI here, and update the <script integrity="…"> in the templates that load it.

Inventory

File Package Version License SRI (sha384)
htmx.min.js htmx 2.0.4 0BSD HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+
mathjax/tex-mml-chtml.js (+ mathjax/output/…/woff-v2/*.woff, mathjax/input/tex/extensions/*.js) MathJax 3.2.2 Apache-2.0 Wuix6BuhrWbjDBs24bXrjf4ZQ5aFeFWBuKkFekO2t8xFU0iNaLQfp2K6/1Nxveei (bundle; the autoloaded extensions are 'self', no SRI)
codemirror/*.js (entry codemirror/cm6.js) CodeMirror 6 (+ lang-python/cpp/json, theme-one-dark, @lezer/*) 6.0.2 MIT ES modules — dynamic-imported, 'self' (no SRI)
xterm/xterm.js xterm.js (@xterm/xterm) 5.5.0 MIT M169f14mRZOXm3hD/v2Ti0ThIT/RnAQagXA9nlE15yHAtrW19gdePJh/HaTzUOe/
xterm/xterm-addon-fit.js @xterm/addon-fit 0.10.0 MIT iF+jqbuti4XlB64clWgFWYEscb+UnSRv3VgVikGYZu+otNFnSHr7y7NcKfBnGizn
xterm/xterm.css @xterm/xterm (stylesheet) 5.5.0 MIT 8Xk9wy/gzEDUKrXtrmCFa2bBuK3BpjpDuL/p0SeKQX19Khl/M+lHOgD/CyYf7efP
hash-wasm/argon2.umd.min.js hash-wasm (Argon2id build) 4.12.0 MIT tP0Wy54CKmng7i9EoTlPySD0hBx6Octj0VS6MfwlnUu111MPa+JLm0CCbep6XJ1W
docs/presentation/fonts/newsreader.woff2, newsreader-italic.woff2 Newsreader (Production Type) Google Fonts v26, Latin OFL-1.1 font, 'self' (no SRI)
docs/presentation/fonts/atkinson-hyperlegible-{400,700}.woff2 Atkinson Hyperlegible (Braille Institute) Google Fonts v12, Latin OFL-1.1 font, 'self' (no SRI)
docs/presentation/fonts/jetbrains-mono.woff2 JetBrains Mono Google Fonts v24, Latin OFL-1.1 font, 'self' (no SRI)

hash-wasm's Argon2id derives the SRP private key x in the browser (/assets/srp.js; the service checks against argon2-cffi, tests/test_srp_interop.py pins the two together). It is the npm tarball's dist/argon2.umd.min.js — tarball integrity sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ== as published — with its WebAssembly embedded, which is why the pages that load it carry 'wasm-unsafe-eval' in script-src (docs/web-server-guide.md § Content-Security-Policy). License at hash-wasm/LICENSE.

MathJax typesets the $…$ TeX in statements/notes (config + re-typeset-on-swap in /assets/site.js); the woff fonts are loaded by the bundle relative to its own URL, so the whole tree ships together. Its full license text is at mathjax/LICENSE. Note: MathJax injects its stylesheet at runtime — the reason style-src carries 'unsafe-inline' (docs/web-server-guide.md § Content-Security-Policy).

The TeX extensions under mathjax/input/tex/extensions/ ship for the same reason as the fonts: the combined bundle carries only the default packages, and the rest are fetched relative to the bundle's own URL, so /vendor/mathjax/input/tex/extensions/<name>.js. Vendored, that request is same-origin and script-src 'self' admits it; not vendored it 404s, and the noundefined package then renders the macro as its own name in red — which is what a statement using \color used to show (problem 230).

They are preloaded, not autoloaded (loader.load + tex.packages in /assets/site.js). MathJax's autoload fetches a package on first use, i.e. in the middle of a typeset, and the swap path lost that race: a statement reached by the terminal's show rendered \color[RGB]124, 192, 255 as literal text while a plain refresh of the same URL was perfect. Preloading costs one fetch per document, of files already vendored, and makes the typesetter's vocabulary independent of which page the reader happened to open first. A package added here must be added to MJ_TEX in site.js as well — preloading a name whose file is missing fails MathJax startup, on every page, which is worse than the autoload it replaced. A test pins both directions (tests/test_web_verbs.py::MathJaxWiringTests).

One file per package actually used by a cached statement, found by scanning them for each trigger macro:

Package Macros Statements
color \color, \textcolor, … 12, e.g. 40 · 66 · 230
mhchem \pu, \ce 5, e.g. 75 · 155 · 222
html \style, \class, \href 3: 282 · 326 · 334
boldsymbol \boldsymbol 2: 323 · 462
enclose \enclose 1: 535
unicode \unicode 1: 991

A statement that starts using another one is the same fix: add the file, re-run the scan. There is no SRI — these are same-origin scripts MathJax injects itself, exactly like the CodeMirror modules.

CodeMirror 6 is the code editor on the edit page (/assets/editor.js, lazy-imported by site.js only when an editor appears, so the ~630 KB graph never loads elsewhere). It lives under codemirror/ as a flat set of ES modules mirrored from esm.sh (target=es2022) with imports rewritten to relative ./ siblings — the full transitive graph of codemirror, the language packages, theme-one-dark, @lezer/*, and helpers — so it loads fully offline with no build step. cm6.js is the entry (re-exports only the symbols editor.js uses). Pin the codemirror meta-package to an explicit 6.0.x: the bare @6 range resolves to the legacy CodeMirror 5 UMD, which has no EditorView/basicSetup. All @codemirror/state stubs re-export one state build (CM's single-instance rule). Like MathJax it injects its stylesheet at runtime (style-src 'unsafe-inline'). Dynamic-imported same-origin modules, so no SRI. License at codemirror/LICENSE.

xterm.js is the terminal in the right pane (/terminal): the UMD xterm.js plus the fit addon (it sizes the terminal to its container; the resulting cols×rows is what terminal.js sends over the socket as the PTY's geometry) and the stock stylesheet. Classic scripts with SRI — unlike CodeMirror they are not ES modules, so they are <script src>-loaded by terminal.html and integrity-checked. Both files carry a //# sourceMappingURL= line whose .map is not vendored: harmless (only devtools ever requests it), and stripping it would make the bytes diverge from the published ones the SRI above attests. License at xterm/LICENSE.

The presentation fonts are the one entry not under this directory: they ship with the deck they serve, in docs/presentation/fonts/, and the per-user service hands them out at /story/fonts/ with the rest of the deck (web-server-guide § 11.5, The story deck). They are bundled for the same reason as everything here — default-src 'self' refuses a Google Fonts stylesheet, and a bundled font also works offline. Each is the Latin subset as Google Fonts serves it (the deck's text is Latin; other characters fall back to system fonts), loaded by @font-face in docs/presentation/deck.css. Fonts are not scripts, so there is no SRI; to check a refresh, compare against these sha384 digests:

File sha384
newsreader.woff2 5TsIb3Y92/m28WF/gV7mdSO1juW62ueaN4e5hRZ4uC6CQV5Igadd+oBHDfLDyCX3
newsreader-italic.woff2 NIkI1/BjDDT2w43BuJRu5j405Z8u8wvUcf6XRL2gHbDDk5sf74z1/ZmpfZXH6KMl
atkinson-hyperlegible-400.woff2 idlpjMgZTjOmx6zrv5Ae+oGIwhOzri+uCqMmDHcHbeZSD83FdoJ3vrtowWb6+kH2
atkinson-hyperlegible-700.woff2 nQ0Z7JQYR9+re+CEt4QFS/80lVM63lK+vHYQhrfJ4nQWibIMndxkRAdPQ3UA6pUw
jetbrains-mono.woff2 9wWypBPwJyfsOnJFplExHD+Wz+6/I6+rtL7ITgA/6wSPgpj+tnQovOLWM9j2rhOF

Their license texts sit beside them (OFL-newsreader.txt, OFL-atkinsonhyperlegible.txt, OFL-jetbrainsmono.txt, trailing whitespace stripped for the commit hook, wording unchanged).

Fetch (pinned):

V=2.0.4
curl -sSfL -o htmx.min.js "https://unpkg.com/htmx.org@${V}/dist/htmx.min.js"
printf 'sha384-%s\n' "$(openssl dgst -sha384 -binary htmx.min.js | openssl base64 -A)"

V=3.2.2   # MathJax: the single-file combined component + its woff-v2 fonts
curl -sSfL -o mathjax/tex-mml-chtml.js "https://cdn.jsdelivr.net/npm/mathjax@${V}/es5/tex-mml-chtml.js"
# fonts: es5/output/chtml/fonts/woff-v2/*.woff from the same release
# the autoloaded TeX extensions the cached statements use (see the table above)
mkdir -p mathjax/input/tex/extensions
for e in color mhchem html boldsymbol enclose unicode; do
  curl -sSfL -o "mathjax/input/tex/extensions/$e.js" \
    "https://cdn.jsdelivr.net/npm/mathjax@${V}/es5/input/tex/extensions/$e.js"
done

XV=5.5.0; FV=0.10.0    # xterm.js + the fit addon (the Phase-6 terminal)
curl -sSfL -o xterm/xterm.js            "https://unpkg.com/@xterm/xterm@${XV}/lib/xterm.js"
curl -sSfL -o xterm/xterm.css           "https://unpkg.com/@xterm/xterm@${XV}/css/xterm.css"
curl -sSfL -o xterm/xterm-addon-fit.js  "https://unpkg.com/@xterm/addon-fit@${FV}/lib/addon-fit.js"
for f in xterm/xterm.js xterm/xterm.css xterm/xterm-addon-fit.js; do
  printf '%-28s sha384-%s\n' "$f" "$(openssl dgst -sha384 -binary "$f" | openssl base64 -A)"
done

# the presentation fonts (into docs/presentation/fonts/, from the repo root): the Latin
# files the Google Fonts css2 API lists for these families, and the OFL texts
cd docs/presentation/fonts
G=https://fonts.gstatic.com/s
curl -sSfL -o newsreader.woff2        "$G/newsreader/v26/cY9AfjOCX1hbuyalUrK4397yjA.woff2"
curl -sSfL -o newsreader-italic.woff2 "$G/newsreader/v26/cY9XfjOCX1hbuyalUrK439vogqCz_goCYw7oRd6JFYYzbA.woff2"
curl -sSfL -o atkinson-hyperlegible-400.woff2 "$G/atkinsonhyperlegible/v12/9Bt23C1KxNDXMspQ1lPyU89-1h6ONRlW45G04pIo.woff2"
curl -sSfL -o atkinson-hyperlegible-700.woff2 "$G/atkinsonhyperlegible/v12/9Bt73C1KxNDXMspQ1lPyU89-1h6ONRlW45G8Wbc9dCWP.woff2"
curl -sSfL -o jetbrains-mono.woff2    "$G/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2"
for f in newsreader atkinsonhyperlegible jetbrainsmono; do
  curl -sSfL "https://raw.githubusercontent.com/google/fonts/main/ofl/$f/OFL.txt" | sed 's/[ \t]*$//' > "OFL-$f.txt"
done

Licenses

Presentation fonts — SIL Open Font License 1.1

Newsreader (Copyright 2020 The Newsreader Project Authors), Atkinson Hyperlegible (Copyright 2020 Braille Institute of America, Inc.) and JetBrains Mono (Copyright 2020 The JetBrains Mono Project Authors) are licensed under the SIL Open Font License, Version 1.1. The full text for each is in docs/presentation/fonts/ (OFL-*.txt), and at https://openfontlicense.org.

htmx — Zero-Clause BSD (0BSD)

Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.