cmux NEXT: smooth chat follow and Jump to latest; no caret once idle

Closed
TE
18d ago
+662-41

SHOW LEO: this PR acts on Leo's design feedback, so it holds for his OK on the before/after recording before merging.

Leo's agent-chat scroll feedback in one batch, on top of the pinned streaming from #20374. It matches Codex and Claude desktop: Jump to latest, follow while tool calls and commands stream, and the streaming caret. It folds in #20432 (the idle caret).

Causes

  • Jump to latest was jarring. The native smooth scroll's first frame read as the reader scrolling up, so the view stopped following. The button came back mid-scroll, and the pin logic fought the animation.
  • Jump to latest landed short. The end was reached only through a hard snap 600 ms later. Any wheel event cancelled that snap, including a trackpad's momentum after the click, so the view stopped short while the reply grew.
  • Following snapped and moved strangely. A step taller than the view snapped, and content that shrank snapped down. A new row rose 4px on its own while the thread glided, which made two motions at once.
  • Opening a command jumped. Opening or closing a command while following pulled it away by its output, because the view re-pinned to the end.
  • The caret kept blinking. Text after the turn's result (a late reply, a background report) opened a streaming row that nothing closed. A reply followed by a newer message also stayed streaming. #18123 was closed unmerged, and 7115d4c15002 covers only the turn end itself.

Changes

  • Jump to latest:
    • The view follows again from the click, so the button hides and stays hidden.
    • It runs its own frames: a 320 ms eased glide to the end, retargeted every frame, so it lands on the end while a reply grows.
    • From far up, it starts one viewport above the end instead of racing through every row.
    • A wheel toward the end does not stop it; the reader scrolling up does. Reduce Motion lands at once.
  • Following:
    • Every change glides, including new rows and content that shrank. A step taller than the view glides its last viewport instead of snapping.
    • A new row only fades in, because the glide already moves it.
    • Scrolled up, nothing moves (as before).
  • Opening a command: a row the reader opens or closes while following holds still under the pointer. If its output pushes the end out of view, the view stops following and the button shows.
  • Caret: a newer message ends the reply before it, and a chat that is not working closes any reply still marked streaming. Replay is not changed.

Testing

  • New browser test webviews/test/agent-pane-jump-latest.test.ts covers:
    • Jump to latest is a steady glide, and the button stays gone.
    • It lands at the very end through momentum and growth, and keeps following.
    • A tool call and a reply taller than the view glide in without moving down. This one passes on base too and is kept as a guard.
    • Opening a command at the end holds it still.
    • Scrolled up, nothing moves while tool calls arrive.
  • New browser test webviews/test/agent-pane-idle-caret.test.ts covers both caret paths. Its red run on base, on #20432, failed both cases: /manaflow-ai/cmux/actions/runs/38512017391/job/115682707315
  • test/transcript-jump-latest.test.tsx now waits for the glide's frames rather than one scrollTo.
  • The tests were committed first (red) and then fixed (green). Results are in the comments below.
  • In-app capture: a recording from a DEV build of this head on a fleet mini will follow in a comment.
  • The related jsdom transcript tests pass locally, and bun run check passes. Browser tests run only in CI.
  • Not covered: a live "Ran N commands" group settling smaller at the end. It glides through the same code path as content that shrank, but no test drives it.

Changelog

  • Fixed: Jump to latest glides smoothly to the very end, the chat follows tool calls and commands without snapping, and the streaming caret no longer stays once the chat is idle.

🤖 Generated with Claude Code