fix(static): force conditional revalidation on /static (Cache-Control: no-cache)

Without Cache-Control Chromium's heuristic freshness (10% of file age)
serves /static/app.js from disk cache for hours-to-days without
revalidating. Desktop consequence: a new build's window ran the previous
build's app.js — the 2026-07-11 ASIO investigation traced 'routing
watcher never installed' + a stems module-plugin SyntaxError to exactly
this (stale loader predating scriptType support). no-cache keeps caching
but revalidates via ETag — unchanged files still cost only a 304.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
OmikronApex
2026-07-11 17:44:18 +02:00
co-authored by Claude Fable 5
parent 6797c03603
commit 381dccc716
+19 -1
View File
@@ -7770,7 +7770,25 @@ def serve_audio(filename: str):
return JSONResponse({"error": "not found"}, status_code=404)
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
class _RevalidatedStaticFiles(StaticFiles):
"""StaticFiles that forces conditional revalidation on every request.
Without Cache-Control, Chromium applies heuristic freshness (10% of the
file's age since Last-Modified) and serves /static/app.js from its disk
cache for hours-to-days without asking the server. In the desktop app that
meant a new build's renderer ran the PREVIOUS build's app.js — the
2026-07-11 ASIO investigation lost a day to a stale loader that couldn't
even load module plugins. `no-cache` does NOT disable caching: the browser
keeps the cached copy and revalidates with If-None-Match; unchanged files
still cost only a 304."""
async def get_response(self, path, scope):
response = await super().get_response(path, scope)
response.headers.setdefault("Cache-Control", "no-cache")
return response
app.mount("/static", _RevalidatedStaticFiles(directory=str(STATIC_DIR)), name="static")
@app.get("/")