diff --git a/docs/web-ui/agent.md b/docs/web-ui/agent.md index 3f3fb89c..a221e3f5 100644 --- a/docs/web-ui/agent.md +++ b/docs/web-ui/agent.md @@ -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 `` 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 `: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 `⚙ ` note. - `Note` → `· text`. - `TurnStart` → `◆ TURN ← ` 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:`. @@ -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 diff --git a/docs/web-ui/css-vars.md b/docs/web-ui/css-vars.md index f7724f46..74c5f950 100644 --- a/docs/web-ui/css-vars.md +++ b/docs/web-ui/css-vars.md @@ -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`) diff --git a/docs/web-ui/design-guide.md b/docs/web-ui/design-guide.md index 4023247e..4a215c1f 100644 --- a/docs/web-ui/design-guide.md +++ b/docs/web-ui/design-guide.md @@ -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 diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md index 9698069d..3a598377 100644 --- a/docs/web-ui/shape.md +++ b/docs/web-ui/shape.md @@ -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 diff --git a/docs/web-ui/terminal-rendering.md b/docs/web-ui/terminal-rendering.md index a6e100aa..91ed9887 100644 --- a/docs/web-ui/terminal-rendering.md +++ b/docs/web-ui/terminal-rendering.md @@ -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`).