From b90c17a919004978fc35519f4c433516c1953d95 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 9 Jan 2026 18:00:34 +0700 Subject: [PATCH] perf: optimize agent stream view rendering - Separate streaming item from FlatList data array to prevent full re-diffs on every streaming chunk - Memoize renderStreamItem with useCallback - Memoize AgentThoughtMessage component with React.memo The main fix is rendering the streaming message in ListHeaderComponent instead of prepending it to the data array, which was causing the entire list to re-diff on every character streamed. --- .../app/src/components/agent-stream-view.tsx | 201 +++++++++--------- packages/app/src/components/message.tsx | 4 +- 2 files changed, 102 insertions(+), 103 deletions(-) diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 515a8154f..3f1013143 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -240,96 +240,99 @@ export function AgentStreamView({ setIsNearBottom(true); } - function renderStreamItem({ item }: ListRenderItemInfo) { - let content: React.ReactNode = null; + const renderStreamItem = useCallback( + ({ item }: ListRenderItemInfo) => { + let content: React.ReactNode = null; - switch (item.kind) { - case "user_message": - content = ( - - ); - break; - - case "assistant_message": - content = ( - - ); - break; - - case "thought": - content = ; - break; - - case "tool_call": { - const { payload } = item; - - if (payload.source === "agent") { - const data = payload.data; + switch (item.kind) { + case "user_message": content = ( - ); - } else { - const data = payload.data; + break; + + case "assistant_message": content = ( - ); + break; + + case "thought": + content = ; + break; + + case "tool_call": { + const { payload } = item; + + if (payload.source === "agent") { + const data = payload.data; + content = ( + + ); + } else { + const data = payload.data; + content = ( + + ); + } + break; } - break; + + case "activity_log": + content = ( + + ); + break; + + case "todo_list": + content = ( + + ); + break; + + default: + content = null; } - case "activity_log": - content = ( - - ); - break; + if (!content) { + return null; + } - case "todo_list": - content = ( - - ); - break; - - default: - content = null; - } - - if (!content) { - return null; - } - - return {content}; - } + return {content}; + }, + [handleInlinePathPress, agent.cwd] + ); const pendingPermissionItems = useMemo( () => @@ -339,30 +342,20 @@ export function AgentStreamView({ [pendingPermissions, agentId] ); - const streamingItem = useMemo(() => { - if (!streamingBuffer) { - return null; - } - return { - kind: "assistant_message", - id: streamingBuffer.id, - text: streamingBuffer.text, - timestamp: streamingBuffer.timestamp, - }; - }, [streamingBuffer]); - - const streamingItemId = streamingItem?.id ?? null; const showWorkingIndicator = agent.status === "running"; const listHeaderComponent = useMemo(() => { - if (pendingPermissionItems.length === 0 && !showWorkingIndicator) { + const hasPermissions = pendingPermissionItems.length > 0; + const hasStreaming = !!streamingBuffer; + + if (!hasPermissions && !showWorkingIndicator && !hasStreaming) { return null; } return ( - {pendingPermissionItems.length > 0 ? ( + {hasPermissions ? ( {pendingPermissionItems.map((permission) => ( @@ -375,18 +368,24 @@ export function AgentStreamView({ ) : null} + + {hasStreaming ? ( + + + + ) : null} ); - }, [pendingPermissionItems, showWorkingIndicator, wsOrInert]); + }, [pendingPermissionItems, showWorkingIndicator, wsOrInert, streamingBuffer, handleInlinePathPress]); const flatListData = useMemo(() => { - const reversed = [...streamItems].reverse(); - if (streamingItem) { - reversed.unshift(streamingItem); - } - return reversed; - }, [streamItems, streamingItem]); + return [...streamItems].reverse(); + }, [streamItems]); const flatListExtraData = useMemo( () => ({ diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index e96c099a3..52fb8a180 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -963,7 +963,7 @@ const agentThoughtStylesheet = StyleSheet.create((theme) => ({ }, })); -export function AgentThoughtMessage({ message, status = "ready" }: AgentThoughtMessageProps) { +export const AgentThoughtMessage = memo(function AgentThoughtMessage({ message, status = "ready" }: AgentThoughtMessageProps) { const { theme } = useUnistyles(); const [isExpanded, setIsExpanded] = useState(false); const markdownContent = useMemo(() => message?.trim() ?? "", [message]); @@ -1102,7 +1102,7 @@ export function AgentThoughtMessage({ message, status = "ready" }: AgentThoughtM isLoading={status !== "ready"} /> ); -} +}); interface ToolCallProps { toolName: string;