From 63d0fa8cbe75d4a3752232850e717325f6de34a0 Mon Sep 17 00:00:00 2001 From: iotserver24 <147928812+iotserver24@users.noreply.github.com> Date: Tue, 5 May 2026 18:57:41 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Use=20useMemo=20for=20mappi?= =?UTF-8?q?ng=20messages=20array=20to=20prevent=20O(N)=20VDOM=20recreation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted the `messages.map` logic in `ChatPanel` into a `useMemo` block. This prevents the entire array from being re-mapped four times a second when `App.tsx` sends the `runElapsed` prop updates. --- .jules/bolt.md | 4 +++ .../src/renderer/components/ChatPanel.tsx | 33 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 678e65a..afcab34 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -35,3 +35,7 @@ ## 2024-05-04 - Unnecessary Timer Re-renders **Learning:** App.tsx has a 250ms setInterval updating state (runElapsed) while the agent is running, which forces the entire app tree (including large lists in FileExplorer and ChatPanel) to re-render 4 times a second. **Action:** Always wrap large list components and recursive tree components (like FileExplorer's Row) in React.memo() when they are descendants of a component with high-frequency state updates. Also use useMemo for mapping large lists (like chat messages) to prevent unnecessary VDOM recreation. + +## 2024-05-18 - [O(N) VDOM Recreation in ChatPanel with React] +**Learning:** Even if list item components (like `MessageBubble` and `ToolCallCard`) are memoized using `React.memo()`, mapping over a large array of messages inside the render cycle of a component that receives high-frequency prop updates (like `runElapsed` from a 250ms `setInterval`) still creates O(N) evaluations and recreates VDOM elements. This adds significant garbage collection overhead and reduces responsiveness. +**Action:** Always wrap the array mapping logic (`messages.map(...)`) itself with `useMemo` in parent components to completely prevent the O(N) iteration and VDOM element recreation on unrelated state changes. diff --git a/packages/desktop/src/renderer/components/ChatPanel.tsx b/packages/desktop/src/renderer/components/ChatPanel.tsx index 9b7e03e..88640ee 100644 --- a/packages/desktop/src/renderer/components/ChatPanel.tsx +++ b/packages/desktop/src/renderer/components/ChatPanel.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useMemo } from 'react'; import { Send, Terminal, Zap, BookOpen } from 'lucide-react'; import type { ChatMessage } from '../App'; import type { ModeState } from '../../preload/index'; @@ -79,6 +79,23 @@ export default function ChatPanel({ if (inputRef.current) inputRef.current.style.height = 'auto'; }; + // ⚡ Bolt: Memoize the mapping of messages to prevent O(N) VDOM recreation on frequent timer ticks (e.g. runElapsed) + const renderedMessages = useMemo(() => { + return messages.map((msg) => + msg.role === 'tool' && msg.toolName ? ( + + ) : msg.role === 'info' ? ( +
+ {msg.content} +
+ ) : msg.role === 'error' ? ( +
{msg.content}
+ ) : ( + + ), + ); + }, [messages]); + return (
{/* Messages area */} @@ -141,19 +158,7 @@ export default function ChatPanel({
) : (
- {messages.map((msg) => - msg.role === 'tool' && msg.toolName ? ( - - ) : msg.role === 'info' ? ( -
- {msg.content} -
- ) : msg.role === 'error' ? ( -
{msg.content}
- ) : ( - - ), - )} + {renderedMessages} {isRunning && (