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:
parent
68c2b3284e
commit
ea1c6e4d05
2 changed files with 48 additions and 41 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -481,12 +481,9 @@ window.marked = marked;
|
|||
for (const node of tree) {
|
||||
const c = node.container;
|
||||
const url = `http://${hostname}:${c.port}/`;
|
||||
// Pending state is overlaid from the transient store first
|
||||
// (operator-initiated spawn/destroy/rebuild — covers the
|
||||
// create+start window when the container literally isn't up
|
||||
// yet), then from the rebuild_queue (covers worker-driven ops
|
||||
// even if no transient was set). `ContainerStateChanged`
|
||||
// doesn't carry either signal.
|
||||
// Pending-state derivation + queued-vs-running split — see
|
||||
// docs/web-ui.md::Container row for the transient → in-flight
|
||||
// queue priority order and the opRunning rationale.
|
||||
const transientKind = transientsState.get(c.name)?.kind || null;
|
||||
const op = !transientKind ? inFlight.get(c.name) : null;
|
||||
const pending = transientKind
|
||||
|
|
@ -497,12 +494,6 @@ window.marked = marked;
|
|||
: (op.kind === 'meta_update' ? 'meta-update queued'
|
||||
: op.kind === 'destroy' ? 'destroy queued'
|
||||
: 'rebuild queued')));
|
||||
// Split the visual into queued vs running (#769): queued ops get
|
||||
// no row highlight — the badge text alone says "wait, queued" —
|
||||
// while running ops keep the amber row tint + add a rotating
|
||||
// amber ring on the agent icon. Operator-initiated transients
|
||||
// always count as running (the op is already in flight even if
|
||||
// the queue worker hasn't picked it up yet).
|
||||
const opRunning = transientKind != null
|
||||
|| (op != null && op.state === 'running');
|
||||
const selected = selectionState.has(c.name);
|
||||
|
|
@ -522,24 +513,12 @@ window.marked = marked;
|
|||
const prefix = treePrefixDom(node);
|
||||
if (prefix) li.prepend(prefix);
|
||||
|
||||
// Full-height square agent icon, left of the card body. The
|
||||
// icon is an <img> absolutely positioned inside a wrapper div:
|
||||
// the div is the flex child and sizes itself via aspect-ratio +
|
||||
// stretch, the <img> is out of flow so its load state — pending,
|
||||
// loaded or broken — can never contribute intrinsic size or
|
||||
// reflow the row. (issue #177)
|
||||
//
|
||||
// The icon points straight at the agent's `<url>/icon`. We don't
|
||||
// guess whether the agent is reachable from the container row —
|
||||
// we just let the <img> try, and if it actually fails to load
|
||||
// (agent stopped, restarting, rebuilding — web server not
|
||||
// answering) the error handler falls it back to the dimmed
|
||||
// hyperhive mark (`/favicon.svg`, served by the dashboard
|
||||
// itself, always reachable). (issues #195, #202)
|
||||
// Agent icon: 5em square wrapper with an absolutely-positioned
|
||||
// <img> + fire-and-forget load with /favicon.svg fallback. The
|
||||
// wrapper doubles as the selection toggle (click / keyboard).
|
||||
// See docs/web-ui.md::Container row for the layout + load-strategy
|
||||
// rationale.
|
||||
const iconImg = el('img', { class: 'container-icon-img', alt: '' });
|
||||
// Icon is the selection toggle. Click → add/remove from
|
||||
// `selectionState` → re-render. role=button + tabindex makes it
|
||||
// keyboard-accessible; aria-pressed reflects the toggle state.
|
||||
const icon = el('div', {
|
||||
class: 'container-icon',
|
||||
role: 'button',
|
||||
|
|
@ -3116,10 +3095,8 @@ window.marked = marked;
|
|||
// synchronous read (e.g. the compose autocomplete pulls agent
|
||||
// names from here instead of refetching on every keystroke).
|
||||
window.__hyperhive_state = s;
|
||||
// #607: surface the M4TR1X → tab strip entry only when the
|
||||
// backend's HIVE_MATRIX_GUI_DIR mount is live (otherwise
|
||||
// clicking would 404). `state.matrix_gui_enabled` flips when
|
||||
// the operator toggles `hyperhive.matrix.gui.enable` and rebuilds.
|
||||
// Gate the M4TR1X → tab strip entry — see
|
||||
// docs/web-ui.md::Tab strip for the matrix_gui_enabled rationale.
|
||||
const matrixTab = $('tab-matrix');
|
||||
if (matrixTab) matrixTab.hidden = !s.matrix_gui_enabled;
|
||||
const openDetails = snapshotOpenDetails();
|
||||
|
|
@ -3201,13 +3178,11 @@ window.marked = marked;
|
|||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
};
|
||||
(function bindDashboardStream() {
|
||||
// #448: route the EventSource through a SharedWorker so all open
|
||||
// hyperhive tabs share ONE backend SSE connection. Survives
|
||||
// Firefox's per-tab connection throttling under many-open-tabs
|
||||
// pressure (the actual mara symptom). `openStream` returns an
|
||||
// EventSource-shaped facade so the rest of this IIFE is unchanged;
|
||||
// graceful fallback to direct `new EventSource` when SharedWorker
|
||||
// isn't supported.
|
||||
// Route through the SharedWorker so all open hyperhive tabs share
|
||||
// one upstream SSE connection — see docs/web-ui.md (SSE multiplexing
|
||||
// paragraph) for the design + Firefox throttling motivation.
|
||||
// `openStream` returns an EventSource-shaped facade with a graceful
|
||||
// direct-EventSource fallback when SharedWorker isn't supported.
|
||||
const es = openStream('/dashboard/stream');
|
||||
es.onmessage = (e) => {
|
||||
let ev;
|
||||
|
|
|
|||
Loading…
Reference in a new issue