# Dashboard layout > Part of [Web UI](README.md). See also: > [Shape (shared)](shape.md) · [Per-agent page](agent.md) This page covers the per-hive UI that hive-c0re serves. Swarm-wide surfaces — the agent roster, agent creation, linking external forge and matrix accounts, the controller's job graph — are on the [swarm UI](../swarm/ui.md). hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`). It has a fixed chrome header at the top and a `
` that shows exactly one tab pane at a time. The URL hash (`#swarm`, `#call`, `#permissions`, `#schedules`) drives which pane is active; hash changes don't reload the page. FL0W, L0GS, and the other standalone pages are reachable from the H0M3 hub at `/`, not from the dashboard tab strip. **Chrome header** (fixed, overlays the active tab pane): - **← home back-link**: top-left of the chrome, links to the H0M3 hub at `/`. Every surface links back to the hub rather than to each other. - **Tab strip**: `◆ SW4RM ◆`, `◆ Y3R C4LL ◆`, `◆ P3RM1SS10NS ◆`, `◆ SCH3DUL3S ◆`. In-page tabs only — the SYST3M panels live on the standalone **C0R3** page (`/core.html`), and FL0W / L0GS / ST4TS live on their own pages too, all reachable from the H0M3 hub (not the tab strip). Count pills on SW4RM (container count), Y3R C4LL (unread operator messages), and SCH3DUL3S (active schedules left to copy). - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) — sits below the tab strip. - **Server-warnings banner** — a generic, sticky top-of-page strip shown on **every** page (dashboard + every stand-alone page — FL0W, L0GS, H0M3, C0R3, BU1LDS, ST4TS), injected at the top of `` by `renderServerWarnings` in `common.js`. Driven by `state.server_warnings` — a list of `{ kind, level, message }` — and coloured by `level` (`warn` amber / `crit` red). hive-c0re owns the threshold + message, so adding a new system warning needs no frontend change. Warnings are a push-based registry (`hive-c0re`'s `warnings.rs`): any subsystem raises/clears its own entry via an RAII guard, and `host_stats::server_warnings()` is just a cheap snapshot of that registry. Producers today include the host disk-pressure check (a `statvfs` probe of `/nix`: ≥85% used → `warn`, ≥95% → `crit`, e.g. `⚠ host nix store N% full (G GiB free) — garbage-collect …`), forge provisioning/CI-runner boot failures, and agent-state warnings (`pending_logins`, `agents_crashing`) computed alongside the container snapshot. Hidden when there are no warnings. - **Browser tab title** — `hyperhive // h1ve-c0re` by default; updated to ` / // h1ve-c0re` once `hive_name` / `swarm_name` arrive in the state snapshot. When there are unread operator messages, the page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the operator can see the call count in an unfocused browser tab without opening the dashboard. The page sets the prefix on the operator-inbox cold-load and updates it live as broker `sent` events arrive; it's preserved when `hive_name` / `swarm_name` later replace the raw title. The FL0W and L0GS pages use a slim header (a `← home` back-link + the page title) rather than the dashboard tab strip — they're standalone surfaces, not tab panes. ## SW4RM tab **C0NTAINERS** — live containers as a flat list, one row per container, sorted alphabetically by agent name; every container the rescan reports gets a row. Pulsing red banner at the top of this section if any two sub-agents hash to the same port (`port_conflicts` from `/api/state`): the operator must rename one of them and rebuild. `lifecycle::{spawn,rebuild}` also preflight this and refuse with a clear error message naming the conflicting agent. `↻ UPD4TE 4LL` button appears above the containers list when any agent is stale. ## Y3R C4LL tab Things blocked on operator decision — the operator inbox. **0PER4T0R 1NB0X** — messages agents have sent to `to="operator"` but the operator hasn't read yet. Cold-loaded from `/api/operator-inbox` on tab activation + page load; appended live from the broker `sent` stream (deduped on row id). Each row shows sender · timestamp · body (with file-path linkification). A `✓ mark all read` button on the right acks all rows via `POST /api/agent/operator/mark-all-read` (reuses the existing mark-read endpoint). Unread count folds into the Y3R C4LL tab pill so messages are visible from any tab even while inactive. Backed by `GET /api/operator-inbox` → `{ messages: [...] }` (id, from, body, at, in_reply_to, file_refs). **◆ PR3F3R3NC3S ◆** — operator-local preferences. State lives in the browser's `localStorage` — preferences **don't** sync between devices and **don't** survive a profile wipe. One section today (browser notifications); future preferences land here as sibling `

