Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: split pages/agents into two pages and the agent components

pages/agents held the roster page, the terminal page and every component under them. It keeps the two pages; the components move to src/agent, by concern: roster (card, wanted menu, actions), detail (panel body, dialog, inference, linked accounts, link menu) and terminal (preview, input, subagents, stream hooks, favicon), with the shared row type and icon at the top. Moves and import rewrites only.
This commit is contained in:
iris 2026-10-12 00:02:15 +02:00
commit 74e588415b
27 changed files with 28 additions and 25 deletions

View file

@ -0,0 +1,102 @@
// <AgentCard> — one card per roster agent (mara: "main view: name,
// status, message, wanted" / "message as second line" / "more like
// card per agent") — the FilterableView `view === "cards"` alternative
// to the original `Table`, not its replacement (see `AgentsPage`'s own
// comment). Clicking a card opens a detail panel repeating this same
// info plus what doesn't fit here (hive, config-PR link, matrix
// link-account) — mara: "the info from main list should be included in
// the agent view" too, not just the leftovers.
//
// Built on the shared `Card` primitive (`ui/card/`) for the clickable/
// selectable-container mechanics — the `stopPropagation` wrapper around
// `WantedMenu` below keeps its own clicks (mouse or keyboard-synthesized,
// `WantedMenu` hosts real `<button>`s) from also firing `Card`'s
// `onClick` and opening the detail panel.
import { Badge } from "@hive/shared/badge.js";
import type { ProblemDetails } from "@hive/shared/api-error.js";
import { Card } from "../../ui/card/Card.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
import { AgentIcon } from "../AgentIcon.js";
import { FRESHNESS, type AgentRow } from "../AgentTypes.js";
import { WantedMenu } from "./WantedMenu.js";
import "./AgentCard.css";
export function AgentCard({
row,
pending,
error,
selected,
onSelectUp,
onSelectOffline,
onSelectPaused,
onOpenDetail,
}: {
row: AgentRow;
pending: boolean;
error: ProblemDetails | undefined;
/** This row is the one currently shown in the detail panel — a
* highlight only (the panel itself is the source of truth), so a
* page-refresh landing on a still-valid selection reads as obviously
* "that one" rather than a silent selection nothing marks. */
selected: boolean;
onSelectUp: (row: AgentRow) => void;
onSelectOffline: (row: AgentRow) => void;
onSelectPaused: (row: AgentRow) => void;
onOpenDetail: (row: AgentRow) => void;
}) {
const { tone, label } = FRESHNESS[row.freshness];
return (
<Card selected={selected} onClick={() => onOpenDetail(row)}>
<div class="ui-agent-card-row">
<AgentIcon name={row.name} />
<div class="ui-agent-card-body">
<div class="ui-agent-card-line1">
<span class="ui-agent-card-name">{row.name}</span>
<Badge
tone={tone}
title={
row.freshness === "unknown"
? "reported by its hive but not registered in swarm-level identity — needs migration"
: undefined
}
value={
<>
{label}
{row.last_seen_unix !== null ? (
<>
{" "}
(<RelativeTime epochMs={row.last_seen_unix * 1000} />)
</>
) : null}
</>
}
/>
<span
class="ui-agent-card-wanted"
onClick={(e) => e.stopPropagation()}
>
<WantedMenu
row={row}
pending={pending}
showDestroy={false}
onSelectUp={onSelectUp}
onSelectOffline={onSelectOffline}
onSelectPaused={onSelectPaused}
/>
{error ? (
<Badge
tone="negative"
value="failed"
title={error.detail ?? "the declaration failed"}
/>
) : null}
</span>
</div>
<div class="ui-agent-card-message">
{row.snapshot?.status_text ?? "—"}
</div>
</div>
</div>
</Card>
);
}