fix(directory-explorer): make batch add work in VS Code; add Turkish locale keys

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<ProjectEntry[]>);
   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ç".
This commit is contained in:
herjarsa
2026-08-28 18:59:47 +02:00
parent b2ab4af157
commit 478f1e9c03
5 changed files with 49 additions and 13 deletions
@@ -486,7 +486,7 @@ export const DirectoryExplorerDialog: React.FC<DirectoryExplorerDialogProps> = (
// 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'),
+2
View File
@@ -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ç',
@@ -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"])
+13 -3
View File
@@ -50,7 +50,7 @@ interface ProjectsStore {
manualProjectOrder: string[];
addProject: (path: string, options?: { label?: string; id?: string }) => Promise<ProjectEntry | null>;
addProjects: (paths: string[]) => ProjectEntry[];
addProjects: (paths: string[]) => Promise<ProjectEntry[]>;
removeProject: (id: string) => void;
setActiveProject: (id: string) => void;
setActiveProjectIdOnly: (id: string) => void;
@@ -654,9 +654,19 @@ export const useProjectsStore = create<ProjectsStore>()(
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));
@@ -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);
});
});