From cbe4355b8d5ef90cbb09d2d7172bdd3d5af35892 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 8 Nov 2026 18:26:38 +0800 Subject: [PATCH] fix(welcome): ask to remove a recent project whose folder is gone (#2131) Opening a recent project whose folder no longer exists reported the failure as a notification, which is not shown when the system group's balloons are off, and the welcome screen has no list to find it in, so the click did nothing. It now opens a dialog offering to remove the entry. The open failure's kind is also read from the raw error the trust check rejects with, so a missing folder is no longer reported as a generic failure. Co-authored-by: Claude --- src/features/welcome/recent.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/features/welcome/recent.ts b/src/features/welcome/recent.ts index 701942b8b..4717964bf 100644 --- a/src/features/welcome/recent.ts +++ b/src/features/welcome/recent.ts @@ -24,6 +24,7 @@ import { import { tx } from "@/platform/i18n"; import { fuzzyMatch } from "@/ui"; import { notifications } from "@/workbench/contributions"; +import { confirm } from "@/workbench/messages"; import { openRemoteProject } from "@/workbench/projectWindow"; interface RecentState { @@ -115,6 +116,14 @@ export async function removeRecent(path: string) { notifications().notify({ level: "error", title: "Cannot remove recent project", body: r.error.message }); } +/** An open failure's kind and message, whether it is an `IpcError` or a raw rejected invoke. */ +function failure(e: unknown): { kind?: string; message: string } { + if (e instanceof IpcError) return { kind: e.kind, message: e.message }; + if (e && typeof e === "object" && "message" in e) + return { kind: (e as { kind?: string }).kind, message: String((e as { message: unknown }).message) }; + return { message: e instanceof Error ? e.message : String(e) }; +} + /** Opens a project from the recent list; offers to remove entries whose folder is gone. */ export async function openRecent(project: Pick): Promise { if (!isTauri()) { @@ -140,12 +149,21 @@ export async function openRecent(project: Pick): return true; } catch (e) { if (isCancelled(e)) return false; - const message = e instanceof Error ? e.message : String(e); - const missing = e instanceof IpcError ? e.kind === "notFound" : /not found|no such/i.test(message); + const { kind, message } = failure(e); + if (kind === "notFound" || (!kind && /not found|no such/i.test(message))) { + // A balloon can be switched off for this group and the welcome screen has no log to open. + const remove = await confirm({ + title: tx`Project folder not found: ${project.name}`, + message: project.path, + ok: "Remove from Recent Projects", + }); + if (remove) await removeRecent(project.path); + return false; + } notifications().notify({ level: "error", - title: missing ? `Project folder not found: ${project.name}` : `Cannot open ${project.name}`, - body: missing ? project.path : message, + title: `Cannot open ${project.name}`, + body: message, key: `welcome.open:${project.path}`, actions: [{ label: "Remove from Recent Projects", run: () => void removeRecent(project.path) }], });