Agent transcript: tool calls animate open and closed again; at the bottom the text above glides up as one stack

Merged
TE
3d ago
+148-33

Leo, 2026-11-04: "how come there aren't animatiosn for expanding/collapsing tool calls anymore???", and on the bottom case: "as for the 'at bottom, the thing expands up' it's not a smooth eough/graceful enough/minimally shifting animation." Earlier (2026-10-31): "CHATGPT WHEN YOU EXPAND THEIR TOOL CALLS THEY PUSH THE CHAT UP ... ANIMATION FOR IT GRACEFULLY ALL MOVING UP AS A STACK TOO FAST NO FLICKER SMOOTH THAT'S IT".

What regressed

#29686 ("Keep transcript bottom pinned while tool folds open") made every disclosure clicked at the very bottom skip its own height and opacity fold (data-bottom-fold in useFold / playResize) and lay out at once, then slide the whole thread on one translate animation. #32943 added held rows and clip cuts on top of it, and #34280 extended it to closing. In the app (WKWebView) that read as the transcript jumping up in one step, with the composer card flashing, and back down on close. The "Received N messages" batch (#32720) never had a fold at all: its body was {open && ...}.

Fix

  • At the very bottom an in-row disclosure (tool group, command, MCP call, received message, thought, Show more) now plays its own fold again (height + opacity, --cv-fold-duration, ease-out). The bottom stack no longer marks the row data-bottom-fold; while it is active, every placement pass, which heightFoldInFlight already runs before paint on each frame of the fold, pins the offset to the new end (settleFollowBottom). So the scroll offset moves with the height frame by frame: the text above glides up as one stack, the rows under the line keep their pixels, and closing glides back down with no room left under the rows. #29686's purpose (bottom pinned, push up, no blank space) stays.
  • Only toggles that add or remove whole rows (toggleRow: Worked for, subagent groups, message runs, shell rows) keep the thread slide; toggleRow marks the stack rows and sets data-bottom-fold then.
  • The follow glide (followGlide.rise) is skipped while a height fold plays (heightFoldPlaying(), an O(1) timestamp), so it never eases rows the fold is already moving.
  • LaneNoticeBatch ("Received N messages") opens and closes its body with useFold like a tool group.

Perf budget

  • No per-token work: the per-frame pin runs only inside a bottom stack the reader's click starts, and heightFoldPlaying() is one performance.now() compare.
  • Stop conditions: the fold's Web Animation ends on finish/cancel after --cv-fold-duration (180 ms; 0 under Reduce Motion or animations off); the bottom stack's cleanup timer stops it after max(400 ms, fold + 150 ms); a wheel, touch or key from the reader, a send or Jump to latest stop it at once. heightFoldsUntil is a timestamp, no timer.
  • Per frame cost during those ~11 frames is the same flushSync placement the away-from-bottom fold already does (heightFoldInFlight); the thread-wide transform, held-row and clip animations no longer run for in-row folds.

Tests

  • webviews/test/transcript-expand-at-bottom.test.ts: new "at the very bottom (chromium|webkit), opening and closing a group glide the text above over several frames": on every painted frame the view stays at its end and the last row keeps its pixel, at least 3 frames sit between where the line was and where it settles, and no single frame moves it most of the way. Runs in WebKit too, the app's engine.
  • transcript-fold-anchor-browser.test.ts: the bottom tests now expect the body's own fold and no thread transform.

Verified with cmux-ci test (Chromium + WebKit): the glide test and transcript-expand-at-bottom pass; the base thread slide also glides in headless engines, so the in-app jump is not reproduced there, and the change removes the thread-wide transform for in-row folds.

Design spec: transcript (design/transcript.md); no settled design changes beyond restoring the fold motion.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Restores smooth fold animations for tool calls and other disclosures at the bottom of the agent transcript, replacing the one-step thread jump that regressed in #29686.

  • In-row disclosures (tool groups, commands, received messages, thoughts, Show more) play their own height fold again; each frame pins the scroll offset to the new end so the text above glides up or down as one stack.
  • Toggles that add or remove whole rows (Worked for, subagent groups, message runs, shell rows) keep the thread slide and mark the row with data-bottom-fold.
  • "Received N messages" batches now fold like tool groups.
  • The follow glide is skipped while a height fold plays to avoid double-easing; the gallery shift gate reads the data-bottom-stack marker every bottom stack sets.

Tests

  • New tests verify the glide spans several painted frames without flicker in both Chromium and WebKit, the app's engine.
  • The bottom fold tests now expect the body's own fold and no thread transform.