fix(chat): reduce rendering jank during streaming #181
+80
−147
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The chat view had several compounding issues causing visible jank during streaming and full conversation clears on network blips.
The scroll logic used three refs, a
scrollendlistener, and multiple heuristics that fought each other. Thescrollendlistener used anonymous arrow functions, soremoveEventListenernever matched and cleanup never ran. This leftisProgrammaticScrollRefstucktrue, preventingisAtBottomReffrom updating on user scroll events. The user could never scroll away to read earlier context.transition-all duration-300on the message container animated height changes during streaming, creating wobbly layout. The URL detection regex used a/gflag, making it stateful acrosssplit/testcalls and causing links to flicker between renders.Replaced the scroll machinery with direct
scrollTopassignment gated on a singleisAtBottomref updated unconditionally on every scroll event. This runs inuseLayoutEffectbefore paint, avoids animation conflicts, and respects user scroll position. Sending a message always scrolls to bottom and re-enables auto-scroll so the agent response stays visible.Separately,
setMessages([])ran on every SSE reconnect, blanking the conversation on any network blip. Manual reconnect logic inonerroralso fought the browser's built-in SSE reconnection. Thefinallyblock insendMessagefiltered out all draft messages, briefly removing the user's message before the server confirmed it.Now the browser handles SSE reconnection natively, the message list is never cleared, and drafts are replaced inline when the confirmed message arrives. Failed sends clean up their draft in the error paths so ghost messages don't linger.