webviews: Static status indicators and Reduce Motion stop every web loop (cx-sya4n)

Merged
TE
19d ago
+387-6

cmux NEXT's web pages now stand still when status indicators are Static or Reduce Motion is on. The look comes in with the theme, like the scrollbar style does: WebTheme carries statusIndicators, and its document-start bootstrap sets data-status-indicators on <html>. Every page gets it, Home included, and the agent pane's theme sends the same value. Under static, no status loop runs:

  • the mic's wait pulse
  • the model picker and harness spinners
  • the floating chat's breathing dot
  • the Thinking sweep (the word stays muted)
  • the reply caret's wait pulse
  • the Jump to Latest dots
  • Home's "writing" marker

Color-only marks also get a shape each:

  • Floating status: a dot while it works, and a triangle drawn in the dot's own 6px box when it needs you, so nothing beside it moves.
  • Subagent dots: โœ“ and โœ•.
  • Lineage badges: dot, !, โœ“, โœ• and ring.

Subagent dots name their state in the tooltip. The diff viewer's base-picker spinner stops under Reduce Motion instead of slowing down. This is cx-sya4n: blinking lights give a teammate headaches.

  • Native:
    • WebTheme carries statusIndicators, which is static under Reduce Motion or appearance.statusIndicator.animation: "static".
    • A PageWebView pushes its theme again when that setting or Reduce Motion changes.
    • AgentPaneTheme sends the same value, through two lines identical to #21664's, so the two merge without conflict.
  • Web: applyAgentTheme sets the attribute from the agent theme. The CSS rules sit under :root[data-status-indicators=static].

The look reads StatusIndicatorSettings.animation from #21659, which has merged; the branch is on current feat-cmux-next. The four commits are 447ddfe0945 (test), 3493030775b, b6c45a343eb (test) and dc50b230def. It doesn't need #21662 (the Settings row).

No new strings: the tooltip reuses the existing STATE_LABEL keys.

Testing

  • webviews/test/static-status-loops.test.ts loads the real stylesheets in Chromium. It turns the look on the app's way: it runs WebTheme.bootstrapScript, read from the Swift source as the gallery does, then pushes a theme carrying statusIndicators. It lists running infinite animations (document.getAnimations()) and checks the following:

    • The loops run by default.
    • Static stops every pane and Home loop.
    • Reduce Motion stops every loop, including the diff viewer's.
    • Thinking stays readable.
    • Each color-only mark gets its own shape.
    • The theme push sets the attribute.
    • The static triangle keeps the dot's box, so its neighbour's position is unchanged.

    It's a browser-lane test, and it skipped locally because Chromium isn't installed on this host.

  • webviews/test/status-indicators-theme.test.ts checks that the agent theme's look reaches the root. It passes locally (bun test).

  • WebThemeStatusIndicatorsTests (CmuxNextDesignTests) checks that the payload carries static under the setting or Reduce Motion, and that the bootstrap sets the attribute. This host is Linux, so it hasn't run yet.

  • CI's first run (on that commit's pre-rebase copy, 2dc90cca633): react-apps-check failed at check:text-focus on DaemonRecoveryControls.swift. That's base-red from #20765, so the test step never ran.

  • Local checks:

    • bun test of gallery-web-theme-bootstrap.test.ts passes, so the bootstrap still parses.
    • check:layers, check:browser-lanes, check-crash-safety.sh and lint-swift-post-edit.sh pass.
    • tsc --noEmit reports only a missing web/data snapshot, which isn't in this sparse checkout.

Changelog

Fixed: in cmux NEXT, Reduce Motion stops every web page's status loops, including the diff viewer's base-picker spinner.

๐Ÿค– Generated with Claude Code