From b7db9fa74f1b9cd9d3b481aa0ba9bab724bc8f2e Mon Sep 17 00:00:00 2001 From: m4 Date: Mon, 10 Aug 2026 11:56:29 +0800 Subject: [PATCH] fix(webui): re-checking a partial folder clears its descendant exclusions --- src/lib/workspaceSelection.test.ts | 10 ++++++++++ src/lib/workspaceSelection.ts | 12 +++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/lib/workspaceSelection.test.ts b/src/lib/workspaceSelection.test.ts index 09ea50b..8ebbb84 100644 --- a/src/lib/workspaceSelection.test.ts +++ b/src/lib/workspaceSelection.test.ts @@ -74,6 +74,16 @@ describe("workspaceSelection", () => { expect(uncheck(sel, "other.txt")).toBe(sel); }); + it("re-checking an indeterminate folder clears all its exclusions", () => { + let sel = check(emptySelection(), "dir"); + sel = uncheck(sel, "dir/a.txt"); + sel = uncheck(sel, "dir/sub"); + sel = check(sel, "dir"); + expect(isChecked(sel, "dir")).toBe(true); + expect(isIndeterminate(sel, "dir")).toBe(false); + expect([...sel.excluded]).toEqual([]); + }); + it("produces sorted include/exclude params", () => { let sel = check(emptySelection(), "z.txt"); sel = check(sel, "dir"); diff --git a/src/lib/workspaceSelection.ts b/src/lib/workspaceSelection.ts index 00190c4..bba685e 100644 --- a/src/lib/workspaceSelection.ts +++ b/src/lib/workspaceSelection.ts @@ -61,11 +61,13 @@ export function check( ): WorkspaceSelection { if (isChecked(sel, path)) return sel; if (coveredBy(sel.checked, path)) { - // Covered by a collapsed ancestor but excluded. Only a DIRECT exclusion - // entry on `path` itself can be cleared by re-checking; an exclusion on a - // strict ancestor (e.g. `dir/sub` while checking `dir/sub/c.txt`) keeps - // the path excluded. - if (!sel.excluded.has(path)) return sel; + // Covered by a collapsed ancestor. Re-checking clears exclusions on + // `path` itself (re-selecting an excluded child) AND on its descendants + // (clicking an indeterminate folder upgrades it to fully checked). An + // exclusion on a strict ANCESTOR (e.g. `dir/sub` while checking + // `dir/sub/c.txt`) is untouched — that path stays excluded. + if (!sel.excluded.has(path) && !hasDescendant(sel.excluded, path)) + return sel; return { checked: sel.checked, excluded: withoutSubtree(sel.excluded, path),