Skip to content

fix(tui): preserve streaming scroll-follow across Ctrl+T thinking toggle#239

Open
alejandro-ao wants to merge 2 commits into
mainfrom
fix/thinking-toggle-stream-scroll
Open

fix(tui): preserve streaming scroll-follow across Ctrl+T thinking toggle#239
alejandro-ao wants to merge 2 commits into
mainfrom
fix/thinking-toggle-stream-scroll

Conversation

@alejandro-ao

Copy link
Copy Markdown
Collaborator

Summary

Fix a re-regression of #175: pressing Ctrl+T to toggle thinking-token display during an active stream broke the smooth bottom-following scroll restored by #175 / PR #177. The toggle now reconciles thinking widgets incrementally while streaming instead of triggering a full transcript redraw.

Motivation / problem

While an assistant message is streaming and the viewport is pinned to the bottom, pressing Ctrl+T to show/hide thinking tokens stopped the smooth auto-follow scroll: the viewport either stopped following or jumped/snapped, and only recovered after the turn ended.

action_toggle_thinking always called _refresh()TranscriptView.update_from_state()_redraw(), which does a full remove_children() + remount of every transcript block. During an active turn that:

  1. Tears down _active_assistant_widget / _active_thinking_widget (reset to None), so the next streamed delta mounts a fresh assistant widget (visible jump / lost incremental markdown stream) — the "thinking stream appears in a different way" symptom.
  2. Reflows the whole transcript; the transient content-height change moves scroll_y, and TranscriptView.watch_scroll_y interprets that as a user scrollback and flips _follow_output = False, snapping the viewport back to the bottom.

This is exactly the "scroll state reset during token deltas" failure mode #175 warned about. The earlier "Reduce transcript toggle latency" change (ae42fd0f) optimized the toggle for the idle transcript and didn't account for the toggle firing mid-stream.

What changed

  • TranscriptView.apply_thinking_visibility(state, theme) reconciles only the thinking-related widgets against state.items thinking blocks, never remounting the active assistant streaming widget so its incremental markdown stream stays intact.
    • Showing: remove all thinking widgets (finalized TranscriptMessageWidget blocks, live StreamingTranscriptMessageWidget blocks, and the hidden-thinking placeholder), then mount a fresh widget for each thinking item ordered ahead of the assistant widget. StreamingTranscriptMessageWidget(item) re-renders the full item.text on mount, and the next thinking delta continues appending to the freshly assigned active widget.
    • Hiding: remove all thinking widgets and, if a thinking item exists, mount a single hidden-thinking placeholder ahead of the assistant widget.
  • Follow/scrollback is preserved across the reflow: _follow_output is snapshotted, _suppress_follow_update makes watch_scroll_y ignore layout-induced scroll movement during the reconcile, and a call_after_refresh callback restores _follow_output and re-requests the bottom-follow scroll after layout settles. A mid-stream toggle therefore does not change follow mode.
  • action_toggle_thinking uses the incremental path while state.running, and keeps the full _refresh() redraw for the idle case. The reconcile is synchronous (fire-and-forget mount / remove_children, matching _redraw) so it stays atomic with respect to the streaming worker.

Acceptance criteria / verification

  • Ctrl+T during an active stream keeps the transcript pinned to the bottom with smooth following scroll when the user was already following.
  • Ctrl+T during an active stream, after the user scrolled up, preserves the user's scrollback position (no jump back to the bottom).
  • Toggling thinking on/off repeatedly mid-stream does not cause reflow jumps or scroll resets.
  • Multiple thinking blocks (across tool calls) all re-render on show and collapse to one placeholder on hide.
  • The active assistant streaming widget survives the toggle and keeps receiving subsequent deltas.
  • Behavior matches Fix TUI scrollback regression while assistant messages stream #175/Fix #175: restore streaming transcript scrollback #177: auto-follow only while at the bottom; opt-out on user scroll-up; resume when returning to the bottom.

Regression tests in tests/test_tui_app.py:

  • test_tui_thinking_toggle_during_stream_preserves_follow_scroll
  • test_tui_thinking_toggle_during_stream_preserves_user_scrollback
  • test_tui_thinking_toggle_during_stream_keeps_all_thinking_blocks
  • test_tui_thinking_toggle_via_event_stream_preserves_assistant_widget
uv run pytest tests/test_tui_app.py -k "thinking_toggle_during_stream or thinking_toggle_via_event_stream"
uv run ruff check . && uv run mypy

All 610 tests pass, ruff + mypy clean.

Related files or areas

  • src/tau_coding/tui/widgets.pyTranscriptView.apply_thinking_visibility, _show_streaming_thinking, _hide_streaming_thinking, _remove_thinking_widgets, watch_scroll_y, _suppress_follow_update
  • src/tau_coding/tui/app.pyaction_toggle_thinking
  • tests/test_tui_app.py — new regression tests
  • dev-notes/architecture/fix-thinking-toggle-stream-scroll.md

Notes

Pressing Ctrl+T to toggle thinking-token display during an active stream
called the full _refresh redraw, which remounted every transcript block,
tore down the live assistant/thinking StreamingTranscriptMessageWidget, and
reflowed the layout so watch_scroll_y flipped _follow_output and snapped
the viewport back to the bottom. This re-regressed the smooth bottom-follow
scroll restored in #175 / PR #177.

While an agent turn is running, toggle thinking incrementally instead:
TranscriptView.apply_thinking_visibility reconciles only the thinking
widgets against state.items, never remounting the active assistant
streaming widget, and preserves _follow_output across the reflow via a
suppress guard plus a deferred settle-follow-scroll callback. The idle
toggle still uses the full _refresh redraw.

Adds regression tests for follow-scroll, user scrollback, multi-block
thinking, and the end-to-end streaming event path.
Explains the #175 re-regression, root cause (full redraw on toggle during
streaming), the incremental apply_thinking_visibility fix, and how to test
the regression suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant