A page opened before a frontend edit keeps running the JavaScript it loaded. It surfaced as a 422 about a field the shipped code no longer sends, which reads as a backend bug and is not one. It is also not an HTTP caching bug, and checking that first was worth the time: every frontend route already sets Cache-Control: no-cache and FileResponse supplies ETag/Last-Modified, so any page LOAD revalidates. Verified against the live service. The tab simply never loads again, so there is no request to attach a header to. The page has to ask instead. GET /admin/api/frontend-version returns a digest over (name, st_mtime_ns, st_size) for every served frontend file -- no contents read, since the question is only whether the files moved. mtime is in there as well as size because the case this exists for is a hand-edit during iteration, which often does not change a length. admin._FRONTEND_FILES is pinned against the directory listing, because a page added to the portal but not to that list would be invisible to the poll -- the same failure, reopened. navbar.js is the one file all eight pages load, so the client half lands there once: capture the digest on load, re-check every 30s and whenever the tab becomes visible again (the moment that matches the failure's shape), and on a change show a fixed pill reading "This page is out of date" with a Reload button. Fixed and hidden until it fires, so it shifts nothing; not a slot in the navbar cluster, because appearing there would shove the status dot and the profile switcher sideways, and the navbar scrolls away while the reason to reload does not. It never reloads by itself. An operator may be mid-edit in a profile modal or holding a dirty config row, and discarding that silently costs more than the staleness does. The interval is a constant, not a config knob: the "every knob belongs in config.yaml" rule is about config the router reads, and nothing in the routing path depends on a browser timer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
6.4 KiB
6.4 KiB