diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md
index 46132f25..1428aff2 100644
--- a/docs/web-ui/dashboard.md
+++ b/docs/web-ui/dashboard.md
@@ -27,22 +27,27 @@ from the dashboard tab strip.
- **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 + the stand-alone FL0W / L0GS / H0M3
- pages), injected at the top of `
` by `renderServerWarnings` in
- `common.js`. Driven by `state.server_warnings` — a list of
- `{ kind, level, message }` from hive-c0re's `host_stats::server_warnings`
- — and coloured by `level` (`warn` amber / `crit` red). The backend owns
- the threshold + message, so adding a new system warning needs no
- frontend change. The only producer today is 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 …`).
- Hidden when there are no warnings.
-- **Browser tab title** — `hive / c0re` by default; updated to
- ` / ` 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 / hive-c0re`
- — so the operator can see the call count in an unfocused browser
- tab without opening the dashboard. The prefix is set on the
+ 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
@@ -55,7 +60,10 @@ surfaces, not tab panes.
## SW4RM tab
**C0NTAINERS** — live containers rendered as a depth-first
-tree using `ContainerView.parent` (populated by `topology.rs`).
+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`
@@ -470,18 +478,22 @@ values follow the `effective`/`assignments` rule described above.
**T00L GR0UPS** — per-agent tool-group permissions. Tool groups are
named buckets of MCP tools; each agent starts with a role default
-(sub-agents: `messaging`, `meta`, `inbox`, `execution`; root agent: all
-groups). Checking / unchecking stages which groups are active for the
-agent; the page-level **save all** button (below) commits it. Columns
-come from `GET /api/tool-groups`. A rebuild is queued so
-`HIVE_TOOL_GROUPS` takes effect.
+(sub-agents: `messaging`, `meta`, `inbox`, `execution` —
+`ToolGroup::AGENT_DEFAULT`; root agent is seeded to
+`ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`,
+`lifecycle`, `approvals`, `scheduling`, `diagnostics`, `execution`,
+i.e. every group except `forge` and `web_tools`). Checking /
+unchecking stages which groups are active for the agent; the
+page-level **save all** button (below) commits it. Columns come from
+`GET /api/tool-groups`. A rebuild is queued so `HIVE_TOOL_GROUPS`
+takes effect.
The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`,
-`approvals`, `scheduling`, `diagnostics`, `execution`, `web_tools`. All
-listed in `ToolGroup::ALL` in `hive-sh4re`. The `web_tools` group is
-special: it carries no MCP tools; instead it adds Claude's built-in
-`WebFetch` and `WebSearch` to `--tools` / `--allowedTools` for that
-agent session.
+`approvals`, `scheduling`, `diagnostics`, `forge`, `execution`,
+`web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The
+`web_tools` group is special: it carries no MCP tools; instead it adds
+Claude's built-in `WebFetch` and `WebSearch` to `--tools` /
+`--allowedTools` for that agent session.
Both tables share the same visual shape: `.cap-table-wrap` /
`.tg-table-wrap` outer scroll container, `thead` with a label column
@@ -588,14 +600,17 @@ attribute; a shared 1s ticker rewrites it in-place — showing
`overdue X ago` once the deadline passes — without triggering a
full re-render of the list.
-## ST4TS tab
+## ST4TS page (`/stats.html`)
Hive-wide turn statistics, aggregated across every agent's
-`hyperhive-turn-stats.sqlite` for the selected window. Distinct from
+`hyperhive-turn-stats.sqlite` for the selected window. Not a dashboard
+tab — a standalone page reached from the **Stats** tile on the H0M3
+hub, same minimal chrome as `/flow.html` / `/logs.html`. Distinct from
each agent's own `/stats` page (which carries the per-agent trend
charts): ST4TS is the swarm-level rollup.
-- **Window selector** (`1h`–`30d`) re-fetches on change.
+- **Window selector** (`1h`, `4h`, `24h`, `3d`, `7d`, `30d`, `all`) —
+ a hash-routed tab strip (`#1h` / `#24h` / …) — re-fetches on change.
- **Summary chips**: active agents, turns, total/input/output/cache-read
tokens, and a labelled **est cost**.
- **Busiest agents** table — one row per agent (most turns first):
@@ -614,7 +629,7 @@ Backed by `GET /api/stats-hive?window=` in `hive-c0re`
500 ms `busy_timeout`, since `turn_stats` is rollback-journal) and rolls
the rows up — missing / unreadable / zero-turn dbs are skipped so one
bad db never fails the endpoint. This is a **pull** surface (no SSE):
-the data is fetched on tab activation and on window change. Rendered
+the data is fetched on page load and on window change. Rendered
with plain tables + CSS bars — the dashboard bundle ships no chart
library.
@@ -627,13 +642,16 @@ the model id, longest match wins) mapping to
`{ input, output, cache_read, cache_write }` USD-per-million-token
prices. Models not covered fall back to hive-c0re's built-in estimate.
-## P33RS tab
+## P33R H1V3S (within the SW4RM tab)
-Peer hives in this swarm. The tab is hidden when the
+Peer hives in this swarm. Not its own tab — a headline block
+(`#peers-block`) rendered under the SW4RM tab's container list (see
+"Chrome header" above: "Peer hives render as a headline under SW4RM
+rather than a tab"). The block is hidden when the
`state.peer_hives` array from `/api/state` is empty — i.e. when
`services.hyperhive.swarm.hives` holds no hive other than this one.
When at least one peer is present the `hidden` attribute is removed
-and the tab becomes active.
+and the cards render.
**P33R H1V3S** — each peer renders as a card row: a hexagon icon
(`⬡`), the peer's DNS domain as the primary name, and the peer
@@ -964,10 +982,12 @@ and it reads the narrow `/api/jobq/rollup` endpoint rather than the
full graph.
**Active model badge** (`model · `, blue) appears when the
-container is running and the harness has persisted a model name
-(`harness/hyperhive-model`). Read by hive-c0re's `ContainerView`
-(`read_active_model`); absent until the agent has completed at least
-one turn and stale values are suppressed for stopped containers.
+container is running and the harness has persisted a model name (the
+`active_model` field of `hyperhive-harness.json`, the consolidated
+harness state file in the agent's state dir). Read by hive-c0re's
+`ContainerView` (`read_active_model`); absent until the agent has
+completed at least one turn and stale values are suppressed for
+stopped containers.
**`ctx · Nk` chip** shows the agent's last-turn context size,
populated from `DashboardState.ctx_tokens` (absent until the
@@ -1019,7 +1039,7 @@ step-level counts instead, unused here.
### Themed dialogs
All confirmations, prompts, and transient error notices use an
-in-app themed dialog system (`assets/modal.js`) rather than the
+in-app themed dialog system (`@hive/shared/modal.js`) rather than the
browser's native `confirm()` / `prompt()` / `alert()` chrome, so
they match the Catppuccin palette and can't be styled away by the
OS. Three primitives, all built on the `openDialog` core:
@@ -1031,7 +1051,7 @@ OS. Three primitives, all built on the `openDialog` core:
button takes focus). Backdrop click and `Esc` both cancel.
- `themedPrompt(...)` — a modal with a text input, resolving to the
entered string or `null`.
-- `themedToast({ type, ... })` — a non-blocking toast (top-right,
+- `themedToast(message, { type, ... })` — a non-blocking toast (top-right,
`info` / `error` / `ok`) for transient validation + action
failures, so an error doesn't trap the operator behind a modal.
Single-action errors auto-dismiss; bulk / partial-failure
@@ -1047,13 +1067,18 @@ and flush state before the container stops` checkbox. When ticked,
the action POSTs `/api/kill/?graceful=true` (the bulk path
appends the flag per-agent); unticked is the instant hard stop
(`/api/kill/` with no query). The backend enqueues a
-`GracefulStop` rebuild-queue transient: the harness runs one
-stop-checkpoint turn (so the agent can flush `/state`) and then
-exits, with a 3-minute timeout that falls back to a hard stop. The
-quiescing progress surfaces through the same rebuild-queue
-transient + build log the card already reads for a rebuild. (The
-`hivectl --graceful` CLI flag enqueues the same `GracefulStop`, so
-the dashboard and CLI paths behave identically.)
+`Signal` → `Drain` job-queue node pair (`NodeKind::Signal` /
+`NodeKind::Drain`): `Signal` sets the graceful-stop fence and kicks
+the harness so it runs one stop-checkpoint turn (so the agent can
+flush `/state`); `Drain` awaits the harness clearing that fence,
+bounded by a 3-minute timeout (`GRACEFUL_STOP_TIMEOUT`) that
+resolves either way and falls back to the downstream mechanical
+stop. The quiescing progress surfaces through the same
+rebuild-queue pending-badge mechanism the card already reads for a
+rebuild — there's no build log, since a graceful stop runs no nix
+build. (The `hivectl stop --graceful` CLI flag enqueues the same
+`Signal`/`Drain` pair, so the dashboard and CLI paths behave
+identically.)
### Topology tree
@@ -1434,9 +1459,10 @@ payload):
- `container_state_changed` (container: ContainerView) /
`container_removed` (name) — per-row container mutations,
emitted by `Coordinator::rescan_containers_and_emit` from
- every mutation site (`actions::approve` post-spawn,
- `actions::destroy`, the lifecycle_action wrapper,
- `auto_update::rebuild_agent`) and from the 10s
+ many mutation sites — post-spawn approval bookkeeping
+ (`actions::approve`), the job queue's own node execution
+ (`job_queue::exec`, e.g. after a rebuild's stop/swap/start
+ steps or a destroy's teardown step) — and from the 10s
`crash_watch` poll. Client upserts/removes by name; the
pending overlay is read from `transientsState` since the
payload doesn't carry it.