Skip to content

fix(tui): restore auto-scroll during streaming#506

Closed
avoidwork wants to merge 4 commits into
mainfrom
fix/auto-scroll
Closed

fix(tui): restore auto-scroll during streaming#506
avoidwork wants to merge 4 commits into
mainfrom
fix/auto-scroll

Conversation

@avoidwork

Copy link
Copy Markdown
Owner

Problem

Commit 6a89549 extracted createStreamingHandler and finalizeStreaming helpers, eliminating duplicated callback logic. In the process, the setMessages() calls that triggered React re-renders were removed. The streaming handlers now mutate messagesRef.current directly, but without setMessages, React never re-renders and the scroll effect in ConversationPanel never fires.

Restoring setMessages defeats the useRef optimization that was the whole point of the refactor.

Fix

Trigger scroll-to-bottom from the setInterval that already drives the render tick. It reads messagesRef.current each frame and checks streaming state — no state churn, no re-render overhead.

Changes

  • src/tui/app.js: Added scroll-to-bottom check in the render tick interval when the last message is streaming

avoidwork added 4 commits July 2, 2026 18:47
The refactoring in 6a89549 extracted createStreamingHandler and
finalizeStreaming but accidentally removed the setMessages() calls
that trigger React re-renders. Without setMessages, the messages
state never updates, so the scroll effect in conversationPanel.js
never fires.

Added setMessages(cloned) to both createStreamingHandler and
finalizeStreaming to restore the re-render pipeline.
The refactoring in 6a89549 extracted createStreamingHandler and
finalizeStreaming but removed the setMessages() calls that triggered
re-renders. Restoring setMessages defeats the useRef optimization
that was the whole point of the refactor.

Instead, trigger scroll-to-bottom from the setInterval that already
drives the render tick — it reads messagesRef.current each frame and
can check streaming state without any state churn.
The refactoring in 6a89549 extracted createStreamingHandler and
finalizeStreaming but removed the setMessages() calls. The handlers
now mutate messagesRef.current directly, but without a state update
React never re-renders and the scroll effect never fires.

Adding setMessages() in the streaming handlers defeats the useRef
optimization. Instead, call setMessages(messagesRef.current) from
the existing setInterval that already drives the render tick — this
keeps the 30fps cadence, creates a new array reference each frame
so the scroll effect detects changes, and avoids per-event state
churn.
Replace the messages-state-dependent scroll effect with ScrollView's
onContentHeightChange callback. The previous approach tracked message
count and streaming content hash, but never fired during streaming
because messagesRef.current mutations don't change the messages
array reference.

onContentHeightChange fires when ink-scroll-view's internal content
height actually changes — driven by MeasurableItem useLayoutEffect
measurements — so it catches both streaming text growth and new
messages without any state churn.
@avoidwork avoidwork closed this Jul 2, 2026
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