docs: fix genuine passive-voice hits in docs/web-ui (small files)

agent.md, css-vars.md, design-guide.md, shape.md, terminal-rendering.md --
26 of 40 write-good.Passive hits rewritten to name the actor (hive-agent,
browsers, the harness, the client, lifecycle::{spawn,rebuild}, CSS, the
router, ...), reusing an actor already named nearby wherever one exists.
14 left alone: established config-state conditionals ("when X is
set/enabled/given/called"), negative-capability/state-descriptor idioms
("is gone", "is hidden", "is unchanged"), one false-positive
("typed slash commands" as a compound noun, not a passive action), a
backward-looking changelog fact with no actor worth naming, and two
deliberately-parallel rhetorical contrasts (sanitized vs XSS-safe;
cache-hit vs cache-miss) left symmetric on purpose.

dashboard.md (70 more hits) is the next docs/web-ui/ batch, not part of
this one -- big enough to deserve its own PR.
This commit is contained in:
iris 2026-09-08 14:58:43 +02:00 committed by mara
commit e9d59f3d10
5 changed files with 43 additions and 43 deletions

View file

@ -21,8 +21,8 @@ output, not something a selector reaches by id).
glass — `backdrop-filter: blur` lets scrolled terminal rows show
through. Measures its own rendered height via `ResizeObserver`
(`Header.tsx`) and writes it to a CSS custom property the content
below reads for its offset — a fixed `6em` guess used to be baked into
`agent.css`, which silently broke (content overlapping the header) the
below reads for its offset — `agent.css` used to bake in a fixed `6em`
guess, which silently broke (content overlapping the header) the
moment any row of badges/pills wrapped onto an extra line at some
viewport width; measuring instead of guessing closes that bug class
structurally rather than for one specific trigger. Two columns:
@ -77,8 +77,8 @@ structurally rather than for one specific trigger. Two columns:
extras) — all as real `<a>` elements, not a `Dropdown`-style
command list, so ctrl/middle-click and "copy link address" keep
working. Each `AgentLink.kind` resolves differently: `container`
same-origin path; `forge``state.forge_public_url + url`, and the
link is dropped entirely when that's unset (never guessed from
same-origin path; `forge``state.forge_public_url + url`, and
hive-agent drops the link entirely when that's unset (never guessed from
`<host>:3000`); `external` → already absolute. Same source
(`GET /api/state`'s `links` field) also feeds
`DashboardState.links` for the dashboard card's icon strip —
@ -121,7 +121,7 @@ and scrolls behind the fixed header + footer.
shoulder-surfers or screenshots. `autocomplete="one-time-code"`
is the semantic value for OAuth codes (per WHATWG): browsers may
silently ignore `autocomplete="off"` on `type="password"`, but
`one-time-code` is honoured and suppresses the "save password
browsers honour `one-time-code` and suppress the "save password
for this site?" prompt that would otherwise fire on submit.
- Terminal-wrap: live event tail (sticky-bottom autoscroll +
`↓ N new` pill when not at bottom). The pill is **anchored in
@ -154,8 +154,8 @@ journald logs). Inbox flyout: unread messages addressed to this agent
with `↳ reply ·` in amber. A `✓ mark all read` button appears in the
flyout header when the inbox is non-empty; selecting it confirms then
POSTs cross-origin to the core dashboard's `POST
/api/agent/{name}/mark-all-read` — all pending messages for this agent
are acked, the harness won't receive wake-prompts for them. A `{
/api/agent/{name}/mark-all-read`, which acks all pending messages for
this agent so the harness won't receive wake-prompts for them. A `{
marked: N }` pill surfaces the count. After the drain the inbox list
empties on reload (the filter is `acked_at IS NULL`, so drained
messages disappear). Todos flyout ("loose-ends v2"): the harness-local
@ -219,7 +219,7 @@ the full row taxonomy and dispatch logic):
`compact_boundary` emit muted notes; `commands_changed` emits an
expandable details row listing slash commands; `thinking_tokens`
updates a single in-place `🧠` counter; `init`, `result`, and
`rate_limit_event` are dropped (noise / used elsewhere); other
`rate_limit_event` drop silently (noise / used elsewhere); other
subtypes → muted `⚙ <subtype>` note.
- `Note``· text`.
- `TurnStart``◆ TURN ← <from>` with the wake-prompt body; a
@ -259,8 +259,8 @@ Slash commands today:
Arms a one-shot on the Bus; next turn runs without
`--continue`, dropping the resume session entirely.
- `/logout``POST /api/logout` (confirms first). Wipes OAuth
credential files, parks the agent in `needs_login`. Session
history (`~/.claude/projects/`) is preserved.
credential files, parks the agent in `needs_login`, and preserves
session history (`~/.claude/projects/`).
Unknown `/foo` shows an error row instead of being silently sent.
@ -314,17 +314,17 @@ shaped).
session continuity — logout must stay narrowed to just the
credential files.
3. Flip `LoginState::NeedsLogin` + emit a `LiveEvent::Note`
describing exactly what was wiped, then emit
naming the files it just deleted, then emit
`needs_login_idle`. The turn-loop's next iteration parks
into `wait_for_login`, which snapshots the credential dir
(now missing the wiped files) and resumes when a fresh
credentials file appears via the dashboard's `/login/code`
flow (the same mtime-resumption path manual re-login uses).
Always returns 200 with a body describing what happened —
per-file errors are folded into the response + the Note so the
Always returns 200 with a body describing what happened — it
folds per-file errors into the response + the Note so the
operator sees them in the live panel rather than as an HTTP
error. Missing files (already logged out) are treated as
error. Missing files (already logged out) count as
idempotent.
- `GET /api/state` — cold-load snapshot (`StateSnapshot`) consumed by
the frontend (`useAgentState.ts`) on page load and, on a poll, while
@ -374,7 +374,7 @@ shaped).
dimensions; enabled once the server advertises the
`ExtendedDesktopSize` pseudo-encoding (`-308` rect in the header).
Fit-mode state persists in `localStorage` (`screen-fit`); default
is on. Pointer coordinates are rescaled in `sendPointer` so clicks
is on. `sendPointer` rescales pointer coordinates so clicks
land on the right pixel regardless of CSS scale.
- `GET /screen/ws` — raw RFB byte relay: proxies WebSocket
frames to the weston VNC server at `127.0.0.1:<vnc_port>`.
@ -400,8 +400,8 @@ Bus events (new vocabulary on `/events/stream`):
- `status_changed { status }``online` / `rate_limited` /
`needs_login_idle` / `needs_login_in_progress`. Drives the
alive-badge. `rate_limited` is set when the harness detects a
429 response and cleared when the retry sleep expires.
alive-badge. The harness sets `rate_limited` when it detects a
429 response and clears it when the retry sleep expires.
- `model_changed { model }` — drives the model chip.
- `effort_changed { effort }` — drives the effort picker chip.
Emitted from `Bus::set_effort` on `POST /api/effort`; applies on
@ -452,7 +452,7 @@ input tokens of the most recent fresh claude session's first turn —
a proxy for system-prompt + CLAUDE.md sprawl (a fresh session's first
turn pays the full static prefix uncached). It's derived in `stats.rs`
(`first_turn_ctx`: the first turn, `ORDER BY started_at LIMIT 1`, of
the latest `sessions` row in the window) and is omitted from the JSON
the latest `sessions` row in the window) and stays out of the JSON
until the `sessions` / `turn_stats.session_id` capture has rows — so
the chip stays hidden on older dbs (inert-until-capture).
`stats.rs` opens the sqlite db read-only and degrades to an

View file

@ -19,7 +19,7 @@ for the raw `--baseNN` slots directly. (`base.css` holds only the shared
`colors.css` declares each palette's 16 hex values exactly once —
`--mocha-baseNN` (dark, Catppuccin Mocha) and `--latte-baseNN` (light,
Catppuccin-Latte-derived; `base08`-`base0F` are darkened from stock
Catppuccin-Latte-derived; `base08`-`base0F` run darker than stock
Latte to actually clear WCAG AA against how this app uses them — real
contrast failures found in review, see the comment above those
declarations in `colors.css` for the numbers and why). Everything else
@ -43,8 +43,8 @@ them **first** (`colors.css` then `theme.css`) ahead of the page CSS. Note
`theme.css` does **not** `@import` `colors.css` — that would bake the
slots back into it; they're two separate output files so a swap replaces
just `colors.css`. (Load order doesn't actually affect resolution —
custom properties resolve at computed-value time — but `colors.css` is
linked first for clarity.)
custom properties resolve at computed-value time — but each page still
links `colors.css` first for clarity.)
## Semantic palette (`theme.css`)

View file

@ -89,7 +89,7 @@ needs a refresh story; a value that's silently gone stale with no way to
tell is worse than one that's visibly stale.
- **Relative-time labels must actually tick.** A label like "fresh (5s
ago)" is computed from a stored timestamp and re-renders itself on an
ago)" derives from a stored timestamp and re-renders itself on an
interval — never a value frozen at fetch time that quietly goes stale
while still reading as current. Pauses while its tab is hidden.
- **Pages that poll get a refresh-interval control, not silent
@ -143,8 +143,8 @@ just bare column headers floating over nothing.
## Component-first design
Build the `src/ui/` primitive before or alongside the first real page
that needs it, not after it's been styled inline and left for later —
a page reaching for a component that's already there has ready-made
that needs it, not after a contributor styles it inline and leaves it
for later — a page reaching for a component that's already there has ready-made
blocks to build with, instead of the next contributor duplicating
ad-hoc styling that someone then has to hunt down and consolidate.
`Panel`/`Badge`/`Table`/`TextField`/`SelectField`/`Button` are the

View file

@ -8,8 +8,8 @@
- `GET /``index.html` from the bundled frontend dist (see
`frontend/`). Both binaries' routers declare their dynamic
endpoints first and then `fallback_service(ServeDir::new(...))`
pointed at `HIVE_STATIC_DIR`anything not matched by an API or
action route is served from the dist. Dashboard dist lives at
pointed at `HIVE_STATIC_DIR`that fallback serves anything not
matched by an API or action route from the dist. Dashboard dist lives at
`${frontend}/dashboard`; per-agent dist is the merged
`hyperhive.frontend.mergedDist` (default agent dist + per-agent
`extraFiles` overlay).
@ -43,7 +43,7 @@
to `target="_blank"`.
- `GET /api/state` → JSON snapshot the JS app renders into the
DOM. Includes a top-level `seq` (the dashboard event channel's
high-water mark at the moment the snapshot was assembled);
high-water mark at the moment the server assembled the snapshot);
clients use it to dedupe their buffered SSE traffic against
the snapshot (drop frames with `seq <= snapshot.seq`).
- `GET /api/dashboard/stream` (dashboard) / `GET /events/stream`
@ -88,9 +88,9 @@
## Shared terminal pane
Both surfaces' scrollable log streams (`#msgflow` on the dashboard,
`#live` on the per-agent page) are backed by the shared terminal
factory in `@hive/shared/terminal.js`. The factory wires up
The shared terminal factory in `@hive/shared/terminal.js` backs both
surfaces' scrollable log streams (`#msgflow` on the dashboard, `#live`
on the per-agent page). The factory wires up
sticky-bottom autoscroll, a "↓ N new" pill, history backfill, and
SSE replay. Pages register a `kind → renderer` map; unknown kinds
fall through to a JSON-dump note row. The factory ships three row
@ -175,8 +175,8 @@ initial visual lag to one frame instead of one microtask + frame.
**Backfill + SSE.** Cold load fetches `historyUrl` (replay), then
subscribes to `streamUrl` (live tail). Both endpoints return
`{ seq, events }` so the client can dedupe — events with
`seq <= snapshot.seq` from the SSE stream are dropped silently
`{ seq, events }` so the client can dedupe — the client silently
drops events with `seq <= snapshot.seq` from the SSE stream
(the snapshot already covers them). History rows render with a
`.no-anim` class so they don't stagger in like live events. The
optional `streamFactory(url)` callback lets the dashboard hand
@ -221,8 +221,8 @@ leave it empty for a paint (on async builders) or a whole frame (on
large synchronous ones), which reads as a visible flash on every
poll cycle. Builders receive the fragment as their
`root`, so existing renderer code carries over unchanged; early
returns inside the builder still commit whatever was appended
before they returned.
returns inside the builder still commit whatever it appended
before returning.
**Keyed DOM caching:** for sections whose rows hold interactive state
(textarea drafts, checkboxes, focused inputs) `paintAtomic` isn't
@ -260,8 +260,8 @@ the left edge captures pointer events, resizes the drawer in
real-time (pointer capture keeps dragging even if the cursor
outpaces the handle), and persists the chosen width to
`localStorage` (key `hyperhive:side-panel-width`) so it
survives page reload. Width is clamped to CSS `min-width: 320px`
/ `max-width: 96vw`; the viewport-resize handler re-clamps
survives page reload. CSS clamps the width between `min-width: 320px`
and `max-width: 96vw`; the viewport-resize handler re-clamps
persisted values after a window shrink. File
previews are type-aware:
@ -286,9 +286,9 @@ Both bind their TCP listener with `SO_REUSEADDR` via
the previous process's socket release resolves itself, but the two
binaries differ on the attempt budget. The dashboard's
`bind_with_retry` (`hive-c0re/src/dashboard/mod.rs`) has **no
attempt cap** — retrying forever is deliberate, since genuine port
collisions are preflighted host-side (`lifecycle::{spawn,rebuild}`
refuses with a clear error, surfaced on the dashboard as a banner),
attempt cap** — retrying forever is deliberate, since
`lifecycle::{spawn,rebuild}` preflights genuine port collisions
host-side (refuses with a clear error, surfaced on the dashboard as a banner),
so at this layer a persistent `AddrInUse` always reflects a
recoverable stale socket. Its first 12 attempts log at WARN; after
that the level drops to INFO so a long-held stale socket doesn't
@ -325,10 +325,10 @@ at without rebuilding the dist. The cases that matter:
The gateway upstream config strips the prefix before forwarding to
the per-agent server, so the agent's Rust routes (`api/state`,
`events/stream`, `screen/ws`, `login/start`, …) keep their absolute
paths server-side. Only the browser-facing URLs are gated on the
paths server-side. Only the browser-facing URLs key off the
mount prefix.
Subpages (`stats`, `screen`) are served without a trailing slash so
The router serves subpages (`stats`, `screen`) without a trailing slash so
the relative-path resolution stays correct: `static/stats.js` from
`/stats` becomes `/static/stats.js` (last segment `stats` gets
replaced), not `/stats/static/stats.js`. Adding a trailing slash to

View file

@ -4,8 +4,8 @@ The per-agent web UI's live pane renders one row per `TermMsg`
(`hive-agent/src/term_msg.rs`): `{icon?, level: debug|info|warn|error,
summary, body?, body_format?: markdown|diff, coalesce_key?}`. Classification
(icon, summary text, whether a tool call gets an expandable body) happens
server-side, once — `term_msg.rs` + `stream_enrich.rs` — and is served
identically by both `GET /api/events/history` and `GET /api/events/stream`
server-side, once — `term_msg.rs` + `stream_enrich.rs` — and both
`GET /api/events/history` and `GET /api/events/stream` serve it identically
as `TermEnvelope { ts, seq?, msgs: TermMsg[] }` frames
(`hive-agent/src/web_ui/stream.rs`).