From 67eb0c660dc2c322f25a5f605a8f8960b6312c69 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 12 Aug 2026 21:05:12 +0200 Subject: [PATCH 1/3] swarm-ui: page shell + layout primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Structural foundation split out of hyperhive#3118 per mara's steer: structure first so the real overview page (hive roster) and later routes (swarm-wide agent roster) land as content changes rather than each reinventing chrome + nav + a table/panel/chip shape. - : header bar (branding) + nav row, wraps every route. Route list lives in Shell itself (one small SPA, one place to know its own nav). - ui/Panel, ui/StatusChip, ui/Table: the three primitives the overview page's actual scope (hive roster: name/domain/status, linking out to each hive's own dashboard) calls for, nothing speculative beyond that. Preact-native styling (plain CSS files imported via swarm-ui.css, no shadow DOM — this package renders into light DOM) — not @hive/shared's chrome.css, which is the old MPA dashboard's visual language. Same base16/Catppuccin color tokens via theme.css/colors.css so it still reads as hyperhive. npm run build (whole frontend workspace) + npm run typecheck both clean. Verified with a real headless-chromium screenshot against the built dist, not just source-reading. Fixes hyperhive#3211 --- frontend/packages/swarm-ui/src/App.tsx | 35 ++++++++------- .../packages/swarm-ui/src/shell/Shell.css | 40 +++++++++++++++++ .../packages/swarm-ui/src/shell/Shell.tsx | 41 ++++++++++++++++++ frontend/packages/swarm-ui/src/swarm-ui.css | 15 ++----- frontend/packages/swarm-ui/src/ui/Panel.css | 15 +++++++ frontend/packages/swarm-ui/src/ui/Panel.tsx | 14 ++++++ .../packages/swarm-ui/src/ui/StatusChip.css | 21 +++++++++ .../packages/swarm-ui/src/ui/StatusChip.tsx | 11 +++++ frontend/packages/swarm-ui/src/ui/Table.css | 18 ++++++++ frontend/packages/swarm-ui/src/ui/Table.tsx | 43 +++++++++++++++++++ 10 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 frontend/packages/swarm-ui/src/shell/Shell.css create mode 100644 frontend/packages/swarm-ui/src/shell/Shell.tsx create mode 100644 frontend/packages/swarm-ui/src/ui/Panel.css create mode 100644 frontend/packages/swarm-ui/src/ui/Panel.tsx create mode 100644 frontend/packages/swarm-ui/src/ui/StatusChip.css create mode 100644 frontend/packages/swarm-ui/src/ui/StatusChip.tsx create mode 100644 frontend/packages/swarm-ui/src/ui/Table.css create mode 100644 frontend/packages/swarm-ui/src/ui/Table.tsx diff --git a/frontend/packages/swarm-ui/src/App.tsx b/frontend/packages/swarm-ui/src/App.tsx index 5430dffb..10598605 100644 --- a/frontend/packages/swarm-ui/src/App.tsx +++ b/frontend/packages/swarm-ui/src/App.tsx @@ -1,35 +1,34 @@ -// Root shell component. Empty start page for now — functionality -// (swarm roster, target-state view, whatever the swarm-controller -// epic eventually needs) is out of scope until auth against authelia -// is figured out. What's here IS in scope: a real router + deep-link -// shape that the per-hive dashboard's vanilla-JS + custom-element MPA -// doesn't have, so later work has a shell to land routes into instead -// of bolting routing on after the fact. +// Root shell component. Real route (`/`) is still a placeholder — the +// swarm's hive roster is the next piece of work to land into it — but +// it now mounts inside and uses the ui/ primitives, so that +// page lands as a content change, not a structural one. import { Route, Switch } from 'wouter-preact'; +import { Shell } from './shell/Shell.js'; +import { Panel } from './ui/Panel.js'; function Home() { return ( -
-

swarm ui

-

nothing here yet — project-bootstrap placeholder.

-
+ +

hive roster lands here, sequenced after this shell.

