The where-it-lives card actually behaves; assets stop serving stale
Eric, again with the screenshot that tells the truth: "when I click it
it greys out and nothing seems to happen." Two roots. My own
#storedform { display:flex } silently DEFEATED the hidden attribute
(an author display beats the UA's [hidden] rule), so the form was
visible from load and the click's only visible effect was disabling
the link. The rule is now :not([hidden])-scoped, the click swaps the
sentence for the form and focuses the select. And the "unstyled"
select he saw was last week's stylesheet: /static assets had no
cache-busting, so my fresh-fetch Chrome verification passed while his
browser held the old CSS. Asset URLs now carry ?v=<app version> —
an upgrade busts every browser's cache by construction.
Verified the full interaction in-browser this time: form hidden on
load, click reveals with 142 container options in a styled select.
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
f435cffea0
commit
e85f72c546
@@ -536,7 +536,10 @@ select {
|
||||
border: 2px solid var(--board-edge); border-radius: var(--radius);
|
||||
padding: .25rem .45rem; background: #fff; max-width: 24rem;
|
||||
}
|
||||
#storedform { display: flex; gap: .5rem; align-items: center; }
|
||||
/* :not([hidden]) so the hidden attribute still works — a bare
|
||||
display:flex would override the browser's [hidden] rule and show
|
||||
the form permanently */
|
||||
#storedform:not([hidden]) { display: flex; gap: .5rem; align-items: center; }
|
||||
#storedform select { flex: 1 1 auto; }
|
||||
|
||||
.empty {
|
||||
|
||||
@@ -74,11 +74,12 @@ function wireStored(g) {
|
||||
const btn = document.getElementById("editstored");
|
||||
if (!btn) return;
|
||||
btn.addEventListener("click", async () => {
|
||||
btn.closest("p").hidden = true; // the sentence yields to the form
|
||||
const form = document.getElementById("storedform");
|
||||
form.hidden = false;
|
||||
btn.disabled = true;
|
||||
const all = await fetchJSON("/api/library");
|
||||
const pick = document.getElementById("storedpick");
|
||||
pick.focus();
|
||||
const all = await fetchJSON("/api/library");
|
||||
pick.innerHTML = `<option value="">its own box</option>` + all
|
||||
.filter(o => o.bgg_id && o.bgg_id !== g.bgg_id)
|
||||
.sort((a, b) => (a.name || "").localeCompare(b.name || ""))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png">
|
||||
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
|
||||
<meta name="apple-mobile-web-app-title" content="bggpipe">
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
<link rel="stylesheet" href="/static/app.css?v=__ASSET_V__">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main">Skip to content</a>
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="banner" role="status"></div>
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?v=__ASSET_V__"></script>
|
||||
<main id="main">
|
||||
<!--PAGE-->
|
||||
</main>
|
||||
|
||||
@@ -724,7 +724,13 @@ def create_app(
|
||||
(a detail page keeps its section lit)."""
|
||||
active = active or name
|
||||
templates = resources.files("bggpipe") / "templates"
|
||||
shell = (templates / "shell.html").read_text()
|
||||
# version-stamped asset URLs: a browser that cached last week's
|
||||
# stylesheet must fetch the new one the moment the app upgrades
|
||||
from bggpipe import __version__
|
||||
|
||||
shell = (
|
||||
(templates / "shell.html").read_text().replace("__ASSET_V__", __version__)
|
||||
)
|
||||
fragment = (templates / "pages" / f"{name}.html").read_text()
|
||||
nav = "\n".join(
|
||||
f' <a href="{href}"'
|
||||
|
||||
@@ -205,7 +205,7 @@ def test_stylesheet_is_served_and_linked_by_every_page(tmp_path):
|
||||
assert css.headers["content-type"].startswith("text/css")
|
||||
assert "--accent" in css.text # the token layer, not an empty file
|
||||
for path in ("/", "/review"):
|
||||
assert 'href="/static/app.css"' in web.get(path).text
|
||||
assert 'href="/static/app.css?v=' in web.get(path).text
|
||||
|
||||
|
||||
def test_every_page_marks_itself_current_in_the_nav(tmp_path):
|
||||
@@ -653,7 +653,7 @@ def test_photo_detail_page_serves_with_photos_nav_active(tmp_path):
|
||||
|
||||
(current,) = re.findall(r'<a href="([^"]+)" aria-current="page"', html)
|
||||
assert current == "/photos"
|
||||
assert 'src="/static/app.js"' in html
|
||||
assert 'src="/static/app.js?v=' in html
|
||||
|
||||
|
||||
def test_pipeline_counts_pending_work_not_queue_rows(tmp_path):
|
||||
@@ -675,3 +675,14 @@ def test_pipeline_counts_pending_work_not_queue_rows(tmp_path):
|
||||
p = _app(cfg).get("/api/pipeline").json()
|
||||
assert p["to_update"] == 0 # applied
|
||||
assert p["queued_total"] == 1 # still listed until diff reruns
|
||||
|
||||
|
||||
def test_assets_are_version_stamped(tmp_path):
|
||||
"""A cached stylesheet from last week must not survive an upgrade:
|
||||
asset URLs carry the app version."""
|
||||
from bggpipe import __version__
|
||||
|
||||
html = _app(_cfg(tmp_path)).get("/").text
|
||||
assert f"/static/app.css?v={__version__}" in html
|
||||
assert f"/static/app.js?v={__version__}" in html
|
||||
assert "__ASSET_V__" not in html
|
||||
|
||||
Reference in New Issue
Block a user