| ... | @@ -17,6 +17,7 @@ import { | ... | @@ -17,6 +17,7 @@ import { |
| 17 | import { useNativeApi } from "../hooks/useNativeApi"; | 17 | import { useNativeApi } from "../hooks/useNativeApi"; |
| 18 | import { gitRemoveWorktreeMutationOptions } from "../lib/gitReactQuery"; | 18 | import { gitRemoveWorktreeMutationOptions } from "../lib/gitReactQuery"; |
| 19 | import { serverConfigQueryOptions } from "../lib/serverReactQuery"; | 19 | import { serverConfigQueryOptions } from "../lib/serverReactQuery"; |
| | 20 | import { toastManager } from "./ui/toast"; |
| 20 | import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; | 21 | import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; |
| 21 | | 22 | |
| 22 | const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; | 23 | const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; |
| ... | @@ -342,17 +343,59 @@ export default function Sidebar() { | ... | @@ -342,17 +343,59 @@ export default function Sidebar() { |
| 342 | const project = state.projects.find((entry) => entry.id === projectId); | 343 | const project = state.projects.find((entry) => entry.id === projectId); |
| 343 | if (!project) return; | 344 | if (!project) return; |
| 344 | | 345 | |
| | 346 | const projectThreads = state.threads.filter((thread) => thread.projectId === projectId); |
| | 347 | const linkedWorktreeCount = new Set( |
| | 348 | projectThreads |
| | 349 | .map((thread) => thread.worktreePath?.trim() ?? "") |
| | 350 | .filter((worktreePath) => worktreePath.length > 0), |
| | 351 | ).size; |
| | 352 | const confirmed = await api.dialogs.confirm( |
| | 353 | [ |
| | 354 | `Delete project "${project.name}"?`, |
| | 355 | `This will delete ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}.`, |
| | 356 | linkedWorktreeCount > 0 |
| | 357 | ? `Linked worktrees to remove: ${linkedWorktreeCount}.` |
| | 358 | : "No linked worktrees detected.", |
| | 359 | ].join("\n"), |
| | 360 | ); |
| | 361 | if (!confirmed) return; |
| | 362 | |
| 345 | if (isElectron) { | 363 | if (isElectron) { |
| 346 | try { | 364 | try { |
| 347 | await api.projects.remove({ id: projectId }); | 365 | await api.projects.remove({ id: projectId }); |
| 348 | } catch { | 366 | } catch (error) { |
| | 367 | const message = |
| | 368 | error instanceof Error ? error.message : "Unknown error deleting project."; |
| | 369 | console.error("Failed to remove project", { projectId, error }); |
| | 370 | toastManager.add({ |
| | 371 | type: "error", |
| | 372 | title: `Failed to delete "${project.name}"`, |
| | 373 | description: message, |
| | 374 | }); |
| 349 | return; | 375 | return; |
| 350 | } | 376 | } |
| 351 | } | 377 | } |
| 352 | | 378 | |
| 353 | const projectThreads = state.threads.filter((thread) => thread.projectId === projectId); | 379 | const retainedThreads = state.threads.filter((thread) => thread.projectId !== projectId); |
| | 380 | const removedWorktreePaths = new Set<string>(); |
| 354 | await Promise.all( | 381 | await Promise.all( |
| 355 | projectThreads.map(async (thread) => { | 382 | projectThreads.map(async (thread) => { |
| | 383 | const orphanedWorktreePath = getOrphanedWorktreePathForThread( |
| | 384 | [thread, ...retainedThreads], |
| | 385 | thread.id, |
| | 386 | ); |
| | 387 | if (orphanedWorktreePath && !removedWorktreePaths.has(orphanedWorktreePath)) { |
| | 388 | removedWorktreePaths.add(orphanedWorktreePath); |
| | 389 | try { |
| | 390 | await removeWorktreeMutation.mutateAsync({ |
| | 391 | cwd: project.cwd, |
| | 392 | path: orphanedWorktreePath, |
| | 393 | }); |
| | 394 | } catch { |
| | 395 | // Worktree deletion is best-effort and should not block project deletion. |
| | 396 | } |
| | 397 | } |
| | 398 | |
| 356 | if (thread.session?.sessionId) { | 399 | if (thread.session?.sessionId) { |
| 357 | try { | 400 | try { |
| 358 | await api.providers.stopSession({ | 401 | await api.providers.stopSession({ |
| ... | @@ -376,7 +419,7 @@ export default function Sidebar() { | ... | @@ -376,7 +419,7 @@ export default function Sidebar() { |
| 376 | | 419 | |
| 377 | dispatch({ type: "DELETE_PROJECT", projectId }); | 420 | dispatch({ type: "DELETE_PROJECT", projectId }); |
| 378 | }, | 421 | }, |
| 379 | [api, dispatch, state.projects, state.threads], | 422 | [api, dispatch, removeWorktreeMutation, state.projects, state.threads], |
| 380 | ); | 423 | ); |
| 381 | | 424 | |
| 382 | useEffect(() => { | 425 | useEffect(() => { |