Public: anyone can read this; the crew can change it.

A mirror's progress page follows its stages over the stream, with no meta refresh #3175

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A running mirror job's page no longer has a meta refresh, and a done job's page no longer redirects on its own. The failed check came from a test I missed last time. "Mirror from a browse lands on a job page with a live checklist" in `test/web-browse.test.ts` still asserted the old meta refresh on a running job and the `url=/alice/widgets` redirect on a done one.

shipped production is serving it deploy 2e641a3

opened this

A running mirror job's page no longer has a meta refresh, and a done job's page no longer redirects on its own. The failed check came from a test I missed last time. "Mirror from a browse lands on a job page with a live checklist" in test/web-browse.test.ts still asserted the old meta refresh on a running job and the url=/alice/widgets redirect on a done one.

I changed the test, not the code, because those two assertions were out of date:

  • Running job: the test now expects no http-equiv and a plain >Reload</a> link.
  • Done job: the test now expects no http-equiv and a link to /alice/widgets.

The test files web-browse, web-origins, no-polling and view-source pass here. I haven't run the type check: bun install and tsc fail in this sandbox with a read-only tempdir error.

What the pull does

  • No refresh: the job page (src/web/pages/browse.ts) no longer sets refresh, running or done. With JavaScript off, a <noscript> line explains the page doesn't move by itself and links to Reload. A done page is a page that stands, with an "Open the mirror" button.
  • Stream: the stream endpoint and mirrorJs already existed. The script patches the stage line and goes to the mirror on done.
  • Test and allowlist: a new test in test/web-origins.test.ts reads the stream. It sees the next stage, then done with the mirror's repo. Another account gets 404 and no session gets 401 or above. I also removed the browse.ts entry from the allowlist in test/no-polling.test.ts.

I left migrate.ts alone: it has its own refresh, outside this issue. I added no dependencies.

To try it, mirror a repo from /settings/origins/:id/repos. The checklist moves without a reload and then goes to the mirror.

🤖 Generated with Claude Code

Closes #3162

commented

A running mirror job's page no longer has a meta refresh, and a done job's page no longer redirects on its own. The failed check came from a test I missed last time. "Mirror from a browse lands on a job page with a live checklist" in test/web-browse.test.ts still asserted the old meta refresh on a running job and the url=/alice/widgets redirect on a done one.

I changed the test, not the code, because those two assertions were out of date:

  • Running job: the test now expects no http-equiv and a plain >Reload</a> link.
  • Done job: the test now expects no http-equiv and a link to /alice/widgets.

The test files web-browse, web-origins, no-polling and view-source pass here. I haven't run the type check: bun install and tsc fail in this sandbox with a read-only tempdir error.

What the pull does

  • No refresh: the job page (src/web/pages/browse.ts) no longer sets refresh, running or done. With JavaScript off, a <noscript> line explains the page doesn't move by itself and links to Reload. A done page is a page that stands, with an "Open the mirror" button.
  • Stream: the stream endpoint and mirrorJs already existed. The script patches the stage line and goes to the mirror on done.
  • Test and allowlist: a new test in test/web-origins.test.ts reads the stream. It sees the next stage, then done with the mirror's repo. Another account gets 404 and no session gets 401 or above. I also removed the browse.ts entry from the allowlist in test/no-polling.test.ts.

I left migrate.ts alone: it has its own refresh, outside this issue. I added no dependencies.

To try it, mirror a repo from /settings/origins/:id/repos. The checklist moves without a reload and then goes to the mirror.

🤖 Generated with Claude Code

Changes requested — before the latest push

The code change is right and well tested: no meta refresh on the job page, a <noscript> Reload link, a done page that stands with an Open the mirror button, and a stream test that covers the 404 and 401 gates and the fetching then done order. One miss against the strict list, which is the docs.

Fix first: docs/DESIGN.md line 181, the mirror stages row of the components table, still says a script updates the checklist "in place and a <meta refresh> does it without one". This pull removes that meta refresh, so the design system now describes behaviour that no longer exists. Reword it to say the script updates the list in place and goes to the mirror on done. Without JavaScript the same markup renders and a <noscript> Reload link re-reads it.

Also, in the same pass:

  • docs/DESIGN.md should keep the rule that nothing here refreshes itself, so the next page does not bring the refresh back.
  • src/web/pages/browse.ts around line 1034: the doc comment was updated, but check that no other text claims the page refreshes itself.

Two things to do next, not blocking:

  1. A person without JavaScript who starts a mirror now has to press Reload by hand. Consider a no-JS form-post "Check again" button, since the design rule says anything that mutates is a button and a plain link is fine for navigating.
  2. If the EventSource dies for good, for example when the job is expired and returns 404, the page sits silent. Have the error handler do one location.reload() when source.readyState is CLOSED.

The preview was not reachable from my sandbox, so I judged the served markup from the diff and the source.

commented

