From 82a4324b17043fc0c6618c875452d9edf2f6eae5 Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 7 Sep 2026 19:02:23 +0200 Subject: [PATCH] swarm-ui: extract ConfirmDialog, use it for the destroy confirm mara, reviewing the swarm-ui destroy-trigger PR: "why the extra styling? shouldnt there be a component that does this already?" There wasnt one -- Dialog is deliberately content-agnostic (see its own file-top comment), so the destroy confirm had grown its own page-scoped .agents-destroy-confirm/-actions CSS for what is really a generic "message + cancel/confirm button row" shape. Extracted ui/confirm-dialog/ConfirmDialog.tsx: wraps Dialog, owns the button row, leaves the message body to the caller via children. AgentsPage now uses it instead of a bare Dialog + bespoke CSS; deleted the now-unused AgentsPage.css. --- .../swarm-ui/src/pages/AgentsPage.css | 17 ------ .../swarm-ui/src/pages/AgentsPage.tsx | 35 +++++------- .../src/ui/confirm-dialog/ConfirmDialog.css | 13 +++++ .../src/ui/confirm-dialog/ConfirmDialog.tsx | 55 +++++++++++++++++++ 4 files changed, 81 insertions(+), 39 deletions(-) delete mode 100644 frontend/packages/swarm-ui/src/pages/AgentsPage.css create mode 100644 frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.css create mode 100644 frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.tsx diff --git a/frontend/packages/swarm-ui/src/pages/AgentsPage.css b/frontend/packages/swarm-ui/src/pages/AgentsPage.css deleted file mode 100644 index ad1b4542..00000000 --- a/frontend/packages/swarm-ui/src/pages/AgentsPage.css +++ /dev/null @@ -1,17 +0,0 @@ -/* — the destroy-confirm dialog's own layout. Everything - else on this page (the table, the wanted/destroy badges) draws chrome - from the shared `ui/` kit and needs nothing page-scoped; this file - exists only because the confirm dialog's copy + button row needed - somewhere to live, same reasoning as CreateAgentForm.css owning its - own layout next to the shared form-kit chrome it wraps. */ -.agents-destroy-confirm { - display: flex; - flex-direction: column; - gap: 1em; - max-width: 28em; -} -.agents-destroy-confirm-actions { - display: flex; - justify-content: flex-end; - gap: 0.75em; -} diff --git a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx index b8ee4b5c..680f266b 100644 --- a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx @@ -33,6 +33,7 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge, type BadgeTone } from "@hive/shared/badge.js"; import { Button } from "../ui/button/Button.js"; +import { ConfirmDialog } from "../ui/confirm-dialog/ConfirmDialog.js"; import { Dialog } from "../ui/dialog/Dialog.js"; import { Panel } from "../ui/panel/Panel.js"; import { RelativeTime } from "../ui/relative-time/RelativeTime.js"; @@ -43,7 +44,6 @@ import { } from "../ui/refresh-interval/RefreshInterval.js"; import { Table, type TableColumn } from "../ui/table/Table.js"; import { CreateAgentForm } from "./CreateAgentForm.js"; -import "./AgentsPage.css"; interface ConfigPrStatus { pr_number: number; @@ -415,32 +415,23 @@ export function AgentsPage() { > - setDestroyTarget(null)} label="destroy agent" + onCancel={() => setDestroyTarget(null)} + onConfirm={() => destroyTarget && void destroyAgent(destroyTarget)} + confirmLabel="destroy" > {destroyTarget ? ( -
-

- Destroy {destroyTarget.name}? The hive tears its - container down on its next reconcile sweep. This is not reversible - from here — bringing it back means redeploying via "+ agent", - which reuses the agent's existing identity, config repo, and forge - collaborator access rather than starting over. -

-
- - -
-
+

+ Destroy {destroyTarget.name}? The hive tears its + container down on its next reconcile sweep. This is not reversible + from here — bringing it back means redeploying via "+ agent", which + reuses the agent's existing identity, config repo, and forge + collaborator access rather than starting over. +

) : null} -
+ ); } diff --git a/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.css b/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.css new file mode 100644 index 00000000..bec51820 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.css @@ -0,0 +1,13 @@ +/* — body + button-row layout only; `Dialog.css` owns the + surrounding modal chrome, `Button.css` owns the buttons themselves. */ +.confirm-dialog { + display: flex; + flex-direction: column; + gap: 1em; + max-width: 28em; +} +.confirm-dialog-actions { + display: flex; + justify-content: flex-end; + gap: 0.75em; +} diff --git a/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.tsx b/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.tsx new file mode 100644 index 00000000..b73badf7 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/confirm-dialog/ConfirmDialog.tsx @@ -0,0 +1,55 @@ +// — a `Dialog` pre-wired for the "message + cancel/confirm +// button row" shape every confirm-before-acting flow needs. Extracted out +// of AgentsPage's destroy confirmation on mara's review question ("why the +// extra styling? shouldnt there be a component that does this already?") +// — there wasn't one yet, so that page had grown its own +// `.agents-destroy-confirm`/`-actions` layout CSS for what turns out to be +// a generic shape. `Dialog` itself stays opinion-free about content (see +// its own file-top comment); this is the one layer up that isn't, so a +// future confirm flow gets the button row for free instead of another +// page-scoped CSS file. +// +// The message itself stays caller-owned via `children` — only the dialog +// wiring, spacing, and button row are shared. +import type { ComponentChildren } from "preact"; +import { Button } from "../button/Button.js"; +import { Dialog } from "../dialog/Dialog.js"; +import "./ConfirmDialog.css"; + +export function ConfirmDialog({ + open, + label, + onCancel, + onConfirm, + confirmLabel = "confirm", + cancelLabel = "cancel", + confirmDisabled = false, + children, +}: { + open: boolean; + label: string; + onCancel: () => void; + onConfirm: () => void; + confirmLabel?: string; + cancelLabel?: string; + confirmDisabled?: boolean; + children: ComponentChildren; +}) { + return ( + +
+
{children}
+
+ + +
+
+
+ ); +}