Agent chat: Worked for folds and unfolds with motion (cx-o90g7)
cx-o90g7. Stacked on #20582 (its commits show here until it lands). SHOW LEO: hold for Leo before merge.
What
The "Worked for" fold moves instead of jumping:
- Turn ends (running ā done): the steps fold away as the answer and the edits card settle into place. The steps fade out while the new rows fade in, so no frame goes empty.
- Reader opens the fold: the header keeps its place. The answer slides down (
--agent-motion-move, 190 ms ease-out), then the opened steps fade in under it. - Reader closes the fold: the steps fade out (
--agent-motion-out, 80 ms) while the answer slides up into place. - While the turn runs: "Working for Ns" carries the existing "Thinking" sweep. It stops when the clock stops.
Rows are placed absolutely, so nothing animates a height. Each row's translate runs from where it was drawn to its new place (a FLIP, the way the New Tab list moves). The bottom-pin glide is off while this runs, so the whole pane never slides. A toggled fold keeps its header still and stops following when it opens past the end.
Reduce Motion keeps only a short fade (ā¤100 ms) and no movement. With animation speed off, there is no motion at all.
Variants
For comparison, #foldMotion= picks the variant:
slide: proposed, and the default.fade: rows jump, steps fade.none: no motion.
scripts/agent-pane/fold-motion-tour.mjs records all three against the mock agent.
Captures
Side by side: before (this branch's base, #20582), slide, and fade only. Real speed, then 4Ć slower. Recorded headless in Chromium against the mock agent on the private builder. I watched them frame by frame.

Full quality:

Full quality:
Real app (cmux NEXT DEV)
Fleet DEV builds on a capture mini: after = this head (4152f790a721), before = its base on feat-cmux-next (2a68993bc732). The chat is the app's showcase mock agent (9 steps Ć 350 ms per turn), at the default animation speed (fast). Each app's agent-pane bundle was checked before shooting: foldMotion is in the head's pane.js and absent from the base's. Stills are the main window, cropped to the sidebar and chat; the mini's machine name in the composer is blanked.
Composer overlap
The end of a long chat (five turns). In the before shots the last turn's time, Copy and Fork row is clipped by the composer's top edge, the multi-line draft included; in the after shots it sits clear of the composer.
| Before, light | After, light | Before, dark | After, dark | |
|---|---|---|---|---|
| Turn ended | ![]() | ![]() | ![]() | ![]() |
| After opening and closing the fold | ![]() | ![]() | ![]() | ![]() |
| Multi-line draft | ![]() | ![]() | ![]() | ![]() |
Fold states
| Before, light | After, light | Before, dark | After, dark | |
|---|---|---|---|---|
| Running (Working for, sweep) | ![]() | ![]() | ![]() | ![]() |
| Folded | ![]() | ![]() | ![]() | ![]() |
| Opened | ![]() | ![]() | ![]() | ![]() |
A still catches the sweep at one point of its pass; in the after clips the highlight moves across "Working for" while the turn runs (the base's label stays flat gray). In the opened shots the fold runs past the end, so the view stops following and shows the jump-to-latest button.
Clips: turn ends and folds, then the fold opens and closes
The slide, fade and none clips are from a DEV build of 284dd5005850, which fixed the fade and none issues the first recording showed; the stills and the base clip are unchanged (the opened still matched on 284dd5005850). Each is 8 s: send at 0.3 s, the turn ends and folds at about 3.5 s, open at about 4.7 s, close at about 6.5 s. The mode was set through the agent pane's registry.js customization (it puts #foldMotion=<mode> on the page and reloads it); no app code changed for the captures.
| Mode | Clip |
|---|---|
| slide (default) | |
fade (7d1c6d2f2fd9) | |
| none | |
| before (base) | |
Watched frame by frame at 60 fps (284dd5005850):
- slide: the answer slides down on open and up on close, and the steps fade in after it; no empty frame.
- fade (re-recorded on
7d1c6d2f2fd9): no near-empty frame on open or close. Rows the fold moves fade from about 0.4 opacity and new steps from 0, settling over about 3 to 5 frames. - none: open and close are instant, and the "Worked for" header keeps its place in both.
Verification
bun run checkpasses on the builder.- Transcript tests pass:
transcript-worked-fold,jump-latest,long-chat-updates.followruns in the browser lane. - Seen in real cmux NEXT DEV builds of this head and its base: see Real app (cmux NEXT DEV) above.
Changelog
Changed: the agent chat's "Worked for" fold slides open and closed, and a finished turn folds its steps with a short crossfade.
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
The agent chat's "Worked for" fold now moves instead of jumping: finished turns fold their steps with a crossfade, and opening or closing a fold slides the rows that stay into place while the toggled header keeps its place on screen. The last turn's time, Copy and Fork rows always clear the composer, even with a long draft or while the next reply streams; slide is the default, with fade (moved rows fade from 0.4, steps from 0) and none variants selectable via #foldMotion=.
Also fixes turns that end on a tool call: the interim text now folds with the rest instead of remaining open as the answer, including when an edit runs a tool in the same step.
- Rows sit absolutely and slide via
translate(a FLIP); steps fade in or out where they are, so no height animates and no frame goes empty. - Reduce Motion keeps only a short fade; the transcript neither glides nor follows while a change plays.
- The branch also carries participant avatars (SHA-256 deduplicated, read in the Channels page),
agent.message.releasewithdrawal, the background-process registry, and daemon/Chief handoffs.























