docs: fix genuine passive-voice hits in docs/web-ui/dashboard.md (part 1/2)

First half (lines 1-697) of dashboard.md's write-good.Passive backlog --
31 of the file's 70 hits rewritten to name an actor (hive-c0re, the page,
the operator, the table, the filter, builds.js, Provision, the matrix
daemon, ...), reusing an actor already named nearby wherever one exists.
7 left alone in this half: a fixed idiom ("not to be confused with"),
two false positives ("named buckets" / compound adjective; a
field-is-set data-state conditional), a negative-capability idiom
("nothing is written"), and two established state-descriptor idioms
("is unchanged", "is tooltipped").

Part 2 (lines 762-1389, ~32 more hits) is a separate follow-up, not
started -- dashboard.md is too big for one PR.
This commit is contained in:
iris 2026-09-08 15:10:01 +02:00 committed by mara
commit a52d344600

View file

@ -4,7 +4,7 @@
> [Shape (shared)](shape.md) · [Per-agent page](agent.md) > [Shape (shared)](shape.md) · [Per-agent page](agent.md)
The dashboard is served at `/dashboard.html` (with the home page at `/`). hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`).
It has a fixed chrome header at the top and a `<main>` that shows exactly It has a fixed chrome header at the top and a `<main>` that shows exactly
one tab pane at a time. The URL hash (`#swarm`, `#call`, `#system`, one tab pane at a time. The URL hash (`#swarm`, `#call`, `#system`,
`#permissions`, `#schedules`) drives which pane is `#permissions`, `#schedules`) drives which pane is
@ -42,13 +42,13 @@ from the dashboard tab strip.
snapshot. Hidden when there are no warnings. snapshot. Hidden when there are no warnings.
- **Browser tab title**`hyperhive // h1ve-c0re` by default; updated - **Browser tab title**`hyperhive // h1ve-c0re` by default; updated
to `<swarm> / <hive> // h1ve-c0re` once `hive_name` / `swarm_name` to `<swarm> / <hive> // h1ve-c0re` once `hive_name` / `swarm_name`
arrive in the state snapshot. When there are pending approvals, a arrive in the state snapshot. When there are pending approvals, the
`(N)` prefix is prepended `(3) pr1ma // h1ve-c0re` — so the page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the
operator can see the call count in an unfocused browser tab without operator can see the call count in an unfocused browser tab without
opening the dashboard. The prefix is set on the initial `/api/state` opening the dashboard. The page sets the prefix on the initial
cold-load and updated live by `approval_added` / `approval_resolved` `/api/state` cold-load and updates it live via `approval_added` /
SSE events; it's preserved when `hive_name` / `swarm_name` later `approval_resolved` SSE events; it's preserved when `hive_name` /
replace the raw title. `swarm_name` later replace the raw title.
The FL0W and L0GS pages use a slim header (a `← home` back-link + the 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 page title) rather than the dashboard tab strip — they're standalone
@ -61,13 +61,13 @@ tree using `ContainerView.parent` (populated by
`hive-c0re/src/agent_config/topology.rs` — not to be confused with `hive-c0re/src/agent_config/topology.rs` — not to be confused with
`hive-c0re/src/dashboard/topology.rs`, which only holds the `hive-c0re/src/dashboard/topology.rs`, which only holds the
set-parent endpoints). set-parent endpoints).
Each container's row is prefixed with ASCII tree glyphs (`├─`, ASCII tree glyphs (`├─`, `└─`, `│ ` continuation columns) prefix
`└─`, `│ ` continuation columns) showing the agent each container's row, showing the agent parent/child hierarchy.
parent/child hierarchy. When every container has `parent = null` When every container has `parent = null`
(flat topology) the tree collapses to a plain list with no (flat topology) the tree collapses to a plain list with no
glyphs. Children are sorted alphabetically within each parent; glyphs. The tree sorts children alphabetically within each parent;
roots likewise. Cycles in the parent graph are tolerated roots likewise. The tree tolerates cycles in the parent graph
orphaned containers (not reachable from any root) are appended it appends orphaned containers (not reachable from any root)
as roots so no agent disappears. Pulsing red banner at the top 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 of this section if any two sub-agents hash to the same port
(`port_conflicts` from `/api/state`): the operator must rename (`port_conflicts` from `/api/state`): the operator must rename
@ -138,12 +138,12 @@ page — see below.)
claude-creds badge). One action: `PURG3` (wipes state + applied claude-creds badge). One action: `PURG3` (wipes state + applied
dirs; `POST /api/purge-tombstone/{name}`). dirs; `POST /api/purge-tombstone/{name}`).
⚠️ **Not only *destroyed* agents.** Nothing records a destroy: every ⚠️ **Not only *destroyed* agents.** Nothing records a destroy: `Provision`
definition-side artifact (state subvolume, proposed + applied repos, writes every definition-side artifact (state subvolume, proposed + applied
meta registration, topology entry) is written by `Provision` *before* repos, meta registration, topology entry) *before* the container exists,
the container exists and survives `lifecycle::destroy`. An agent and that artifact set survives `lifecycle::destroy`. An agent
part-way through a spawn is byte-identical on disk to a tombstone, and 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 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 the row badge says `offline` rather than `destroyed` — the absence of a
container is all the backend can actually prove. Fixing it properly container is all the backend can actually prove. Fixing it properly
needs a recorded destroy: **#3020**, deferred to the swarm-controller / needs a recorded destroy: **#3020**, deferred to the swarm-controller /
@ -169,8 +169,8 @@ files read-only (world-readable; no `hive-priv`) and skips agents whose
scope dir is absent (= not running). Pull-only: `core.js` polls every 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 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`) 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 rides the same row, but a separate ~5 min background `du` sampler feeds
(state dir + container writable rootfs, shared nix store excluded via it (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 `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 last-sampled value (`null` until the first sample). Network is intentionally
omitted — agents share the host netns, so there is no per-container net omitted — agents share the host netns, so there is no per-container net
@ -231,8 +231,8 @@ calls the mount's `onCancel(id)` callback, and the page does the actual
own domain concept (the component knows nothing about that endpoint). own domain concept (the component knows nothing about that endpoint).
**Rows carry no source chip, kind label, timing, or build-log **Rows carry no source chip, kind label, timing, or build-log
deep-link** — the generic graph wire doesn't carry those fields, and deep-link** — the generic graph wire doesn't carry those fields, and
rows are meant to present exactly what the endpoint provides rather rows deliberately present exactly what the endpoint provides rather
than reconstruct chrome the backend no longer sends. Settled entries than reconstructing chrome the backend no longer sends. Settled entries
render their **full step tree**, not just a bare summary — the wire render their **full step tree**, not just a bare summary — the wire
doesn't filter `Done` nodes out. doesn't filter `Done` nodes out.
@ -243,12 +243,12 @@ operator narrow which root groups render; unchecking a state re-fetches
hiding rows client-side, so the `onUpdate` callback's node list (and hiding rows client-side, so the `onUpdate` callback's node list (and
everything downstream of it — the count pill, the live-log panel) only everything downstream of it — the count pill, the live-log panel) only
ever sees what's actually shown. Filtering is by a **root's own** ever sees what's actually shown. Filtering is by a **root's own**
state, which is already its subtree's rolled-up answer, so a group is state, which is already its subtree's rolled-up answer, so the filter
kept or dropped whole, never split mid-tree. Default selection is keeps or drops a whole group, never splitting mid-tree. Default selection is
every state **except** `Done`/`Skipped` — a fresh queue view leads every state **except** `Done`/`Skipped` — a fresh queue view leads
with what's still moving or needs attention, not the settled tail; the with what's still moving or needs attention, not the settled tail;
`states` param is omitted entirely (identical request to before this builds.js omits the `states` param entirely (identical request to
filter existed) when every state is checked. Server-side: the query before this filter existed) when every state is checked. Server-side: the query
narrows [`Queue::graph_snapshot`]'s already-bounded (`MAX_HISTORY_DAGS`) narrows [`Queue::graph_snapshot`]'s already-bounded (`MAX_HISTORY_DAGS`)
root set — the history cap and the state filter are independent root set — the history cap and the state filter are independent
concerns, so a narrow filter never reaches further back in time to concerns, so a narrow filter never reaches further back in time to
@ -274,14 +274,14 @@ selectively `nix flake update`, rendered as an indented tree:
every fetched input at every depth (`hyperhive`, every fetched input at every depth (`hyperhive`,
`hyperhive/nixpkgs`, `agent-<n>`, `agent-<n>/mcp-<x>`, …), each `hyperhive/nixpkgs`, `agent-<n>`, `agent-<n>/mcp-<x>`, …), each
shown once at its shallowest path. `read_meta_inputs` walks the shown once at its shallowest path. `read_meta_inputs` walks the
lock graph with a `visited` set `follows` aliases and rev-less lock graph with a `visited` set, skipping `follows` aliases and rev-less
nodes are skipped. A `select all / select none` nodes. A `select all / select none`
control sits above the tree. Checking inputs + submitting bumps control sits above the tree. Checking inputs + submitting bumps
the lock in `/meta/` and rebuilds the selected agents in the lock in `/meta/` and rebuilds the selected agents in
sequence; each outcome reaches the root agent as a `rebuilt` sequence; each outcome reaches the root agent as a `rebuilt`
system event. `POST /meta-update`. While a lock-bump ripple runs, system event. `POST /meta-update`. While a lock-bump ripple runs,
the panel shows a pulsing "⏳ meta-update running" banner and the the panel shows a pulsing "⏳ meta-update running" banner and disables
update button is disabled (snapshot field `meta_update_running`, the update button (snapshot field `meta_update_running`,
live event `meta_update_running`). live event `meta_update_running`).
**BUILD L0GS** — all-agents build log history (moved from `/logs.html`). **BUILD L0GS** — all-agents build log history (moved from `/logs.html`).
@ -318,9 +318,9 @@ An agent picker (populated from `state.containers`, the live roster) drives a li
agent's accounts — name, homeserver, user id, and a status dot — agent's accounts — name, homeserver, user id, and a status dot —
read from `GET /api/matrix-accounts?agent=<name>` read from `GET /api/matrix-accounts?agent=<name>`
`{ accounts: [ { name, homeserver, token_present, live, user_id } ], as_of_unix }`. `{ accounts: [ { name, homeserver, token_present, live, user_id } ], as_of_unix }`.
`token_present` is whether a token is **stored**; `live`, `homeserver`, `token_present` is whether a token is **stored**; the matrix daemon
and `user_id` are backfilled from the matrix daemon's backfills `live`, `homeserver`, and `user_id` from its
`matrix-accounts.json` snapshot — a host-visible file the daemon `matrix-accounts.json` snapshot — a host-visible file it
**force-rewrites every ~30s** (a heartbeat), so `as_of_unix` (the **force-rewrites every ~30s** (a heartbeat), so `as_of_unix` (the
snapshot mtime) advances while the daemon is alive and a *stalled* value snapshot mtime) advances while the daemon is alive and a *stalled* value
genuinely means "stopped publishing," not just "old snapshot." An account genuinely means "stopped publishing," not just "old snapshot." An account
@ -357,7 +357,7 @@ privileged write path as the hive-internal `matrix-token`; the token is
**never** echoed back, and the page clears the secret inputs on submit **never** echoed back, and the page clears the secret inputs on submit
regardless of outcome. The account list reflects what's *provisioned* regardless of outcome. The account list reflects what's *provisioned*
(an account with a stored token), so a config-declared-but-unprovisioned (an account with a stored token), so a config-declared-but-unprovisioned
account appears only once it has been provisioned through the form. account appears only once the operator provisions it through the form.
### GITHUB tab ### GITHUB tab
@ -445,19 +445,19 @@ The current capabilities are:
Each row is one agent. Columns are the capability names returned by Each row is one agent. Columns are the capability names returned by
`GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking `GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking
boxes only stages the change in-browser; nothing is written until the boxes only stages the change in-browser; nothing is written until the
page-level **save all** button (described below) is selected. Row operator selects the page-level **save all** button (described below). Row
values follow the `effective`/`assignments` rule described above. values follow the `effective`/`assignments` rule described above.
**T00L GR0UPS** — per-agent tool-group permissions. Tool groups are **T00L GR0UPS** — per-agent tool-group permissions. Tool groups are
named buckets of MCP tools; each agent starts with a role default named buckets of MCP tools; each agent starts with a role default
(sub-agents: `messaging`, `meta`, `inbox`, `execution` (sub-agents: `messaging`, `meta`, `inbox`, `execution`
`ToolGroup::AGENT_DEFAULT`; root agent is seeded to `ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to
`ToolGroup::MANAGER_DEFAULT``messaging`, `meta`, `inbox`, `ToolGroup::MANAGER_DEFAULT``messaging`, `meta`, `inbox`,
`lifecycle`, `approvals`, `scheduling`, `diagnostics`, `execution`, `lifecycle`, `approvals`, `scheduling`, `diagnostics`, `execution`,
that is every group except `forge` and `web_tools`). Checking / that is every group except `forge` and `web_tools`). Checking /
unchecking stages which groups are active for the agent; the unchecking stages which groups are active for the agent; the
page-level **save all** button (below) commits it. Columns come from page-level **save all** button (below) commits it. Columns come from
`GET /api/tool-groups`. A rebuild is queued so `HIVE_TOOL_GROUPS` `GET /api/tool-groups`. hive-c0re queues a rebuild so `HIVE_TOOL_GROUPS`
takes effect. takes effect.
The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`, The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`,
@ -479,9 +479,10 @@ buttons exist. A single page-level `.perm-save-bar` with a **save all (N
agents)** button sits at the bottom of the pane, enabled only when some agents)** button sits at the bottom of the pane, enabled only when some
checkbox diverges from its baseline. Selecting it diffs every checkbox checkbox diverges from its baseline. Selecting it diffs every checkbox
across *both* matrices and POSTs one batch to `POST /api/permissions` as across *both* matrices and POSTs one batch to `POST /api/permissions` as
`{ changes: [ { agent, tool_groups?, capabilities? } ] }` — only the `{ changes: [ { agent, tool_groups?, capabilities? } ] }` — the batch
perm-types that actually changed for each agent are included (an omitted includes only the perm-types that actually changed for each agent (an
field leaves that file untouched; an included array fully replaces it). omitted field leaves that file untouched; an included array fully
replaces it).
The backend coalesces an agent's capabilities + tool-groups into a The backend coalesces an agent's capabilities + tool-groups into a
single rebuild, so changing both for one agent is one rebuild, not two. single rebuild, so changing both for one agent is one rebuild, not two.
The batch is atomic: it validates every change first and on any error The batch is atomic: it validates every change first and on any error
@ -493,8 +494,8 @@ isn't clobbered.
## SCH3DUL3S tab ## SCH3DUL3S tab
Anything that fires at a future time. Operator-set schedules Anything that fires at a future time. The operator creates schedules
are created inline in the table (last row); agent self-paced inline in the table (last row); agent self-paced
reminders surface at the bottom as a sibling list — they reminders surface at the bottom as a sibling list — they
share enough conceptual ground to live together. share enough conceptual ground to live together.
@ -519,13 +520,13 @@ headers tilt -45° via CSS so each column reads as a narrow
Per-schedule action column: Per-schedule action column:
- **`↯ fire now`** — sends an out-of-band manual pulse to every - **`↯ fire now`** — sends an out-of-band manual pulse to every
active target (recurring schedules keep their cadence; one-shots active target (recurring schedules keep their cadence; the manual
are consumed after the manual fire). Disabled for paused schedules. fire consumes one-shots). Disabled for paused schedules.
- **`⏸` / `▶` pause/resume toggle** — only on recurring schedules - **`⏸` / `▶` pause/resume toggle** — only on recurring schedules
(one-shots may also be paused to delay a one-time fire, so the (the operator may also pause one-shots to delay a one-time fire, so
button always appears). Paused rows show a `⏸ paused` badge in the the button always appears). Paused rows show a `⏸ paused` badge in
`next` column and are sorted to the middle bucket (after active, the `next` column; the table sorts them to the middle bucket (after
before cancelled). A paused schedule doesn't fire regardless of active, before cancelled). A paused schedule doesn't fire regardless of
`next_fire_at`; resuming (`▶`) clears the pause without adjusting `next_fire_at`; resuming (`▶`) clears the pause without adjusting
the next-fire time. POSTs to `/api/schedules/{id}/pause` or the next-fire time. POSTs to `/api/schedules/{id}/pause` or
`/api/schedules/{id}/resume`. `/api/schedules/{id}/resume`.
@ -598,15 +599,15 @@ Backed by `GET /api/stats-hive?window=<w>` in `hive-c0re`
`Coordinator::kept_state_names()` it opens `Coordinator::kept_state_names()` it opens
`agent_harness_dir(name)/hyperhive-turn-stats.sqlite` read-only (with a `agent_harness_dir(name)/hyperhive-turn-stats.sqlite` read-only (with a
500 ms `busy_timeout`, since `turn_stats` is rollback-journal) and rolls 500 ms `busy_timeout`, since `turn_stats` is rollback-journal) and rolls
the rows up — missing / unreadable / zero-turn dbs are skipped so one the rows up, skipping missing / unreadable / zero-turn dbs so one
bad db never fails the endpoint. This is a **pull** surface (no SSE): bad db never fails the endpoint. This is a **pull** surface (no SSE):
the data is fetched on page load and on window change. Rendered the page fetches the data on page load and on window change. Rendered
with plain tables + CSS bars — the dashboard bundle ships no chart with plain tables + CSS bars — the dashboard bundle ships no chart
library. library.
The cost figure is a deliberately rough estimate from a per-model The cost figure is a deliberately rough estimate from a per-model
price table (`est_cost_usd`); it drifts with list pricing and is price table (`est_cost_usd`); it drifts with list pricing and the page
labelled accordingly. The table is operator-tunable via the labels it accordingly. The table is operator-tunable via the
`services.hyperhive.c0re.modelPrices` nix option — each key is a `services.hyperhive.c0re.modelPrices` nix option — each key is a
model-family short name (matched case-insensitively as a substring of model-family short name (matched case-insensitively as a substring of
the model id, longest match wins) mapping to the model id, longest match wins) mapping to
@ -658,8 +659,8 @@ Cold load backfills the last ~200 messages from `/api/dashboard/history`;
live frames arrive on `/api/dashboard/stream`. Each row is one broker live frames arrive on `/api/dashboard/stream`. Each row is one broker
event — `sent` or `delivered` — with `from → to: body`. When a `sent` event — `sent` or `delivered` — with `from → to: body`. When a `sent`
and `delivered` event for the same message arrive within 3 seconds and `delivered` event for the same message arrive within 3 seconds
(immediate delivery to a live recipient), the row is upgraded in place (immediate delivery to a live recipient), the page upgrades the row in
(arrow becomes green ✓, title reads "sent + delivered") instead of place (arrow becomes green ✓, title reads "sent + delivered") instead of
rendering two near-identical lines — genuine delivery latency (recipient rendering two near-identical lines — genuine delivery latency (recipient
was busy) still appears as a second row. Each row carries `data-from` / was busy) still appears as a second row. Each row carries `data-from` /
`data-to` attributes; an **agent filter select in the FL0W header** narrows `data-to` attributes; an **agent filter select in the FL0W header** narrows
@ -680,7 +681,7 @@ stream sits a terminal-style compose box: `@name` picks the recipient
(sticky via localStorage; autocomplete from the live container list, (sticky via localStorage; autocomplete from the live container list,
Tab/Enter to confirm; `@*` broadcasts). `POST /api/op-send` drops Tab/Enter to confirm; `@*` broadcasts). `POST /api/op-send` drops
`{from:"operator", to, body}` into the broker; the resulting SSE frame `{from:"operator", to, body}` into the broker; the resulting SSE frame
re-renders the terminal row. The root agent is addressed as `@root`. re-renders the terminal row. The operator addresses the root agent as `@root`.
## H0M3 page (`/`) ## H0M3 page (`/`)
@ -693,8 +694,8 @@ from the shared theme (Catppuccin Mocha via `common.css` + `theme.css`).
`home.js` fills the swarm/hive identity line at the top. All dashboard `home.js` fills the swarm/hive identity line at the top. All dashboard
sub-pages include a `← Home` back-link for navigation. sub-pages include a `← Home` back-link for navigation.
No Matrix or Forge tile here — those surfaces (`/matrix/`, the forge's own No Matrix or Forge tile here — the operator reaches those surfaces
public URL) are reached directly, not linked from the dashboard UI. (`/matrix/`, the forge's own public URL) directly, not via the dashboard UI.
## L0GS page (`/logs.html`) ## L0GS page (`/logs.html`)