diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index db36ead9..cf17723c 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -77,7 +77,9 @@ card in place; select it again to close it. Several cards can be open at once, and a card stays open across the page's refresh, but not across a reload. The filter card, which also holds the **+ agent** button and the refresh picker, filters by name, hive, status, message, wanted state -and config PR. +and config PR. While the roster loads for the first time, the page shows grey +placeholder cards with the same layout as the real ones, so the cards don't jump +when the data arrives. The placeholders stand still where motion is off. An opened card has four sections, and each reads nothing until you open it. **status** and **terminal** start open, the other two folded: diff --git a/frontend/packages/swarm-ui/src/agent/roster/AgentCard.css b/frontend/packages/swarm-ui/src/agent/roster/AgentCard.css index c9e6ded6..e9b37bd1 100644 --- a/frontend/packages/swarm-ui/src/agent/roster/AgentCard.css +++ b/frontend/packages/swarm-ui/src/agent/roster/AgentCard.css @@ -66,6 +66,10 @@ transform: rotate(-45deg); transition: transform 0.15s ease; } +/* The placeholder card keeps the chevron's box without its arrow. */ +.ui-agent-card-chevron-blank { + border-color: transparent; +} .ui-agent-card-row[aria-expanded="true"] .ui-agent-card-chevron { transform: rotate(45deg); } diff --git a/frontend/packages/swarm-ui/src/agent/roster/AgentCard.tsx b/frontend/packages/swarm-ui/src/agent/roster/AgentCard.tsx index 5a0e6188..0f14d492 100644 --- a/frontend/packages/swarm-ui/src/agent/roster/AgentCard.tsx +++ b/frontend/packages/swarm-ui/src/agent/roster/AgentCard.tsx @@ -26,6 +26,7 @@ import { termPaths } from "../terminal/AgentTermPreview.js"; import { FRESHNESS, showsLoginNeeded, type AgentRow } from "../AgentTypes.js"; import type { AgentActions } from "./AgentActions.js"; import { WantedMenu } from "./WantedMenu.js"; +import { AgentCardLayout } from "./AgentCardLayout.js"; import "./AgentCard.css"; export function AgentCard({ @@ -44,24 +45,24 @@ export function AgentCard({ const term = termPaths(row.name); return ( -
{ - if (e.target !== e.currentTarget) return; - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - toggle(); - } + { + if (e.target !== e.currentTarget) return; + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggle(); + } + }, }} - > - -
-
- {row.name} + icon={} + name={{row.name}} + status={ + <> ) : null} - e.stopPropagation()} - > + + } + trailing={ + e.stopPropagation()} + > + } + title={`open ${row.name}'s terminal in its own tab`} + onClick={() => openTab({ href: term.page, label: term.label })} + /> + + {error ? ( } - title={`open ${row.name}'s terminal in its own tab`} - onClick={() => openTab({ href: term.page, label: term.label })} + tone="negative" + value="failed" + title={error.detail ?? "the declaration failed"} /> - - {error ? ( - - ) : null} - -
-
- {row.snapshot?.status_text ?? "—"} -
-
-
+ ) : null} + + } + message={row.snapshot?.status_text ?? "—"} + chevron={