diff --git a/.jules/bolt.md b/.jules/bolt.md index 427be48..6763a17 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -67,3 +67,6 @@ ## 2025-05-17 - Replace O(N²) array search with O(N) Set lookup in session listing **Learning:** Using `array.some(...)` with string operations (like `replace()`) inside a loop over files creates an unnecessary O(N²) performance bottleneck, particularly when reading directories with many files (like the sessions history directory). **Action:** When filtering files against another list of files, pre-compute a `Set` of base names outside the loop for O(1) lookups, changing the overall time complexity from O(N²) to O(N). +## 2023-10-27 - O(N) Re-renders in List Components +**Learning:** React list components mapped from arrays (e.g. `sessions.map(...)`) will experience severe O(N) re-renders when a single element changes state if the evaluation is done inline inside the map without memoization. Passing the active check (e.g., `isActive={activeSessionId === s.id}`) down to a `React.memo` wrapper component is necessary to convert this into an O(1) re-render (only the two affected components will update). +**Action:** When creating high-frequency updating list elements with individual active states in React, extract the items into `React.memo` components and pass simple boolean flags to prevent massive VDOM updates. diff --git a/packages/desktop/src/renderer/components/ChatHistory.tsx b/packages/desktop/src/renderer/components/ChatHistory.tsx index 3cf5798..f016985 100644 --- a/packages/desktop/src/renderer/components/ChatHistory.tsx +++ b/packages/desktop/src/renderer/components/ChatHistory.tsx @@ -31,6 +31,44 @@ function relativeTime(iso: string): string { return new Date(iso).toLocaleDateString(); } +const ChatHistoryItem = memo(function ChatHistoryItem({ + session, + isActive, + onSelect, + onDelete +}: { + session: SessionItem; + isActive: boolean; + onSelect: (id: string) => void; + onDelete: (e: React.MouseEvent, id: string) => void; +}) { + return ( + + ); +}); + const ChatHistory = memo(function ChatHistory({ activeSessionId, onSelectSession, onNewChat }: ChatHistoryProps) { const [sessions, setSessions] = useState([]); @@ -41,11 +79,11 @@ const ChatHistory = memo(function ChatHistory({ activeSessionId, onSelectSession useEffect(() => { refresh(); }, [refresh]); - const handleDelete = async (e: React.MouseEvent, id: string) => { + const handleDelete = useCallback(async (e: React.MouseEvent, id: string) => { e.stopPropagation(); await xibe.session.delete(id); refresh(); - }; + }, [refresh]); const today = new Date(); today.setHours(0, 0, 0, 0); @@ -89,30 +127,13 @@ const ChatHistory = memo(function ChatHistory({ activeSessionId, onSelectSession
{group.label}
{group.items.map((s) => ( - + session={s} + isActive={activeSessionId === s.id} + onSelect={onSelectSession} + onDelete={handleDelete} + /> ))}