` blocks in `dashboard.html` — a standalone page for one toggle wouldn't earn its own tile. **◇ browser notifications** — `🔔 enable notifications` button when permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted (mute silences the dispatch without revoking the OS-level permission). On unsupported origins (non-secure context, or browsers without the `Notification` API) the controls hide and a single status line explains why. See `### Browser notifications` below for the dispatch model + the three signals the dashboard emits OS notifications on. The "expand tool output panels" toggle lives on the per-agent page's own settings popover (`SettingsMenu`, `@hive/shared/settings-menu.js`) instead — it's agent-terminal-only, unrelated to this section. ## C0R3 page (`/core.html`) Passive / rare-interaction state, reached from the **Core** tile on the H0M3 hub (served at `/core.html`), with the same minimal chrome as `/logs.html`: a `← home` back-link + a `createTabStrip` sub-tab nav (**K3PT ST4T3** default, then **C0NT41N3R L04D**). The page is its own esbuild bundle (`core.js`) that cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for `tombstones_changed`, `capabilities_changed`, and `tool_groups_changed` (the latter two re-render the stale-perms sub-section when permission data changes). (Rebuild queue and meta inputs live on the BU1LDS page — see below.) **K3PT ST4T3** — two sub-sections on one pane: *Tombstones*: agents with kept state and **no container** (size + age + claude-creds badge). One action: `PURG3` (wipes state + applied dirs; `POST /api/purge-tombstone/{name}`). ⚠️ **Not only *destroyed* agents.** Nothing records a destroy: `Provision` writes every definition-side artifact (state subvolume, proposed + applied repos, meta registration, topology entry) *before* the container exists, and that artifact set survives `lifecycle::destroy`. An agent part-way through a spawn is byte-identical on disk to a tombstone, and the pane lists both. The pane carries a standing warning to that effect, and the row badge says `offline` rather than `destroyed` — the absence of a container is all hive-c0re can actually prove. Fixing it properly needs a recorded destroy — **deferred** to the swarm-controller / snapshot-storage rework where the problem changes shape. *Stale permission entries*: agents with explicit capability or tool-group JSON entries but no live container — typically renamed or deleted agents whose entries persisted in `capabilities.json` / `tool-groups.json`. Lazy-loaded on first K3PT ST4T3 tab activation; autorefreshes on `capabilities_changed` + `tool_groups_changed` SSE events. Each ghost agent gets a `✕ clear perms` button (`DELETE /api/permissions/{name}`). **C0NT41N3R L04D** — live CPU + memory per agent container, read straight from cgroup v2 on the host (`cpu.stat`, `memory.current`, `memory.peak`, `memory.max` under `/sys/fs/cgroup/machine.slice/machine-h\x2d.scope/`). CPU is a host-normalised percentage (0..100 across all cores) sampled over a short (~200 ms) two-read interval; memory shows current + peak with a bar against the `memory.max` quota. Backed by `GET /api/container-resources` (`container_stats.rs`), which reads the files read-only (world-readable; no `hive-priv`) and skips agents whose scope dir is absent (= not running). Pull-only: `core.js` polls every 5 s **only while the C0NT41N3R L04D sub-tab is active** (CPU needs a fresh sample each refresh), and stops on sub-tab change. Disk size (`disk_bytes`) rides the same row, but a separate background sampler feeds it every 30 min: `hive-priv` runs one bounded `du -x` as root over the agent's `state/` + `work/` + container writable rootfs (shared nix store excluded), so the 5 s poll stays cheap cgroup-only reads. The row carries the last measured value; the column shows "unknown" (`null`) before the first measurement and whenever the last one failed, never 0. Network isn't tracked per container — see `docs/networking/network.md` for the current per-agent netns model. ## BU1LDS page (`/builds.html`) The build lifecycle hub — rebuild queue, live build log, meta inputs, and build log history on one page. Standalone page reached from the **Builds** tile on the H0M3 hub, same minimal chrome as `/core.html`. Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**, **BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for `rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`. The SW4RM tab (`/dashboard.html`) does **not** read this endpoint at all, by design — the swarm view stays independent of job-queue internals. Its per-agent pending badges are transient-only, and its queue-summary banner reads the much narrower `GET /api/jobq/rollup` instead (see Container row, below) — a handful of pre-tallied counts, not the graph. **R3BU1LD QU3U3** — pending, in-flight, and recently settled container operations: rebuilds, meta-update cascades, and first-spawns. One operation runs at a time; the worker drains FIFO. **Is a mounted `JobqGraph`** (the shared generic graph-viewer Preact component, `@hive/shared/jobq-graph.js` — the same one swarm-ui uses) — `builds.js` doesn't render the queue itself; it just mounts the component (`mountJobqGraph(container, props)`, since this page has no JSX pipeline) with an `onUpdate` callback to drive the two things below it that the generic view doesn't show. The component owns fetching, cold and live: `GET /api/jobq/graph` on mount, and a refetch via the mount handle's `.refresh()` on every `rebuild_queue_changed` SSE tick (that event is a bare `{ seq }` trigger carrying no queue payload; both dashboard pages treat the tick as a pure refetch trigger). Each row is one root graph node (`parent: null`); a multi-step op's per-agent subgraphs and sub-steps render as nodes within that one entry (structural `parent` edges define the tree; sibling order is array order, unchanged from the wire). A row shows a state glyph (`⏸` pending / `▶` running / `◐` finishing — own work done, a sub-node still running / `✔` done / `✖` failed / `⊘` cancelled / `·` skipped) and each step's own label/agent. A `Node`-kind dep on a sibling shows as a plain "waits on: `