From 381dccc7167daf52db98c3c6154b6601899dfe78 Mon Sep 17 00:00:00 2001 From: OmikronApex Date: Sat, 11 Jul 2026 17:44:18 +0200 Subject: [PATCH] fix(static): force conditional revalidation on /static (Cache-Control: no-cache) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- server.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 3551858..82978de 100644 --- a/server.py +++ b/server.py @@ -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("/")