Asset stamps become content hashes — version stamps bust nothing
Eric's screenshot showed week-old CSS again, straight through the cache-buster: the stamp was the app VERSION, unchanged at 1.0.0 since release, so every stylesheet change shipped at the same ?v= URL and browsers rightly kept their copies. The stamp is now an 8-char hash over the static bundle's bytes — it changes exactly when the files do, releases or not. The test now pins that property instead of the version equality it used to celebrate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
708883313f
commit
446579e96c
@@ -338,6 +338,19 @@ def _default_stages(cfg: Config) -> dict[str, Callable[..., object]]:
|
|||||||
# (href, page-name, label, badge-name) — badge names match app.js
|
# (href, page-name, label, badge-name) — badge names match app.js
|
||||||
# ordered as the real workflow runs: photograph, proofread the reads
|
# ordered as the real workflow runs: photograph, proofread the reads
|
||||||
# (titles), resolve + decide (review), then what ships (queue, library)
|
# (titles), resolve + decide (review), then what ships (queue, library)
|
||||||
|
def _asset_stamp() -> str:
|
||||||
|
"""Eight hex chars over the static bundle's bytes. Computed once per
|
||||||
|
request path is fine — the files cannot change under a running
|
||||||
|
server, and the read is microseconds."""
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
static = resources.files("bggpipe") / "static"
|
||||||
|
digest = hashlib.md5()
|
||||||
|
for name in ("app.css", "app.js"):
|
||||||
|
digest.update((static / name).read_bytes())
|
||||||
|
return digest.hexdigest()[:8]
|
||||||
|
|
||||||
|
|
||||||
NAV_PAGES = (
|
NAV_PAGES = (
|
||||||
("/", "pipeline", "Pipeline", ""),
|
("/", "pipeline", "Pipeline", ""),
|
||||||
("/photos", "photos", "Photos", "photos"),
|
("/photos", "photos", "Photos", "photos"),
|
||||||
@@ -779,12 +792,13 @@ def create_app(
|
|||||||
(a detail page keeps its section lit)."""
|
(a detail page keeps its section lit)."""
|
||||||
active = active or name
|
active = active or name
|
||||||
templates = resources.files("bggpipe") / "templates"
|
templates = resources.files("bggpipe") / "templates"
|
||||||
# version-stamped asset URLs: a browser that cached last week's
|
# content-hashed asset URLs: the stamp changes whenever the FILES
|
||||||
# stylesheet must fetch the new one the moment the app upgrades
|
# change — a version-number stamp proved worthless between
|
||||||
from bggpipe import __version__
|
# releases, serving week-old CSS at an unchanged ?v=1.0.0
|
||||||
|
|
||||||
shell = (
|
shell = (
|
||||||
(templates / "shell.html").read_text().replace("__ASSET_V__", __version__)
|
(templates / "shell.html")
|
||||||
|
.read_text()
|
||||||
|
.replace("__ASSET_V__", _asset_stamp())
|
||||||
)
|
)
|
||||||
fragment = (templates / "pages" / f"{name}.html").read_text()
|
fragment = (templates / "pages" / f"{name}.html").read_text()
|
||||||
nav = "\n".join(
|
nav = "\n".join(
|
||||||
|
|||||||
@@ -689,9 +689,14 @@ def test_pipeline_counts_pending_work_not_queue_rows(tmp_path):
|
|||||||
def test_assets_are_version_stamped(tmp_path):
|
def test_assets_are_version_stamped(tmp_path):
|
||||||
"""A cached stylesheet from last week must not survive an upgrade:
|
"""A cached stylesheet from last week must not survive an upgrade:
|
||||||
asset URLs carry the app version."""
|
asset URLs carry the app version."""
|
||||||
from bggpipe import __version__
|
import re
|
||||||
|
|
||||||
html = _app(_cfg(tmp_path)).get("/").text
|
html = _app(_cfg(tmp_path)).get("/").text
|
||||||
assert f"/static/app.css?v={__version__}" in html
|
(stamp,) = set(re.findall(r'/static/app\.css\?v=([0-9a-f]{8})"', html))
|
||||||
assert f"/static/app.js?v={__version__}" in html
|
assert f"/static/app.js?v={stamp}" in html
|
||||||
assert "__ASSET_V__" not in html
|
assert "__ASSET_V__" not in html
|
||||||
|
# the stamp is a CONTENT hash: it must change when the css changes,
|
||||||
|
# not when the release version does — that was the original sin
|
||||||
|
from bggpipe import webreview
|
||||||
|
|
||||||
|
assert stamp == webreview._asset_stamp()
|
||||||
|
|||||||
Reference in New Issue
Block a user