# Dashboard layout > Part of [Web UI](../web-ui.md). See also: > [Shape (shared)](shape.md) · [Per-agent page](agent.md) The dashboard is served 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`, `#system`, `#permissions`, `#schedules`, `#settings`) drives which pane is active; hash changes don't reload the page. FL0W, L0GS, and the optional M4TR1X client are separate pages 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 moved to the standalone **C0R3** page (`/core.html`), and FL0W / L0GS / ST4TS / S3TT1NGS / M4TR1X live on their own pages too, all reachable from the H0M3 hub (not the tab strip). Count pills on SW4RM (container count), Y3R C4LL (pending approvals + questions + unread operator messages), and SCH3DUL3S (active schedules); S3TT1NGS has no count. - **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, CR3D3NTIALS, ST4TS, S3TT1NGS), 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). The backend 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 pending approvals or unanswered questions, a `(N)` prefix is prepended — `(3) pr1ma // h1ve-c0re` — so the operator can see the call count in an unfocused browser tab without opening the dashboard. The prefix is set on the initial `/api/state` cold-load and updated live by `approval_added` / `approval_resolved` / `question_added` / `question_resolved` SSE events; 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 rendered as a depth-first tree using `ContainerView.parent` (populated by `hive-c0re/src/agent_config/topology.rs` — not to be confused with `hive-c0re/src/dashboard/topology.rs`, which only holds the set-parent endpoints). Each container's row is prefixed with ASCII tree glyphs (`├─`, `└─`, `│ ` continuation columns) showing the agent parent/child hierarchy. When every container has `parent = null` (flat topology) the tree collapses to a plain list with no glyphs. Children are sorted alphabetically within each parent; roots likewise. Cycles in the parent graph are tolerated — orphaned containers (not reachable from any root) are appended as roots so no agent disappears. 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 — approvals and questions share a tab because they're the same concept ("something is waiting on you"). **P3NDING APPR0VALS** — the queue (see "Approval card" below). The R3QU3ST SP4WN form lives at the top of this section. A `pending · N` / `history · N` tab pair switches between the live queue and the last 30 resolved approvals (see "Approval card" for the history row shape). **M1ND H4S QU3STI0NS** — pending `ask` calls waiting on the operator, with amber pulsing border. Anatomy of each card: - **Filter chips** — `all · N`, `@operator · N`, `@peer · N`, plus one chip per participant name (`@asker · N` / `@target · N`). Every chip shows its own count so the operator can see the distribution at a glance. Clicking a chip narrows the visible list; selection persists in localStorage so a tab switch doesn't lose the filter. - **Question card** — timestamp · asker → target · body text (with file-path links). Operator-targeted questions (`target = null`) show `▸ ANSW3R`; peer-targeted questions (`target = agent`) show `⤿ 0V3RR1D3` so the operator can unblock an agent-to-agent exchange. Questions with a `ttl_seconds` show a `⏳ MM:SS` live countdown chip; the host-side watchdog resolves with `answerer = "ttl-watchdog"` on expiry. - **Answer form** — free-text textarea (Enter = submit, Shift+Enter = newline) + optional option list (radio for single-select, checkboxes for `multi=true`). Submit merges selected options + free text comma-joined into a single `answer` field. `✗ CANC3L` is a separate form so the submit merge handler doesn't interfere. - **◆ answ3red (N)** — collapsible `
` below the pending list; shows the last 20 resolved questions with their answers. **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). ## C0R3 page (`/core.html`) Passive / rare-interaction state. No longer a dashboard tab — it's a standalone page 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**, then **1NFR4**). 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 have moved to 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). Two actions: `⊕ R3V1V3` (queues a Spawn approval; existing state is reused), `PURG3` (wipes state + applied dirs; `POST /api/purge-tombstone/{name}`). ⚠️ **Not only *destroyed* agents.** Nothing records a destroy: every definition-side artifact (state subvolume, proposed + applied repos, meta registration, topology entry) is written by `Provision` *before* the container exists and survives `lifecycle::destroy`. So an agent part-way through a spawn is byte-identical on disk to a tombstone, and both are listed. 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 the backend can actually prove. Fixing it properly needs a recorded destroy: **#3020**, 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; auto-refreshes 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 is fed by a separate ~5 min background `du` sampler (state dir + container writable rootfs, shared nix store excluded via `du -x`), so the 5 s poll stays cheap cgroup-only reads; the row carries the last-sampled value (`null` until the first sample). Network is intentionally omitted — agents share the host netns, so there is no per-container net counter (per-agent network needs the netns-isolation roadmap in `docs/network.md`). **1NFR4** — start / stop / restart the four hive infrastructure services (`hive-ci`, `hive-forge`, `hive-gateway`, `hive-matrix`) directly from the dashboard, without needing an `infra_admin` agent's `restart` MCP tool. Three are containers; `hive-gateway` is the host's `nginx.service`, and is the one an agent may **not** restart — this panel is the way it gets bounced. One row per service: name, a `badge-ok`/`badge-fail` running/stopped dot, and `↺ R3ST4RT` + `■ ST0P` (running) or `▶ ST4RT` (stopped) buttons, same themed-confirm pattern as the K3PT ST4T3 tombstone actions. Backed by `POST /api/infra-container/{name}/{action}` (`action` ∈ `start|stop|restart`), which calls the same `priv_client::control_infra_container` helper the agent-facing `infra_admin` path uses — no new privileged-helper surface, no capability check (the dashboard is already operator-authenticated). Every attempt is written to the audit log (actor `"operator"`, action `start_infra`/`stop_infra`/`restart_infra`) alongside agent-driven infra restarts. Status rows ride the `infra_containers` field on `GET /api/state`'s `StateSnapshot` (`{name, running}`, live `systemctl is-active container@.service` read); `core.js` polls `/api/state` every 5 s only while the 1NFR4 sub-tab is active, same cadence/lifecycle as C0NT41N3R L04D's polling. ## 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` does not 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 — it carried a typed queue snapshot until every consumer had moved to the generic endpoint, and now carries none; 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: `