The adversarial review confirmed three defects in this PR, all reproduced in
the browser.
1. Superseded graphs queued up in front of routes. The worker answers
messages one at a time and cannot drop one it has started. With the
jump-link layer on, every pause on the range slider posted a full graph
build, seconds of work at 6-8 pc. Answers no longer wanted were thrown
away only once built. A route asked for afterwards waited behind every
one of them: a one-jump route took 44 s.
RoutingClient now holds requests and sends them one at a time. While one
is out, only the latest of each kind waits: a newer graph replaces an
older one before it is ever built, and the older promise is rejected with
SupersededRequest. Routes go ahead of graphs. The same question asked
again while outstanding shares the answer rather than being worked twice,
as when the layer is turned off and on during a build.
The same scenario in the browser (layer on, range stepped 5 -> 8 pc with
400 ms pauses, then Sol to Proxima): the route came back in 110 ms. The
worker was sent "links 3, links 5, route, links 8"; 6 and 7 were never
built.
2. A worker that failed left the panel stuck. With no error handling, a
worker that failed to load (a 404 on its chunk after a redeploy) or
threw left "Plotting…" and a disabled button for good, and a graph at a
range could not be asked for again.
The worker now answers an exception with a 'failed' message, which
rejects that request. A worker that fails to load or dies is abandoned,
and what it left outstanding, and everything asked afterwards, is
answered in place. The scene releases the panel when a route fails, and
forgets a graph range that was never drawn so it can be asked for again.
3. Nothing type-checked the worker. The application builder never reads
webWorkerTsConfig, and bundles the worker with esbuild, which strips
types without checking them. tsconfig.app.json leaves the file out. A
type error in the worker shipped.
`npm run worker:typecheck` (tsc -p tsconfig.worker.json) now runs in CI
beside the other project checks. webWorkerTsConfig is removed from
angular.json, since it only suggested that something checked the worker.
Tests with a fake worker cover one request at a time, a waiting graph
replaced and a route sent ahead of it, a question shared, a failure rejected
and the next request sent, and a failed worker's requests answered in place.
A scene test covers the panel released after a failed route. Negative
controls, each caught: several requests sent at once, a waiting graph kept,
graphs ahead of routes, a question asked twice, a failure answered as a
success, a failed worker waited on, the panel left pending, and a type error
in the worker (caught by worker:typecheck).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
Mondays 05:23 UTC: re-run the ETL cold against the live archives, and if the
output differs by a byte, gate it on the unit suite and a production build,
commit it to main, and dispatch the Pages deploy. If nothing changed, say so
in the run summary and touch nothing.
The gates run inside this workflow because they cannot run after it: a push
made with GITHUB_TOKEN fires no push workflows at all — GitHub's recursion
guard — so an unguarded push would deploy nothing and be checked by nothing.
The same guard is why the deploy and a visible CI record are dispatched
explicitly afterwards; dispatch events do go through where push events do
not. ci.yml gains a workflow_dispatch trigger for exactly that call.
Also corrects pages.yml's claim that configure-pages enables Pages on first
run. It cannot: the action's `enablement` input requires an admin-scoped
token, which GITHUB_TOKEN is not. If the site has never been enabled, the
first deploy fails at that step and the one-time fix is Settings → Pages →
Source: GitHub Actions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
`develop` became the integration branch when #3 merged into it, but CI's push
trigger still named only `main` — so the merge commit itself ran nothing. A
pull request is checked before the merge, not after, which leaves the state of
the branch people actually build from unverified whenever two green pull
requests conflict semantically.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
Nothing has ever checked this project automatically. There are no workflows,
so the pull request's green tick means only that the checks were run by hand
on one machine, and nothing would catch a regression pushed later.
The lockfile had to come first. `npm ci` is the only install that guarantees
CI tests the dependency tree that is actually committed, and it refuses to
run without package-lock.json — which was gitignored. Un-ignoring it also
pins the 617 packages this was built and verified against; without it, a
transitive release could change what CI runs from one day to the next with
no commit to point at. Checked before committing: every entry resolves to
registry.npmjs.org, and it carries no credentials.
Two jobs rather than one, run in parallel. The typecheck/unit/build job is
fast and deterministic; the end-to-end job drives a real headless browser
through WebGL2 software rendering and is the one that will be slow and, if
anything here is going to be flaky, flaky. Keeping them apart means a
browser timeout cannot hide a failing unit test behind it.
Between the four steps, all four TypeScript projects are compiled: the ETL
and end-to-end configs explicitly, since nothing else ever builds them, and
the spec and app configs by `ng test` and `ng build` respectively.
One thing this cannot verify from here: the runner installs Chromium to
match the pinned Playwright, where this container ships an older build. The
suite was run locally against that older browser instead, and passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G