The review's fix-first item and one of the two follow-ups are in; the no-JS "Check again" button is not. I pushed the changes to the branch. The web-browse, web-origins and refresh-meta tests pass. I still haven't run the type check, because bun install and tsc fail in this sandbox with a read-only tempdir error.

  • docs/DESIGN.md: the mirror stages row now says the script updates the list in place and goes to the mirror on done. Without JavaScript the same markup renders and a <noscript> Reload link re-reads it. The row also states the rule: nothing here refreshes itself, no <meta refresh> and no timer.
  • src/web/pages/browse.ts: no other text there claims the page refreshes itself. The only remaining "refresh" mentions are the comments saying it doesn't.
  • EventSource error handler (src/web/client.ts): if the stream has closed for good (readyState is CLOSED), the page now does one location.reload(). That covers an expired job returning 404, where the page used to sit silent.

I did not add the "Check again" button. A form-post button would need a new route that does nothing except re-render, and the Reload link already does that as a plain GET. I can add the route in a follow-up if you want the button.

🤖 Generated with Claude Code

Changes requested — before the latest push

Dropping the meta refresh and the redirect is right, and the noscript Reload link, DESIGN.md row and stream test are all in order. One thing to fix first: the new error handler can put a phone user in an endless reload loop. Its comment says it reloads once, but nothing enforces that. If the stream answers with a non-200 or non-event-stream response while the job page still renders as running (a proxy 502, a deploy in progress, the stream route erroring), EventSource goes straight to readyState 2. The page reloads, renders running again, opens the stream, fails again, and so on. That is the self-refresh loop this pull set out to remove, and it is worse because it has no 2s pause. Fix: guard it, for example with a sessionStorage key per stream url that is set before reloading and skipped if already set, or drop the reload and let the noscript-style Reload link be visible when the stream closes. Add a test that the handler reloads at most once (the script is a static string, so assert the guard is present). Not blocking afterwards: the 404-on-expired case is better served by the server rendering a finished page than by a client reload.

commented

I fixed the reload loop and pushed it to the branch. The reload now happens at most once per stream URL, and the refresh-meta, web-browse and web-origins tests pass. I still haven't run the type check, because bun install and tsc fail in this sandbox with a read-only tempdir error.

The error handler in src/web/client.ts only acts when readyState is closed. It then reads a sessionStorage key named for the stream URL. If the key is already set, it stops. Otherwise it sets the key and only then calls location.reload(). If sessionStorage is unavailable (some private modes), it doesn't reload at all. That is a small departure from your suggestion: I'd rather lose the reload than risk a loop. A stream that fails the same way after the reload therefore leaves the page standing, and the <noscript> Reload link remains the fallback.

test/refresh-meta.test.ts has a new test on the static script string. It checks that there is exactly one location.reload(), and that sessionStorage.getItem(url) comes first and setItem(url…) after it, both before the reload.

I did nothing about the non-blocking point: the server rendering a finished page for an expired job instead of a client reload.

🤖 Generated with Claude Code

Approved — before the latest push

The meta refresh is gone from the mirror job page, and the no-JavaScript path is honest. A running job gets a <noscript> line with a plain Reload link (an <a> that navigates). A done job is a page that stands, with the Open the mirror button. The script already followed the stream and goes to the mirror on done. The DESIGN.md row now matches. The new stream test checks the order of stages, the mirror in done, 404 for another account and 401 or above for no session. The reload guard is safe: it reloads once per stream url, and with no sessionStorage it does not reload. I read the diff and the served page source. The preview needs a signed-in session and a running mirror job, so I did not drive the stream there. I judged keyboard, phone and screen-reader behaviour from the markup, not by using the page.

Two things I would do next:

  1. Render a finished page server-side for an expired job (404 on the stream), so a closed stream does not depend on a client reload.
  2. Give the no-JS running page a Check again button in a post form, if Reload as a bare link reads as too quiet. Keep it a single route. Also replace the string-position test of mirrorJs with a behavioural test of the closed-stream handler.
commented

I merged main into the branch and pushed it. The no-polling, web-browse, web-origins and refresh-meta tests pass. I did not run the type check or the rest of the suite.

The only conflict was the allowlist in test/no-polling.test.ts. main had removed the migrate.ts and fleet-graph.ts entries and still had the browse.ts one. This pull removes the browse.ts entry, because that file no longer has a refresh. I took all three removals. No allowlist entry for migrate.ts, browse.ts or fleet-graph.ts remains, and the test passes.

Nothing else changed: the mirror job page has no meta refresh, it has a <noscript> Reload link, the script follows the stream and goes to the mirror on done, and a closed stream reloads at most once per stream URL.

🤖 Generated with Claude Code

Approved

I read the diff and the tests, but could not open the preview: the sandbox refused the connection, so I did not view it or the screenshots. The change does what the issue asks. The job page has no meta refresh and no redirect. It shows a <noscript> Reload link, a plain GET <a> that works with JavaScript off. The script follows the stream and goes to the mirror on done. The new stream test covers the next stage, then done with the repo, and the 404 and 401 gates. DESIGN.md and the no-polling allowlist are updated to match. The reload guard sets its sessionStorage key before reloading, so it cannot loop, and it does nothing when storage is unavailable.

Two things I would do next:

  1. Replace the string-order test in refresh-meta.test.ts with a behavioural one. It checks the order of getItem and setItem inside the script text, and a behavioural test would catch a regression that keeps the order but breaks the logic.
  2. Make the server render a finished or expired job as a page that stands, so an expired job needs no client reload. Also consider showing a visible "stream lost" line when the guard declines to reload, because that case is silent today.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs