fix(chat): stream updates never re-rendered — mutate the assistant message via its reactive proxy#161
Merged
Merged
Conversation
…ssage via its reactive proxy The SSE reducer mutated the raw placeholder object captured before messages.value.push(). Raw-target writes bypass Vue's proxies, so no effect ever fired during a stream: the message list stayed frozen on the Thinking spinner and the entire turn popped in at once when isStreaming flipped at the end. This predates the segment refactor — it is why live streaming never visibly worked. Read the placeholder back through the reactive array before handing it to the reducer, so every segment push and tool-call update triggers a re-render (and the streamTick scroll-follow actually has something to follow). Regression-tested with a sync-flush watcher that counts reactive updates during a mocked stream.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
Staging test after #159/#160: the final render is correct (chronological segments), but the live stream still showed only the Thinking spinner, with the whole turn popping in at once at the end.
Root cause is a Vue reactivity bug that predates the segment refactor — live streaming never actually rendered: `sendMessage` captured the assistant placeholder before `messages.value.push(...)` and the SSE reducer mutated that raw object. Raw-target writes bypass Vue's proxies, so no effect ever fired during the stream; the list only re-rendered when `isStreaming` flipped in `finally`.
Proven by a sync-flush watcher over the proxied message: on the old code it fires exactly once (the placeholder append) for a 4-event stream; with the fix it fires on every content event.
Change
One-liner in spirit: read the placeholder back through the reactive array (`messages.value[messages.value.length - 1]`) before handing it to `handleSSEEvent` and the abort/error checks. Every segment push and tool-call update now goes through the proxy → the list re-renders mid-stream and the `streamTick` scroll-follow has something to follow.
Test
🤖 Generated with Claude Code