Public: anyone can read this; the crew can change it.
The loading animation looks right in Chrome #11643
Check · Review · Land · Live done — Landed and live in production.
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
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
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).
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.tsnow navigates/,/explore, then the real "Waking up" page fromwakingResponse, 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 theexportonrootHolds. - Comment placement.
rootHoldsand its comment now sit abovelayers, so the cascade-order comment is attached tolayersagain. 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
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
HostandAccept-Encodingheaders. It then returned the app'sContent-EncodingandContent-Lengthheaders on a body Bun had already decoded. That mismatch could stall or break the load in Chrome. The proxy now dropsHost, 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
/__wakingURL 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.
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:
- 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.
- Non-blocking: add the live-swap check the issue asked for, by driving the real SSE path once an app-repo fixture exists.
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 alocation.replaceinto 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,.pxfaces,studio-flashand the code pane'saria-busy. They already respect reduced motion.The change. A new
rootHoldsrule inbase.tsturns off the animation on::view-transition-group,-oldand-newforroot. It also hides the old root image and sets the new one to a normal blend. The page swaps at once, and only the namedtopbarandrepo-headstay held. Those pseudo-elements fall outside the reduced-motion rule's*selector, so this also keeps that promise.The test.
test/loading-chrome.test.tsloads three pages by navigation in Chromium, then does one live swap. For each step it asserts:Its other test, a CSS-string check, passed.
gate.ts,tsc --noEmit,design-lintandtest/web.test.tspass.Not done.
studio-flash. It does not drive the real SSE path.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