From 3533aa3358f555cbe9b213a8ec91d518b0e29faa Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Thu, 5 Mar 2026 19:09:34 -0800 Subject: [PATCH] feat(web): persist file tree expansion and add column visibility callbacks Save expanded file tree paths to localStorage so they survive page reloads, and surface an onColumnVisibilityChanged callback from DataTable/ObjectTable. --- .../app/components/workspace/data-table.tsx | 8 +++- .../workspace/file-manager-tree.tsx | 38 +++++++++++-------- .../app/components/workspace/object-table.tsx | 3 ++ 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/apps/web/app/components/workspace/data-table.tsx b/apps/web/app/components/workspace/data-table.tsx index 9632c29cfb6..06a7cd76c93 100644 --- a/apps/web/app/components/workspace/data-table.tsx +++ b/apps/web/app/components/workspace/data-table.tsx @@ -60,6 +60,7 @@ export type DataTableProps = { enableColumnReordering?: boolean; onColumnReorder?: (newOrder: string[]) => void; initialColumnVisibility?: VisibilityState; + onColumnVisibilityChanged?: (visibility: VisibilityState) => void; // pagination pageSize?: number; // actions @@ -173,6 +174,7 @@ export function DataTable({ enableColumnReordering = false, onColumnReorder, initialColumnVisibility, + onColumnVisibilityChanged, pageSize: defaultPageSize = 100, onRefresh, onAdd, @@ -345,7 +347,11 @@ export function DataTable({ onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, + onColumnVisibilityChange: (updater) => { + const next = typeof updater === "function" ? updater(columnVisibility) : updater; + setColumnVisibility(next); + onColumnVisibilityChanged?.(next); + }, onRowSelectionChange: (updater) => { if (onRowSelectionChange) { onRowSelectionChange(updater); diff --git a/apps/web/app/components/workspace/file-manager-tree.tsx b/apps/web/app/components/workspace/file-manager-tree.tsx index a01694d0dc5..5af3eeb5f02 100644 --- a/apps/web/app/components/workspace/file-manager-tree.tsx +++ b/apps/web/app/components/workspace/file-manager-tree.tsx @@ -716,8 +716,27 @@ function flattenVisible(tree: TreeNode[], expanded: Set): TreeNode[] { // --- Main Exported Component --- +const STORAGE_KEY = "denchclaw-tree-expanded"; + +function loadExpandedPaths(): Set { + try { + const raw = window.localStorage.getItem(STORAGE_KEY); + if (raw) { + const arr = JSON.parse(raw) as string[]; + if (Array.isArray(arr)) return new Set(arr); + } + } catch { /* ignore corrupt data */ } + return new Set(); +} + +function saveExpandedPaths(paths: Set) { + try { + window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...paths])); + } catch { /* storage full or unavailable */ } +} + export function FileManagerTree({ tree, activePath, onSelect, onRefresh, compact, parentDir, onNavigateUp, browseDir: _browseDir, workspaceRoot, onExternalDrop }: FileManagerTreeProps) { - const [expandedPaths, setExpandedPaths] = useState>(() => new Set()); + const [expandedPaths, setExpandedPaths] = useState>(() => loadExpandedPaths()); const [selectedPath, setSelectedPath] = useState(null); const [renamingPath, setRenamingPath] = useState(null); const [dragOverPath, setDragOverPath] = useState(null); @@ -771,21 +790,10 @@ export function FileManagerTree({ tree, activePath, onSelect, onRefresh, compact const containerRef = useRef(null); - // Auto-expand first level on mount. - // Keep ~skills and ~memories collapsed by default; always expand ~chats. - const collapsedByDefault = new Set(["~skills", "~memories"]); + // Persist expanded paths to localStorage whenever they change useEffect(() => { - if (tree.length > 0 && expandedPaths.size === 0) { - const initial = new Set(); - for (const node of tree) { - if (collapsedByDefault.has(node.path)) {continue;} - if (node.children && node.children.length > 0) { - initial.add(node.path); - } - } - setExpandedPaths(initial); - } - }, [tree]); + saveExpandedPaths(expandedPaths); + }, [expandedPaths]); const handleToggleExpand = useCallback((path: string) => { setExpandedPaths((prev) => { diff --git a/apps/web/app/components/workspace/object-table.tsx b/apps/web/app/components/workspace/object-table.tsx index 991b236479b..bfb7d2d3c95 100644 --- a/apps/web/app/components/workspace/object-table.tsx +++ b/apps/web/app/components/workspace/object-table.tsx @@ -50,6 +50,7 @@ type ObjectTableProps = { onRefresh?: () => void; /** Column visibility state keyed by field ID. */ columnVisibility?: Record; + onColumnVisibilityChanged?: (visibility: Record) => void; /** Server-side pagination props. */ serverPagination?: ServerPaginationProps; /** Server-side search callback. */ @@ -440,6 +441,7 @@ export function ObjectTable({ onEntryClick, onRefresh, columnVisibility, + onColumnVisibilityChanged, serverPagination, onServerSearch, }: ObjectTableProps) { @@ -711,6 +713,7 @@ export function ObjectTable({ rowActions={getRowActions} stickyFirstColumn initialColumnVisibility={columnVisibility} + onColumnVisibilityChanged={onColumnVisibilityChanged} serverPagination={serverPagination} onServerSearch={onServerSearch} />