All entries

Five scenarios the audit did not check

The PWA audit of 26 September marked five scenarios as not assessed. Each now has a test. Two found real faults: the 501st star silently dropped the first, and the page of a first visit did not open offline. A third showed a choice, and the 6-in-1 made it: when the network hangs and a copy exists, the reader sees the copy after 10 seconds.

What the audit left

Besides the faults R1–R5, the PWA audit report of 26 September listed what it had not checked at all. Five of those items can be closed without a person, by automated tests. The 6-in-1 gathered them in issue #31 and made them the first step of the plan to open the site to search engines: before the site starts being found, offline should behave the way the About page promises.

The rule was the one used for R1: a test of the contract first, and a fix only if it fails on the current code. Of the five tests, two failed on real faults, one showed a choice of behaviour, and two passed at once.

Headers in production

A live request to https://www.aitimeline.dev on 27 September. Files under /_astro/ come with public, max-age=31536000, immutable and the right type (application/javascript, text/css). Stills under /media/ come with max-age=604800 and image/jpeg. Both classes carry every security header: CSP, nosniff, Referrer-Policy, COOP, Permissions-Policy and HSTS. The test of the vercel.json rules now checks each class of file through the same matcher the local Preview answers by, so a rule that goes missing, or starts catching more than it should, stops the build.

The live request showed one thing outside the contract. A 404 under /_astro/ also gets immutable for a year. Vercel cannot limit a rule to a response status. The risk is narrow: a browser that once saw a file missing will not believe a later build that brings back a file of the same name. I recorded it as a residual risk and changed nothing.

Cache limits

The worker keeps 60 recently opened pages. The test fills the cache to that limit from the page rather than opening 60 pages. A page opened again becomes the newest, the next one pushes out the oldest, and a saved record opened online refreshes its own copy and is not counted among the sixty. All of this worked before the test.

The stars did not. Their cap was 500, and the comment beside it promised it was “far above the size of the collection”. The collection had meanwhile grown to 885 records. A reader who saved a 501st would silently have lost the first, with its offline copy. The cap is now 10,000, and a test holds it at least twice the number of published records, so next time it is raised long before anyone could reach it.

The kill switch

Until now the kill switch test replaced only dist/sw.js, in one tab. Now the test builds the whole site with the worker off, into a temporary folder. A Vite plugin turns the flag, and switch.ts stays as it is, so an interrupted run breaks nothing. The test then serves, on the same origin, first the normal build and then the one with the worker off, to two open tabs. Both are let go, the atlas-* caches disappear, and no registration is left. Opened again, the pages register no worker and the axis reads from the network. Saved stars in localStorage stay, since they are not a cache.

Intercepting the request through Playwright did not work here: the browser checks /sw.js for an update past the interception. So the test runs a server of its own and swaps one build for the other on the same port, as a deployment does. It does not change dist/, and a chat rebuilding the site at the same time cannot break it. The kill switch passed at once.

A first visit straight to a record

The audit checked offline only after starting from the axis. The test arrives on a record’s page as a first visit, waits for the worker and turns the network off. On the old code the axis opened offline, but the record itself showed “This page is not available offline”. The About page promises otherwise: without a network, pages you opened recently open. That is a fault. The page of a first visit loads before the worker exists, so the worker never saw it.

Now such a page names its own address to the worker, in the same message that carries the language and the saved records. The worker keeps it together with its files. At first the test missed the files: the browser’s HTTP cache served them, and that cache can be cleared at any time. So the test clears the HTTP cache before going offline, and then two of the page’s scripts failed. The worker now keeps those too.

A network that hangs

The test holds the worker’s request without an answer, without dropping it. On the old code the page waited more than 150 seconds, although a dated copy sat in the cache. The axis card waited 20 seconds, asked again for another 20, and only then said the text had not loaded. The reader never saw the copy.

How long to wait is a choice, not a fault, so I asked. The 6-in-1 chose this: if the network has not answered within 10 seconds and a copy exists, the worker serves the copy. The page gets the line “The network is not answering: this is a copy saved on …”, not “You are offline”, since the reader may be online. The axis card shows its existing line about a copy of the text. The request is not cancelled: an answer that comes later replaces the copy for the next visit. Without a copy the worker waits for the network as long as it takes and shows no placeholder. Ten seconds is under the 20 the axis waits by itself, so the card fills from the copy in time.

What is not verified

Everything was checked in 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 has not seen this pass. Real Android and iOS, Safari and Firefox are not checked. The case of a hanging network with no copy is checked only by the code: a test would have to wait a fixed 10 seconds, and the PWA tests have no such waits by rule.

Entry written September 27, 2026

Commits this entry accounts for

  • a63850f