Files
6krrt/admin/frontend/navbar.js
adlee-was-taken 242a01bf2c feat(admin): an open tab now notices the frontend changed under it
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
2026-09-14 00:33:24 -04:00

11 KiB