cmux NEXT: agent work breathes a calm accent dot instead of a spinner on every row

Merged
TE
16d ago
+905-50

Leo (2026-10-20), on a list of ~17 running sessions with a grey spinner on each row: "Can you think of anything better than just having these spinners? After a certain point, isn't this mildly ugly? What are the alternatives?"

Part 2 of the activity-indicators lane. Part 1, the frozen transcript spinners, was #22786 (merged).

What changes

  • Agent work defaults to a breathing dot. It's a small dot in the theme accent (Palette.accentFill: the text color, or the hue appearance.accent names; never Claude orange) that breathes once every 2.4 s. It runs on the shared clock, so a list of working rows breathes as one instead of 17 rings turning.
    • Native: every status indicator that draws work (sidebar rows and group headers, the switcher, tabs and tab badges, hover cards).
    • Agent pane: the running transcript rows (compaction, tool groups, MCP calls, message cards, command and shell rows).
  • Loading keeps its spinner (a page, a command, a harness check), so loading and agent work still read apart.
  • Reduce Motion and the static look: nothing moves. Native keeps its existing still glyphs; on web the dot holds at full strength. An occluded or off-screen row's dot also stays still and stays a dot.
  • Debug Settings → Status Indicators → "Working look" (status.working) keeps the alternatives:
    • Shimmer: a still dot, and on web pages a light band crosses the running row's words.
    • Spinner: the old turning arc.
    • Web pages get the choice as data-working-look, through both WebTheme and the agent pane theme.
  • New Motion loop breath (2.4 s, Debug-tunable, never under 1 s). Web: cmux-breathe and cmux-shimmer keyframes in desktop.css, kept in phase by ui/spinnerClock.ts.

The options (Linux gallery, agent-pane.working-look, dark)

These are the agent pane's running rows; the "Compacting the conversation…" row carries the working mark. The native sidebar, switcher and tabs draw the same plan with the native indicator layer; there's no native capture here (no Mac build on this host).

Breathing Dot (default), brightBreathing Dot, dim (1.2 s later)
breathe brightbreathe dim
Shimmer (still dot; a light band crosses the words)Spinner (before)
shimmerspinner

Reduce Motion / Static: the dot holds at full strength.

static

Why Breathing Dot is the default: one small accent dot per row, breathing slowly and in step, reads as calm "still working" even across 17 rows. Shimmer moves more of the screen (every running row's words), and a spinner on each row is the noise Leo pointed at. Shimmer is a good fit for a single row (it's what "Thinking" already does), so it stays as an option.

Changelog

Changed: A working agent shows a calm breathing dot in your accent color instead of a spinner; Debug Settings keeps Shimmer and Spinner.

Tests

  • StatusWorkingLookTests (CmuxNextDesign): breathe is the default; each look's plan; a still or hidden row keeps the dot; loading and known progress keep their marks; the breath is slower than the waiting pulse.
  • test/static-status-loops.test.ts (browser): each look runs exactly its own loop on agent work, a loading spinner still turns, and Static and Reduce Motion stop all of them.
  • spinnerTurns.test.tsx: running rows mark themselves as agent work; a loading spinner never takes the dot.

Observed / Not run

  • Ran on huge-blue: bun run check (clean), and bun test on conversation, started, gallery and the spinner clock (82 pass). With CMUX_BROWSER_TESTS=1: static-status-loops, agent-pane-plan and spinner-clock (18 pass). Gallery tests: 43 pass. prepush-gate ran on push.
  • Elapsed time: hovering a sidebar row already shows how long it has been working (the hover card's activitySince). Elapsed time on a wide row is not in this PR; it needs native layout and a capture, so it comes as a follow-up.
  • Not run: the Swift build and StatusWorkingLookTests. No Mac compiles on this host, so those run on CI.

Agent session: huge-blue

🤖 Generated with Claude Code