tabs.js + docs: migrate icon + pending-state prose to docs (#712 batch 11)

Mara on #774: previous batches were cookie-stripping rather than
prose migration. This batch actually moves substantive comment
prose from tabs.js into docs/web-ui.md.

Moved to docs/web-ui.md:
- Container row → **Icon layout + load strategy** sub-paragraph
  (new): absolute-positioning rationale (so img load state can't
  reflow row), fire-and-forget load pattern (no pre-check
  reachability), favicon fallback chain, icon-unreachable class,
  the immediate-fallback-when-stopped optimisation.
- Container row → **Pending-state derivation** paragraph (new):
  three-source priority order (transient → in-flight queue → none),
  why ContainerStateChanged isn't enough, the opRunning flag's role
  in driving the pending-running class + spinner.

Collapsed in tabs.js:
- Icon block (~14 lines of WHY comments + pointer to docs) →
  4-line pointer + behavioural one-liner. Drops #177 / #195 / #202
  cookies en passant since their substance now lives in docs.
- Pending-state block (~22 lines split across two paragraphs) →
  4-line pointer. Drops #769 self-cookie (the queued vs running
  split lives in docs::Container row now).
- SharedWorker EventSource (~7 lines) → 5-line pointer. Drops
  #448 cookie (the SSE multiplexing paragraph in docs already
  has the design + Firefox throttling rationale; the in-code
  comment was duplicating).
- M4TR1X tab gating (~4 lines) → 2-line pointer. Drops #607 cookie
  in both tabs.js + docs/web-ui.md::Tab strip (the substance was
  already in docs, just had the cookie attached).

tabs.js: 11 → 6 refs (92% reduction from baseline 73). Net effect:
~47 lines of substantive prose moved out of tabs.js into
docs/web-ui.md, where it belongs.
This commit is contained in:
iris 2026-05-31 14:23:49 +02:00
commit ea1c6e4d05
2 changed files with 48 additions and 41 deletions

View file

@ -193,7 +193,7 @@ cross-page link (`◆ FL0W ◆ →`), not a pane swap.
`services.hyperhive.matrix.gui.enable` is off (defaults to
`matrix.enable`) so operators without the matrix GUI on don't
see a dead link — tabs.js gates the `hidden` attribute on
`state.matrix_gui_enabled` from `/api/state` (#607).
`state.matrix_gui_enabled` from `/api/state`.
- **Notification controls**: `🔔 enable notifications` when
permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted.
Always visible in the chrome regardless of active tab.
@ -404,6 +404,23 @@ falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
body sits to the right with three stacked lines
(`assets/tabs.js::renderContainers`).
**Icon layout + load strategy:** the `<img>` is absolutely
positioned (`inset: 0`) inside the `.container-icon` wrapper —
the wrapper is the flex child and sizes itself via `width: 5em` +
`aspect-ratio: 1`, the `<img>` is out of flow so its load state
(pending, loaded, broken) can never contribute intrinsic size or
reflow the row. Without that, the row would briefly grow as the
image's natural dimensions arrived, then snap back on
`object-fit: contain`. The load itself is fire-and-forget: the
dashboard doesn't pre-check whether the agent is reachable, it
just lets the `<img>` try and listens for an `error` event. On
failure the handler swaps the `src` to `/favicon.svg` (served by
the dashboard itself, always reachable) and adds the
`icon-unreachable` class for the dimmed look. When the container
is known stopped up front (`ContainerView.running = false`) the
fallback fires immediately, skipping the doomed `<url>/icon`
fetch entirely.
- Line 1: agent name (link → new tab), m1nd/ag3nt chip, an
**icon-only nav strip** populated async from the agent backend
(`📊 stats`, `🖥 screen` when GUI is enabled, `⬡ forge profile`,
@ -440,6 +457,21 @@ body sits to the right with three stacked lines
row tint AND draws a **rotating amber ring** around the agent
icon, so it's obvious at a glance which container is actually
moving.
**Pending-state derivation:** the pill is sourced from two
separate stores in priority order. (1) The operator-initiated
**transient** (`transientsState`) is set on the dashboard the
moment the operator clicks start / stop / restart / rebuild /
destroy / spawn — covers the create-and-start window where the
container literally isn't up yet, before any backend state event
has fired. (2) If no transient is set, the **rebuild-queue
entry** for this agent is consulted (`rebuildQueueState`); this
covers worker-driven ops — meta-update cascades, crash-recover
rebuilds, approval-driven rebuilds — that the operator didn't
click. `ContainerStateChanged` carries neither signal, so the
dashboard reads from the two snapshots directly. The
`opRunning` flag (driving the `pending-running` row class +
spinner) is true when (1) is set OR (2) is in `running` state;
queued entries leave `opRunning` false.
Container name + port, and a `ctx · Nk` chip showing the
agent's last-turn context size (from `ContainerView.ctx_tokens`,
read from the turn-stats sqlite on each `build_all` sweep;