New markup over an old stylesheet
Reported from a desktop browser: a stray close button beside the logo, badly drawn, and a page that would not scroll. None of it was in the code that was running -- it was the code the browser had not fetched. The worker caches /static/ under a cache named for the release while the files in it carried no version, and a page is fetched network-first. That only ever worked because the worker used to seize every open tab the instant it installed and wipe the old cache. 1.1.0 stopped it doing that, rightly -- it was swapping stylesheets out from under a streaming reply -- and a momentary mismatch became a permanent one: new markup over the previous release's CSS for as long as the old worker lived. `.sidebar__close` had no rule there, so `.btn--icon` made it inline-flex: visible everywhere, placed by nothing. Every /static/ URL carries the release now, written by `templating.asset` and precached by `sw.js:versioned` -- both halves, because caches.match compares the query too and precaching the bare path would cache entries nothing requests. Self-correcting: updating is enough. The header was also a brand with a button appended and margin-left:auto doing the placing, which holds exactly while that button is last. Two slots now: a brand that shrinks and truncates, and a rail on the trailing edge. Verified before changing anything: with the current stylesheet the button is display:none at 1280 and, with thirty chats and forty messages, both scrollers scroll. The first measurement said the thread did not -- that was scroll-behavior: smooth reporting where it started. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,34 @@ templates.env.filters["tokens"] = highlight_tokens
|
||||
templates.env.globals["tool_label"] = tool_labels.label_for
|
||||
templates.env.globals["tool_icon"] = tool_labels.icon_for
|
||||
|
||||
def asset(path: str) -> str:
|
||||
"""A static asset's URL, with the release stamped into it.
|
||||
|
||||
🚨 This is not cache politeness, it is what stops a release drawing itself
|
||||
from two versions at once.
|
||||
|
||||
The service worker caches `/static/...` under a cache named for the
|
||||
release, and a *page* is fetched network-first while its assets come from
|
||||
that cache. So the moment the worker stops taking over open tabs the
|
||||
instant it installs -- which it must, or it swaps the stylesheets under
|
||||
somebody mid-reply -- the new HTML and the old CSS are served together and
|
||||
the interface is subtly wrong until the worker is replaced. That shipped in
|
||||
1.1.0: a close button intended for a phone drawer appeared, unstyled, on
|
||||
every desktop, because the markup knew about it and the stylesheet did not.
|
||||
|
||||
A version in the URL settles it without anybody having to be careful: the
|
||||
new HTML asks for a URL the old cache has never heard of, so it goes to the
|
||||
network. The two can no longer disagree, whichever worker is in charge.
|
||||
|
||||
Not a hash of the file: `__version__` is the one thing that already moves
|
||||
with every release, and a hash would mean reading every asset on every
|
||||
render or a build step, and there is deliberately no build step here.
|
||||
"""
|
||||
return f"/static/{path.lstrip('/')}?v={__version__}"
|
||||
|
||||
|
||||
templates.env.globals["asset"] = asset
|
||||
|
||||
# A finished reply as the sequence of steps it was. A global for exactly the
|
||||
# reason the two above are, and it is why turning the bubble into a sequence
|
||||
# needed no change in `pages.py`, `post_message`, `regenerate` or the `done`
|
||||
|
||||
Reference in New Issue
Block a user