cmux-next: Reduce Motion stops every loop; Status indicators setting (Animated / Static) (cx-sya4n)

Closed
TE
19d ago
+3156-35

Reduce Motion now stops every repeating animation in cmux NEXT, native and web. A new setting, Status indicators: Animated / Static, swaps pulsing and blinking status lights for steady shapes (cx-sya4n). Austin gets headaches from the pulsing lights.

What changed

  • Setting: appearance.statusIndicator.animation takes animated (the default, unchanged look) or static. It is under Settings > Appearance > Loading Indicator > Status Indicators. Reduce Motion forces Static.

    • Which default NEXT should ship is a needs-a-call: #20403 proposes Static.
  • Static, native: each state has a steady shape, so states differ without color:

    • working: solid dot
    • busy: arc
    • paused: outlined circle
    • waiting: !
    • permission: hand
    • question: question bubble
    • sign-in: key
    • error: circled โœ•
    • done: โœ“

    Short labels are tooltip and VoiceOver text only; sidebar rows get no new visible text. The attention ring uses the steady style, which matches Leo's call to make steady the attention default.

  • Static, web (agent pane): these stop: the mic, model-picker and harness spinners, the floating breathe, the Thinking sweep (its text stays muted), the reply caret, the jump-to-latest dots and the writing marker. Color-only marks get their own shapes (triangle, โœ“, โœ•, dot, ring) and accessible labels in every mode.

  • Reduce Motion also stills Home's caret and typing dots, the update and import spinners, and the diff base-picker spinner.

  • Blink cap in every mode: nothing flashes faster than about 2 Hz (MotionLoop.minimumPeriod). The flash is 1.0 s and the pulse 0.5 s.

  • Motion kit for late content (asked for by cx-next-handoff):

    • Web: webviews/src/ui/motion.ts + motion.css provide spring tokens (linear()), reveal (a fade plus a 3 px rise), crossfade, and stagger (25 ms steps, at most 4 items).
    • Native: Motion.stagger(index:) and Motion.reveal(_:).
    • Under Reduce Motion both are a plain fade with no stagger. The "Late content" section of plans/cmux-next/motion.md documents them.
  • Classic is unchanged. Its audit and a needs-a-call note for Austin are in #20403.

Testing

Red, then green: the failing tests were committed first.

  • Web:
    • webviews/test/static-status-loops.test.ts asserts that under Static or Reduce Motion, document.getAnimations() finds no running infinite animation.
    • motion-kit.test.ts covers the kit.
    • Red: run 38524527894 (1fb2d2d), with 4 static-status-loops failures and 3 motion-kit failures. Green: run 38528233410 (395e046).
  • Swift:
    • ReduceMotionLoopTests covers no running CAAnimation loops under Reduce Motion. StaticStatusIndicatorTests covers distinct shapes per state. BlinkRateCapTests covers the 2 Hz cap.
    • Red: fleet step 0432026f, a compile failure (the new API is missing). Green: step 0c4def9a.
  • On the final head c98fa577:
    • Fleet Swift suites: 293 passed, 0 failed.
    • Release compile: fleet step 22fd4288, BUILD SUCCEEDED.
    • Settings exports were regenerated on the merged head. gen-strings --check is clean. Catalogs and exports only add lines; none were deleted.
    • Locally: check-motion, crash_ratchet, system-blue, type-scale and text-focus pass. God files: none of these files grew.
  • Captures: real cmux NEXT DEV builds on a fleet capture slot, base on the left and head on the right. Host names are blurred. I watched them.
    • Chat after a turn in Animated, Static and Reduce Motion. In Static and Reduce Motion, nothing on the head side loops except the system text caret in the composer (see Left over).
    • The sidebar and attention ring in Animated vs Static.

Captures

In each clip, base is on the left and head on the right. In order: chat in Static, chat under Reduce Motion, the attention ring in Static, then a still of the Static shapes.

plan gif 20511/chat-static.gif (from chat-static.mp4)
plan video 20511/chat-static.mp4
plan gif 20511/chat-reduce.gif (from chat-reduce.mp4)
plan video 20511/chat-reduce.mp4
plan gif 20511/clip-attention-static.gif (from clip-attention-static.mp4)
plan video 20511/clip-attention-static.mp4
plan image 20511/still-static.png
uploaded 20511/chat-static.gif
uploaded 20511/chat-static.mp4
uploaded 20511/chat-reduce.gif
uploaded 20511/chat-reduce.mp4
uploaded 20511/clip-attention-static.gif
uploaded 20511/clip-attention-static.mp4
uploaded 20511/still-static.png

chat static

Full quality:

chat reduce

Full quality:

clip attention static

Full quality:

still static

Left over

  • The composer's text-input caret still blinks, at about 1 Hz. That is the system caret.
  • The floating, summary and jump dots don't appear in the live captures. The browser test covers them.
  • Base reds this PR does not touch: react-apps-check check:layers on summary.css (from d77476fd), main-thread concurrency in ControlWorkspaceInfoCache.swift, and PaneAnimationsSetting in the package conventions lint.

Changelog

Added: a Status indicators setting (Animated / Static); Reduce Motion now stops every looping animation, and nothing blinks faster than 2 Hz.

Light and dark, base vs head (real cmux NEXT DEV builds of the merge-base and this head, on a capture mini; host name blurred). Each sheet has three columns: base (animated), Status indicators = Static, and Reduce Motion. The rows are the sidebar status rows at 2x, the tab strip and the attention-ring corner. The clips put the sidebar and tabs side by side: on base the spinners turn, and on head with Static or Reduce Motion they are a still dot. In the attention clip a notification arrives: base flashes the ring and head shows a steady one. Pixel diffs of the head recordings show nothing moving except the composer's system caret. The "Release notes" row's subtitle differs only because of when the tabs were seeded. The set has no running agent turn; the chat clips above cover the web Thinking sweep and the reply caret.

Showcase

sheet light

sheet dark

clip light static

clip dark static

clip light reduce

clip dark reduce

clip light attention

clip dark attention

Open the live gallery

๐Ÿค– Generated with Claude Code