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 && 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">
<CreateAgentForm />
</Dialog>

View file

@ -21,3 +21,8 @@
.ui-table tr:last-child td {
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,
rows,
rowKey,
emptyMessage,
}: {
columns: TableColumn<T>[];
rows: T[];
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 (
<div class="ui-table-scroll">
@ -38,13 +45,21 @@ export function Table<T>({
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={rowKey(row)}>
{columns.map((c) => (
<td key={c.key}>{c.render(row)}</td>
))}
{rows.length === 0 && emptyMessage ? (
<tr>
<td class="ui-table-empty" colSpan={columns.length}>
{emptyMessage}
</td>
</tr>
))}
) : (
rows.map((row) => (
<tr key={rowKey(row)}>
{columns.map((c) => (
<td key={c.key}>{c.render(row)}</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>