From 3f7eddc7ac94a1d88adc7f8c50754dc5bdc713e6 Mon Sep 17 00:00:00 2001 From: Piyush Date: Wed, 8 Jul 2026 15:26:41 +0530 Subject: [PATCH 1/2] Feat(frontend): Adding a Confirm Delete component to the Sidebar --- .../src/renderer/components/ConfirmDialog.tsx | 76 +++++++++++++++++++ frontend/src/renderer/components/Sidebar.tsx | 37 +++++++-- .../src/renderer/components/ui/button.tsx | 1 + 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 frontend/src/renderer/components/ConfirmDialog.tsx diff --git a/frontend/src/renderer/components/ConfirmDialog.tsx b/frontend/src/renderer/components/ConfirmDialog.tsx new file mode 100644 index 0000000000..71370f2580 --- /dev/null +++ b/frontend/src/renderer/components/ConfirmDialog.tsx @@ -0,0 +1,76 @@ +import * as Dialog from "@radix-ui/react-dialog"; +import { Loader2, TriangleAlert } from "lucide-react"; +import { Button } from "./ui/button"; + +type ConfirmDialogProps = { + open: boolean; + title: string; + description: React.ReactNode; + confirmLabel: string; + destructive?: boolean; + busy?: boolean; + error?: string | null; + onConfirm: () => void; + onOpenChange: (open: boolean) => void; + size?: "default" | "sm" | "lg"; +}; + +export function ConfirmDialog({ + open, + title, + description, + confirmLabel, + destructive, + busy, + error, + onConfirm, + onOpenChange, + size = "default", +}: ConfirmDialogProps) { + return ( + + + + +
+ {destructive && ( +
+
+ )} +
+ {title} + +
{description}
+
+
+
+ {error &&
{error}
} +
+ + +
+
+
+
+ ); +} diff --git a/frontend/src/renderer/components/Sidebar.tsx b/frontend/src/renderer/components/Sidebar.tsx index 49d2f3ef76..488962cbb5 100644 --- a/frontend/src/renderer/components/Sidebar.tsx +++ b/frontend/src/renderer/components/Sidebar.tsx @@ -67,6 +67,7 @@ import { cn } from "../lib/utils"; import { useUiStore } from "../stores/ui-store"; import { CreateProjectAgentSheet, type CreateProjectAgentSelection } from "./CreateProjectAgentSheet"; import { Button } from "./ui/button"; +import { ConfirmDialog } from "./ConfirmDialog"; // The macOS hiddenInset traffic lights and the fixed TitlebarNav overlay live // in the full-width topbar's left inset (_shell renders the bar above the @@ -434,6 +435,7 @@ function ProjectItem({ const queryClient = useQueryClient(); const [removeError, setRemoveError] = useState(null); const [isRemoving, setIsRemoving] = useState(false); + const [confirmOpen, setConfirmOpen] = useState(false); const [isSpawning, setIsSpawning] = useState(false); const restartingProjectIds = useUiStore((state) => state.restartingProjectIds); const isProjectRestarting = restartingProjectIds.has(workspace.id); @@ -475,22 +477,21 @@ function ProjectItem({ } }; - const removeProject = async () => { + const removeProject = () => { setRemoveError(null); - const confirmed = window.confirm( - `Remove project ${workspace.name}? This stops its live sessions and removes it from the sidebar, but keeps the repository folder and stored history on disk.`, - ); - if (!confirmed) return; + setConfirmOpen(true); + }; + const handleConfirmRemove = async () => { setIsRemoving(true); try { await onRemoveProject(workspace.id); + setConfirmOpen(false); // The route for a removed project no longer resolves; fall back home. if (selection.activeProjectId === workspace.id) selection.goHome(); } catch (err) { const message = err instanceof Error ? err.message : "Could not remove project"; setRemoveError(message); - window.alert(message); } finally { setIsRemoving(false); } @@ -617,6 +618,30 @@ function ProjectItem({ ))} )} + { + if (!isRemoving) setConfirmOpen(open); + }} + size="lg" + title={`Remove project`} + description={ + <> +

