Agent pane: a line joining the running tray enters like a transcript row

Merged
TE
5d ago
+115-1

Leo, on the running tray above the composer:

"When I said to animate this stuff in line with everything else, like when you open, when you close, this too, something running, whatever, this more slightly more dynamic. I don't even know. Like why is this not animating and, like, following along with the exact same things as everything else? This section right here, it just doesn't conform to the animating sort of train." (2026-10-26)

"plus the bottom running situation still not animating correctly" (2026-10-27)

"there's the animations, right? Of the whole, like, um, you know I mentioned the background section area. I don't know if we made a term for that, but it's, like, how it animates differently from everything else." (2026-10-28)

What was still different

#25719 put the tray card on a live transcript row's entrance: cv-enter, a fade and a 4px rise on --agent-motion-in and --agent-ease-out. #27355 eased its exit out. A line that joins the open list (a second or third subagent or command starting) still rose on the popup's ui-enter, through its data-enter="bottom" (ui/popupSurface.css). That is a different motion: 8px of travel, 1.25x the duration (150ms instead of 120ms) and its own curve, cubic-bezier(0.2, 0, 0, 1). So in one tray the card arrived one way and its lines another. Design foundations §6 lists this as an open violation, "Running tray matches the rest".

What changed

  • subagents/subagents.css: a line arriving in the list now runs the card's own entrance, through a new --acpmux-tray-line-enter token: cv-enter on --agent-motion-in and --agent-ease-out. Under Reduce Motion it uses cv-enter-fade, as a transcript row does. The data-enter attribute stays, because SubagentTray.tsx listens for the line's animationend through it and the leaving copy in trayMotion.tsx holds it where it is drawn.
  • gallery/fixtures/trayMotionArms.css: the gallery's other two arms (tight fade, slight spring) now give the line the same entrance as their card. All three arms stay comparable in Compare arms. The shipped arm is still message, the transcript's own.
  • SubagentTrayMotion.gallery.tsx: a new line-joins variant. Two subagents run with the list open, a third starts, and its line rises in. That makes four variants: enter-exit, start-empty, line-joins and two-running.
  • test/subagent-tray-line-motion.test.ts (browser lane, Chromium, real stylesheets) checks three things:
    • a live transcript row, the tray card and an arriving tray line compute the same animation-name, duration and timing function;
    • in each gallery arm the line runs the card's keyframe, never ui-enter;
    • under Reduce Motion the line gets the row's cv-enter-fade.

Not changed, deliberately: the tray's running lines keep the shared running wave on their icon, and a live command keeps the spinner. On 2026-10-28 Leo said of this tray: "I NEVER SAID TO PUT THE SPINNER HERE". The agent working-state rule covers the sidebar, tab and status mark only, and nobody named the wave. #27672's flush card, the tray layout PRs (#28133, #28533, #28587) and the fold default (#27930) are not touched.

Spec: leo-workspace design/foundations.md §1 (OPEN motion) and §6 ("Running tray matches the rest") get updated in their own leo-workspace PR, linked below.

Evidence

  • cmux-ci (Linux, Blacksmith 32 vCPU): bun test test/subagent-tray-line-motion.test.ts test/subagent-tray-motion.test.tsx test/calm-motion.test.ts test/subagent-tray.test.tsx gave 54 pass, 0 fail, including the 3 new tests (run).
  • tsc --noEmit reports only test/latency/probe.ts(115,60) interactionId. That file isn't touched here, and the error is on base too.

Review

Independent review (Codex gpt-6-astra, high, through router-review): SHIP at b1f9249549fae3fb09552741daf7395220d9cbba, no follow-ups.

Risk

Low. This is a CSS-only motion change to one element: an arriving tray line. Its timing goes from 150ms to 120ms and its travel from 8px to 4px. No new timer, observer or animation loop. Arrivals are still marked only when motion is allowed (SubagentTray.tsx), and the leaving copy still stops every animation inside it.

Before / after

Gallery agent-pane.subagent-tray-motion/two-running: press Start subagent, then freeze the third line ("Review the diff") 40ms into its entrance. Rendered in Linux Chromium with synthetic data; the CSS is base's for before and this head's for after.

Before (base 0acabeca)After (this head)
Darkbefore darkafter dark
Lightbefore lightafter light

What the arriving line computes, read live in that gallery:

animationdurationcurveat 40ms: opacity, rise left
Beforeui-enter150mscubic-bezier(0.2, 0, 0, 1)0.64, 2.9px of 8px
Aftercv-enter (a transcript row's)120mscubic-bezier(0, 0, 0.58, 1)0.49, 2.0px of 4px

Settled, the tray looks the same as before: this changes how a line arrives, not where anything sits. I viewed the after images (dark and light). The line is still mid-entrance at 40ms, the spinner and wave are as before, and the rows, columns, chevrons and header are unchanged.

Gallery: agent-pane.subagent-tray-motion/line-joins, agent-pane.subagent-tray-motion/two-running, agent-pane.subagent-tray-motion/enter-exit

Agent session: agent session