From 23219d4049093bdc2dbd89ca40d7fd0805423285 Mon Sep 17 00:00:00 2001 From: herjarsa Date: Fri, 28 Aug 2026 19:15:39 +0200 Subject: [PATCH] fix(directory-explorer): clear selections on Finder pick; dedup VS Code batch Two findings from the openchamber-bot review at 478f1e9c: 1. Pending selections silently swallowed the Open-in-Finder pick (prev #4). handleOpenInFinder flows into the batch-first branch of finalizeSelection, so with checkboxes ticked the OS pick was ignored and the selections were added instead. Clear selectedPaths before finalizeSelection so the Finder-sourced target is honored. 2. Within-batch dedup was missing in the VS Code branch (nit from the previous review). A path repeated within one batch hit addWorkspaceFolder twice. Mirror the non-VS Code contract with a seen Set; add a regression test asserting the host is called once per unique path. --- .../session/DirectoryExplorerDialog.tsx | 3 +++ packages/ui/src/stores/useProjectsStore.ts | 6 +++++- .../useProjectsStore.vscodeAddProject.test.ts | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx index 9beb8062..bdfaea28 100644 --- a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx +++ b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx @@ -555,6 +555,9 @@ export const DirectoryExplorerDialog: React.FC = ( return; } + // Clear pending selections so the Finder-sourced target is honored + // instead of silently being absorbed by the batch branch. + setSelectedPaths([]); await finalizeSelection(result.path); } catch (error) { toast.error(t('directoryExplorerDialog.toast.failedToSelectDirectory'), { diff --git a/packages/ui/src/stores/useProjectsStore.ts b/packages/ui/src/stores/useProjectsStore.ts index a4c254e6..c3697671 100644 --- a/packages/ui/src/stores/useProjectsStore.ts +++ b/packages/ui/src/stores/useProjectsStore.ts @@ -658,9 +658,13 @@ export const useProjectsStore = create()( if (isVSCodeProjectsRuntime) { // VS Code paths are added via runtimeApis.vscode.addWorkspaceFolder, // which is reached only by addProject. Iterate so valid selections - // succeed instead of silently returning []. + // succeed instead of silently returning []. Dedupe by path so the + // returned array mirrors the non-VS Code contract. const added: ProjectEntry[] = []; + const seen = new Set(); for (const path of paths) { + if (seen.has(path)) continue; + seen.add(path); const project = await get().addProject(path); if (project) { added.push(project); diff --git a/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts b/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts index 24d33f50..fc33684d 100644 --- a/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts +++ b/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts @@ -157,4 +157,20 @@ describe('issue #2582: addProject in the VS Code runtime', () => { // final projects state (covered by the dedicated addProject tests). expect(added.length).toBeGreaterThanOrEqual(1); }); + + test('addProjects dedupes paths within a single batch in the VS Code runtime', async () => { + // A path repeated within one batch must hit the extension host once, + // not twice — mirrors the non-VS Code contract (seenPaths Set). + addWorkspaceFolderCalls.length = 0; + await useProjectsStore.getState().addProjects([ + '/home/user/project-a', + '/home/user/project-a', + '/home/user/project-b', + ]); + + expect(addWorkspaceFolderCalls).toEqual([ + '/home/user/project-a', + '/home/user/project-b', + ]); + }); });