+ This will remove {workspace.name} +

+

+ This stops its live sessions and removes it from the sidebar, but keeps the repository folder and stored + history on disk. +

+ + } + confirmLabel={isRemoving ? "Removing…" : "Remove"} + destructive + busy={isRemoving} + error={removeError} + onConfirm={handleConfirmRemove} + /> ); } diff --git a/frontend/src/renderer/components/ui/button.tsx b/frontend/src/renderer/components/ui/button.tsx index 478f75b320..b3af6ad4a7 100644 --- a/frontend/src/renderer/components/ui/button.tsx +++ b/frontend/src/renderer/components/ui/button.tsx @@ -18,6 +18,7 @@ const buttonVariants = cva( size: { default: "h-8 px-3", sm: "h-7 px-2.5 text-xs", + lg: "h-10 px-4 text-sm", icon: "h-8 w-8", "icon-sm": "h-7 w-7", }, From 169d7c380e313ab68b25e577f1c40dd909c6e3c1 Mon Sep 17 00:00:00 2001 From: Piyush Date: Wed, 8 Jul 2026 21:45:23 +0530 Subject: [PATCH 2/2] Adding Test --- .../src/renderer/components/ConfirmDialog.tsx | 29 +++++----- .../src/renderer/components/Sidebar.test.tsx | 54 ++++++++++++++++--- frontend/src/renderer/components/Sidebar.tsx | 12 ++--- .../src/renderer/components/ui/button.tsx | 1 - 4 files changed, 61 insertions(+), 35 deletions(-) diff --git a/frontend/src/renderer/components/ConfirmDialog.tsx b/frontend/src/renderer/components/ConfirmDialog.tsx index 71370f2580..4b7b23b4d0 100644 --- a/frontend/src/renderer/components/ConfirmDialog.tsx +++ b/frontend/src/renderer/components/ConfirmDialog.tsx @@ -1,5 +1,5 @@ import * as Dialog from "@radix-ui/react-dialog"; -import { Loader2, TriangleAlert } from "lucide-react"; +import { Loader2, XCircle } from "lucide-react"; import { Button } from "./ui/button"; type ConfirmDialogProps = { @@ -12,7 +12,7 @@ type ConfirmDialogProps = { error?: string | null; onConfirm: () => void; onOpenChange: (open: boolean) => void; - size?: "default" | "sm" | "lg"; + size?: "default" | "sm"; }; export function ConfirmDialog({ @@ -31,34 +31,29 @@ export function ConfirmDialog({ - +
- {destructive && ( -
-
- )}
- {title} + {title}
{description}
- {error &&
{error}
} + {error && ( +
+
+ )}
-
- {removeError && ( - - {removeError} - - )} {/* project-sidebar__sessions: indented under the project parent so worker sessions read as children without adding a persistent guide rail. */} {expanded && sessions.length > 0 && ( @@ -623,14 +618,13 @@ function ProjectItem({ onOpenChange={(open) => { if (!isRemoving) setConfirmOpen(open); }} - size="lg" title={`Remove project`} description={ <> -

- This will remove {workspace.name} +

+ This will remove {workspace.name} from AO

-

+

This stops its live sessions and removes it from the sidebar, but keeps the repository folder and stored history on disk.

diff --git a/frontend/src/renderer/components/ui/button.tsx b/frontend/src/renderer/components/ui/button.tsx index b3af6ad4a7..478f75b320 100644 --- a/frontend/src/renderer/components/ui/button.tsx +++ b/frontend/src/renderer/components/ui/button.tsx @@ -18,7 +18,6 @@ const buttonVariants = cva( size: { default: "h-8 px-3", sm: "h-7 px-2.5 text-xs", - lg: "h-10 px-4 text-sm", icon: "h-8 w-8", "icon-sm": "h-7 w-7", },