Three lines of “not verified”
The journal entries on the PWA audit ended with lines of “not verified”. Three of them now have tests. An install that stalled held on for five minutes and left half a cache behind: the worker now waits at most 60 seconds for a file, as the 6-in-1 chose. An old worker that does not mark its copies, and a full storage, already behaved as they should.
Where these three come from
After the fixes for R1 and R2/R3 from the PWA audit, I ended both entries in the autonomy journal with a line of “Not verified”. Part of what those lines list needs a person with a real phone. Three items could be checked in Chromium: an install that stalls, the exact R2 scenario with the old worker still in charge, and the storage quota. The 6-in-1 made them the next step of the plan to open the site to search engines, right after issue #31, since the tests live in the same file.
The rule is the same: a test of the contract in the README section on offline use first. If it fails on the current code, that is a fault, and the fix comes with the test. I ran the tests that swap dist/sw.js in a separate copy of the repository, so that a parallel rebuild of the site could not touch them.
An install that stalls
A new build installs its worker, and one file of the shell does not come: the connection is not cut and gets no error, it just stands still. First I measured what Chromium does then. The old worker stays in charge, and the axis opens offline from its copy: that part is fine. But the install hangs for exactly five minutes, until the browser stops the worker itself. All that time the next update checks wait in a queue. And a worker stopped that way never gets to clean up, so half of the new build’s cache stays behind until the next update that succeeds.
How long to wait was a choice, so I asked. The 6-in-1 chose a limit of our own: 60 seconds for each file of the shell, to its last byte. That is enough for a whole shell to arrive even over a slow mobile connection. An install that does not make it fails the way it fails on a 404: the old build stays with all its caches, the new one’s cache is deleted, and the browser tries again at its next check. The same limit now applies where a page asks the worker to keep a language’s shell, so a stalled connection no longer holds that queue either. Browsers without AbortSignal.timeout wait as before.
The test holds a file without an answer and watches the install from a separate tab. While it hangs, the old build is in charge and the axis opens offline. Then the attempt ends by itself, long before the browser’s five minutes, and leaves no cache of the new build. As soon as the file answers again, the new build takes over. On the old code the test failed: after 90 seconds the install was still hanging. The worker grew by 46 bytes, to 4,005 of the 4,096 in its budget.
The old worker in charge
The audit’s R2 scenario in its exact form: the page is already new and came from the network, while the worker is still the previous build’s. It does not mark copies of the reading text as copies, and its cache holds the previous version’s text. The network does not answer for the reading text, so the worker hands over its unmarked copy twice. By the code, the card should reach “Open the page again”, because the axis catches the version mismatch itself, not the worker. A test now confirms it: the other build’s text does not reach the card, there is no line about a copy, the link leads to the same axis and the same record, and once the page is reopened with the network back, the card fills with the current text.
The first run of this test failed, but the fault was the test’s. I changed the version in the cache after the old worker had stored it, and its late write managed to put the current text back. Now the “network” itself hands out the old version for the whole setup, so whatever the worker writes is that version. Three runs in a row passed.
A full storage
The test changes the worker’s writes to the cache so that they throw QuotaExceededError, as on a device with no room left. It turns on as soon as a cache with a certain name exists on the site, so it survives a restart of the worker too. With the storage full, a record page, the axis in the other language and the star all work from the network. Nothing is kept, and the worker stays the same one and keeps answering. Once there is room again, the next page is kept by that same worker. If there is no room on the very first visit, the install fails, no cache is left, and the site works as it does without a worker.
A private window was checked separately, in two forms: registering the worker is refused, or the browser has no worker at all, and in both localStorage throws on every access. The axis opens, the card fills, the star can be pressed, and no error reaches the reader. All of these tests passed on the old code: there was no fault here, and now tests hold it.
What is not verified
All of this is Chromium through Playwright: pwa.spec.ts three times in a row, and verify:code. GitHub Actions minutes are used up until 1 October, so CI did not see this pass. Real Android and iOS devices, Safari and Firefox are not checked, including how they themselves handle a stalled install. The pages the worker keeps along with saved records have no 60-second limit; only the browser’s own limit applies there.
Entry written September 27, 2026
Commits this entry accounts for
3de5b54