diff --git a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx index 63128fad..454d569f 100644 --- a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx @@ -212,13 +212,13 @@ export function AgentsPage() { { key: "status", header: "status", + // Technical freshness only — the agent's own free-text status + // string used to be concatenated into this same badge, which + // stuffed a full sentence into a pill meant for a short discrete + // label and blew the row out (mara: "looks messy"). That string + // now lives in its own "message" column below. render: (a) => { const { tone, label } = FRESHNESS[a.freshness]; - const text = a.snapshot?.status_text; - // A `running: false` snapshot always carries `status_text: null` - // (the wire contract's own rule, not something this page derives), - // so an agent that reported recently but isn't running still shows - // a bare freshness badge rather than a stale status string. return ( - {text ? `${text} — ` : ""} {label} {a.last_seen_unix !== null ? ( <> @@ -243,6 +242,17 @@ export function AgentsPage() { ); }, }, + { + key: "message", + header: "message", + cellClass: "ui-table-prose", + // Plain text, not a Badge — the agent's own free-form status + // sentence, as distinct from the technical freshness state in the + // "status" column. A `running: false` snapshot always carries + // `status_text: null` (the wire contract's own rule), so a stopped + // agent just shows an em dash here rather than a stale message. + render: (a) => a.snapshot?.status_text ?? "—", + }, { key: "wanted", header: "wanted", diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.css b/frontend/packages/swarm-ui/src/ui/table/Table.css index cec0a17c..258cc238 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.css +++ b/frontend/packages/swarm-ui/src/ui/table/Table.css @@ -26,3 +26,12 @@ text-align: center; padding: 1.25em 0.75em; } + +/* A cell holding free-form prose (an operator- or agent-provided string, + not a short discrete label) rather than open-ended growth that drags + the whole table wider — caps the column and wraps within it instead. */ +.ui-table-prose { + max-width: 32em; + white-space: normal; + word-break: break-word; +} diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.tsx b/frontend/packages/swarm-ui/src/ui/table/Table.tsx index 2f6d0e44..3655c611 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.tsx +++ b/frontend/packages/swarm-ui/src/ui/table/Table.tsx @@ -20,6 +20,10 @@ export interface TableColumn { // can pass real markup instead of forking a second table primitive. header: ComponentChildren; render: (row: T) => ComponentChildren; + /** Extra class on this column's `` — e.g. `ui-table-prose` for a + * free-text cell that should cap its width and wrap instead of + * dragging the whole table wider. Omit for the default cell styling. */ + cellClass?: string; /** * ARIA sort state for this column's `` — `'ascending'` / * `'descending'` while this is the active sort column, `'none'` while @@ -72,7 +76,9 @@ export function Table({ rows.map((row) => ( {columns.map((c) => ( - {c.render(row)} + + {c.render(row)} + ))} ))