cmux NEXT: fold a long reasoning trace the way Codex does

Closed
TE
18d ago
+4416-994

Summary

A long reasoning trace used to fill the transcript: a thought always drew in full, so a model that thinks for a screen pushed everything else away. It now folds the way Codex desktop folds its reasoning trace (Leo's 10-15 feedback, item 7).

  • While the agent is still thinking it: a thought taller than seven lines (140 px, Codex's 8.75rem viewport) stays in a fixed viewport. The newest line sits at the bottom and older lines fade out at the top over 16 px. The label reads "Thinking" with the existing shimmer.
  • Once the agent moves on (a tool call, its answer, or the turn ending): the thought folds to one line, "Thought for 12s". Its duration runs from the thought's first chunk to its latest. The line opens on a click, Enter or Space. Opened, the full text fades in under it (cv-enter, --agent-motion-in). Closing is instant, as "Worked for" closes.
  • A short thought never folds. It shows in full after a muted "Thought", as before (Leo 10-13).
  • An opened thought stays open when the virtual list draws its row again, or when the row moves under "Worked for". Find opens a folded thought that holds its match.

How Codex does it, read from its shipped web bundle (ChatGPT.app 26.930.51102):

  • The trace's body is a 140 px scroller, pinned to the bottom while it streams, with a 16 px edge fade.
  • The header reads "Thinking", then "Thought for {elapsed}".
  • Every trace folds when it completes, with a 300 ms height and opacity animation (expo-out).
  • The open state is component-local.

cmux differs on purpose in three places:

  • Only long traces fold, since Leo asked that thoughts read in full.
  • An opened trace shows its whole text, not a 140 px scroller.
  • There's no height tween, because virtualized rows place absolutely by their measured height.

The full spec, with each claim marked as read from code, seen, or inferred, is in the lane's brief codex-reasoning-collapse.md.

Mechanism:

  • turns.ts marks the live turn's last activity row streaming, with a negated version so the memoized row and the height cache redraw when it stops.
  • ThoughtRow measures its text before paint, so a long thought never draws full height first.
  • appendThought keeps each thought's first and latest chunk time.
  • The new string tools.thoughtFor is in all 21 pane locales. English is translated; the other locales are needs_review, like their neighbours.

Testing

Gallery plays at the transcript boundary, run in Chromium through webviews/test/transcript-thought.test.ts. Same command, red then green, on the builder:

sbx -- bash -lc 'export CMUX_BROWSER_TESTS=1 LD_LIBRARY_PATH=...; cd webviews && bun install --frozen-lockfile; bun test test/transcript-thought.test.ts'
  • Red at bb930529355f (tests only):
    • "a long live thought grows to 426px instead of folding"
    • the fold play's waitFor never found a toggle
    • the short-thought and existing thought tests passed
  • Green at 86013c41740e: all 5 pass.
    • A long live thought stays within 140 px with its newest line in view and its first line out of view.
    • An ended one shows a closed "Thought for 12s" without its text; a click opens it, Enter closes it and Enter opens it again.
    • A short live thought has no toggle and isn't cut off.
  • bun run check: passes (lint, types, format, layers, type scale, system blue).
  • Crash ratchet: ok.
  • Also passed: transcript-worked-fold, transcript-long-chat-updates, transcript-hidden-follow, agent-pane-follow-live, agent-pane-step-rows, thread-turn-keys and agent-pane-replay. These cover follow-at-end and virtualization.
  • After the review subagent's findings (green again at the head, with bun run check):
    • A thought re-measures on resize until it folds, so one mounted in a hidden pane, or narrowed later, still folds.
    • Thoughts key by their place in the row, so a regrouped run never moves an open state.
  • Didn't run on the builder: agent-pane-locale needs the built pane bundle, which isn't there.

Live Codex wasn't re-recorded this pass. The thresholds and timings come from Codex's shipped code, not from frames.

Changelog

Added: A long reasoning trace streams in a fixed seven-line viewport with its newest line in view, then folds to "Thought for 12s", which opens on a click or Enter

Proof

These are gallery stills of the real transcript components in Chromium (agent-pane.transcript, 760 px), not app captures. A Codex reference stays private and isn't shown here.

A long thought while the agent is still thinking it. Before: the whole trace draws. After: a seven-line viewport with the newest line at the bottom and older lines fading out at the top.

BeforeAfter
Lightbefore live lightafter live light
Darkbefore live darkafter live dark

After the agent moves on. Before: still the whole trace. After: one line, "Thought for 12s", then opened.

BeforeAfter, foldedAfter, opened
Lightbefore done lightfolded lightopen light
Darkbefore done darkfolded darkopen dark

The folded stills show the focus ring because the play opened and closed the row with the keyboard.

Open and close (clip):

fold clip dark

Full quality: ,

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • UI, settings, menu, schema, help-text or user-facing docs change: localization audited, and the result is stated above. One new pane key, tools.thoughtFor, is in all 21 locales, and generated/strings.json is regenerated.
  • Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved

šŸ¤– Generated with Claude Code