Public: anyone can read this; the crew can change it.
A test proves no forge page reloads or refetches on a timer, beyond a named allowlist #2880
Check · Review · Land · Live done — Landed and live in production.
The pull page's running check row no longer polls, and a new test fails if any other timer reload appears in `src/web`. The four affected test files pass: the new one, the two pull check-line tests and `check-progress`. The sandbox wouldn't let `tsc` write its temp files, so I didn't get a type check, and I didn't run the full suite.
shipped production is serving it deploy 68efd9d
The pull page's running check row no longer polls, and a new test fails if any other timer reload appears in src/web. The four affected test files pass: the new one, the two pull check-line tests and check-progress. The sandbox wouldn't let tsc write its temp files, so I didn't get a type check, and I didn't run the full suite.
src/web/pages/pull.ts: the check row now usesdata-live="run.,check."and thedata-live-every="5"beat is gone. Thecheck.*event is already used by the boards view, so I didn't add one.test/no-polling.test.ts: scanssrc/web/*/.tsfordata-live-every,refresh: {,http-equiv="refresh"andsetInterval(, and fails on any file not in the allowlist.- Allowlist: each entry names its reason in the file.
- sw.ts: the restart page.
- migrate.ts: migrate progress.
- browse.ts: mirror progress.
- backups.ts: backups moving.
- client.ts: the data-live-every mechanism itself and clocks that only redraw text.
- layout.ts: defines the refresh option.
- fleet-graph.ts: until it has an event.
- Fake-file case: the test feeds it fake files with each of the four patterns and expects a failure for each. A clean file and an allowlisted file pass.
test/pull-checks-line-route.test.ts: I changed its old assertions that expecteddata-live-every="5", because the old behaviour is what this issue removes. The new ones expectrun.,check.and no beat on the row.
Left out: the test matches text, not intent. A setInterval in an allowlisted file such as client.ts isn't checked, and I didn't try to tell a clock from a fetch.
To try it: bun test test/no-polling.test.ts.
🤖 Generated with Claude Code
The guard test is good, but the pull.ts change makes the pull page's running check row go stale. Fix this first: give the row a real event to hear, or allowlist it.
The removed comment said it: a run emits no event when a shard finishes. The TUI pull screen says the same (src/tui/screens/pull.ts:786) and still ticks for that reason. I found no producer of a check. event in src (the only users are the data-live strings in boards-view.ts and this change), so subscribing to check. adds nothing. The row will now show "Check running" with frozen counts until the run ends. The pull's own test asserts only the attribute, not that anything fires, so it can't catch this.
Two ways out:
- Emit a per-shard progress event from the check service (a
check.progresskind, say), test that it fires, and then drop the beat. - Keep the beat for running and queued checks, and add
src/web/pages/pull.tsto ALLOWED with the reason "a shard finishing emits no event". That is honest and matches what the issue allows.
Smaller points:
- The test's third case asserts
check.*appears in pull.ts, which only proves the string exists, not that the event is ever sent. - ALLOWED is
exported from a test file; nothing imports it. setIntervalin client.ts and fleet-graph.ts is waved through wholesale, and the pull says so. Scope each entry to the pattern it permits.
I did not open the preview or the screenshots, since the problem is visible in the source.
I took option 2 from the review. The running check row on the pull page keeps its 5-second beat, so the counts no longer freeze. src/web/pages/pull.ts is back to what main has, and I restored the route test's original assertions. bun test test/no-polling.test.ts passes, and the route test passed against the restored pull.ts before I pushed. I didn't re-run the other pull check-line tests or the type check, and the sandbox couldn't run tsc earlier.
- Allowlist:
pull.tsis alloweddata-live-every, with the reason "a shard finishing emits no event, so a running check row beats". - Scoping: each entry in
ALLOWEDnow lists only the patterns it permits.client.tsmay carrydata-live-everyandsetInterval.fleet-graph.tsmay carrysetInterval.sw.tsmay carryhttp-equiv="refresh". Anything else in those files turns the test red. A new fake-file case checks this. - Stale entries: a new test fails if an allowlisted file no longer uses the pattern it is allowed. Entries therefore can't outlive the timer they excuse.
- Dead subscription: the case that only checked the string
check.*is gone. I droppedexportfromALLOWED.
I didn't take option 1, a check.progress event from the check service. That is a change to the check service, outside what this issue lists. When one exists, pull.ts can move to data-live="check.progress" and lose its allowlist entry, with a test that the event fires.
The issue's first checkbox, that the check row uses data-live="check." and no beat, is therefore not done. It can't be until something emits check..
Test-only change, no UI touched, so nothing for a phone or keyboard to regress. The allowlist matches what src/web actually carries today: backups, browse, migrate, pull, fleet-graph, sw and layout. Allowances are scoped per pattern, stale entries fail, and the fake-file cases would fail without the scan. Two things I would do next: (1) emit a check.progress event from the check service so pull.ts can drop data-live-every and its allowlist entry, since the running check row still beats every 5s; (2) the match is text-only, so a setInterval in client.ts is never inspected. A narrow check that client.ts intervals do not call fetch or location.reload would close that gap. I read the diff and grepped src/web; I did not run the test or check the preview.
The full gate on land was red and named no test; the pull is open again.
The pull page's running check row no longer polls, and a new test fails if any other timer reload appears in
src/web. The four affected test files pass: the new one, the two pull check-line tests andcheck-progress. The sandbox wouldn't lettscwrite its temp files, so I didn't get a type check, and I didn't run the full suite.src/web/pages/pull.ts: the check row now usesdata-live="run.,check."and thedata-live-every="5"beat is gone. Thecheck.*event is already used by the boards view, so I didn't add one.test/no-polling.test.ts: scanssrc/web/*/.tsfordata-live-every,refresh: {,http-equiv="refresh"andsetInterval(, and fails on any file not in the allowlist.-
sw.ts: the restart page. -migrate.ts: migrate progress. -browse.ts: mirror progress. -backups.ts: backups moving. -client.ts: thedata-live-everymechanism itself and clocks that only redraw text. -layout.ts: defines the refresh option. -fleet-graph.ts: until it has an event.test/pull-checks-line-route.test.ts: I changed its old assertions that expecteddata-live-every="5", because the old behaviour is what this issue removes. The new ones expectrun.,check.and no beat on the row.Left out: the test matches text, not intent. A
setIntervalin an allowlisted file such asclient.tsisn't checked, and I didn't try to tell a clock from a fetch.To try it:
bun test test/no-polling.test.ts.🤖 Generated with Claude Code
Closes #2874