diff --git a/src/renderer/src/components/panels/RightPanel.tsx b/src/renderer/src/components/panels/RightPanel.tsx index a938902..906ffa8 100644 --- a/src/renderer/src/components/panels/RightPanel.tsx +++ b/src/renderer/src/components/panels/RightPanel.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useEffect, useMemo } from "react" +import { useState, useRef, useCallback, useEffect, useMemo, memo } from "react" import { ListTodo, FolderTree, @@ -746,15 +746,11 @@ function buildFileTree(files: FileInfo[]): TreeNode[] { } function FileTree({ files }: { files: FileInfo[] }): React.JSX.Element { + const { currentThreadId } = useAppStore() + const threadState = useThreadState(currentThreadId) + const openFile = threadState?.openFile const tree = useMemo(() => buildFileTree(files), [files]) - const [expanded, setExpanded] = useState>(() => { - // Start with all directories expanded - const dirs = new Set() - files.forEach((f) => { - if (f.is_dir ?? false) dirs.add(f.path) - }) - return dirs - }) + const [expanded, setExpanded] = useState>(new Set()) const toggleExpand = useCallback((path: string) => { setExpanded((prev) => { @@ -777,91 +773,109 @@ function FileTree({ files }: { files: FileInfo[] }): React.JSX.Element { depth={0} expanded={expanded} onToggle={toggleExpand} + openFile={openFile} /> ))} ) } -function FileTreeNode({ - node, - depth, - expanded, - onToggle -}: { - node: TreeNode - depth: number - expanded: Set - onToggle: (path: string) => void -}): React.JSX.Element { - const { currentThreadId } = useAppStore() - const threadState = useThreadState(currentThreadId) - const openFile = threadState?.openFile - const isExpanded = expanded.has(node.path) - const hasChildren = node.children.length > 0 - const paddingLeft = 8 + depth * 16 +const FileTreeNode = memo( + function FileTreeNode({ + node, + depth, + expanded, + onToggle, + openFile + }: { + node: TreeNode + depth: number + expanded: Set + onToggle: (path: string) => void + openFile?: (path: string, name: string) => void + }): React.JSX.Element { + const isExpanded = expanded.has(node.path) + const hasChildren = node.children.length > 0 + const paddingLeft = 8 + depth * 16 - const handleClick = (): void => { - if (node.is_dir) { - onToggle(node.path) - } else if (openFile) { - // Open file in a new tab - openFile(node.path, node.name) + const handleClick = (): void => { + if (node.is_dir) { + onToggle(node.path) + } else if (openFile) { + // Open file in a new tab + openFile(node.path, node.name) + } } + + return ( + <> +
+ {/* Expand/collapse chevron for directories */} + {node.is_dir ? ( + + {hasChildren && + (isExpanded ? ( + + ) : ( + + ))} + + ) : ( + + )} + + {/* Icon */} + + + {/* Name */} + {node.name} + + {/* Size for files */} + {!node.is_dir && node.size !== undefined && ( + + {formatSize(node.size)} + + )} +
+ + {/* Children */} + {node.is_dir && + isExpanded && + node.children.map((child) => ( + + ))} + + ) + }, + (prevProps, nextProps) => { + // Only re-render if: + // 1. The node itself changed + // 2. The expansion state of THIS node changed + // 3. The openFile callback changed + // 4. The onToggle callback changed + return ( + prevProps.node === nextProps.node && + prevProps.expanded.has(prevProps.node.path) === + nextProps.expanded.has(nextProps.node.path) && + prevProps.openFile === nextProps.openFile && + prevProps.onToggle === nextProps.onToggle && + prevProps.depth === nextProps.depth + ) } - - return ( - <> -
- {/* Expand/collapse chevron for directories */} - {node.is_dir ? ( - - {hasChildren && - (isExpanded ? ( - - ) : ( - - ))} - - ) : ( - - )} - - {/* Icon */} - - - {/* Name */} - {node.name} - - {/* Size for files */} - {!node.is_dir && node.size !== undefined && ( - - {formatSize(node.size)} - - )} -
- - {/* Children */} - {node.is_dir && - isExpanded && - node.children.map((child) => ( - - ))} - - ) -} +) function FileIcon({ name,