Skip to content

Commit d52e0a0

Browse files
committed
Fix initial file tree expansion persistence
1 parent b96c04c commit d52e0a0

2 files changed

Lines changed: 112 additions & 1 deletion

File tree

packages/web/src/features/workspace/views/shared/file-tree-panel.test.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,104 @@ describe("FileTreePanel", () => {
267267
expect(store.get(expandedDirsAtomFamily("ws-test"))).toBeNull();
268268
});
269269

270+
it("preserves default root expansion when the user first expands a non-default directory", async () => {
271+
const sendCommand = vi
272+
.fn()
273+
.mockImplementation(async (op: string, args?: { subPath?: string }) => {
274+
if (op === "workspace.uiState.set") {
275+
return {
276+
id: "ws-test",
277+
path: "/workspace",
278+
targetRuntime: "native",
279+
openedAt: 1,
280+
lastActiveAt: 1,
281+
uiState: args?.uiState,
282+
};
283+
}
284+
285+
if (args?.subPath === "src") {
286+
return {
287+
path: "src",
288+
children: [{ path: "src/index.ts", name: "index.ts", kind: "file" }],
289+
};
290+
}
291+
292+
if (args?.subPath === "lib") {
293+
return {
294+
path: "lib",
295+
children: [{ path: "lib/foo.ts", name: "foo.ts", kind: "file" }],
296+
};
297+
}
298+
299+
return {
300+
path: "/workspace",
301+
children: [
302+
{ path: "lib", name: "lib", kind: "dir" },
303+
{ path: "src", name: "src", kind: "dir" },
304+
],
305+
};
306+
});
307+
const store = createStore();
308+
store.set(wsClientAtom, { sendCommand } as never);
309+
store.set(workspacesAtom, {
310+
"ws-test": {
311+
id: "ws-test",
312+
path: "/workspace",
313+
targetRuntime: "native",
314+
openedAt: 1,
315+
lastActiveAt: 1,
316+
uiState: {
317+
leftPanelWidth: 280,
318+
bottomPanelHeight: 200,
319+
focusMode: false,
320+
},
321+
},
322+
} as never);
323+
store.set(
324+
fileTreeAtomFamily("ws-test"),
325+
new Map([
326+
[
327+
".",
328+
[
329+
{
330+
path: "lib",
331+
name: "lib",
332+
kind: "dir",
333+
},
334+
{
335+
path: "src",
336+
name: "src",
337+
kind: "dir",
338+
},
339+
],
340+
],
341+
])
342+
);
343+
344+
render(
345+
<Provider store={store}>
346+
<FileTreePanel workspaceId="ws-test" />
347+
</Provider>
348+
);
349+
350+
fireEvent.click(screen.getByText("lib"));
351+
352+
await waitFor(() => {
353+
expect(sendCommand).toHaveBeenCalledWith(
354+
"workspace.uiState.set",
355+
expect.objectContaining({
356+
workspaceId: "ws-test",
357+
uiState: expect.objectContaining({
358+
fileTreeExpandedDirs: expect.arrayContaining(["lib", "src"]),
359+
}),
360+
}),
361+
undefined
362+
);
363+
});
364+
365+
expect(Array.from(store.get(expandedDirsAtomFamily("ws-test")) ?? [])).toEqual(["lib", "src"]);
366+
});
367+
270368
it("reloads the file tree after creating a file from the toolbar", async () => {
271369
const sendCommand = vi
272370
.fn()

packages/web/src/features/workspace/views/shared/file-tree-panel.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ export const FileTreePanel: FC<FileTreePanelProps> = ({
157157
() => (hasSearch ? searchResults.length : countVisibleFiles(treeNodes)),
158158
[hasSearch, searchResults.length, treeNodes]
159159
);
160+
const defaultExpandedRootPaths = useMemo(
161+
() =>
162+
treeNodes
163+
.filter(
164+
(node) => node.kind === "dir" && DEFAULT_EXPANDED_ROOT_DIRS.has(node.name.toLowerCase())
165+
)
166+
.map((node) => node.path),
167+
[treeNodes]
168+
);
160169

161170
useEffect(() => {
162171
if (expandedDirs !== null || !workspace) {
@@ -276,6 +285,7 @@ export const FileTreePanel: FC<FileTreePanelProps> = ({
276285
onSelectFile={handleSelectFile}
277286
onLoadChildren={loadChildren}
278287
onToggleDirs={applyExpandedDirs}
288+
defaultExpandedRootPaths={defaultExpandedRootPaths}
279289
isLoadingDir={isLoadingDir}
280290
/>
281291
))
@@ -356,6 +366,7 @@ interface FileTreeNodeProps {
356366
node: FileNode;
357367
depth: number;
358368
expandedDirs: Set<string> | null;
369+
defaultExpandedRootPaths: string[];
359370
selectedPath: string | null;
360371
onRequestCreate: (mode: "file" | "folder", baseDir: string | null) => void;
361372
onRequestDelete: (path: string, name: string) => void;
@@ -369,6 +380,7 @@ const FileTreeNode: FC<FileTreeNodeProps> = ({
369380
node,
370381
depth,
371382
expandedDirs,
383+
defaultExpandedRootPaths,
372384
selectedPath,
373385
onRequestCreate,
374386
onRequestDelete,
@@ -406,7 +418,7 @@ const FileTreeNode: FC<FileTreeNodeProps> = ({
406418
return;
407419
}
408420

409-
const nextExpanded = new Set(expandedDirs ?? []);
421+
const nextExpanded = new Set(expandedDirs ?? defaultExpandedRootPaths);
410422
if (isExpanded) {
411423
nextExpanded.delete(node.path);
412424
} else {
@@ -509,6 +521,7 @@ const FileTreeNode: FC<FileTreeNodeProps> = ({
509521
onSelectFile={onSelectFile}
510522
onLoadChildren={onLoadChildren}
511523
onToggleDirs={onToggleDirs}
524+
defaultExpandedRootPaths={defaultExpandedRootPaths}
512525
isLoadingDir={isLoadingDir}
513526
/>
514527
))}

0 commit comments

Comments
 (0)