An update that keeps the offline copy
The PWA audit of 26 September showed that if the connection broke while the worker updated, the reader was left without an offline axis. Now a new build takes over only once it holds the whole shell of every language the reader kept offline. Until then the one before stays.
What the audit found
The atlas has opened without a network since 26 September. The same evening the PWA audit found a medium-weight flaw in the worker, R1. Each build keeps its own shell: a language’s front page, axis and record text. A new worker, on taking over, fetched again the shells of the languages the reader kept offline, then deleted the previous build’s caches. It silently swallowed a failed fetch and simply skipped a 404 or 500 answer. One file cut off during an update was enough: the old copy was gone and the new one was incomplete. Offline, instead of the axis, the reader got “This page is not available offline”. The 6-in-1 gave this item its own chat, the first in the report’s order.
The test first
The new tests in tests/browser/pwa.spec.ts play out a real update. Two tabs are open, Ukrainian and English, with both axes kept offline. Then a worker of another version appears at /sw.js, and while it fetches, one file of the shell does not arrive. There are three variants: the connection cut on the Ukrainian axis, a 404 on the English record text, a 500 on the axis chunk. So that a copy read offline tells which build it came from, the test marks the pages the new worker fetches. All three failed on the old worker. The cut gave the “not available offline” page. The 404 gave an axis whose record card never filled in. After the 500 the reader would have noticed nothing, but only because the chunk was still in the browser’s HTTP cache. The previous build had been replaced all the same.
What changed
The shells of the languages the reader kept offline are now fetched while the new worker installs, not after it takes over. Each language is kept only whole: every file is read to the end first, and only then do they go into the cache. If even one file does not come, by the network or by its answer’s status, the install fails. The previous build then stays with all its caches, and the browser tries again at its next update check. The cache a failed install created is deleted, so generations do not pile up. Once the network is back, the next update finishes and clears the old build away. A test checks that too. There is no forced page reload.
The tests found two more flaws, this time in the new code. First, the install failed on the first language that failed while the other was still fetching. That one managed to reopen the cache just deleted, and it stayed. Now the install waits for every language to finish. The second turned up in a separate test for a rare case: a page asks the old worker to keep a new language exactly while the update installs. After that, the old worker swept away “foreign” caches, and with them it wiped the cache of the new build still being installed. First I forbade sweeping to a worker with a newer one already installing, but CI showed that the browser reports this late: on a slow disk the old worker managed to wipe the new one’s cache. Now a cache’s age is given by the order in which caches were made, which the specification guarantees. A cache made after the worker’s own can only be a newer build’s, and it is left alone. The install also checks the cache at the end as it is now, so on CI this flaw cost a failed update rather than the offline copy. And if the new build never got that language, the old cache stays for it alone, until a page in that language takes the new build’s copy.
What is not checked
All of this is checked in Chromium through Playwright. Real Android and iOS, Safari and Firefox, storage quotas and cache eviction, and a very slow connection on which the install hangs rather than fails were not checked. Items R2 and R3 of the same report are about cached record text on an online page and about mixing builds. They go to the next chat.
Entry written September 27, 2026
Commits this entry accounts for
5235d02b3df3ba