diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md
index de0372aa..17a21fbc 100644
--- a/docs/swarm/ui.md
+++ b/docs/swarm/ui.md
@@ -55,14 +55,16 @@ works too.
### The agents page
-`/agents` is the hives card, then one column of cards, one per agent. A card's
+`/agents` is a column of separate cards: the hives card, a filter card, then one
+card per agent (they are not wrapped in one big card). A card's
header shows its icon, name, freshness, the status message, a **terminal**
button that opens the agent's terminal tab, the **wanted** menu (up, offline,
paused, destroy), and a **login needed** badge with a
**log in** button when its claude has no session. Select the header to open the
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 bar above the cards filters by name, hive, status, message, wanted state
+filter card, which also holds the **+ agent** button and the refresh picker,
+filters by name, hive, status, message, wanted state
and config PR.
An opened card has four sections, each folded except **overview**, and each
diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.css b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.css
index 2c13fb49..6097ce72 100644
--- a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.css
+++ b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.css
@@ -1,6 +1,6 @@
/* The folded hives card over the agents. */
.ui-agents-hives {
- margin-block-end: 1em;
+ margin-block-end: 0.5em;
}
/* The roster's refresh picker, at the top right of the open card. */
.ui-hives-refresh {
diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx
index df33be1f..989911c7 100644
--- a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx
+++ b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx
@@ -18,8 +18,10 @@
// natural home for the action that populates it. The form itself
// (`CreateAgentForm`) mounts inside a `Dialog` here, not its own route.
//
-// The roster is one column of cards, each opening in place into its details
-// (mara: "one column of expandable cards"). This file owns the page's own
+// The page is a column of separate cards: the hives card, a filter card (with
+// the "+ agent" button and the refresh picker), then one card per agent, each
+// opening in place into its details (mara: "one column of expandable cards";
+// the agents are not wrapped in one big card). This file owns the page's own
// state and the filter fields only — `AgentRow` and friends live in
// `AgentTypes.ts`, the wanted-state control in `WantedMenu.tsx`, the actions
// on an agent (declare a state, link an account) in `AgentActions.tsx`, the
@@ -34,7 +36,6 @@ import { FRESHNESS, type AgentRow } from "../../agent/AgentTypes.js";
import { Button } from "../../ui/button/Button.js";
import { Dialog } from "../../ui/dialog/Dialog.js";
import { FilterableView } from "../../ui/filterable-view/FilterableView.js";
-import { Panel } from "../../ui/panel/Panel.js";
import { HivesCard } from "./HivesCard.js";
import {
RefreshIntervalPicker,
@@ -128,43 +129,39 @@ export function AgentsPage() {
: null}
- {rows ? (
- a.name}
- storageKey="swarm-ui:agents:table-filters"
- emptyMessage="no agents yet — the swarm-wide identity store has no agents registered on any hive"
- // Each card gets its live row from `rows` on every render, so an
- // opened card follows a refresh or a state change made inside it.
- renderCard={(a) => }
- />
- ) : null}
-
+ {error ? (
+
+ ) : null}
+ {!error && rows === null ?
loading…
: null}
+ {rows ? (
+
+
+
+ >
+ }
+ columns={FIELDS}
+ rows={rows}
+ rowKey={(a) => a.name}
+ storageKey="swarm-ui:agents:table-filters"
+ emptyMessage="no agents yet — the swarm-wide identity store has no agents registered on any hive"
+ // Each card gets its live row from `rows` on every render, so an
+ // opened card follows a refresh or a state change made inside it.
+ renderCard={(a) => }
+ />
+ ) : null}