@@ -72,6 +72,10 @@ function findActiveStreamForChat(chatId: string) {
7272 return undefined ;
7373}
7474
75+ // Top-level hook that wires together the streaming pipeline for a single chat view.
76+ // Composes useStreamCallbacks (envelope processing), useStreamReconnect (resume on
77+ // navigation), useMessageActions (send/stop), and useInputState (draft persistence).
78+ // Returns the full set of state and handlers consumed by the chat UI components.
7579export function useChatStreaming ( {
7680 chatId,
7781 currentChat,
@@ -152,10 +156,13 @@ export function useChatStreaming({
152156 onPendingUserMessageIdChange : setPendingUserMessageIdState ,
153157 } ) ;
154158
159+ // Keep the global stream store's callbacks in sync with the latest hook closures.
160+ // Streams outlive individual renders, so without this the store would dispatch
161+ // events through stale callbacks that close over outdated chatId/messages.
155162 useEffect ( ( ) => {
156163 if ( ! chatId ) return ;
157164
158- const updateCallbacks = ( ) => {
165+ const syncCallbacksToStore = ( ) => {
159166 const existingStream = findActiveStreamForChat ( chatId ) ;
160167 if ( existingStream ) {
161168 useStreamStore . getState ( ) . updateStreamCallbacks ( chatId , existingStream . messageId , {
@@ -167,13 +174,13 @@ export function useChatStreaming({
167174 }
168175 } ;
169176
170- updateCallbacks ( ) ;
177+ syncCallbacksToStore ( ) ;
171178
172179 let prevStreams = useStreamStore . getState ( ) . activeStreams ;
173180 const unsubscribe = useStreamStore . subscribe ( ( state ) => {
174181 if ( state . activeStreams !== prevStreams ) {
175182 prevStreams = state . activeStreams ;
176- updateCallbacks ( ) ;
183+ syncCallbacksToStore ( ) ;
177184 }
178185 } ) ;
179186 return ( ) => unsubscribe ( ) ;
@@ -189,10 +196,13 @@ export function useChatStreaming({
189196 setMessages ( [ ] ) ;
190197 }
191198
199+ // Subscribes to the stream store and mirrors active-stream presence into
200+ // local React state (streamState, currentMessageId). This is the bridge
201+ // between the global EventSource lifecycle and the per-chat UI indicators.
192202 useEffect ( ( ) => {
193203 if ( ! chatId ) return ;
194204
195- const syncStreamState = ( ) => {
205+ const reconcileStreamState = ( ) => {
196206 const activeStreamForChat = findActiveStreamForChat ( chatId ) ;
197207
198208 if ( activeStreamForChat ) {
@@ -215,9 +225,9 @@ export function useChatStreaming({
215225 }
216226 } ;
217227
218- syncStreamState ( ) ;
228+ reconcileStreamState ( ) ;
219229
220- const unsubscribe = useStreamStore . subscribe ( syncStreamState ) ;
230+ const unsubscribe = useStreamStore . subscribe ( reconcileStreamState ) ;
221231 return ( ) => unsubscribe ( ) ;
222232 } , [ chatId ] ) ;
223233
@@ -261,7 +271,10 @@ export function useChatStreaming({
261271 replayStream,
262272 } ) ;
263273
264- const handleStopStream = useCallback (
274+ // Sends stop requests for one or all active streams in the current chat.
275+ // Immediately marks the UI as idle (optimistic) and tracks pending stops
276+ // so incoming envelopes for the stopping stream are ignored.
277+ const stopActiveStreams = useCallback (
265278 async ( messageId ?: string ) => {
266279 const pendingIds = new Set < string > ( ) ;
267280 const stopPromises : Promise < void > [ ] = [ ] ;
@@ -306,9 +319,9 @@ export function useChatStreaming({
306319 } , [ currentMessageId ] ) ;
307320
308321 const handleStop = useCallback ( ( ) => {
309- void handleStopStream ( currentMessageIdRef . current || undefined ) ;
322+ void stopActiveStreams ( currentMessageIdRef . current || undefined ) ;
310323 clearInput ( ) ;
311- } , [ handleStopStream , clearInput ] ) ;
324+ } , [ stopActiveStreams , clearInput ] ) ;
312325
313326 useMountEffect ( ( ) => {
314327 cleanupExpiredPdfBlobs ( ) ;
0 commit comments