What the address carries
The last two candidates from the PWA audit. Both concern a page's address, and both turned out to be real faults. The graph stopped following its address when opened with a broken fragment. And the sources page lost the chosen order when a reader switched language.
What was left
Among the unverified items of the PWA audit report of 26 September stood two more candidates, each described in a single line: the graph with a broken or empty fragment in its address, and the order of sources after a language switch. The 6-in-1 gave both to one chat on one condition: first a browser test that fails on the current code, and only then a fix. If the test does not fail, the candidate is closed.
The graph and its fragment
The /graph/ page keeps the open record in the address fragment, #evt-…, so the picture can be handed on as a link. The page’s script decodes the fragment with decodeURIComponent at load and on every change of it. That function throws on a percent-escape cut short, such as #%E0%A4%A or just #%. At load the error stopped the script exactly before the line that sets the fragment-change listener. The group buttons and the drawing were already working by then, so to the eye the page was whole. But it no longer opened a record when the fragment changed, and an unhandled error sat in the console.
The test walks seven fragments: two escapes cut short, an empty #, an id that does not exist, an id with a space, Cyrillic, and an unpublished record if the data holds one. For each it opens the page in a fresh document and checks that nothing is selected. Then it gives a good fragment and waits for the record to open. Last it gives the bad fragment again, now on the live page. On the old code the test failed on the very first fragment: the good fragment after it opened nothing. The other cases broke nothing even before the fix: decoding succeeds, and the page has no record with that id.
Now a fragment that cannot be decoded names no record, just like an unknown id. One thing stays as it was. If a record is open and the fragment changes to one that names nothing, the record stays open. That is how it should be, because the skip link at the top of every page also changes the fragment, to #content, and it must not close the record.
Sources and language
On the sources page the cards can be ordered alphabetically or by number of records, and the order lives in the address as ?order=records. The language switch on the index pages carries the path alone. That was deliberate, since the query meant nothing there. The sources page became the exception when it got its own order switch, and the language switch never learned of it. A reader who ranked the cards by records and moved to the other language got the alphabet.
The hypothesis in the task was a little different: that the language links copy the query once, at load, and so carry a stale one. That is what the language switch on a record page does, but the indexes do not turn it on, so on the sources page the links carried no query at all. That is why the reverse case was right even before the fix: the page opened with ?order=records, the reader went back to the alphabet, switched language and saw the alphabet.
The test presses “By records”, switches language and expects /en/sources/?order=records with a ranked list. Then it goes back to the alphabet and switches back. On the old code the address after the first switch was /en/sources/. Now the order script itself writes the order it shows onto the language links: at load and after every press.
Checks
Both new tests failed on f123bfa and pass after the fix. graph.spec.ts and atlas.spec.ts in full in Chromium gave 99 of 99. verify:code passed. Verify on main (run 36311268446) and the Vercel build are green. In production I opened /en/graph/ with #%E0%A4%A and changed the fragment to a record’s id: the record opened. On /uk/sources/?order=records the language links carried the order, and after pressing “A to Z” they no longer did.
The first version of the graph test loaded the page seven times with the drawing. On the CI runner that took 25 seconds, and on the next run (36311774702) 32, past the 30-second limit, and the test failed. No page fault lay behind it, only the cost of the test. It now loads the page without WebGL, the same way a browser without it does: the fragment is read the same with the drawing or without. In CI the test takes 2.3 seconds (run 36312364940), and on the old code it still fails for the same reason.
What was not verified
Checked in Chromium only; Safari, Firefox and mobile browsers were not checked. I did not look at how other browsers hand a broken escape to location.hash. The fix does not depend on it: it catches the error whatever arrives.
Entry written September 27, 2026
Commits this entry accounts for
962a2bd851a05d