fix(ui): stop the pages being cached, and the labels duplicating #19

Merged
hermes merged 1 commits from fix/stale-cache-and-label-duplication into master 2026-11-09 22:06:59 +10:00
4 changed files with 70 additions and 21 deletions
+12
View File
@@ -87,6 +87,18 @@ def create_app(config: dict | None = None) -> Flask:
if config: if config:
app.config.update(config) app.config.update(config)
# The pages must not be cached by the browser.
# A phone held on to a cached copy of the brewing page and showed
# the old inline advice instead of the button and the pop-out, so
# the change was not visible until the cache was cleared by hand.
@app.after_request
def _no_store(response):
if response.mimetype == "text/html":
response.headers["Cache-Control"] = "no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
return response
csrf = CSRFProtect(app) csrf = CSRFProtect(app)
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
+16 -21
View File
@@ -484,9 +484,14 @@ table.data .num { font-family: var(--mono); text-align: right; white-space: nowr
table.data .comment { color: var(--text-muted); font-size: .84rem; } table.data .comment { color: var(--text-muted); font-size: .84rem; }
/* A heading that is too long for a phone column. The long form shows /* A heading that is too long for a phone column. The long form shows
on a desktop and the short form on a phone - see the media query. */ on a desktop and the short form on a phone - see the media query.
table.data .short { display: none; }
table.data .advice-trigger .short { display: none; } The rule is on `.short` alone, not on `table.data .short`, so it
applies everywhere the pattern is used - in a table heading, in the
advice button, and on the Active Beers page. When only the table
was covered, the advice button could show both forms at once and
read "Read the adviceAdvice". */
.short { display: none; }
/* On a narrow screen the header wraps onto two rows: the brand and the /* On a narrow screen the header wraps onto two rows: the brand and the
sign-out on the first, the nav tabs on the second where they get the sign-out on the first, the nav tabs on the second where they get the
@@ -570,19 +575,17 @@ table.data .advice-trigger .short { display: none; }
overflow-wrap: break-word; overflow-wrap: break-word;
} }
/* The heading gets a short form, because a heading that is cut off /* The long form gives way to the short form on a phone, because a
is worse than a heading that is brief. */ heading that is cut off is worse than a heading that is brief.
table.data .col-abv .long,
table.data .col-advice .long, The rule is on the span itself rather than on the column, so it
table.data .col-finished .long, applies everywhere the pattern is used - in a table heading, in
table.data .col-comment .long { the advice button, and on the Active Beers page. */
.long {
display: none; display: none;
} }
table.data .col-abv .short, .short {
table.data .col-advice .short,
table.data .col-finished .short,
table.data .col-comment .short {
display: inline; display: inline;
} }
@@ -594,14 +597,6 @@ table.data .advice-trigger .short { display: none; }
gap: .2rem; gap: .2rem;
} }
table.data .advice-trigger .long {
display: none;
}
table.data .advice-trigger .short {
display: inline;
}
/* The action column holds a button. */ /* The action column holds a button. */
table.data .col-action { table.data .col-action {
white-space: nowrap; white-space: nowrap;
+12
View File
@@ -714,3 +714,15 @@ def test_the_api_exposes_the_attenuation_and_curve(logged_in, repo):
assert len(entry["curve"]) == 2 assert len(entry["curve"]) == 2
assert entry["curve"][0]["progress"] == 0.0 assert entry["curve"][0]["progress"] == 0.0
assert entry["curve"][1]["progress"] == 1.0 assert entry["curve"][1]["progress"] == 1.0
def test_the_pages_are_not_cached(client):
"""The HTML pages must not be cached by the browser.
A phone held on to a cached copy of the brewing page and kept
showing the old inline advice after the button and the pop-out had
been deployed, so the change was invisible until the cache was
cleared by hand.
"""
response = client.get("/login")
assert "no-store" in response.headers.get("Cache-Control", "")
+30
View File
@@ -1049,6 +1049,36 @@ def test_the_tables_keep_their_columns_on_a_phone(client, repo):
assert "Cider raspberry elderberry" in body assert "Cider raspberry elderberry" in body
def test_the_long_and_short_labels_are_both_in_the_page(client, repo):
"""The long and the short label are both in the markup.
CSS picks which one shows - the long form on a desktop, the short
form on a phone. If only one is in the markup the other cannot be
shown at all, and if the CSS rule misses one of them the page shows
"Read the adviceAdvice".
This is a markup assertion, not a CSS assertion: it makes sure
both forms exist for the stylesheet to choose between.
"""
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)
# The heading has both forms.
assert '<span class="long">Brewing advice</span>' in body
assert '<span class="short">Advice</span>' in body
# The button has both forms.
assert '<span class="long">Read the advice</span>' in body
assert '<span class="short">Advice</span>' in body
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"})