Compare commits

...
2 Commits
Author SHA1 Message Date
hermes 8e4770f870 fix(ui): make the advice pop-out fail-safe against a stale stylesheet (#22)
Build and Push Image / Build and push image (push) Successful in 3m33s
fix(ui): make the advice pop-out fail-safe against a stale stylesheet (#22)
2026-11-09 22:41:44 +10:00
hermes fb1088ceec fix(ui): make the advice pop-out fail-safe against a stale stylesheet
The screenshot from Firefox showed the advice expanded inline in the
table cell, running down the column and off the screen, with the long
and short labels both showing ("BREWING ADVICEADVICE").  The page had
the new markup and Firefox was serving a stale stylesheet.

Two fixes.

The static files now send Cache-Control: no-store.  They sent
no-cache, which means "revalidate" - and Firefox was reusing them
without revalidating.  no-store is the only one that means "do not
keep this".  The HTML pages already sent no-store.

The pop-out carries the hidden attribute, and the script removes it
when the panel opens and puts it back when it closes.  That makes the
MARKUP fail-safe rather than the stylesheet: a stylesheet that does
not know about .advice-pop at all still leaves the panel hidden, so
the advice can never appear expanded inside the cell.

Verified by removing all eight .advice-pop rules from the live
stylesheet - the panel stays hidden, 0x0, and the advice is not
rendered.  Before this it would have rendered inline.

The button still opens it on tap and on hover, and the close button
still closes it.  The desktop is unchanged: the same full headings and
the same hover pop-out.

195 tests pass.
2026-11-09 22:41:20 +10:00
5 changed files with 56 additions and 1 deletions
+13
View File
@@ -97,6 +97,19 @@ def create_app(config: dict | None = None) -> Flask:
if response.mimetype == "text/html":
response.headers["Cache-Control"] = "no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
elif response.mimetype in ("text/css", "application/javascript",
"text/javascript"):
# The static files must NEVER be reused from a cache.
# Firefox kept serving a stale stylesheet even with
# `no-cache`, which meant the page had the new markup and
# the old CSS: the advice was expanded inline in the table
# instead of in a pop-out, and the long and short labels
# were both showing ("BREWING ADVICEADVICE").
# `no-store` is the only one that means "do not keep this".
response.headers["Cache-Control"] = "no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
return response
csrf = CSRFProtect(app)
+11
View File
@@ -999,10 +999,21 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
/* The script opens and closes the panel, and places it. The CSS only
decides whether it is shown. */
/* The panel is opened by the script, which removes the `hidden`
attribute and adds `is-open`.
The `hidden` attribute is what makes this fail-safe: if the
stylesheet is stale and does not know about `.advice-pop` at all,
the panel is STILL hidden, so the advice cannot appear expanded
inside the table cell. */
.advice-pop.is-open {
display: block;
}
.advice-pop[hidden] {
display: none;
}
/* On a phone there is no hover, so the trigger is a button the script
toggles, and the panel is pinned to the middle of the screen so it is
readable whatever the table's width. */
+8
View File
@@ -131,6 +131,11 @@
clearTimeout(closeTimer);
closeTimer = null;
}
/* The `hidden` attribute is removed as well as the
class added, so the panel is shown even if the
stylesheet is an older one that only knows about
the class. */
panel.removeAttribute("hidden");
panel.classList.add("is-open");
trigger.setAttribute("aria-expanded", "true");
place();
@@ -138,6 +143,9 @@
function close() {
panel.classList.remove("is-open");
/* Put the attribute back, so a stale stylesheet
cannot leave the advice expanded in the cell. */
panel.setAttribute("hidden", "");
trigger.setAttribute("aria-expanded", "false");
}
+1 -1
View File
@@ -126,7 +126,7 @@
{% endif %}
</span>
</button>
<div class="advice-pop" role="tooltip">
<div class="advice-pop" role="tooltip" hidden>
<div class="advice-pop__head">
<span class="advice__label">Brewing advice</span>
<button type="button" class="advice-pop__close"
+23
View File
@@ -1162,6 +1162,29 @@ def test_stale_advice_cannot_overwrite_a_comment_edited_outside_the_app(repo):
assert doc["comment"] == "edited outside the app"
def test_the_advice_pop_out_is_hidden_in_the_markup(client, repo):
"""The pop-out carries the `hidden` attribute.
A stale stylesheet - one that does not know about `.advice-pop` at
all - would otherwise leave the advice expanded inline in the table
cell, running down the column and off the screen. The `hidden`
attribute makes the markup fail-safe: the panel stays hidden until
the script opens it.
"""
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
client.post("/login", data={"username": "beer", "password": "beer_pass"},
follow_redirects=True)
seed_readings(repo, [("1", "Ale", "1042", "2026-01-01", False, "")])
doc = repo.readings.find_one({"beer_run_id": "1"})
repo.set_reading_advice(str(doc["_id"]), "Warm it to 21C and wait.", ok=True)
body = client.get("/brews").get_data(as_text=True)
assert 'class="advice-pop"' in body
assert 'hidden' in body.split('class="advice-pop"')[1][:60]
def test_a_failed_advice_is_marked_as_such(client, repo):
"""A generation failure reads differently from real advice."""
repo.users.insert_one({"username": "beer", "password": "beer_pass"})