Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui/agents: move the split-out agent-page files into pages/agents/

mara, PR review: "make agentspage a subdir now that its split into
sub components". AgentsPage.tsx/.css, AgentCard.tsx/.css,
AgentTypes.ts, and WantedMenu.tsx move as a family into their own
pages/agents/ directory; CreateAgentForm and LinkMatrixAccountForm
stay in pages/ since they aren't part of this split (CreateAgentForm
is still rendered inside AgentsPage's own dialog but is a standalone,
independently-named form, not one of the pieces carved out of the
page itself).

Pure move: relative imports within the new pages/agents/ family are
unchanged (they were always siblings), only the ones reaching back
out to ui/ and the two forms above gained one more '../', plus
App.tsx's route import.
This commit is contained in:
iris 2026-09-11 22:35:44 +02:00
commit 1176de08bd

View file

@ -1,124 +0,0 @@
// <WantedMenu> — the "wanted" state control: one badge showing the
// current declaration, opening a `Dropdown` with the four explicit
// states — replaces the old toggle-badge-plus-separate-destroy-badge
// pair. Explicit options also fix a real bug the toggle had: for an
// undeclared row, the toggle inferred a target as "the opposite of
// `snapshot.running`", so a click on an undeclared-but-running agent
// silently declared it *offline* rather than making its real state
// explicit — a dropdown just lets "up" be picked directly regardless
// of what's inferred, no flip-to-the-opposite-of-a-guess involved
// (mara: "when no state is declared, i want to set it to online").
// Presentational only, same split as `StatusChips`'s `Picker`/
// `StatusMenu`: the caller owns what each selection actually does.
//
// "paused" has no separate resume option here — selecting "up" from a
// paused row is the resume, same PUT either way (`AgentsPage`'s
// `declareState`, `hive-c0re`'s `decide_pause` backend counterpart,
// treats a declared `Up` with the marker still set as "clear it"). A
// dedicated "resume" entry would just be a second spelling of the
// option already above it.
import { useRef, useState } from "preact/hooks";
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js";
import type { AgentRow } from "./AgentTypes.js";
export function WantedMenu({
row,
pending,
onSelectUp,
onSelectOffline,
onSelectPaused,
onDestroy,
showDestroy = true,
portal = false,
}: {
row: AgentRow;
pending: boolean;
onSelectUp: (row: AgentRow) => void;
onSelectOffline: (row: AgentRow) => void;
onSelectPaused: (row: AgentRow) => void;
onDestroy?: (row: AgentRow) => void;
/**
* The card view (see `AgentCard`) passes `false` here — its
* quick-access menu keeps to the three everyday states, so "destroy"
* can't be reached by an extra click off a state that's already open.
* `AgentsPage`'s detail-panel `WantedMenu` keeps the default (`true`)
* — mara: "destroy is already available via wanted state", i.e. the
* full menu there, not a separate button.
*/
showDestroy?: boolean;
/**
* Forwarded to `Dropdown`'s own `portal` (see its file-top comment) —
* only the table's "wanted" column needs it, to escape
* `.ui-table-scroll`'s clip on the table's last row (the bug `Dropdown`'s
* own `portal` prop exists to fix in the first place).
* Neither the card list nor the detail panel has a clipping ancestor,
* and inside the detail panel specifically `portal` is actively wrong:
* a `position: fixed` element appended to `document.body` renders
* *behind* an open native `<dialog>` (the dialog is promoted to the
* browser's top layer, which composites above ordinary body content
* regardless of z-index) — found while screenshotting this exact
* dropdown open inside the detail panel, only the portion extending
* past the dialog's own edge was visible.
*/
portal?: boolean;
}) {
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLDivElement>(null);
const destroyed = row.wanted === "destroyed";
const tone: BadgeTone =
row.wanted === "up"
? "positive"
: row.wanted === "paused"
? "warning"
: destroyed
? "negative"
: "neutral";
const options: DropdownOption[] = [
{ value: "up", label: "up" },
{ value: "paused", label: "paused" },
{ value: "offline", label: "offline" },
...(showDestroy
? [{ value: "destroy", label: "destroy", danger: true }]
: []),
];
return (
<div class="ui-dropdown-anchor" ref={anchorRef}>
<Badge
tone={tone}
value={pending ? "…" : (row.wanted ?? "no declaration")}
// `destroyed` blocks opening the menu at all — same UX-only
// guard as before (real enforcement is server-side, see
// `declareState`'s caller), just applied to the trigger instead
// of a `disabled` toggle badge.
onClick={row.hive && !destroyed ? () => setOpen((o) => !o) : undefined}
expanded={open}
disabled={pending || !row.hive || destroyed}
title={
destroyed
? `${row.name} is destroyed — redeploy via "+ agent" to bring it back`
: row.hive
? `declare a new state for ${row.name}`
: "no hive on record for this agent — nothing to declare against"
}
/>
<Dropdown
open={open}
portal={portal}
options={options}
activeValue={row.wanted ?? undefined}
label={`declare ${row.name}`}
onSelect={(value) => {
setOpen(false);
if (value === "up") onSelectUp(row);
else if (value === "offline") onSelectOffline(row);
else if (value === "paused") onSelectPaused(row);
else onDestroy?.(row);
}}
onClose={() => setOpen(false)}
anchorRef={anchorRef}
/>
</div>
);
}