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

The loading animation looks right in Chrome #11643

Merged opened this in answer to

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

Merged

Chrome no longer cross-fades the whole page on navigation. This is my best reading of "super weird on chrome", and I could not confirm it in Chrome. There was no before/after capture, and the Chromium test below was skipped in my sandbox, so the owner needs to look at the preview in Chrome before this lands.

shipped production is serving it deploy e1bbdcb

opened this

Chrome no longer cross-fades the whole page on navigation. This is my best reading of "super weird on chrome", and I could not confirm it in Chrome. There was no before/after capture, and the Chromium test below was skipped in my sandbox, so the owner needs to look at the preview in Chrome before this lands.

What was wrong. @view-transition { navigation: auto } (src/web/style/base.ts) was there to hold the topbar and repo header still. Chrome's default also fades the whole page from the old document to the new one over 250 ms, using a plus-lighter blend. That fade runs on every navigation. It is worst on pages that reload themselves: "Waking up" refreshes every 3 s, and "Setting up…" does a location.replace into the forge. Those pages pulsed, a second loading indicator on top of the page's own. Firefox has no cross-document transitions, so it never showed this. I did not find a fault in the other loading states I read: the spinner, .px faces, studio-flash and the code pane's aria-busy. They already respect reduced motion.

The change. A new rootHolds rule in base.ts turns off the animation on ::view-transition-group, -old and -new for root. It also hides the old root image and sets the new one to a normal blend. The page swaps at once, and only the named topbar and repo-head stay held. Those pseudo-elements fall outside the reduced-motion rule's * selector, so this also keeps that promise.

The test. test/loading-chrome.test.ts loads three pages by navigation in Chromium, then does one live swap. For each step it asserts:

  • no running root view-transition animations;
  • at most one indicator at a time;
  • no indicator left once the page settles;
  • layout shift of at most 0.1.

Its other test, a CSS-string check, passed. gate.ts, tsc --noEmit, design-lint and test/web.test.ts pass.

Not done.

  • Browser test not run: headless Chromium does not start in this sandbox, so the forge's check will be its first real run.
  • Live swap is simulated: the test replaces a node in place and applies studio-flash. It does not drive the real SSE path.
  • Other loading states untouched: I did not change the Safari or Firefox behaviour, the skeletons or the spinners.

To try it. Open the preview in Chrome and move between pages. Open a sleeping forge's address for "Waking up", and start a forge for "Setting up…". Check that the page swaps with no fade, and that the topbar stays still.

The push needed a "Try:" commit, so one empty commit carries it.

🤖 Generated with Claude Code

Closes #11624

commented

Chrome no longer cross-fades the whole page on navigation. This is my best reading of "super weird on chrome", and I could not confirm it in Chrome. There was no before/after capture, and the Chromium test below was skipped in my sandbox, so the owner needs to look at the preview in Chrome before this lands.

What was wrong. @view-transition { navigation: auto } (src/web/style/base.ts) was there to hold the topbar and repo header still. Chrome's default also fades the whole page from the old document to the new one over 250 ms, using a plus-lighter blend. That fade runs on every navigation. It is worst on pages that reload themselves: "Waking up" refreshes every 3 s, and "Setting up…" does a location.replace into the forge. Those pages pulsed, a second loading indicator on top of the page's own. Firefox has no cross-document transitions, so it never showed this. I did not find a fault in the other loading states I read: the spinner, .px faces, studio-flash and the code pane's aria-busy. They already respect reduced motion.

The change. A new rootHolds rule in base.ts turns off the animation on ::view-transition-group, -old and -new for root. It also hides the old root image and sets the new one to a normal blend. The page swaps at once, and only the named topbar and repo-head stay held. Those pseudo-elements fall outside the reduced-motion rule's * selector, so this also keeps that promise.

The test. test/loading-chrome.test.ts loads three pages by navigation in Chromium, then does one live swap. For each step it asserts:

  • no running root view-transition animations;
  • at most one indicator at a time;
  • no indicator left once the page settles;
  • layout shift of at most 0.1.

Its other test, a CSS-string check, passed. gate.ts, tsc --noEmit, design-lint and test/web.test.ts pass.

Not done.

  • Browser test not run: headless Chromium does not start in this sandbox, so the forge's check will be its first real run.
  • Live swap is simulated: the test replaces a node in place and applies studio-flash. It does not drive the real SSE path.
  • Other loading states untouched: I did not change the Safari or Firefox behaviour, the skeletons or the spinners.

To try it. Open the preview in Chrome and move between pages. Open a sleeping forge's address for "Waking up", and start a forge for "Setting up…". Check that the page swaps with no fade, and that the topbar stays still.

The push needed a "Try:" commit, so one empty commit carries it.

🤖 Generated with Claude Code

Changes requested — before the latest push

The CSS fix is small and plausible, but it is unverified and the test does not exercise the pages the issue is about. Fix first: make the Chromium test cover the real failing page (the "Waking up" self-reload) and show it running, or give a before/after capture. Right now the author says Chrome behaviour was never seen and the only browser test was skipped, so nothing shows the change helps. Also: (1) keep the doc comment for layers attached to layers; the new block was inserted between it and its export. (2) The CSS-string test only restates the source; drop it or replace it with the browser assertion. (3) No design finding beyond this: no new tokens, accent or components, and nothing is added to src/web markup; JavaScript-off behaviour is unchanged. No README or tui change is needed (no new feature).

commented

Pushed a revision that answers the three review points, but the Chromium test has still not run, so I have no before/after capture. Headless Chromium does not start in my sandbox, so the test skips here. The first green run, or the first failure, will be in the forge's check log.

What changed

  • Test covers the reported page. test/loading-chrome.test.ts now navigates /, /explore, then the real "Waking up" page from wakingResponse, and then watches that page for 4.5 s so its own 3 s refresh happens.

- How it's served: a small front server serves /__waking and forwards everything else to the app, so the page gets the app's stylesheet. - What each page must show: no running root view-transition animations, at most one indicator at a time, none left once the page settles, and layout shift of at most 0.1. - Expected before the fix: the home to Waking-up navigation should show root animations without rootHolds. - Uncertain: I'm not sure Chrome treats a Refresh-header reload as transition-eligible. If it doesn't, the step in the test is harmless, and the navigation into the page is what carries the proof.

  • CSS-string test dropped. The test that only restated the source, including the expect(layers).toContain(rootHolds) tautology, is gone, along with the export on rootHolds.
  • Comment placement. rootHolds and its comment now sit above layers, so the cascade-order comment is attached to layers again. I also cut the repeated sentence about the fade.
  • Live swap dropped. I did not drive a real swap. It needs an app repo and SSE events, and the only swap that moves a card is the 1.1 s background flash of studio-flash, which can't repeat. The hand-built replacement is gone, and the test header says so. This leaves the issue's "one live swap" acceptance item unmet, which I'm flagging rather than faking.

tsc --noEmit and gate.ts pass. The owner still needs to look at the preview in Chrome before this lands.

🤖 Generated with Claude Code

commented

I pushed a fix for the failing Chromium test, but I haven't confirmed the cause or that the test now passes. Headless Chromium won't start in my sandbox, and the check log you sent only showed the test failing after about 10.4 s, with no assertion message.

That duration matches the test's 10 s wait for a page load, so I suspect the page never finished loading. Two things in the test's setup could cause that, and I changed both:

  • Proxy fix. The small front server that forwards pages to the app also forwarded the browser's Host and Accept-Encoding headers. It then returned the app's Content-Encoding and Content-Length headers on a body Bun had already decoded. That mismatch could stall or break the load in Chrome. The proxy now drops Host, asks the app for uncompressed pages, and strips the encoding and length headers from the reply.
  • Distinct refresh URL. The last step re-navigated to the same /__waking URL as the step before it. It now uses /__waking?again=1, so Chrome can't treat it as a reload that fires no new load event.

tsc --noEmit passes. The next check run is the first real test of this change. If it fails again, the log should say which page and which assertion.

the crew's run was interrupted: restart, resumes on the next pass — cancelled
Approved

Approved. The earlier asks are met: the test now covers the real Waking-up page and its own refresh, the CSS-string tautology is gone, the comment sits with the rule, and the unmet live-swap item is flagged rather than faked. The preview serves the rootHolds rules in its stylesheet: the root group and old/new images get no animation, and the old image is hidden. Topbar and repo-head transitions are kept. No colour, border or markup was added, so docs/DESIGN.md principles 1-8 are not touched. The change is CSS-only, with no new dependency, and works with JavaScript off. The Chromium test skips where no Chromium runs, and says so.

Two things I would do next:

  1. Non-blocking: have someone look at the preview in real Chrome (home to Waking up) to confirm the fade is gone. Nobody has watched it in a browser yet, only the test has.
  2. Non-blocking: add the live-swap check the issue asked for, by driving the real SSE path once an app-repo fixture exists.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs