diff --git a/src/hooks/useAutoScroll.ts b/src/hooks/useAutoScroll.ts index 091b49aa9..540adf47b 100644 --- a/src/hooks/useAutoScroll.ts +++ b/src/hooks/useAutoScroll.ts @@ -24,12 +24,16 @@ export function useAutoScroll() { const performAutoScroll = useCallback(() => { if (!contentRef.current) return; + // Double RAF: First frame for DOM updates (e.g., DiffRenderer async highlighting), + // second frame to scroll after layout is complete requestAnimationFrame(() => { - // Check ref.current not state - avoids race condition where queued frames - // execute after user scrolls up but still see old autoScroll=true - if (contentRef.current && autoScrollRef.current) { - contentRef.current.scrollTop = contentRef.current.scrollHeight; - } + requestAnimationFrame(() => { + // Check ref.current not state - avoids race condition where queued frames + // execute after user scrolls up but still see old autoScroll=true + if (contentRef.current && autoScrollRef.current) { + contentRef.current.scrollTop = contentRef.current.scrollHeight; + } + }); }); }, []); // No deps - ref ensures we always check current value diff --git a/src/utils/messages/StreamingMessageAggregator.ts b/src/utils/messages/StreamingMessageAggregator.ts index 40b6d1d24..1e8b2efb5 100644 --- a/src/utils/messages/StreamingMessageAggregator.ts +++ b/src/utils/messages/StreamingMessageAggregator.ts @@ -84,6 +84,18 @@ export class StreamingMessageAggregator { return this.recencyTimestamp; } + /** + * Check if two TODO lists are equal (deep comparison). + * Prevents unnecessary re-renders when todo_write is called with identical content. + */ + private todosEqual(a: TodoItem[], b: TodoItem[]): boolean { + if (a.length !== b.length) return false; + return a.every((todoA, i) => { + const todoB = b[i]; + return todoA.content === todoB.content && todoA.status === todoB.status; + }); + } + /** * Get the current TODO list. * Updated whenever todo_write succeeds. @@ -438,7 +450,10 @@ export class StreamingMessageAggregator { data.result.success ) { const args = toolPart.input as { todos: TodoItem[] }; - this.currentTodos = args.todos; + // Only update if todos actually changed (prevents flickering from reference changes) + if (!this.todosEqual(this.currentTodos, args.todos)) { + this.currentTodos = args.todos; + } } } this.invalidateCache();