diff --git a/apps/desktop/src/app/chat/sidebar/projects/entered-content.tsx b/apps/desktop/src/app/chat/sidebar/projects/entered-content.tsx
index 6b09788157..7d2fc7117c 100644
--- a/apps/desktop/src/app/chat/sidebar/projects/entered-content.tsx
+++ b/apps/desktop/src/app/chat/sidebar/projects/entered-content.tsx
@@ -2,16 +2,8 @@ import { useStore } from '@nanostores/react'
import type * as React from 'react'
import { useMemo, useState } from 'react'
-import { Button } from '@/components/ui/button'
import { Codicon } from '@/components/ui/codicon'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle
-} from '@/components/ui/dialog'
+import { ConfirmDialog } from '@/components/ui/confirm-dialog'
import type { HermesGitWorktree } from '@/global'
import type { SessionInfo } from '@/hermes'
import { useI18n } from '@/i18n'
@@ -190,43 +182,26 @@ function RepoFlatSection({
destructiveLabel: string,
onDestructive: (group: SidebarSessionGroup) => void
) => (
-
+ setTarget(null)}
+ onConfirm={() => {
+ if (target) {
+ onDestructive(target)
+ }
+ }}
+ open={Boolean(target)}
+ secondaryAction={{
+ label: s.projects.removeFromSidebar,
+ onClick: () => target && dismissWorktree(target.id)
+ }}
+ title={`${s.projects.removeWorktree} "${target?.label ?? ''}"?`}
+ />
)
const removeDialog = (
diff --git a/apps/desktop/src/components/ui/confirm-dialog.test.tsx b/apps/desktop/src/components/ui/confirm-dialog.test.tsx
new file mode 100644
index 0000000000..ad107dac7f
--- /dev/null
+++ b/apps/desktop/src/components/ui/confirm-dialog.test.tsx
@@ -0,0 +1,50 @@
+import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+
+import { ConfirmDialog } from './confirm-dialog'
+
+afterEach(cleanup)
+
+describe('ConfirmDialog secondary action', () => {
+ function renderWithSecondary() {
+ const onConfirm = vi.fn()
+ const onClose = vi.fn()
+ const onSecondary = vi.fn()
+
+ render(
+
+ )
+
+ return { onClose, onConfirm, onSecondary }
+ }
+
+ it('runs the secondary action and closes without confirming', async () => {
+ const { onClose, onConfirm, onSecondary } = renderWithSecondary()
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Remove from sidebar' }))
+
+ expect(onSecondary).toHaveBeenCalledTimes(1)
+ expect(onClose).toHaveBeenCalledTimes(1)
+ expect(onConfirm).not.toHaveBeenCalled()
+ })
+
+ it('still opens focused on Confirm, so Enter confirms rather than picking the secondary', async () => {
+ const { onConfirm, onSecondary } = renderWithSecondary()
+
+ const dialog = await screen.findByRole('dialog')
+
+ // eslint-disable-next-line no-restricted-globals -- asserting real focus requires the live document
+ await waitFor(() => expect(dialog.contains(document.activeElement)).toBe(true))
+ // eslint-disable-next-line no-restricted-globals -- asserting real focus requires the live document
+ fireEvent.keyDown(document.activeElement!, { key: 'Enter' })
+
+ await waitFor(() => expect(onConfirm).toHaveBeenCalledTimes(1))
+ expect(onSecondary).not.toHaveBeenCalled()
+ })
+})
diff --git a/apps/desktop/src/components/ui/confirm-dialog.tsx b/apps/desktop/src/components/ui/confirm-dialog.tsx
index 3d60069c4b..9e30011b8e 100644
--- a/apps/desktop/src/components/ui/confirm-dialog.tsx
+++ b/apps/desktop/src/components/ui/confirm-dialog.tsx
@@ -28,6 +28,14 @@ interface ConfirmDialogProps {
destructive?: boolean
/** Close as soon as onConfirm resolves — for optimistic actions that finish in the background. */
dismissOnConfirm?: boolean
+ /** A third, non-destructive way out, shown between Cancel and Confirm (e.g.
+ * "Remove from sidebar" beside "Delete worktree"). Closes on click. */
+ secondaryAction?: ConfirmSecondaryAction
+}
+
+interface ConfirmSecondaryAction {
+ label: string
+ onClick: () => void
}
// Shared confirmation dialog: opens focused on Confirm, Enter confirms (from
@@ -45,7 +53,8 @@ export function ConfirmDialog({
doneLabel,
cancelLabel,
destructive = false,
- dismissOnConfirm = false
+ dismissOnConfirm = false,
+ secondaryAction
}: ConfirmDialogProps) {
const { t } = useI18n()
const confirmRef = useRef(null)
@@ -131,6 +140,19 @@ export function ConfirmDialog({
+ {secondaryAction && (
+
+ )}