Public: anyone can read this; the crew can change it.
No served page polls: a test fails on a timer or meta refresh that reloads or refetches a page without the live stream #10929
Check · Review · Land · Live done — Landed and live in production.
`test/no-polling.test.ts` now renders the owner's main pages and fails if any of them polls, naming the page in the failure. I ran that file, `bun x tsc --noEmit` and `bun scripts/gate.ts`; all pass. I did not run the full suite.
shipped production is serving it deploy daaa1f6
test/no-polling.test.ts now renders the owner's main pages and fails if any of them polls, naming the page in the failure. I ran that file, bun x tsc --noEmit and bun scripts/gate.ts; all pass. I did not run the full suite.
What changed
- The test signs in as the owner and fetches
/,/system,/flow,/boards,/explore,/forgesand/changelog. - For each page it loads the markup plus every script file the page names, and expects a 200.
- It fails, as
<path>: <reason>, on any of these:
- a <meta http-equiv=refresh>;
- data-live-every;
- a setInterval that fetches or reloads on a page with no data-live or /events stream.
PAGE_EXEMPTis the place for a named, justified exemption, such as a legitimate countdown clock. It is empty because no page needed one.
What I found and left alone
- The source-level guard from #10817 already existed. I kept it and added the rendered check next to it, so the new test covers the "name the page" part of the issue.
/systemand/floware covered by the rendered test, and neither polls today./systemtakes its numbers fromsrc/system/live.ts, a server-side timer feeding the stream. Nothing in the rendered pages needed an exemption.- No page polled, so nothing was moved to the stream. That also means no before/after screenshots, since nothing visible changed.
/crewreturned 404 for the owner, so I left it off the list instead of letting the test pass on an error page.
To try it: bun test test/no-polling.test.ts.
The rendered-page guard is a good idea, but as written it cannot be trusted to fail, and it leaves two of the issue boxes unproven. Fixes, all in one round:
- Prove the detector fires: add a unit-level test that feeds the page check a fixture with a meta refresh, a setInterval+fetch with no stream, and data-live-every, and expects each to be named. Extract the per-page check into a pure function so the fixture and the real pages share it (the source guard above already does this with offenders()).
- Box 2: assert /system and /flow actually carry a live stream (data-live or /events) and no timer at all. Today they pass by absence only.
- The stream exemption is page-wide: any data-live anywhere lets a fetching setInterval through. Scope it to the script that owns the timer, or drop the exemption for these pages.
- /crew is dropped because it 404s for the owner; say in the test why, or fix the owner route, rather than silently narrowing the list.
- Tidy: remove the double blank line after OWNER_PAGES.
Box 3 is conditional and no page polled, so it is vacuous; I leave it open until the above shows the check is real.
The rendered-page polling check is now a pure function with fixture tests, and /system is asserted to stream. /flow has no stream, so I made it the one named exemption instead of building one. Tests, tsc and the gate pass; I didn't run the full suite.
- Detector proven.
pagePolls(path, {html, scripts})is the one check the fixtures and the real pages share. Fixtures name each case:
- a meta refresh;
- data-live-every;
- a setInterval with fetch or reload and no stream;
- a stream in a different script, which doesn't excuse the timer;
- a clock that only redraws text, which passes.
- Box 2,
/system. It must carry a live stream and nosetIntervalin any of its scripts. It does carry one:new EventSource('/system/stream')in its script. - Box 2,
/flow: stream not built./flowhas no stream at all. It is a form of valves that only the owner's own submit changes. It is held to no timer, and the test names it as the exemption from the stream requirement.
- Why not a stream: building one would be a feature the issue doesn't ask for, and a pushed patch could move a valve handle mid-drag. Say if you'd rather have it streamed.
- Exemption scoped. A fetching timer is excused only by a stream in the same script, not anywhere on the page.
/crew. It 404s for the owner, so the test now says why aboveOWNER_PAGES. I did not fix the route.- Tidy. The extra blank line is gone.
- Added. The real-page test also asserts the scripts were actually read, so a broken loader can't pass it silently.
Box 3 stays vacuous: no page polled, so nothing moved to the stream and there are no screenshots.
The detector is now a pure function with fixtures and /flow is a named exemption. Three gaps still let a real poll through, so the guard can pass while a page polls. Fix all three in one round.
- Only
<script src="/assets/...">is read (render). An inline<script>holding asetInterval(fetch…)is never scanned. A script tag with another attribute first (type="module" src=,defer src=) or a non-/assets src is skipped too. Scan inline script bodies, excludingapplication/jsonandld+jsonblocks, and matchsrcin any attribute position. Add a fixture for each. STREAM = /EventSource\s*\(|data-live\b/matchesdata-live-every, because\bfalls betweeneand-. A page withdata-live-everytherefore counts as streaming and excuses its timer. Usedata-live(?![-\w])and add a fixture.scripts.length > 0is asserted per page, but the check never asserts that/system'sEventSourcecame from a script actually fetched. It would also pass if/systemcarried the string only in markup. Say which you mean, or require the stream in the script for /system.
Box 3 is vacuous: no page polled, so nothing moved and there are no screenshots. I leave it open.
Tests are the only change, so no docs or TUI are needed. No dependencies were added.
test/no-polling.test.ts renders the owner's main pages and fails, naming the page, on a meta refresh, on data-live-every, or on a setInterval that fetches or reloads when that same script opens no live stream. The new tests, bun x tsc --noEmit and bun scripts/gate.ts pass; the full suite was not run here. The change is test-only, with no new dependencies.
How the check works:
- Checking a page:
pagePolls(path, {html, scripts})is a pure function. The real pages and the fixtures go through the same code, and each kind of poll has a fixture that expects it to be named. - Finding scripts:
scriptTags(html)finds every script a page runs:
- a src in any attribute position (data-src does not count);
- inline code bodies;
- not application/json or ld+json blocks, which are data.
Same-origin scripts are fetched and read. A script from another origin can't be read here, so the test fails and names it instead of skipping it.
- What counts as a stream:
EventSource(ordata-live(?![-\w]), sodata-live-everyno longer passes as a stream. A stream only excuses a timer in the same script; one elsewhere on the page doesn't. /system: it must open its stream in a script and carry no timer. It does:new EventSource('/system/stream'). A stream named only in the markup fails, and a fixture covers that./flow: it must carry no timer. It is the one named exemption from needing a stream: it is a form of valves that only the owner's own submit changes, and a pushed update could move a valve handle in the middle of a drag./crew: left off the list, with the reason in the test: it answers 404 for the owner.
No page polled, so nothing moved to the stream and there are no before/after screenshots.
To try it: bun test test/no-polling.test.ts.
🤖 Generated with Claude Code
All five earlier asks are met. (1) The pure pagePolls/scriptTags functions have fixtures for each kind of poll. (2) /system must open a stream in a script and carry no timer, and /flow carries no timer. /flow is the one named exemption from needing a stream, with its reason stated. (3) A stream now excuses only a timer in the same script, and data-live-every no longer counts as a stream. (4) The reason /crew is left off is stated above OWNER_PAGES. (5) The extra blank line is gone. The test is test-only, adds no dependency, and its real-page check also fails if no scripts were read. Next, non-blocking: 1) if /flow ever gains a pushed state, move it from the exemption into STREAM_PAGES; 2) fix the owner /crew 404 or open an issue so the page can join OWNER_PAGES.
test/no-polling.test.tsnow renders the owner's main pages and fails if any of them polls, naming the page in the failure. I ran that file,bun x tsc --noEmitandbun scripts/gate.ts; all pass. I did not run the full suite.What changed
/,/system,/flow,/boards,/explore,/forgesand/changelog.<path>: <reason>, on any of these:- a
<meta http-equiv=refresh>; -data-live-every; - asetIntervalthat fetches or reloads on a page with nodata-liveor/eventsstream.PAGE_EXEMPTis the place for a named, justified exemption, such as a legitimate countdown clock. It is empty because no page needed one.What I found and left alone
/systemand/floware covered by the rendered test, and neither polls today./systemtakes its numbers fromsrc/system/live.ts, a server-side timer feeding the stream. Nothing in the rendered pages needed an exemption./crewreturned 404 for the owner, so I left it off the list instead of letting the test pass on an error page.To try it:
bun test test/no-polling.test.ts.Closes #10921