From fb1088ceecabe9a90f893e0e139e139036f8f384 Mon Sep 17 00:00:00 2001 From: hermes Date: Mon, 9 Nov 2026 22:41:20 +1000 Subject: [PATCH] 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. --- src/flask/app_factory.py | 13 +++++++++++++ src/flask/static/css/app.css | 11 +++++++++++ src/flask/static/js/app.js | 8 ++++++++ src/flask/templates/brews.html | 2 +- tests/test_brew_advice.py | 23 +++++++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/flask/app_factory.py b/src/flask/app_factory.py index 348dbd6..5e40a69 100644 --- a/src/flask/app_factory.py +++ b/src/flask/app_factory.py @@ -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) diff --git a/src/flask/static/css/app.css b/src/flask/static/css/app.css index 11dd675..9f5c882 100644 --- a/src/flask/static/css/app.css +++ b/src/flask/static/css/app.css @@ -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. */ diff --git a/src/flask/static/js/app.js b/src/flask/static/js/app.js index a8b0400..6c6b6d4 100644 --- a/src/flask/static/js/app.js +++ b/src/flask/static/js/app.js @@ -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"); } diff --git a/src/flask/templates/brews.html b/src/flask/templates/brews.html index 7640b8a..b165c9a 100644 --- a/src/flask/templates/brews.html +++ b/src/flask/templates/brews.html @@ -126,7 +126,7 @@ {% endif %} -