swarm-ui: agents page — explainer message when the roster is empty

Table gains an optional emptyMessage prop, rendered as a single
full-width row in place of a bare empty tbody; AgentsPage is the first
consumer. Screenshot-verified against a mock server returning an empty
roster.
This commit is contained in:
iris 2026-08-24 00:14:36 +02:00
commit 46d1271603
3 changed files with 34 additions and 7 deletions

View file

@ -115,7 +115,14 @@ export function AgentsPage() {
> >
{error ? <ApiErrorPanel context="failed to load the agent roster" problem={error} /> : null} {error ? <ApiErrorPanel context="failed to load the agent roster" problem={error} /> : null}
{!error && rows === null ? <p>loading</p> : null} {!error && rows === null ? <p>loading</p> : null}
{rows ? <Table columns={COLUMNS} rows={rows} rowKey={(a) => a.name} /> : null} {rows ? (
<Table
columns={COLUMNS}
rows={rows}
rowKey={(a) => a.name}
emptyMessage="no agents yet — the swarm-wide identity store has no agents registered on any hive"
/>
) : null}
<Dialog open={createOpen} onClose={() => setCreateOpen(false)} label="create agent"> <Dialog open={createOpen} onClose={() => setCreateOpen(false)} label="create agent">
<CreateAgentForm /> <CreateAgentForm />
</Dialog> </Dialog>

View file

@ -21,3 +21,8 @@
.ui-table tr:last-child td { .ui-table tr:last-child td {
border-bottom: none; border-bottom: none;
} }
.ui-table-empty {
color: var(--muted);
text-align: center;
padding: 1.25em 0.75em;
}

View file

@ -22,10 +22,17 @@ export function Table<T>({
columns, columns,
rows, rows,
rowKey, rowKey,
emptyMessage,
}: { }: {
columns: TableColumn<T>[]; columns: TableColumn<T>[];
rows: T[]; rows: T[];
rowKey: (row: T) => string; rowKey: (row: T) => string;
// Rendered as a single full-width row when `rows` is empty. Omit to
// leave a bare empty `<tbody>` — a caller whose "no rows yet" is
// covered by its own loading/error state (rendered instead of the
// table entirely) has nothing useful to add here, so this stays
// opt-in rather than every table growing a mandatory default string.
emptyMessage?: ComponentChildren;
}) { }) {
return ( return (
<div class="ui-table-scroll"> <div class="ui-table-scroll">
@ -38,13 +45,21 @@ export function Table<T>({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows.map((row) => ( {rows.length === 0 && emptyMessage ? (
<tr>
<td class="ui-table-empty" colSpan={columns.length}>
{emptyMessage}
</td>
</tr>
) : (
rows.map((row) => (
<tr key={rowKey(row)}> <tr key={rowKey(row)}>
{columns.map((c) => ( {columns.map((c) => (
<td key={c.key}>{c.render(row)}</td> <td key={c.key}>{c.render(row)}</td>
))} ))}
</tr> </tr>
))} ))
)}
</tbody> </tbody>
</table> </table>
</div> </div>