From 478f1e9c033b1b8b4703fcada153579d57015ec0 Mon Sep 17 00:00:00 2001 From: herjarsa Date: Fri, 28 Aug 2026 18:59:47 +0200 Subject: [PATCH] fix(directory-explorer): make batch add work in VS Code; add Turkish locale keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two non-blockers from the openchamber-bot review at b2ab4af15: 1. VS Code batch add reported a misleading failure. addProjects returned [] unconditionally for the VS Code runtime because addWorkspaceFolder is reached only by addProject. Iterate addProject per path so valid selections succeed and the host is called once per selection. addProjects is now async (returns Promise); the call site in DirectoryExplorerDialog awaits it; existing tests in useProjectsStore.test.ts updated to await. 2. Turkish locale (tr.ts) was missing the two new keys that other 11 dictionaries received: actions.addSelected and browse.selectForAdd. Add both with real Turkish translations: "Seçilenleri ekle" and "Eklemek için seç". --- .../session/DirectoryExplorerDialog.tsx | 2 +- packages/ui/src/lib/i18n/messages/tr.ts | 2 ++ .../ui/src/stores/useProjectsStore.test.ts | 18 +++++++------- packages/ui/src/stores/useProjectsStore.ts | 16 ++++++++++--- .../useProjectsStore.vscodeAddProject.test.ts | 24 +++++++++++++++++++ 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx index 594d95a7..9beb8062 100644 --- a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx +++ b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx @@ -486,7 +486,7 @@ export const DirectoryExplorerDialog: React.FC = ( // Batch path wins over single-target create: with checkboxes ticked, // the user wants the selections added, not a fresh directory created // for whatever happens to be typed in the filter. - const added = addProjects(selectionToAdd); + const added = await addProjects(selectionToAdd); if (added.length === 0) { toast.error(t('directoryExplorerDialog.toast.failedToAddProject'), { description: t('directoryExplorerDialog.toast.selectValidDirectoryPath'), diff --git a/packages/ui/src/lib/i18n/messages/tr.ts b/packages/ui/src/lib/i18n/messages/tr.ts index ad200dd2..4229756a 100644 --- a/packages/ui/src/lib/i18n/messages/tr.ts +++ b/packages/ui/src/lib/i18n/messages/tr.ts @@ -1775,6 +1775,7 @@ export const dict = { 'directoryExplorerDialog.actions.openInFinder': 'Finder\'da aç', 'directoryExplorerDialog.actions.adding': 'Ekleniyor...', 'directoryExplorerDialog.actions.addProject': 'Proje ekle', + 'directoryExplorerDialog.actions.addSelected': 'Seçilenleri ekle', 'directoryExplorerDialog.actions.addLocalProject': 'Yerel proje ekle', 'directoryExplorerDialog.actions.cloneRepository': 'Depoyu klonla', 'directoryExplorerDialog.actions.cloneAndAdd': 'Klonla ve ekle', @@ -1791,6 +1792,7 @@ export const dict = { 'directoryExplorerDialog.browse.retry': 'Yeniden dene', 'directoryExplorerDialog.browse.parentDirectory': 'Üst dizin', 'directoryExplorerDialog.browse.addedBadge': 'Eklendi', + 'directoryExplorerDialog.browse.selectForAdd': 'Eklemek için seç', 'directoryExplorerDialog.browse.quickAdd': 'Ekle', 'directoryExplorerDialog.footer.navigate': 'Gezin', 'directoryExplorerDialog.footer.select': 'Seç', diff --git a/packages/ui/src/stores/useProjectsStore.test.ts b/packages/ui/src/stores/useProjectsStore.test.ts index 25a09f96..e2a77d8a 100644 --- a/packages/ui/src/stores/useProjectsStore.test.ts +++ b/packages/ui/src/stores/useProjectsStore.test.ts @@ -130,10 +130,10 @@ describe("useProjectsStore.addProjects", () => { }) } - test("adds multiple new projects in one update and activates the first", () => { + test("adds multiple new projects in one update and activates the first", async () => { resetProjects() - const added = useProjectsStore.getState().addProjects(["/one", "/two", "/three"]) + const added = await useProjectsStore.getState().addProjects(["/one", "/two", "/three"]) expect(added).toHaveLength(3) expect(useProjectsStore.getState().projects.map((p) => p.path)).toEqual(["/one", "/two", "/three"]) @@ -141,30 +141,30 @@ describe("useProjectsStore.addProjects", () => { expect(added[0].addedAt).toBe(added[1].addedAt) }) - test("skips already-added paths and duplicates within the batch", () => { + test("skips already-added paths and duplicates within the batch", async () => { resetProjects() - useProjectsStore.getState().addProjects(["/one"]) + await useProjectsStore.getState().addProjects(["/one"]) - const added = useProjectsStore.getState().addProjects(["/one", "/two", "/two", "/one"]) + const added = await useProjectsStore.getState().addProjects(["/one", "/two", "/two", "/one"]) expect(added).toHaveLength(1) expect(added[0].path).toBe("/two") expect(useProjectsStore.getState().projects.map((p) => p.path)).toEqual(["/one", "/two"]) }) - test("skips invalid paths and returns an empty array when nothing is addable", () => { + test("skips invalid paths and returns an empty array when nothing is addable", async () => { resetProjects() - const added = useProjectsStore.getState().addProjects(["", " ", 42 as unknown as string]) + const added = await useProjectsStore.getState().addProjects(["", " ", 42 as unknown as string]) expect(added).toEqual([]) expect(useProjectsStore.getState().projects).toEqual([]) }) - test("normalizes paths (trailing separators, backslashes, tilde expansion)", () => { + test("normalizes paths (trailing separators, backslashes, tilde expansion)", async () => { resetProjects() - const added = useProjectsStore.getState().addProjects(["/repo/", "C:\\repo", "~/project"]) + const added = await useProjectsStore.getState().addProjects(["/repo/", "C:\\repo", "~/project"]) const home = useDirectoryStore.getState().homeDirectory; expect(added.map((p) => p.path)).toEqual(["/repo", "C:/repo", home ? `${home}/project` : "~/project"]) diff --git a/packages/ui/src/stores/useProjectsStore.ts b/packages/ui/src/stores/useProjectsStore.ts index f7398967..a4c254e6 100644 --- a/packages/ui/src/stores/useProjectsStore.ts +++ b/packages/ui/src/stores/useProjectsStore.ts @@ -50,7 +50,7 @@ interface ProjectsStore { manualProjectOrder: string[]; addProject: (path: string, options?: { label?: string; id?: string }) => Promise; - addProjects: (paths: string[]) => ProjectEntry[]; + addProjects: (paths: string[]) => Promise; removeProject: (id: string) => void; setActiveProject: (id: string) => void; setActiveProjectIdOnly: (id: string) => void; @@ -654,9 +654,19 @@ export const useProjectsStore = create()( return entry; }, - addProjects: (paths: string[]) => { + addProjects: async (paths: string[]) => { if (isVSCodeProjectsRuntime) { - return []; + // VS Code paths are added via runtimeApis.vscode.addWorkspaceFolder, + // which is reached only by addProject. Iterate so valid selections + // succeed instead of silently returning []. + const added: ProjectEntry[] = []; + for (const path of paths) { + const project = await get().addProject(path); + if (project) { + added.push(project); + } + } + return added; } const current = get(); const existingPaths = new Set(current.projects.map((project) => project.path)); diff --git a/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts b/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts index eb32663a..24d33f50 100644 --- a/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts +++ b/packages/ui/src/stores/useProjectsStore.vscodeAddProject.test.ts @@ -133,4 +133,28 @@ describe('issue #2582: addProject in the VS Code runtime', () => { expect(added).toBeNull(); expect(useProjectsStore.getState().projects.find((p) => p.path === '/other/path')).toBeFalsy(); }); + + test('addProjects iterates addProject in the VS Code runtime so valid selections succeed', async () => { + // Regression: addProjects used to return [] unconditionally for the + // VS Code runtime, which made the batch-add path toast "Failed to + // add project" even for valid selections. The fix calls + // addWorkspaceFolder per path; we assert the host is invoked once + // per selection (not skipped) and that any successful add returns + // a non-null entry. + const added = await useProjectsStore.getState().addProjects([ + '/home/user/project-a', + '/home/user/project-b', + ]); + + expect(addWorkspaceFolderCalls).toEqual([ + '/home/user/project-a', + '/home/user/project-b', + ]); + // The mock's addWorkspaceFolder returns the second entry keyed by + // `path`, so project-a lands; project-b is not reflected in + // projects because the mock's hardcoded return array doesn't + // include it. The point of the test is the call sequence, not the + // final projects state (covered by the dedicated addProject tests). + expect(added.length).toBeGreaterThanOrEqual(1); + }); });