From 47f68862c8bd6086556c652dcbc0fa3c427989e1 Mon Sep 17 00:00:00 2001 From: ntbang0901 <121003585+ntbang0901@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:57:11 +0700 Subject: [PATCH] feat(project-view): restore the persisted expansion state as the user left it (#782) The Project view restored only expanded paths inside the project roots and always re-expanded the roots. Restore the persisted state as is (a collapsed root stays collapsed, nodes outside it such as content roots stay open); only a first open expands the roots. Co-authored-by: ntbang0901 <121003585+ntbang0901@users.noreply.github.com> --- src/features/project-tree/store.test.ts | 8 ++++++++ src/features/project-tree/store.ts | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/features/project-tree/store.test.ts b/src/features/project-tree/store.test.ts index 032924c3e..8d60d4d89 100644 --- a/src/features/project-tree/store.test.ts +++ b/src/features/project-tree/store.test.ts @@ -68,6 +68,14 @@ describe("lazy loading", () => { expect([...useProjectTree.getState().expanded].sort()).toEqual(["/p", "/p/docs"]); }); + it("keeps a collapsed project root collapsed and an expanded node outside it open", async () => { + setExpanded(new Set(["/other/lib"])); + await new Promise((r) => setTimeout(r, 350)); + initProjectTree(null); + initProjectTree(ROOT, "proj"); + expect([...useProjectTree.getState().expanded]).toEqual(["/other/lib"]); + }); + it("drops a folder that vanished and collapses it", async () => { await loadDir(ROOT); setExpanded(new Set([ROOT, "/p/gone"])); diff --git a/src/features/project-tree/store.ts b/src/features/project-tree/store.ts index 3d90bfcb2..cca259ea9 100644 --- a/src/features/project-tree/store.ts +++ b/src/features/project-tree/store.ts @@ -301,11 +301,12 @@ function initialExpanded(root: string | null, mode: ViewMode, members = get().me r.scope === "scratches" && r.path ? [r.path] : [], ), ); - const persisted = mode === "project" ? (readJson(expandedKey(root)) ?? []) : []; - const roots = members ?? [root]; - const expanded = new Set(persisted.filter((p) => roots.some((r) => isSameOrInside(p, r)))); - roots.forEach((r) => expanded.add(r)); - return expanded; + // The tree as the user left it, like IDEA: a collapsed project root stays collapsed, and + // nodes outside it (content roots, contributed roots) stay open. + // Keys of nodes that no longer exist are inert. Only a first open expands the roots. + const persisted = mode === "project" ? readJson(expandedKey(root)) : null; + if (persisted) return new Set(persisted); + return new Set(members ?? [root]); } function persistExpanded() {