cmux NEXT Inspector: running subagents shimmer in one wave instead of pulsing out of step
Summary
Leo, on the Inspector's Subagents avatars: "these glimmer at different intervals ... it would look cool if they went in a wave, like how thinking glimmers in ChatGPT."
Before this, each running avatar ran its own acpmux-chat-inspector-breathe loop (2.4 s). Each loop started whenever its avatar mounted, so the avatars pulsed out of step. Now there is one shared shimmer that sweeps across the running avatars from left to right:
- Shared primitive
webviews/src/ui/wave.ts+ui/wave.css, loaded through the desktop layer that every page imports:waveIndices(items, live)gives running items consecutive indices in display order. Finished and failed items get none.waveProps(index)returns theacpmux-wave-itemclass and--wave-index.installWave(doc)starts everycmux-waveloop on one epoch: Unix time mod the period, the same base as the spinner clock'sspinnerStartTime. Items that mount at different moments still line up, so only their index offsets them. It also sets<html data-cmux-page-hidden>whilevisibilityStateis hidden.
- Look: a 1.6 s loop, 120 ms step per index, opacity only. Each item rests at 0.55 and brightens to 1 as the crest passes. The delay is pulled back whole periods so it is never positive, which means no item sits waiting at full strength.
- Gates: the wave runs only under
prefers-reduced-motion: no-preference. It stops whendata-cmux-motionisofforreduced(the WebTheme motion gate), whendata-status-indicators="static"is set, and when the page is hidden. A stopped avatar shows at full strength. - Applied to: the Inspector's avatar row (
SubagentsSection.tsx, throughAvatar's newwaveprop and the started-chat avatars). The old per-avatar breathe is gone. The summary's subagent dots stay still as before, so this adds no new pulsing dot (see #23870). - Other rows can join: any running row joins the wave by setting
acpmux-wave-itemand--wave-indexon its indicator. #23916 already does this on itsAgentLineicons. - Overlap with #23916: #23916 replaces this avatar row with named lines, so the two PRs conflict in
SubagentsSection.tsxandinspector.css. Whichever lands second takes #23916's side of those two files and keepsui/wave.*. #23916's running lines then join the wave as they are. - Overlap with #23870: none. This diff stays away from
desktop.css,spinnerClock.tsandstatic-status-loops.test.ts.
No capture: webview-only (Linux gallery / Playwright video below).
Video: Playwright recordings (Chromium, 9.6 s, dark) of the Inspector gallery entry agent-pane.chat-inspector / full, with 5 running and 3 ended avatars, served by bun run gallery:dev on huge-blue. The files are in Leo's lane folder:
- after (head):
leo-workspace/briefs/2026-10-20-gui-push/lanes/session-panel/wave/after-dark.webm - before (base):
.../wave/before-dark.webm - still:
.../wave/after-still-dark.png
Measured in the page at the head:
- All 5 running avatars share one
startTime. - Their
animation-delays are -32000, -31880, -31760, -31640 and -31520 ms, so one crest crosses left to right at 120 ms per avatar. - The 3 ended avatars run no animation and stay at opacity 1.
Testing
bun test test/running-wave.test.tsx checks:
- running items get consecutive indices, and done or failed items get none;
alignWavegives loops that start at different moments one shared start;- the rendered
SubagentsSectionmarkup; - the CSS gates: Reduce Motion,
data-cmux-motionoff or reduced, static indicators, hidden page; - the keyframes animate opacity only.
CMUX_BROWSER_TESTS=1 bun test test/running-wave-loops.test.ts runs in real Chromium and checks:
- items 2 and 3 indices apart get delays 240 and 360 ms apart, all negative;
- every start sits on the Unix epoch (
(start + timeOrigin) % 1600 ā 0), including the start of an item that mounts 337 ms later. WithinstallWavedisabled, this test fails; - the WebTheme bootstrap's
motion: off,motion: reducedandstatusIndicators: staticeach stop the wave, and so does Reduce Motion; - a
visibilitychangeto hidden stops it, and becoming visible again restarts it on the epoch.
Also:
test/static-status-loops.test.ts,test/spinner-clock.test.tsandtest/chat-inspector.test.tsxpass unchanged.scripts/web-lint.shandpython3 scripts/cmux-next/prepush-gate.pypass.cmux-ci compile-check --apppasses.
Observed / Not run
- Observed: the commands above, run on huge-blue (Linux, Chromium).
- Not run: I did not view the change in the native app. It is webview-only, and the video is from Chromium.
Changelog
Changed: running subagents' avatars in the Inspector shimmer together in one wave instead of pulsing out of step.
š¤ Generated with Claude Code
Agent session: cosmic-gray