diff --git a/templates/content/app/i18n-data.ts b/templates/content/app/i18n-data.ts index 1e67e25336d..0ec255c3a2e 100644 --- a/templates/content/app/i18n-data.ts +++ b/templates/content/app/i18n-data.ts @@ -3118,7 +3118,7 @@ const localFilesMessages = { importedLocalFiles: "Imported local files", importedSource: "Imported source", lastSync: "Last sync", - localFolders: "Local folders", + localFolders: "Folders", mainFolder: "Main folder", metaTitle: "Local files - Content", noFoldersLinked: "No folders linked", @@ -3654,7 +3654,7 @@ const rawLiteralLocaleMessages: Partial> = { folderLinked: "已链接 {{count}} 个文件夹", foldersLinked: "已链接 {{count}} 个文件夹", lastSync: "上次同步", - localFolders: "本地文件夹", + localFolders: "文件夹", mainFolder: "主文件夹", metaTitle: "本地文件 - Content", noFoldersLinked: "未链接文件夹", @@ -3895,7 +3895,7 @@ const rawLiteralLocaleMessages: Partial> = { folderLinked: "{{count}} carpeta enlazada", foldersLinked: "{{count}} carpetas enlazadas", lastSync: "Última sincronización", - localFolders: "Carpetas locales", + localFolders: "Carpetas", mainFolder: "Carpeta principal", metaTitle: "Archivos locales - Content", noFoldersLinked: "No hay carpetas enlazadas", diff --git a/templates/content/app/i18n/zh-TW.ts b/templates/content/app/i18n/zh-TW.ts index d5699828d51..9fc3baf5760 100644 --- a/templates/content/app/i18n/zh-TW.ts +++ b/templates/content/app/i18n/zh-TW.ts @@ -1227,7 +1227,7 @@ const messages = { importedLocalFiles: "匯入的本機檔案", importedSource: "匯入的來源", lastSync: "上次同步", - localFolders: "本機資料夾", + localFolders: "資料夾", mainFolder: "主資料夾", metaTitle: "本機檔案 - Content", noFoldersLinked: "未連結資料夾", diff --git a/templates/content/app/lib/local-folder-picker-safety.test.ts b/templates/content/app/lib/local-folder-picker-safety.test.ts index 13a1c0a58f4..96d640d72be 100644 --- a/templates/content/app/lib/local-folder-picker-safety.test.ts +++ b/templates/content/app/lib/local-folder-picker-safety.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { hasInterruptedNativeFolderPickerAttempt, + isUserCancelledFolderPickerError, runNativeFolderPickerWithCrashSentinel, } from "./local-folder-picker-safety"; @@ -57,3 +58,30 @@ describe("native folder picker crash sentinel", () => { ).toBe(true); }); }); + +describe("isUserCancelledFolderPickerError", () => { + it("recognizes the DOMException showDirectoryPicker() rejects with on user cancel", () => { + expect( + isUserCancelledFolderPickerError( + new DOMException("The user aborted a request.", "AbortError"), + ), + ).toBe(true); + }); + + it("recognizes an abort by error code when the name is unavailable", () => { + expect( + isUserCancelledFolderPickerError({ code: DOMException.ABORT_ERR }), + ).toBe(true); + }); + + it("does not treat a real failure as a user cancellation", () => { + expect( + isUserCancelledFolderPickerError( + new DOMException("Permission denied.", "NotAllowedError"), + ), + ).toBe(false); + expect(isUserCancelledFolderPickerError(new Error("boom"))).toBe(false); + expect(isUserCancelledFolderPickerError(null)).toBe(false); + expect(isUserCancelledFolderPickerError("nope")).toBe(false); + }); +}); diff --git a/templates/content/app/lib/local-folder-picker-safety.ts b/templates/content/app/lib/local-folder-picker-safety.ts index 1fbfd2c30fd..78ea07612e7 100644 --- a/templates/content/app/lib/local-folder-picker-safety.ts +++ b/templates/content/app/lib/local-folder-picker-safety.ts @@ -22,6 +22,15 @@ export function hasInterruptedNativeFolderPickerAttempt( } } +export function isUserCancelledFolderPickerError(error: unknown): boolean { + // DOMException does not extend Error, so check by name/code instead of + // `instanceof Error`. `showDirectoryPicker()` rejects with this shape when + // the user dismisses the native picker without choosing a folder. + if (typeof error !== "object" || error === null) return false; + const named = error as { name?: unknown; code?: unknown }; + return named.name === "AbortError" || named.code === DOMException.ABORT_ERR; +} + export async function runNativeFolderPickerWithCrashSentinel( operation: () => Promise, options: { diff --git a/templates/content/app/routes/_app.local-files.tsx b/templates/content/app/routes/_app.local-files.tsx index 5f0c0425007..cb5dc86e184 100644 --- a/templates/content/app/routes/_app.local-files.tsx +++ b/templates/content/app/routes/_app.local-files.tsx @@ -53,6 +53,7 @@ import { } from "@/lib/local-folder-live-sync"; import { hasInterruptedNativeFolderPickerAttempt, + isUserCancelledFolderPickerError, runNativeFolderPickerWithCrashSentinel, } from "@/lib/local-folder-picker-safety"; import { isUnsafeNativeFolderPickerHost } from "@/lib/local-folder-picker-support"; @@ -1163,6 +1164,10 @@ export default function LocalFilesRoute() { toast.success(t("localFiles.pulledLocalFiles")); await connectLocalComponentWorkspaces([selected]); } catch (err) { + if (isUserCancelledFolderPickerError(err)) { + // Backing out of the native picker isn't a failure worth surfacing. + return; + } setStatus({ kind: "error", title: t("localFiles.folderAddFailed"),