From 26b55f34d35a0cd0df123df98a1d3d7192a5a355 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 15 Aug 2026 12:22:52 +0200 Subject: [PATCH] docs(web-ui/shape): fix stale CSS bundle count, SSE route prefix, bind-retry cap claims Verified against frontend/packages/dashboard/build.mjs, hive-c0re/src/dashboard/mod.rs, and hive-agent/src/web_ui/mod.rs: - dashboard ships 9 page CSS bundles + colors/theme/common, not 4 - dashboard SSE routes are /api/dashboard/stream + /api/dashboard/history, not /dashboard/stream + /dashboard/history - per-agent bind_with_retry has a MAX_BIND_ATTEMPTS=12 cap (gives up), unlike the dashboard's genuinely uncapped retry loop - assets/swarm.js, assets/call.js -> swarm.js, call.js (no assets/ dir exists) --- docs/web-ui/shape.md | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md index 97d0b410..e142299f 100644 --- a/docs/web-ui/shape.md +++ b/docs/web-ui/shape.md @@ -16,10 +16,14 @@ - `GET /static/*` → bundled CSS + JS produced by esbuild (`frontend/packages/{dashboard,agent}/build.mjs`). Both pages pull the shared terminal pane + Catppuccin palette + typography - from `@hive/shared` (was `hive-fr0nt`); the dashboard ships four - CSS bundles (`common.css` loaded by every page, plus per-page - `dashboard.css` / `flow.css` / `logs.css`); `common.css` inlines - `base.css` + `terminal.css` via esbuild's `@import` resolution. + from `@hive/shared` (was `hive-fr0nt`); the dashboard ships one + CSS bundle per page (`colors.css` + `theme.css` + `common.css`, + loaded by every page, plus a page-specific bundle — + `dashboard.css` / `flow.css` / `logs.css` / `home.css` / + `settings.css` / `stats.css` / `core.css` / `builds.css` / + `credentials.css`); `common.css` inlines `@hive/shared`'s + `base.css` + `terminal.css` + `tabs.css` + `chrome.css` + + `pill.css` via esbuild's `@import` resolution. `terminal.js` exports `{ create, linkify }` as ES module members; a back-compat shim in the IIFE bodies still exposes a `window.HiveTerminal` global for callers that need it. The dashboard's @@ -42,14 +46,14 @@ high-water mark at the moment the snapshot was assembled); clients use it to dedupe their buffered SSE traffic against the snapshot (drop frames with `seq <= snapshot.seq`). -- `GET /dashboard/stream` (dashboard) / `GET /events/stream` +- `GET /api/dashboard/stream` (dashboard) / `GET /events/stream` (per-agent) → `text/event-stream` SSE for live updates. The dashboard stream carries broker `Sent` / `Delivered` (mirrored by a forwarder task from the broker's intra-process channel) plus mutation events (`approval_added` / `approval_resolved`, `question_added` / `question_resolved`, `transient_set` / `transient_cleared`). Each frame carries a `seq`. The - matching backfill endpoint is `GET /dashboard/history` (last + matching backfill endpoint is `GET /api/dashboard/history` (last ~200 broker messages wrapped in `{ seq, events }`) on the dashboard and `GET /events/history` (last 2000 `LiveEvent`s also wrapped in `{ seq, events }`) on the agent. @@ -201,7 +205,7 @@ re-render), call `refreshState()`. State shapes live in `hive-c0re/src/dashboard/state_snapshot.rs::StateSnapshot` and `web_ui/state.rs::StateSnapshot` — when adding state fields, plumb through the snapshot struct and the -relevant domain module (`assets/swarm.js`, `assets/call.js`, etc.) render function. +relevant domain module (`swarm.js`, `call.js`, etc.) render function. **Focus preservation:** `refreshState` checks whether `document.activeElement` sits inside one of the managed sections @@ -277,18 +281,20 @@ previews are type-aware: Both bind their TCP listener with `SO_REUSEADDR` via `tokio::net::TcpSocket` plus a retry loop on `AddrInUse` -(exponential backoff capped at 2s, **no attempt cap**) so an nspawn -restart that races the previous process's socket release resolves -itself. The retry is uncapped on purpose: a capped budget once -left the harness silently UI-less for the rest of its lifetime -when a back-to-back restart held the port longer than the cap -allowed. Genuine port collisions are preflighted host-side -(`lifecycle::{spawn,rebuild}` 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 — retrying forever is the safe choice. The first 12 -attempts log at WARN; after that the level drops to INFO so a -long-held stale socket doesn't flood the journal. +(exponential backoff capped at 2s) so an nspawn restart that races +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), +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 +flood the journal. The per-agent UI's `bind_with_retry` +(`hive-agent/src/web_ui/mod.rs`) instead gives up after +`MAX_BIND_ATTEMPTS` (12) and returns the `AddrInUse` error rather +than looping forever. The per-agent UI optionally binds a `UnixListener` instead of TCP when `HIVE_WEB_SOCKET` is set — the unix-socket transition