fix(ui): make the advice pop-out fail-safe against a stale stylesheet #22

Merged
hermes merged 1 commits from fix/fail-safe-advice-popout into master 2026-11-09 22:41:44 +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": if response.mimetype == "text/html":
response.headers["Cache-Control"] = "no-store, must-revalidate" response.headers["Cache-Control"] = "no-store, must-revalidate"
response.headers["Pragma"] = "no-cache" 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 return response
csrf = CSRFProtect(app) 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 /* The script opens and closes the panel, and places it. The CSS only
decides whether it is shown. */ 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 { .advice-pop.is-open {
display: block; display: block;
} }
.advice-pop[hidden] {
display: none;
}
/* On a phone there is no hover, so the trigger is a button the script /* 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 toggles, and the panel is pinned to the middle of the screen so it is
readable whatever the table's width. */ readable whatever the table's width. */
+8
View File
@@ -131,6 +131,11 @@
clearTimeout(closeTimer); clearTimeout(closeTimer);
closeTimer = null; 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"); panel.classList.add("is-open");
trigger.setAttribute("aria-expanded", "true"); trigger.setAttribute("aria-expanded", "true");
place(); place();
@@ -138,6 +143,9 @@
function close() { function close() {
panel.classList.remove("is-open"); 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"); trigger.setAttribute("aria-expanded", "false");
} }
+1 -1
View File
@@ -126,7 +126,7 @@
{% endif %} {% endif %}
</span> </span>
</button> </button>
<div class="advice-pop" role="tooltip"> <div class="advice-pop" role="tooltip" hidden>
<div class="advice-pop__head"> <div class="advice-pop__head">
<span class="advice__label">Brewing advice</span> <span class="advice__label">Brewing advice</span>
<button type="button" class="advice-pop__close" <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" 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): def test_a_failed_advice_is_marked_as_such(client, repo):
"""A generation failure reads differently from real advice.""" """A generation failure reads differently from real advice."""
repo.users.insert_one({"username": "beer", "password": "beer_pass"}) repo.users.insert_one({"username": "beer", "password": "beer_pass"})