diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index f6a7fdcd..3a223f62 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -44,7 +44,9 @@ The Jobs page has none yet. The pages that only display (agents, alerts, builds, review runs and the hives card) show the answer they last saw at once, then replace it with the fresh one -while the spinner runs, so opening one never starts on an empty page. The browser +while the spinner runs, so opening one never starts on an empty page. On the very +first visit, the hives card and the alerts table show their own header with three +grey placeholder rows until the first answer comes. The browser keeps the answer for itself only, and forgets it after a day, after a new build of the UI, at **log out**, and when the login ends. The pages with an editor (schedules, notification flows, broadcast) start empty as before. diff --git a/frontend/packages/swarm-ui/src/pages/AlertsPage.tsx b/frontend/packages/swarm-ui/src/pages/AlertsPage.tsx index d7bfc4fe..31bba464 100644 --- a/frontend/packages/swarm-ui/src/pages/AlertsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/AlertsPage.tsx @@ -15,6 +15,7 @@ import { } from "../ui/refresh-interval/RefreshInterval.js"; import { fetchJson, usePolled } from "../ui/polled/usePolled.js"; import { Table, type TableColumn } from "../ui/table/Table.js"; +import { TableSkeleton } from "../ui/table/TableSkeleton.js"; import { activeSinceMs, alertKey, listedAlerts, type Alert } from "./alerts.js"; const STATE_TONE: Record = { @@ -111,7 +112,9 @@ function AlertsBody() { problem={error} /> ) : null} - {!error && alerts === null ?

loading…

: null} + {!error && alerts === null ? ( + + ) : null} {listed && listed.length === 0 ? (

{showAll ? "no alerts." : "nothing is alerting."}

) : null} diff --git a/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx b/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx index 50e1bd19..33a844d8 100644 --- a/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/ComponentsPage.tsx @@ -23,6 +23,7 @@ import { } from "../ui/refresh-interval/RefreshInterval.js"; import { SplitView } from "../ui/split-view/SplitView.js"; import { Table, type TableColumn } from "../ui/table/Table.js"; +import { TableSkeleton } from "../ui/table/TableSkeleton.js"; import { TextField } from "../ui/text-field/TextField.js"; import { TextAreaField } from "../ui/text-area-field/TextAreaField.js"; import { @@ -491,6 +492,9 @@ export function ComponentsPage() { + + +
diff --git a/frontend/packages/swarm-ui/src/pages/swarm/HivesCard.tsx b/frontend/packages/swarm-ui/src/pages/swarm/HivesCard.tsx index 6d763db2..2d6bc8b5 100644 --- a/frontend/packages/swarm-ui/src/pages/swarm/HivesCard.tsx +++ b/frontend/packages/swarm-ui/src/pages/swarm/HivesCard.tsx @@ -24,6 +24,7 @@ import { } from "../../ui/refresh-interval/RefreshInterval.js"; import { fetchJson, usePolled } from "../../ui/polled/usePolled.js"; import { Table, type TableColumn } from "../../ui/table/Table.js"; +import { TableSkeleton } from "../../ui/table/TableSkeleton.js"; import { domainCell } from "../hive-domain.js"; type Freshness = "fresh" | "stale" | "never_reported" | "unknown"; @@ -142,7 +143,9 @@ function HiveRoster() { problem={error} /> ) : null} - {!error && hives === null ?

loading…

: null} + {!error && hives === null ? ( + + ) : null} {hives ? ( — the twin of
for a view that is waiting on its rows: +// the same header cells (the page passes the `columns` it gives its
) +// over a few blank rows, with a placeholder shape in each cell instead of the +// real cell. Sort and filter are left out: there is nothing to sort or filter +// yet. The overall width matches, but the width of each column does not: a +// table sizes its columns from their content, which is not known yet, and the +// real header also carries sort and filter controls. The table is hidden from +// assistive tech; the region's label says that rows are loading. +import { Skeleton, SkeletonRegion } from "../skeleton/Skeleton.js"; +import { Table, type TableColumn } from "./Table.js"; + +export function TableSkeleton({ + columns, + label, + rows = 3, +}: { + columns: TableColumn[]; + /** What is loading, as a screen reader says it: "the hives". */ + label: string; + rows?: number; +}) { + const shapes: TableColumn[] = columns.map((c) => ({ + key: c.key, + header: c.header, + render: () => , + })); + return ( + +
i)} + rowKey={String} + storageKey="swarm-ui:table-skeleton" + /> + + + ); +}