From 87a6e71aa93997b548bc0aea6541fba330ab6b02 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 31 Jan 2026 17:50:21 +0000 Subject: [PATCH] fix(workspace): improve file tree refresh behavior and state management Fix file tree not properly resetting when refreshing workspace directory. Add useEffect hook to reset children state when entry path changes, preventing stale data display. Update handleRefresh to properly clear cache before reload and handle cases where selected files are deleted during refresh. Remove children.length check from loadChildren to allow directory re-expansion after refresh. --- src/components/workspace/FileTree.tsx | 12 +++-- src/routes/workspace/index.tsx | 73 ++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/components/workspace/FileTree.tsx b/src/components/workspace/FileTree.tsx index 658c5c8..7ff6fa6 100644 --- a/src/components/workspace/FileTree.tsx +++ b/src/components/workspace/FileTree.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react' +import { useState, useCallback, useEffect } from 'react' import { ChevronRight, ChevronDown, Folder, FolderOpen, FileText } from 'lucide-react' import { motion, AnimatePresence } from 'framer-motion' import type { DirectoryEntry } from '~/lib/workspace-fs' @@ -33,12 +33,18 @@ function FileTreeItem({ entry, selectedPath, onSelect, onLoadDirectory, level }: const [expanded, setExpanded] = useState(false) const [children, setChildren] = useState([]) const [loading, setLoading] = useState(false) + + // Reset children when entry path changes (e.g., on refresh) + useEffect(() => { + setChildren([]) + setExpanded(false) + }, [entry.path]) const isSelected = selectedPath === entry.path const isDirectory = entry.type === 'directory' const paddingLeft = level * 16 + 8 const loadChildren = useCallback(async () => { - if (!isDirectory || children.length > 0 || !onLoadDirectory) return + if (!isDirectory || !onLoadDirectory) return setLoading(true) try { const entries = await onLoadDirectory(entry.path) @@ -48,7 +54,7 @@ function FileTreeItem({ entry, selectedPath, onSelect, onLoadDirectory, level }: } finally { setLoading(false) } - }, [entry.path, isDirectory, children.length, onLoadDirectory]) + }, [entry.path, isDirectory, onLoadDirectory]) const handleToggle = useCallback( async (e: React.MouseEvent) => { diff --git a/src/routes/workspace/index.tsx b/src/routes/workspace/index.tsx index e06f715..9f77f4c 100644 --- a/src/routes/workspace/index.tsx +++ b/src/routes/workspace/index.tsx @@ -235,15 +235,74 @@ function WorkspacePage() { const handleRefresh = useCallback(async () => { if (!workspacePath || !pathValid) return - // Clear cache and reload - setPathCache(new Map()) - await loadDirectory(workspacePath) + // Store current selection before clearing cache + const currentSelectedPath = selectedPath - // Reload selected file if any - if (selectedPath) { - await loadFile(selectedPath) + // Clear cache first, then reload + // Use a callback to ensure cache is cleared before loading + setPathCache(new Map()) + + // Small delay to ensure React has processed the state update + // before we try to load the directory + await new Promise(resolve => setTimeout(resolve, 0)) + + // Reload root directory - this will repopulate the file tree + // Force reload by bypassing cache check + setLoading(true) + try { + const result = await trpc.workspace.listDirectory.query({ + workspaceRoot: workspacePath, + path: workspacePath, + }) + + if (result.error) { + throw new Error(result.error) + } + + // Update cache with fresh data + setPathCache(new Map([[workspacePath, result.entries]])) + } catch (error) { + console.error('Failed to load directory:', error) + } finally { + setLoading(false) } - }, [workspacePath, pathValid, selectedPath]) + + // Reload selected file if any (with error handling for deleted files) + if (currentSelectedPath) { + try { + const result = await trpc.workspace.readFile.query({ + workspaceRoot: workspacePath, + path: currentSelectedPath, + }) + + if (result.error) { + // File no longer exists - clear selection gracefully + setSelectedPath(null) + setSelectedFileContent('') + setSelectedFileName('') + setSelectedFileSize(undefined) + setSelectedFileModified(undefined) + setFileError(result.error) + } else { + setSelectedFileContent(result.content) + setSelectedFileName(result.name) + // Get file metadata from the parent directory entry if available + const parentDir = pathCache.get(getParentDirPath(currentSelectedPath) || workspacePath) + const fileEntry = parentDir?.find(e => e.path === currentSelectedPath) + setSelectedFileSize(fileEntry?.size) + setSelectedFileModified(fileEntry?.modifiedAt) + } + } catch (error) { + // File no longer exists - clear selection gracefully + setSelectedPath(null) + setSelectedFileContent('') + setSelectedFileName('') + setSelectedFileSize(undefined) + setSelectedFileModified(undefined) + setFileError(error instanceof Error ? error.message : 'Failed to read file') + } + } + }, [workspacePath, pathValid, selectedPath, loadFile]) const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') {