Document web UI interactions and content-versioned assets

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UXA39Sh9coAx4FhRkd8R2
This commit is contained in:
2026-09-15 17:15:01 +09:00
parent 5420975f8e
commit 239b764393
2 changed files with 26 additions and 5 deletions
+16 -2
View File
@@ -115,6 +115,12 @@ A custom mastering toolkit that provides metrics to evaluate audio masterings th
- A static site: decoding and every metric run in the visitor's tab; nothing is - A static site: decoding and every metric run in the visitor's tab; nothing is
uploaded or stored. Served with `Content-Security-Policy: connect-src 'none'` uploaded or stored. Served with `Content-Security-Policy: connect-src 'none'`
(`deploy/nginx.conf`), so the page and its worker cannot make network requests (`deploy/nginx.conf`), so the page and its worker cannot make network requests
- The image versions assets by content: everything but `index.html` is copied to
`v/<hash>/` and the page's references rewritten (Dockerfile `assets` stage);
nginx serves `v/` immutable and the page `no-cache`. Needed because Cloudflare
forces browser caching of .js/.css to 4 h, which paired a fresh index.html with
a stale app.js (`Cannot set properties of null`). Source `web/index.html` keeps
plain relative paths, so serving `web/` directly still works
- `decode.js` — `AudioFile.__init__` counterpart. WAV/AIFF PCM & float are parsed - `decode.js` — `AudioFile.__init__` counterpart. WAV/AIFF PCM & float are parsed
directly with libsndfile scaling (bit-identical to `librosa.load`; Chromium's directly with libsndfile scaling (bit-identical to `librosa.load`; Chromium's
own WAV decoder scales positive int16 by 1/32767). Other formats go through own WAV decoder scales positive int16 by 1/32767). Other formats go through
@@ -133,8 +139,16 @@ A custom mastering toolkit that provides metrics to evaluate audio masterings th
metric per request, transfers typed arrays back metric per request, transfers typed arrays back
- `app.js` — `MainWindow` + `AnalysisResultsManager` + renderer: track list, - `app.js` — `MainWindow` + `AnalysisResultsManager` + renderer: track list,
overlay set, per-metric result cache, job queue (foreground jobs jump ahead of overlay set, per-metric result cache, job queue (foreground jobs jump ahead of
background prefetch), uPlot for curves/bands + canvas spectrogram (log axis background prefetch; LUFS + true peak prefetched first for the metadata
drawn from linear rows), draggable reference lines per metric, metric hlines panel's loudness summary), uPlot for curves/bands + canvas spectrogram (log
axis drawn from linear rows, cached offscreen image). Guide and reference
lines are painted in a uPlot draw hook, so dragging a line is a cheap redraw
and moves live. One zoom/pan controller serves both renderers: wheel zoom
(Ctrl = x only, Shift = y only, over an axis = that axis), rAF-throttled drag
pan, double-click reset; `state.zoom` survives redraws and resets when units
change. In-plot legend overlay like pyqtgraph's
- Font: CSS `--font` stack prefers M PLUS 1 Code, else the system UI font;
canvas/uPlot text reads the same variable
- `vendor/` — uPlot 1.6.32, vendored (the CSP only allows same-origin scripts) - `vendor/` — uPlot 1.6.32, vendored (the CSP only allows same-origin scripts)
- Needs an HTTP server (module scripts and workers don't load from `file://`), - Needs an HTTP server (module scripts and workers don't load from `file://`),
but no secure context — plain `http://` on a LAN works but no secure context — plain `http://` on a LAN works
+10 -3
View File
@@ -71,9 +71,12 @@ not just promised: the page is served with `Content-Security-Policy: connect-src
request. request.
Everything the desktop app does works the same way: all seven metrics, the Everything the desktop app does works the same way: all seven metrics, the
overlay, lin/log toggles, absolute/relative time and draggable reference lines overlay, lin/log toggles, absolute/relative time, an in-plot legend, wheel zoom
(kept per metric), plus the metrics' own reference lines (−14 LUFS, integrated around the cursor (Ctrl: time only, Shift: values only, or scroll over an axis),
loudness, 0 dBTP…). Analysis runs at the file's native sample rate. live drag-to-pan and double-click to reset, and draggable reference lines (kept
per metric) alongside the metrics' own guides (−14 LUFS, integrated loudness,
0 dBTP…). The metadata panel and file list show integrated loudness, LRA and
max true peak. Analysis runs at the file's native sample rate.
Serve `web/` with any static file server, over `http://` or `https://` — no Serve `web/` with any static file server, over `http://` or `https://` — no
secure context is needed. (ES modules and workers don't load from `file://`, so secure context is needed. (ES modules and workers don't load from `file://`, so
@@ -122,6 +125,10 @@ float32 paths (true peak, spectrogram dB, RMS) to ~1e-6.
The image is nginx (unprivileged, read-only root filesystem) serving `web/` with The image is nginx (unprivileged, read-only root filesystem) serving `web/` with
`deploy/nginx.conf`, which sets the CSP and turns access logging off. There is `deploy/nginx.conf`, which sets the CSP and turns access logging off. There is
nothing to persist, upload or log on the server side, so there are no volumes. nothing to persist, upload or log on the server side, so there are no volumes.
Assets are served from a content-hashed `v/<hash>/` directory (cached
indefinitely) while the page itself is always revalidated, so a deploy can never
leave a browser running old scripts against a new page — even behind a CDN that
overrides cache headers.
### Web architecture ### Web architecture