A reload offer only for a page that is older, and a tab switch that moves one scroller

The update toast fired whenever a service worker was waiting, so after every
release it appeared on pages that were already the release -- including one
fetched with Ctrl+Shift+R. It now compares the waiting worker's release with
the page's own, and so does the reload that follows another tab accepting it.

On Admin -> Prompts, visually hidden labels were positioned against the page
and made the document 6771px tall behind an overflow-hidden root; the tab
handler's scrollIntoView then scrolled that root and lifted the shell 56px.
Every scroll region is now a containing block, and the handler moves only the
container that scrolls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 06:17:18 +00:00
co-authored by Claude Opus 5.5
parent 793f9c8cad
commit db22962164
8 changed files with 170 additions and 23 deletions
+30
View File
@@ -311,3 +311,33 @@ def test_the_worker_precaches_what_a_page_will_ask_for():
source = (STATIC_DIR / "js" / "sw.js").read_text()
assert 'path + "?v=" + VERSION' in source
assert "versioned(path)" in source
def test_the_update_offer_asks_whether_this_page_is_older(client: TestClient, registered):
"""After a release the toast offered a reload on every page, including one
just fetched with Ctrl+Shift+R, and reloading never made it go away. It
fired whenever a worker was waiting. But a page loaded after the update
already IS the update: it comes from the network, and every asset it names
carries `?v=`. The worker that waits is nearly always the previous one,
still holding the tab, because a reload never lets it run out of pages.
So the offer, and the automatic reload when another tab accepts it, compare
the worker's release with the page's own. Driven under Node against stubs
before committing: 1.8.3 offered a reload to a current page and to a page
that another tab's Reload had just made current. This does neither, and
still offers it to a page from an older release.
"""
import re
import lembas
page = client.get("/chat").text
assert f"window.lembasRelease = \"{lembas.__version__}\";" in page
source = (STATIC_DIR / "js" / "app.js").read_text()
code = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
start = code.index("function watchForUpdate")
block = code[start : code.index("navigator.serviceWorker.ready.then(watchForUpdate)")]
# Both ways a waiting worker is found, and the controllerchange reload.
assert block.count("isNewerThanThisPage(") == 3
assert 'searchParams.get("v")' in code
+24
View File
@@ -177,6 +177,30 @@ def test_the_tab_reset_finds_the_container_that_actually_scrolls():
assert "scrollHeight > " in SOURCE
def test_a_tab_switch_moves_only_the_container_that_scrolls():
"""Switching a tab on /admin/prompts lifted the whole shell 56px, with the
topbar gone off the top and a strip of bare background under everything.
Two halves, and either one alone is enough to bring it back. The prompt
cards' `.visually-hidden` labels are `position: absolute`. With no
positioned ancestor they were placed against the page and stretched the
document to 6771px behind an `overflow: hidden` root. And the handler used
`scrollIntoView`, which scrolls every ancestor that can scroll, the root
included. Measured in headless Chromium at 1640x930 before and after.
"""
app = (ROOT / "web/static/css/app.css").read_text(encoding="utf-8")
start = app.index(".scroll-region,")
rule = app[start : app.index("}", start)]
assert ".admin-scroll" in rule
assert "position: relative" in rule
code = re.sub(r"/\*.*?\*/", "", SOURCE, flags=re.S)
start = code.index('closest(".tabs__bar")')
handler = code[start : code.index("})();", start)]
assert "scrollIntoView" not in handler
assert "outer.scrollTop" in handler
def test_the_two_ends_of_the_shell_stay_level():
"""The sidebar footer and the composer sit either side of the same vertical
edge and are both content-sized, so without a common floor they end at