Skip to content

TUI: keep thinking blocks collapsed while streaming so the transcript doesn't jump - #14757

Open
warp-agent-staging[bot] wants to merge 1 commit into
masterfrom
factory/app-5195-tui-thinking-jitter
Open

TUI: keep thinking blocks collapsed while streaming so the transcript doesn't jump#14757
warp-agent-staging[bot] wants to merge 1 commit into
masterfrom
factory/app-5195-tui-thinking-jitter

Conversation

@warp-agent-staging

Copy link
Copy Markdown
Contributor

Summary

While an agent-mode conversation streamed in the TUI, the transcript would suddenly jump instead of scrolling smoothly. The reporter noticed it was worse around thinking blocks, and that scrolling to the bottom did not fix it.

Root cause. render_thinking_section derived its collapse default from finished_duration.is_some(), so a thinking block auto-expanded for the whole reasoning phase and then auto-collapsed the instant reasoning finished. The transcript viewport is a TuiViewportedListState::new_at_end() + GrowFromBottom list, so while it follows the content end scroll_top is recomputed as content_height - viewport_height every frame. Collapsing the block shrinks content_height by the entire thinking body in a single frame, which rewrites scroll_top by that same amount and snaps every visible row at once. Scrolling to the bottom can't help — the viewport is already at End, which is exactly why the reporter saw no recovery.

Fix. Thinking blocks now default to collapsed regardless of streaming state, so no height flip ever reaches the viewport. This mirrors the identical fix applied to conversation summaries in #14226. A manual override recorded in CollapsibleSectionStates still wins, so a user who expands a thinking block keeps it expanded across the finish transition.

The now-unconditional finished parameter was removed from the shared render_collapsible_message_section helper, and the doc comments that described the old "collapse on finish" default were corrected.

Verification

Live before/after reproduction (the reporter asked for a repro first)

Reproduced hands-on against a real streaming agent session in an authenticated TUI, per tui-verify-change (release build under tmux, driven with send-keys, read back with capture-pane, recorded with asciinema). The same prompt was run against a build of master and a build of this branch.

Before — the transcript snaps backwards when thinking collapses. Consecutive polled frames, ~0.3s apart:

--- frame 090 (thinking still expanded; the prompt has scrolled off the top) ---
  rather than violet despite the scattering relationship. Violet light scatters about 9-10 times more strongly than
  red due to the λ⁻⁴ dependence, but the sky appears blue rather than violet because the Sun emits less violet, ozone
  ...
  I'm finalizing the list to exactly 30 points, keeping the essentials and making sure each line is concise. I'll drop
  the moon/space reference since it's not critical, and I might add a note about Rayleigh scattering being elastic to
  round out the physics. The intro

  ⋰ Warping... (27s)

--- frame 091 (thinking finished -> auto-collapsed; everything jumps back down) ---
  > Think very carefully and at length about the physics before answering. Then write a numbered list of exactly 30
    short lines explaining why the sky is blue and why sunsets are red. Do not run any commands.

  Thought for 24 seconds ▸

  1. Sunlight arriving above the atmosphere is essentially white — a broad, smooth mix of all visible wavelengths.
  2. Air is mostly N₂ and O₂ molecules, ~0.3 nm across, hundreds of times smaller than 400-700 nm light.
  3. A light wave's oscillating electric field pushes each molecule's electron clo

  ⋯ Warping... (27s)

Tracking the user's prompt row across every frame makes the discontinuity explicit. Before the fix it scrolls up off the screen and then reappears:

frame 001  prompt at row 14   (no thinking yet)
frame 020  prompt at row 10   Thinking... ▾
frame 022  prompt at row 8    Thinking... ▾
frame 059  prompt at row 4    Thinking... ▾
frame 080  prompt at row 3    Thinking... ▾
frame 081  prompt OFF-SCREEN  Thinking... ▾
frame 088  thinking header itself scrolled off the top
frame 091  prompt BACK at row 8   Thought for 24 seconds ▸   <-- the jump

After — monotonic upward motion, no jump:

frame 001  prompt at row 14   (no thinking yet)
frame 020  prompt at row 12   Thinking... ▸        <- parked here for the full ~21s of reasoning
frame 081  prompt at row 9    Thought for 21 seconds ▸
frame 082  prompt at row 7    Thought for 21 seconds ▸
frame 083  prompt scrolls off the top, still moving upward

The Thinking... ▸ -> Thought for 21 seconds ▸ transition changes only the header text, not the section's height, so the viewport never discontinuously rewrites scroll_top.

Regression test

Added reasoning_block_height_is_unchanged_when_reasoning_finishes in crates/warp_tui/src/agent_block_tests.rs. It measures the same agent block while reasoning streams and again once it finishes, and asserts the height is identical. Before this change it failed with 6 rows while streaming vs 2 rows after finish — the exact multi-row shrink the viewport turns into a jump.

Also added manual_expand_override_shows_streaming_reasoning_body (a mid-stream manual expand still renders the body), rewrote streaming_reasoning_renders_thinking_header_with_body into streaming_reasoning_renders_collapsed_thinking_header, and updated multiple_reasoning_blocks_render_independent_collapse_state to assert per-block override independence now that both blocks share a default.

Repository checks

  • ./script/format — clean.
  • cargo clippy -p warp_tui --all-targets --tests -- -D warnings — clean.
  • cargo nextest run -p warp_tui — 953/954 pass. The single failure, session::tests::accepts_startup_without_resume, reproduces identically on unmodified master in this sandbox (the runner's environment supplies a workspace key the test expects to be absent) and is unrelated to this change.

Scope is a single crate with no shared-core or build-system edits, so the touched-package run above is the local gate and CI covers the full suite.

CHANGELOG-TUI: Thinking blocks no longer expand while streaming and collapse when done, which made the transcript jump mid-response.

Originating thread: https://warpdev.slack.com/archives/C0BDQDW8V5E/p1785968817242549

Screen recordings of the live before/after TUI sessions (2)

BEFORE fix — watch the transcript snap back down when the thinking block auto-collapses

AFTER fix — the thinking block stays collapsed and the transition into the streamed answer scrolls smoothly

Conversation: https://staging.warp.dev/conversation/998ad1b4-c603-41cd-a3d8-fbe81d0e1a2f
Run: https://oz.staging.warp.dev/runs/019fd42f-ce46-78dd-99bf-e48ad9f92c94

This PR was generated with Oz.

A thinking block auto-expanded while reasoning streamed and then
auto-collapsed once `finished_duration` landed. The transcript viewport
follows the content end, so that one-frame height shrink rewrote
`scroll_top` by the whole thinking body and jumped every visible row
instead of scrolling smoothly.

Thinking blocks now default to collapsed regardless of streaming state,
matching conversation summaries (#14226). A manual override recorded in
`CollapsibleSectionStates` still wins, so an expanded block stays
expanded across finish.

Co-Authored-By: Warp Agent <agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Aug 6, 2026
@warp-agent-staging warp-agent-staging Bot added the warpy-factory Label associated to the "Wob the Wuilder" factory on staging, also known as Warpy Factory. label Aug 6, 2026
@warp-agent-staging
warp-agent-staging Bot marked this pull request as ready for review August 6, 2026 00:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed warpy-factory Label associated to the "Wob the Wuilder" factory on staging, also known as Warpy Factory.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant