From e50eff9122b14f4471f59843a66877f5d9ced006 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 14 Sep 2026 02:37:26 +0900 Subject: [PATCH] Use relative asset and API URLs so the UI works under a path prefix index.html and app.js requested /static/... and /api/... from the site root, so serving the app behind a reverse proxy at a sub-path (e.g. https://host/uj/ with the prefix stripped) 404'd every asset and call. Relative URLs resolve against the page, which is the same at / and at /uj/; direct access is unchanged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015UXA39Sh9coAx4FhRkd8R2 --- static/app.js | 16 ++++++++-------- static/index.html | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/static/app.js b/static/app.js index bc2efcb..37d8928 100644 --- a/static/app.js +++ b/static/app.js @@ -100,7 +100,7 @@ async function api(path, options) { } async function loadMetrics() { - const data = await api('/api/metrics'); + const data = await api('api/metrics'); state.metrics = data.metrics; state.metric = data.default; const select = el('metric-select'); @@ -134,7 +134,7 @@ function renderFileList() { check.addEventListener('click', (ev) => ev.stopPropagation()); check.addEventListener('change', async () => { const ids = collectOverlayFromDOM(); - await api('/api/overlay', { + await api('api/overlay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_ids: ids }), @@ -159,7 +159,7 @@ function renderFileList() { remove.title = 'Forget this track (drops the analysis and removes it from browser storage)'; remove.addEventListener('click', async (ev) => { ev.stopPropagation(); - await api(`/api/file?file_id=${encodeURIComponent(f.file_id)}`, { method: 'DELETE' }); + await api(`api/file?file_id=${encodeURIComponent(f.file_id)}`, { method: 'DELETE' }); // The server only ever held numbers; the audio is ours to delete. await store.remove(f.file_id); releaseObjectUrl(f.file_id); @@ -871,7 +871,7 @@ async function refreshPlot() { if (state.yLog !== null) params.set('y_log', String(state.yLog)); if (state.xLog !== null) params.set('x_log', String(state.xLog)); - const payload = await api(`/api/plot?${params.toString()}`); + const payload = await api(`api/plot?${params.toString()}`); state.payload = payload; el('plot-title').textContent = payload.datasets.length ? `${payload.metric_name}${payload.datasets.length > 1 ? ` — ${payload.datasets.length} tracks` : ` — ${payload.datasets[0].label}`}` @@ -933,7 +933,7 @@ async function ingest(fileList) { } } - const s = await api('/api/session'); + const s = await api('api/session'); state.files = s.files; state.overlay = s.overlay; if (!state.selected && state.files.length) state.selected = state.files[0].file_id; @@ -952,7 +952,7 @@ async function ingest(fileList) { * second round trip) and is recorded in `state.analysed`. */ async function analyseOne(fileId, name, metric = state.metric) { - const url = `/api/analyse?` + new URLSearchParams({ + const url = `api/analyse?` + new URLSearchParams({ file_id: fileId, name, metric: metric || state.metric || '', @@ -972,7 +972,7 @@ async function analyseOne(fileId, name, metric = state.metric) { function ensureOverlay(fileId) { if (!state.overlay.includes(fileId)) { state.overlay = [...state.overlay, fileId]; - api('/api/overlay', { + api('api/overlay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_ids: state.overlay }), @@ -1129,7 +1129,7 @@ async function boot() { } await loadMetrics(); - const s = await api('/api/session'); + const s = await api('api/session'); state.files = s.files; state.overlay = s.overlay; state.selected = state.files.length ? state.files[0].file_id : null; diff --git a/static/index.html b/static/index.html index b80fa70..37a06f4 100644 --- a/static/index.html +++ b/static/index.html @@ -4,8 +4,8 @@ Audio Mastering Analysis Toolkit - - + + @@ -89,9 +89,9 @@ - + - +