Watch
0
0
Fork
You've already forked hyperhive
0

dashboard: the SW4RM tab is the agents tab

The swarm lives in the swarm UI; this tab lists the hive's agents. The tab
is 4G3NTS at #agents, its pane and count pill follow, and the module that
renders the containers is agents.js. Comments and docs that named SW4RM or
swarm.js follow.
This commit is contained in:
iris 2026-10-14 20:23:14 +02:00 • committed by mara
commit e455964cb3
16 changed files with 70 additions and 69 deletions

View file

@ -1,6 +1,6 @@
// hive-agent-menu.js — <hive-agent-menu>, the per-agent "⋮" context-menu
// custom element. One instance per agent card, appended after
// .card-body in the container row's flex layout (swarm.js's
// .card-body in the container row's flex layout (agents.js's
// buildContainerLi via buildAgentMenu).
//
// Deliberately a *light-DOM* element with no shadow root of its own: all
@ -66,7 +66,7 @@ async function agentMenuPost(actionPath, name, body, flags) {
class HiveAgentMenu extends HTMLElement {
connectedCallback() {
// Same reconnect-without-detach hazard as <hive-menu> (see its
// connectedCallback comment) — swarm.js's row cache can move an
// connectedCallback comment) — agents.js's row cache can move an
// already-built <li> subtree without a real detach. Without this
// guard a reconnect would append a second <hive-menu> on top of the
// first, doubling the dropdown.

View file

@ -1,4 +1,4 @@
// SW4RM (containers) domain — extracted from tabs.js.
// 4G3NTS (containers) domain — extracted from tabs.js.
// Agent topology, container-row rendering, selection bar, and all
// live-update apply handlers for container-state, transient, and
// job-queue-rollup ops. See docs/web-ui/dashboard.md::Container row for the
@ -52,7 +52,7 @@ const selectionState = new Set();
// ─── job queue rollup (queue-summary banner only) ──────────────────────────
//
// The ONE piece of job-queue-derived state on this page, and deliberately
// narrow — mara: "swarm.js should not need to pull in the jobq to do its
// narrow — mara: "agents.js should not need to pull in the jobq to do its
// job." Rendering is owned by the shared `JobqRollup` Preact component
// (same one swarm-ui's /jobs page mounts, self-fetching
// GET /api/jobq/rollup); this file just calls `render(h(...))` and bumps
@ -752,7 +752,7 @@ export function renderContainers(s) {
}
// ─── selection bar ───────────────────────────────────────────────────────────
// Sticky-bottom strip; visible when >=1 agent selected on the SW4RM
// Sticky-bottom strip; visible when >=1 agent selected on the 4G3NTS
// tab. See docs/web-ui/dashboard.md::Selection bar for the interaction model
// and the per-action availability rules (disabled-with-tooltip for
// actions that don't apply to the full selection). Actions POST per
@ -767,11 +767,12 @@ export function renderSelectionBar(containers) {
if (!countSpan || !namesSpan || !actions) return;
const selected = containers.filter((c) => selectionState.has(c.name));
// Tab-gate: bar only renders on SW4RM (the only tab with agent
// Tab-gate: bar only renders on 4G3NTS (the only tab with agent
// cards to cross-reference). Selection state lives in-memory and
// the bar reappears on return to SW4RM if still non-empty.
const onSwarmTab = (document.body.dataset.activeTab || "swarm") === "swarm";
if (!selected.length || !onSwarmTab) {
// the bar reappears on return to 4G3NTS if still non-empty.
const onAgentsTab =
(document.body.dataset.activeTab || "agents") === "agents";
if (!selected.length || !onAgentsTab) {
bar.hidden = true;
document.body.classList.remove("has-selection");
return;

View file

@ -84,7 +84,7 @@ code {
Semantic colour variants only — shape comes from `hive-pill-sm`
(shared with the agent UI's pill/chip family, @hive/shared/pill.css),
carried directly in the markup alongside each of these classnames
(swarm.js/core.js/builds.js). Used on container rows and build-log
(agents.js/core.js/builds.js). Used on container rows and build-log
rows. */
.badge-warn {
color: var(--amber);

View file

@ -127,14 +127,14 @@ body.dashboard-shell {
font-weight: bold;
}
/* `display` above beats the UA's `[hidden]` rule, so an empty count (the
SW4RM and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
4G3NTS and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
.tab-count[hidden] {
display: none;
}
/* Tab pane visibility — createTabStrip (@hive/shared/tabs.js) sets the
`hidden` attribute on every inactive pane (resolved from the URL hash;
default — no hash — is SW4RM). Only the active pane stays visible. */
default — no hash — is 4G3NTS). Only the active pane stays visible. */
.tab-pane[hidden] {
display: none;
}
@ -322,7 +322,7 @@ hive-agent-menu {
--menu-btn-opacity: 1;
}
/* Card actions dim while a row has any pending-state badge (transient-
driven only now — see swarm.js's pending-badge derivation comment;
driven only now — see agents.js's pending-badge derivation comment;
there is no separate queued-but-not-running row state to tell apart
from this one anymore). */
.container-row.pending-running .actions {
@ -443,12 +443,12 @@ hive-agent-menu {
shared <hive-warn> component — amber/no-pulse for
the standing caveat (a pulsing banner that's always present just
teaches you to stop seeing it), `severity="red" pulse` for the port
collision (an incident, not a caveat). See core.js/swarm.js and
collision (an incident, not a caveat). See core.js/agents.js and
hive-warn.css for the actual styling now. */
/* Build-queue summary banner on the SW4RM tab moved to the shared
/* Build-queue summary banner on the 4G3NTS tab moved to the shared
`JobqRollup` Preact component (`.jqr-*` classes, imported above) —
mounted by swarm.js::initJobqRollup into #jobq-rollup-section. */
mounted by agents.js::initJobqRollup into #jobq-rollup-section. */
.pending-state {
color: var(--amber);

View file

@ -26,14 +26,14 @@
<nav class="tabbar" id="tabbar" role="tablist">
<a
class="tab"
id="tab-swarm"
href="#swarm"
id="tab-agents"
href="#agents"
role="tab"
aria-controls="tab-pane-swarm"
data-tab="swarm"
aria-controls="tab-pane-agents"
data-tab="agents"
>
<span class="tab-label">◆ SW4RM ◆</span>
<span class="tab-count" id="tab-count-swarm" hidden></span>
<span class="tab-label">◆ 4G3NTS ◆</span>
<span class="tab-count" id="tab-count-agents" hidden></span>
</a>
<!-- SYST3M moved to its own standalone page (/core.html, "C0R3"),
reached from the H0M3 hub — keeps the dashboard tab strip lean.
@ -89,7 +89,7 @@
view, not part of the operational tab strip. -->
<!-- (Peer hives are no longer a tab — they render as a headline
under SW4RM. See tab-pane-swarm.) -->
under 4G3NTS. See tab-pane-agents.) -->
<!-- FL0W and the other standalone pages are reachable from the
H0M3 hub (served at /), not from the dashboard tab strip —
@ -99,33 +99,33 @@
<!-- Tab panes. createTabStrip (@hive/shared/tabs.js) keeps exactly one
visible by toggling the `hidden` attribute, resolved from the URL
hash (default SW4RM). Panes start `hidden` to avoid a flash before
hash (default 4G3NTS). Panes start `hidden` to avoid a flash before
the script runs. -->
<!-- Padded content wrapper: carries the 1.5em side gutter
(.page-content, common.css) while <body> stays full-bleed so the
sticky chrome above + the footer below span the full width. -->
<div class="page-content">
<main class="dashboard-main">
<!-- SW4RM: the swarm itself. Container cards (the central thing
<!-- 4G3NTS: the agents. Container cards (the central thing
the operator looks at) and rebuild queue / cascade visualisation
that drives them. No inline `C0NTAINERS` h2 heading + divider
— the tab label SW4RM already says it. -->
— the tab label 4G3NTS already says it. -->
<section
class="tab-pane"
id="tab-pane-swarm"
data-tab-pane="swarm"
id="tab-pane-agents"
data-tab-pane="agents"
hidden
role="tabpanel"
aria-labelledby="tab-swarm"
aria-labelledby="tab-agents"
>
<!-- Swarm / hive identity headline. Populated by refreshState from
hive_name + swarm_name; stays hidden when neither is set. -->
<h2 id="swarm-identity" hidden></h2>
<!-- JobqRollup mount point, kept outside #containers-section
deliberately — that section is wiped + rebuilt on every
container-list render (see swarm.js::renderContainers), which
container-list render (see agents.js::renderContainers), which
would tear down and remount the Preact tree on every
container-state tick. Mounted once by swarm.js::initJobqRollup,
container-state tick. Mounted once by agents.js::initJobqRollup,
refreshed via its own handle rather than by re-rendering. -->
<div id="jobq-rollup-section"></div>
<div id="containers-section">

View file

@ -8,7 +8,7 @@
// live in `./common.js`; the flow-page surface (broker
// terminal, @-mention composer) lives in `./flow.js` — `flow.html` and
// `dashboard.html` each load their own bundle.
// SW4RM (containers) domain lives in `./swarm.js`.
// 4G3NTS (containers) domain lives in `./agents.js`.
import { marked } from "marked";
import { $, openStream, renderServerWarnings } from "./common.js";
@ -33,7 +33,7 @@ import {
applyTransientCleared,
renderContainers,
renderSelectionBar,
} from "./swarm.js";
} from "./agents.js";
import {
initBuilds,
onBuildsTabShown,
@ -126,7 +126,7 @@ window.marked = marked;
window.__hyperhive_state = s;
renderServerWarnings(s.server_warnings);
// Hive identity: render the swarm/hive name as a headline at the
// top of the SW4RM pane + update the page title once the
// top of the 4G3NTS pane + update the page title once the
// server-side display names are known. `hive_name` / `swarm_name`
// come from HYPERHIVE_HIVE_NAME / HYPERHIVE_SWARM_NAME env vars
// (set by services.hyperhive.{hiveName,swarmName} nix options).
@ -148,8 +148,8 @@ window.marked = marked;
// `transientsState` + `containersState`, not from `s.*`).
syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s);
// Job-queue rollup feeds only the SW4RM queue-summary banner
// (per-agent card badges are transient-only — see swarm.js). Its
// Job-queue rollup feeds only the 4G3NTS queue-summary banner
// (per-agent card badges are transient-only — see agents.js). Its
// own `JobqRollup` mount self-fetches GET /api/jobq/rollup — not
// read off `s` (this page's snapshot carries no jobq field).
// initJobqRollup mounts once and is a no-op on later calls; the
@ -182,7 +182,7 @@ window.marked = marked;
// ─── live updates: dashboard event stream ──────────────────────────────
// The dashboard subscribes to /dashboard/stream for live mutation
// events so the SW4RM / SYST3M panes update without an
// events so the 4G3NTS / SYST3M panes update without an
// operator action triggering a refreshState.
//
// Bare `EventSource` (no terminal infrastructure needed — the
@ -202,7 +202,7 @@ window.marked = marked;
container_state_changed: applyContainerStateChanged,
container_removed: applyContainerRemoved,
// rebuild_queue_changed: a payload-less push trigger. Refreshes the
// SW4RM queue-summary banner (see swarm.js) and the R3BU1LD QU3U3
// 4G3NTS queue-summary banner (see agents.js) and the R3BU1LD QU3U3
// tab's JobqGraph (see builds.js).
rebuild_queue_changed: () => {
applyRebuildQueueChanged();
@ -260,9 +260,9 @@ window.marked = marked;
})();
// ─── tab routing ───────────────────────────────────────────────────────
// Hash-based: `#swarm` / `#rebuild` / `#meta` / `#buildlogs` /
// Hash-based: `#agents` / `#rebuild` / `#meta` / `#buildlogs` /
// `#permissions` activate the matching pane on the dashboard. Empty
// hash defaults to SW4RM. FL0W and ST4TS are NOT tabs —
// hash defaults to 4G3NTS. FL0W and ST4TS are NOT tabs —
// they're separate pages (`/flow.html`, `/stats.html`) reached from
// the H0M3 hub. Tab routing only
// applies when the tab DOM is present (e.g. not on the flow page
@ -275,7 +275,7 @@ window.marked = marked;
// to their own pages rather than hide them in a dropdown.
function activateTab(target) {
// Track active tab on the body so renderSelectionBar can gate
// visibility (bar only belongs on SW4RM where agent cards live).
// visibility (bar only belongs on 4G3NTS where agent cards live).
document.body.dataset.activeTab = target;
renderSelectionBar(Array.from(containersState.values()));
// Permissions tables: SSE covers worker-applied changes
@ -292,9 +292,9 @@ window.marked = marked;
// Wire the shared tab strip now that activateTab + the lazy-load fns it
// calls are defined. The strip resolves the active tab from the hash
// (default SW4RM), toggles the active tab/pane + aria-selected, and
// (default 4G3NTS), toggles the active tab/pane + aria-selected, and
// fires activateTab for the per-tab side-effects on every change.
createTabStrip($("tabbar"), { defaultId: "swarm", onShow: activateTab });
createTabStrip($("tabbar"), { defaultId: "agents", onShow: activateTab });
// The rebuild queue mounts now, not on first show, so its tab's count pill
// is live from the start.
initBuilds();
@ -313,14 +313,14 @@ window.marked = marked;
/** Recompute every tab's count from the current state. Called on
* every renderXxx that's tab-relevant. */
function refreshTabCounts() {
// SW4RM — flag any container that's stale (needs_update). Empty
// 4G3NTS — flag any container that's stale (needs_update). Empty
// when everyone's current. Container-row pulse signals state
// transitions; the pill catches "deploy-pending" specifically.
let swarm = 0;
let stale = 0;
for (const c of containersState.values()) {
if (c.needs_update) swarm++;
if (c.needs_update) stale++;
}
setTabCount("swarm", swarm);
setTabCount("agents", stale);
// R3BU1LD QU3U3 — rebuilds in flight.
setTabCount("rebuild", rebuildCount());
}