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); + }); });