From 748ea2ae205122f8571287273599c5767bff1d55 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 15 Feb 2026 14:28:10 -0800 Subject: [PATCH] Block deletion of projects that still have threads - Show warning toast when attempting to delete a non-empty project - Simplify confirmation copy to emphasize irreversible deletion - Remove thread/worktree/session cleanup from project delete flow --- apps/web/src/components/Sidebar.tsx | 65 +++++------------------------ 1 file changed, 11 insertions(+), 54 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 3044700b392094809049cc331475a0483b7821b6..60392d004292a620ba887a3789aa112c1bae9f23 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -344,19 +344,17 @@ export default function Sidebar() { if (!project) return; const projectThreads = state.threads.filter((thread) => thread.projectId === projectId); - const linkedWorktreeCount = new Set( - projectThreads - .map((thread) => thread.worktreePath?.trim() ?? "") - .filter((worktreePath) => worktreePath.length > 0), - ).size; + if (projectThreads.length > 0) { + toastManager.add({ + type: "warning", + title: "Project is not empty", + description: "Delete all threads in this project before deleting it.", + }); + return; + } + const confirmed = await api.dialogs.confirm( - [ - `Delete project "${project.name}"?`, - `This will delete ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}.`, - linkedWorktreeCount > 0 - ? `Linked worktrees to remove: ${linkedWorktreeCount}.` - : "No linked worktrees detected.", - ].join("\n"), + [`Delete project "${project.name}"?`, "This action cannot be undone."].join("\n"), ); if (!confirmed) return; @@ -376,50 +374,9 @@ export default function Sidebar() { } } - const retainedThreads = state.threads.filter((thread) => thread.projectId !== projectId); - const removedWorktreePaths = new Set(); - await Promise.all( - projectThreads.map(async (thread) => { - const orphanedWorktreePath = getOrphanedWorktreePathForThread( - [thread, ...retainedThreads], - thread.id, - ); - if (orphanedWorktreePath && !removedWorktreePaths.has(orphanedWorktreePath)) { - removedWorktreePaths.add(orphanedWorktreePath); - try { - await removeWorktreeMutation.mutateAsync({ - cwd: project.cwd, - path: orphanedWorktreePath, - }); - } catch { - // Worktree deletion is best-effort and should not block project deletion. - } - } - - if (thread.session?.sessionId) { - try { - await api.providers.stopSession({ - sessionId: thread.session.sessionId, - }); - } catch { - // Session may already be stopped - } - } - - try { - await api.terminal.close({ - threadId: thread.id, - deleteHistory: true, - }); - } catch { - // Terminal may already be closed - } - }), - ); - dispatch({ type: "DELETE_PROJECT", projectId }); }, - [api, dispatch, removeWorktreeMutation, state.projects, state.threads], + [api, dispatch, state.projects, state.threads], ); useEffect(() => { -- 2.54.0