+
); } function NotFound() { return ( -
-

404

+

no route here.

-
+ ); } export function App() { return ( - - - - + + + + + + ); } diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css new file mode 100644 index 00000000..4a6de52c --- /dev/null +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -0,0 +1,40 @@ +/* — header bar + nav row + the content column every route + renders into. Colour vars come from ../theme.css (same base16 slots + the per-hive dashboard uses, so this reads as recognizably + hyperhive) — the layout itself is this package's own, not + @hive/shared's chrome.css. */ +.shell-header { + display: flex; + align-items: center; + gap: 1.5em; + padding: 0.75em 1.25em; + border-bottom: 1px solid var(--border); + background: var(--bg-elev); +} +.shell-brand { + font-weight: 600; + color: var(--purple); + letter-spacing: 0.02em; +} +.shell-nav { + display: flex; + gap: 1em; +} +.shell-nav-link { + color: var(--muted); + text-decoration: none; + padding: 0.25em 0; + border-bottom: 2px solid transparent; +} +.shell-nav-link:hover { + color: var(--fg); +} +.shell-nav-link-active { + color: var(--fg); + border-bottom-color: var(--purple); +} +.shell-body { + max-width: 60em; + margin: 0 auto; + padding: 1.5em 1.25em; +} diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx new file mode 100644 index 00000000..508d98f1 --- /dev/null +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -0,0 +1,41 @@ +// — page chrome every swarm-ui route mounts inside: a header +// bar (swarm branding) plus a nav row linking the app's real routes. +// Preact-native styling (shell/Shell.css, imported globally via +// ../swarm-ui.css) — deliberately not @hive/shared's chrome.css +// page-header pattern, which is the per-hive MPA dashboard's visual +// language. This package is a clean field, not an inheritor of that +// look. +// +// Route list lives here, not prop-drilled from App — one small SPA +// has exactly one place that needs to know its own nav, and this is +// it. Grows additively as real routes land (a swarm-wide agent roster +// is next); no speculative entries. +import type { ComponentChildren } from 'preact'; +import { Link, useRoute } from 'wouter-preact'; + +const NAV_ITEMS: { href: string; label: string }[] = [{ href: '/', label: 'overview' }]; + +function NavLink({ href, label }: { href: string; label: string }) { + const [active] = useRoute(href); + return ( + + {label} + + ); +} + +export function Shell({ children }: { children: ComponentChildren }) { + return ( +
+
+ hyperhive swarm + +
+
{children}
+
+ ); +} diff --git a/frontend/packages/swarm-ui/src/swarm-ui.css b/frontend/packages/swarm-ui/src/swarm-ui.css index 43134567..c5bc4cdf 100644 --- a/frontend/packages/swarm-ui/src/swarm-ui.css +++ b/frontend/packages/swarm-ui/src/swarm-ui.css @@ -1,12 +1,5 @@ @import "@hive/shared/base.css"; - -/* Bootstrap placeholder only — real layout comes with the first - route that actually needs one. */ -.swarm-ui-empty { - max-width: 40em; - margin: 4em auto; - padding: 0 1em; -} -.swarm-ui-empty h1 { - color: var(--purple); -} +@import "./shell/Shell.css"; +@import "./ui/Panel.css"; +@import "./ui/StatusChip.css"; +@import "./ui/Table.css"; diff --git a/frontend/packages/swarm-ui/src/ui/Panel.css b/frontend/packages/swarm-ui/src/ui/Panel.css new file mode 100644 index 00000000..23a40980 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/Panel.css @@ -0,0 +1,15 @@ +.ui-panel { + border: 1px solid var(--border); + border-radius: 0.5em; + background: var(--bg-elev); +} +.ui-panel-title { + margin: 0; + padding: 0.75em 1em; + font-size: 1em; + font-weight: 600; + border-bottom: 1px solid var(--border); +} +.ui-panel-body { + padding: 1em; +} diff --git a/frontend/packages/swarm-ui/src/ui/Panel.tsx b/frontend/packages/swarm-ui/src/ui/Panel.tsx new file mode 100644 index 00000000..8998a1e8 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/Panel.tsx @@ -0,0 +1,14 @@ +// — the one generic container primitive every route needs: a +// bordered surface with an optional title, no other opinions. Not a +// card-with-actions/footer/whatever kit — those get added the first +// time a real page actually needs one, not speculatively ahead of it. +import type { ComponentChildren } from 'preact'; + +export function Panel({ title, children }: { title?: string; children: ComponentChildren }) { + return ( +
+ {title ?

{title}

: null} +
{children}
+
+ ); +} diff --git a/frontend/packages/swarm-ui/src/ui/StatusChip.css b/frontend/packages/swarm-ui/src/ui/StatusChip.css new file mode 100644 index 00000000..c6908552 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/StatusChip.css @@ -0,0 +1,21 @@ +.ui-chip { + display: inline-block; + padding: 0.15em 0.6em; + border-radius: 1em; + font-size: 0.85em; + line-height: 1.4; + background: var(--purple-dim); + color: var(--fg); +} +.ui-chip-neutral { + color: var(--muted); +} +.ui-chip-positive { + color: var(--green); +} +.ui-chip-warning { + color: var(--amber); +} +.ui-chip-negative { + color: var(--red); +} diff --git a/frontend/packages/swarm-ui/src/ui/StatusChip.tsx b/frontend/packages/swarm-ui/src/ui/StatusChip.tsx new file mode 100644 index 00000000..d83ad6b8 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/StatusChip.tsx @@ -0,0 +1,11 @@ +// — a small labelled state indicator. Generic over +// `tone` rather than a fixed status vocabulary (`online`/`offline`/…) +// because the first real caller (the hive roster) starts with a +// single static "configured" tone and grows real online/stale/offline +// states once a status rollup lands server-side — same component, +// richer data later, no rebuild. +export type ChipTone = 'neutral' | 'positive' | 'warning' | 'negative'; + +export function StatusChip({ tone = 'neutral', label }: { tone?: ChipTone; label: string }) { + return {label}; +} diff --git a/frontend/packages/swarm-ui/src/ui/Table.css b/frontend/packages/swarm-ui/src/ui/Table.css new file mode 100644 index 00000000..f990dae6 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/Table.css @@ -0,0 +1,18 @@ +.ui-table { + width: 100%; + border-collapse: collapse; +} +.ui-table th { + text-align: left; + font-weight: 600; + color: var(--muted); + padding: 0.5em 0.75em; + border-bottom: 1px solid var(--border); +} +.ui-table td { + padding: 0.5em 0.75em; + border-bottom: 1px solid var(--border); +} +.ui-table tr:last-child td { + border-bottom: none; +} diff --git a/frontend/packages/swarm-ui/src/ui/Table.tsx b/frontend/packages/swarm-ui/src/ui/Table.tsx new file mode 100644 index 00000000..402953a7 --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/Table.tsx @@ -0,0 +1,43 @@ +// — a generic column-defined table, the shape the hive roster +// (name / domain / status chip, one row per hive) needs. Columns own +// their own cell rendering rather than this component knowing about +// any particular row shape, so it stays reusable for the swarm-wide +// agent roster later without a rewrite. +import type { ComponentChildren } from 'preact'; + +export interface TableColumn { + key: string; + header: string; + render: (row: T) => ComponentChildren; +} + +export function Table({ + columns, + rows, + rowKey, +}: { + columns: TableColumn[]; + rows: T[]; + rowKey: (row: T) => string; +}) { + return ( +
+ + + {columns.map((c) => ( + + ))} + + + + {rows.map((row) => ( + + {columns.map((c) => ( + + ))} + + ))} + +
{c.header}
{c.render(row)}
+ ); +} From 55f9e6c15f284c84df8ef831130b1b03ffd04f32 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 12 Aug 2026 21:12:59 +0200 Subject: [PATCH 2/3] swarm-ui: one subdirectory per ui/ component Per mara's PR review request: Panel/StatusChip/Table each move into their own subdir (ui/panel/, ui/status-chip/, ui/table/) colocating the component with its stylesheet, matching shell/ (Shell.tsx + Shell.css already lived this way). Import paths in App.tsx and swarm-ui.css updated to match; no behavior change. npm run build + typecheck both clean. --- frontend/packages/swarm-ui/src/App.tsx | 2 +- frontend/packages/swarm-ui/src/swarm-ui.css | 6 +++--- frontend/packages/swarm-ui/src/ui/{ => panel}/Panel.css | 0 frontend/packages/swarm-ui/src/ui/{ => panel}/Panel.tsx | 0 .../swarm-ui/src/ui/{ => status-chip}/StatusChip.css | 0 .../swarm-ui/src/ui/{ => status-chip}/StatusChip.tsx | 0 frontend/packages/swarm-ui/src/ui/{ => table}/Table.css | 0 frontend/packages/swarm-ui/src/ui/{ => table}/Table.tsx | 0 8 files changed, 4 insertions(+), 4 deletions(-) rename frontend/packages/swarm-ui/src/ui/{ => panel}/Panel.css (100%) rename frontend/packages/swarm-ui/src/ui/{ => panel}/Panel.tsx (100%) rename frontend/packages/swarm-ui/src/ui/{ => status-chip}/StatusChip.css (100%) rename frontend/packages/swarm-ui/src/ui/{ => status-chip}/StatusChip.tsx (100%) rename frontend/packages/swarm-ui/src/ui/{ => table}/Table.css (100%) rename frontend/packages/swarm-ui/src/ui/{ => table}/Table.tsx (100%) diff --git a/frontend/packages/swarm-ui/src/App.tsx b/frontend/packages/swarm-ui/src/App.tsx index 10598605..6909ef8c 100644 --- a/frontend/packages/swarm-ui/src/App.tsx +++ b/frontend/packages/swarm-ui/src/App.tsx @@ -4,7 +4,7 @@ // page lands as a content change, not a structural one. import { Route, Switch } from 'wouter-preact'; import { Shell } from './shell/Shell.js'; -import { Panel } from './ui/Panel.js'; +import { Panel } from './ui/panel/Panel.js'; function Home() { return ( diff --git a/frontend/packages/swarm-ui/src/swarm-ui.css b/frontend/packages/swarm-ui/src/swarm-ui.css index c5bc4cdf..c2f26b80 100644 --- a/frontend/packages/swarm-ui/src/swarm-ui.css +++ b/frontend/packages/swarm-ui/src/swarm-ui.css @@ -1,5 +1,5 @@ @import "@hive/shared/base.css"; @import "./shell/Shell.css"; -@import "./ui/Panel.css"; -@import "./ui/StatusChip.css"; -@import "./ui/Table.css"; +@import "./ui/panel/Panel.css"; +@import "./ui/status-chip/StatusChip.css"; +@import "./ui/table/Table.css"; diff --git a/frontend/packages/swarm-ui/src/ui/Panel.css b/frontend/packages/swarm-ui/src/ui/panel/Panel.css similarity index 100% rename from frontend/packages/swarm-ui/src/ui/Panel.css rename to frontend/packages/swarm-ui/src/ui/panel/Panel.css diff --git a/frontend/packages/swarm-ui/src/ui/Panel.tsx b/frontend/packages/swarm-ui/src/ui/panel/Panel.tsx similarity index 100% rename from frontend/packages/swarm-ui/src/ui/Panel.tsx rename to frontend/packages/swarm-ui/src/ui/panel/Panel.tsx diff --git a/frontend/packages/swarm-ui/src/ui/StatusChip.css b/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.css similarity index 100% rename from frontend/packages/swarm-ui/src/ui/StatusChip.css rename to frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.css diff --git a/frontend/packages/swarm-ui/src/ui/StatusChip.tsx b/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx similarity index 100% rename from frontend/packages/swarm-ui/src/ui/StatusChip.tsx rename to frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx diff --git a/frontend/packages/swarm-ui/src/ui/Table.css b/frontend/packages/swarm-ui/src/ui/table/Table.css similarity index 100% rename from frontend/packages/swarm-ui/src/ui/Table.css rename to frontend/packages/swarm-ui/src/ui/table/Table.css diff --git a/frontend/packages/swarm-ui/src/ui/Table.tsx b/frontend/packages/swarm-ui/src/ui/table/Table.tsx similarity index 100% rename from frontend/packages/swarm-ui/src/ui/Table.tsx rename to frontend/packages/swarm-ui/src/ui/table/Table.tsx From 8642d4acf6cbf3ce6db5c077452397497e8e76a8 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 12 Aug 2026 21:26:44 +0200 Subject: [PATCH 3/3] swarm-ui: colocate component CSS as JS-side-effect imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per mara's review question on PR#3219 ('shouldnt the jsx files import their css?'): each component now does its own import ('./Shell.css', './Panel.css', ...) instead of swarm-ui.css centrally @import-ing every component's stylesheet. esbuild folds every .css reachable from main.tsx's import graph into one main.css companion output next to main.js — no separate build step, this is bundle:true's existing behavior, just not exercised until now. swarm-ui.css keeps only the shared base reset (@hive/shared/base.css) since that isn't any one component's concern. Added src/css.d.ts (ambient '*.css' module) since tsc otherwise rejects a side-effect import of a non-JS/TS specifier. Side benefit: a component nothing imports (yet) no longer ships its CSS either — StatusChip/Table aren't referenced from App.tsx today, and main.css correctly only carries Shell.css + Panel.css. The old central-import approach shipped all four unconditionally. npm run build + typecheck clean. Re-screenshotted the real dist — pixel-identical to before this change. --- frontend/packages/swarm-ui/build.mjs | 11 +++++++++-- frontend/packages/swarm-ui/src/css.d.ts | 4 ++++ frontend/packages/swarm-ui/src/index.html | 1 + frontend/packages/swarm-ui/src/shell/Shell.tsx | 13 ++++++++----- frontend/packages/swarm-ui/src/swarm-ui.css | 10 ++++++---- frontend/packages/swarm-ui/src/ui/panel/Panel.tsx | 1 + .../swarm-ui/src/ui/status-chip/StatusChip.tsx | 2 ++ frontend/packages/swarm-ui/src/ui/table/Table.tsx | 1 + 8 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 frontend/packages/swarm-ui/src/css.d.ts diff --git a/frontend/packages/swarm-ui/build.mjs b/frontend/packages/swarm-ui/build.mjs index 4e680219..7c4874cf 100644 --- a/frontend/packages/swarm-ui/build.mjs +++ b/frontend/packages/swarm-ui/build.mjs @@ -5,10 +5,17 @@ // dist/static/main.js served at /static/main.js (ESM bundle, // Preact + wouter-preact) // dist/static/main.js.map source map sibling +// dist/static/main.css every component's own `import +// './Foo.css'` (Shell.css, Panel.css, …), +// folded by esbuild into one companion +// output alongside main.js — no separate +// build step, this falls out of bundling +// main.tsx with `bundle: true` // dist/static/colors.css served at /static/colors.css // dist/static/theme.css served at /static/theme.css -// dist/static/swarm-ui.css served at /static/swarm-ui.css (@import -// resolved from @hive/shared) +// dist/static/swarm-ui.css served at /static/swarm-ui.css — just the +// shared base reset now (@import resolved +// from @hive/shared), NOT component styles // // Not yet wired into any Rust binary's `ServeDir` — swarm-controller // only serves `/health` today (see swarm-controller/README.md); this diff --git a/frontend/packages/swarm-ui/src/css.d.ts b/frontend/packages/swarm-ui/src/css.d.ts new file mode 100644 index 00000000..8597bb2b --- /dev/null +++ b/frontend/packages/swarm-ui/src/css.d.ts @@ -0,0 +1,4 @@ +// Ambient module for `import './Foo.css'` side-effect imports (esbuild +// resolves these directly, see build.mjs; tsc otherwise has no idea what +// a `.css` specifier is and refuses the whole side-effect import). +declare module '*.css'; diff --git a/frontend/packages/swarm-ui/src/index.html b/frontend/packages/swarm-ui/src/index.html index 9ed89442..6766527b 100644 --- a/frontend/packages/swarm-ui/src/index.html +++ b/frontend/packages/swarm-ui/src/index.html @@ -7,6 +7,7 @@ +
diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx index 508d98f1..9a25878e 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.tsx +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -1,10 +1,12 @@ // — page chrome every swarm-ui route mounts inside: a header // bar (swarm branding) plus a nav row linking the app's real routes. -// Preact-native styling (shell/Shell.css, imported globally via -// ../swarm-ui.css) — deliberately not @hive/shared's chrome.css -// page-header pattern, which is the per-hive MPA dashboard's visual -// language. This package is a clean field, not an inheritor of that -// look. +// Preact-native styling — `./Shell.css` imported right here, not +// wired centrally, so the component and its styles travel together +// (esbuild folds every imported `.css` reachable from `main.tsx` into +// one `main.css` companion output, see build.mjs) — deliberately not +// @hive/shared's chrome.css page-header pattern, which is the per-hive +// MPA dashboard's visual language. This package is a clean field, not +// an inheritor of that look. // // Route list lives here, not prop-drilled from App — one small SPA // has exactly one place that needs to know its own nav, and this is @@ -12,6 +14,7 @@ // is next); no speculative entries. import type { ComponentChildren } from 'preact'; import { Link, useRoute } from 'wouter-preact'; +import './Shell.css'; const NAV_ITEMS: { href: string; label: string }[] = [{ href: '/', label: 'overview' }]; diff --git a/frontend/packages/swarm-ui/src/swarm-ui.css b/frontend/packages/swarm-ui/src/swarm-ui.css index c2f26b80..5289a7a2 100644 --- a/frontend/packages/swarm-ui/src/swarm-ui.css +++ b/frontend/packages/swarm-ui/src/swarm-ui.css @@ -1,5 +1,7 @@ +/* Page-level base only — component styles are colocated with their + component (`./Shell.css` imported by shell/Shell.tsx, etc.) and + land in the `main.css` companion esbuild emits alongside `main.js` + (see build.mjs), not here. This file stays just the shared reset + every page needs regardless of which components a route happens to + use. */ @import "@hive/shared/base.css"; -@import "./shell/Shell.css"; -@import "./ui/panel/Panel.css"; -@import "./ui/status-chip/StatusChip.css"; -@import "./ui/table/Table.css"; diff --git a/frontend/packages/swarm-ui/src/ui/panel/Panel.tsx b/frontend/packages/swarm-ui/src/ui/panel/Panel.tsx index 8998a1e8..de8f88db 100644 --- a/frontend/packages/swarm-ui/src/ui/panel/Panel.tsx +++ b/frontend/packages/swarm-ui/src/ui/panel/Panel.tsx @@ -3,6 +3,7 @@ // card-with-actions/footer/whatever kit — those get added the first // time a real page actually needs one, not speculatively ahead of it. import type { ComponentChildren } from 'preact'; +import './Panel.css'; export function Panel({ title, children }: { title?: string; children: ComponentChildren }) { return ( diff --git a/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx b/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx index d83ad6b8..354c9459 100644 --- a/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx +++ b/frontend/packages/swarm-ui/src/ui/status-chip/StatusChip.tsx @@ -4,6 +4,8 @@ // single static "configured" tone and grows real online/stale/offline // states once a status rollup lands server-side — same component, // richer data later, no rebuild. +import './StatusChip.css'; + export type ChipTone = 'neutral' | 'positive' | 'warning' | 'negative'; export function StatusChip({ tone = 'neutral', label }: { tone?: ChipTone; label: string }) { diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.tsx b/frontend/packages/swarm-ui/src/ui/table/Table.tsx index 402953a7..3e852063 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.tsx +++ b/frontend/packages/swarm-ui/src/ui/table/Table.tsx @@ -4,6 +4,7 @@ // any particular row shape, so it stays reusable for the swarm-wide // agent roster later without a rewrite. import type { ComponentChildren } from 'preact'; +import './Table.css'; export interface TableColumn { key: string;