Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: move agent creation into the agents page as a dialog

Adds a generic ui/dialog/Dialog primitive (native <dialog>, no
third-party modal lib and no shadow-DOM custom element per the esbuild
gap on those) and wires a "+ agent" button into AgentsPage that opens
the existing create-agent form inside it, content unchanged from its
former life as a standalone /create-agent route/nav item.

Removes the "new agent" top-level nav entry and the /create-agent
route entirely -- creation now only reachable from the roster that
gets populated by it. CreateAgentPage.tsx/css renamed to
CreateAgentForm.tsx/css to match its new role as a mounted component
rather than a page.

Screenshot-verified the dialog open/closed states against a mock
server.
This commit is contained in:
iris 2026-08-24 00:28:10 +02:00
commit 13d7c73c12
7 changed files with 165 additions and 23 deletions

View file

@ -11,9 +11,18 @@
// client-side into one row per agent rather than N per-agent config-PR
// calls — exactly why the bulk endpoint exists instead of looping the
// single-agent one.
//
// Owns the "+ agent" trigger too: creation used to be its own
// `/create-agent` route + nav item, but the roster this populates is
// the natural home for the action that populates it — a separate top-
// level nav entry was one click of indirection for no benefit. The form
// itself (`CreateAgentForm`) is unchanged from its page days, just
// mounted inside a `Dialog` instead of a route.
import { useState } from 'preact/hooks';
import { ApiErrorPanel } from '@hive/shared/api-error-panel.js';
import { readApiError, type ProblemDetails } from '@hive/shared/api-error.js';
import { Button } from '../ui/button/Button.js';
import { Dialog } from '../ui/dialog/Dialog.js';
import { Panel } from '../ui/panel/Panel.js';
import {
RefreshIntervalPicker,
@ -22,6 +31,7 @@ import {
} from '../ui/refresh-interval/RefreshInterval.js';
import { StatusChip } from '../ui/status-chip/StatusChip.js';
import { Table, type TableColumn } from '../ui/table/Table.js';
import { CreateAgentForm } from './CreateAgentForm.js';
interface ConfigPrStatus {
pr_number: number;
@ -67,6 +77,7 @@ export function AgentsPage() {
const [rows, setRows] = useState<AgentRow[] | null>(null);
const [error, setError] = useState<ProblemDetails | null>(null);
const [intervalMs, setIntervalMs] = useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
const [createOpen, setCreateOpen] = useState(false);
useRefreshInterval(intervalMs, () => {
(async () => {
@ -93,11 +104,21 @@ export function AgentsPage() {
<Panel
title="agents"
icon="👥"
actions={<RefreshIntervalPicker id="agents-refresh" value={intervalMs} onChange={setIntervalMs} />}
actions={
<>
<Button variant="primary" onClick={() => setCreateOpen(true)}>
+ agent
</Button>
<RefreshIntervalPicker id="agents-refresh" value={intervalMs} onChange={setIntervalMs} />
</>
}
>
{error ? <ApiErrorPanel context="failed to load the agent roster" problem={error} /> : null}
{!error && rows === null ? <p>loading…</p> : null}
{rows ? <Table columns={COLUMNS} rows={rows} rowKey={(a) => a.name} /> : null}
<Dialog open={createOpen} onClose={() => setCreateOpen(false)} label="create agent">
<CreateAgentForm />
</Dialog>
</Panel>
);
}