Compare commits

...
Author SHA1 Message Date
iris
40cc115a0a swarm.js: drop the queue-summary banner rather than ship it on an interim jobq fetch
mara, on the already-approved PR: "dont replace one legacy thing with
another. then we will have to either wait with this pr or split it
into what can and cannot be done now."

Splitting: the transients-only per-agent badge fix is real, correct,
and fixes a live regression (the old DagView fields it read no longer
exist) — nothing about it depends on job-queue data at all, so it
ships as-is. The queue-summary banner is the part that doesn't belong
in this shape: it was reading GET /api/jobq/graph directly and
deriving counts client-side as an interim stand-in for the dedicated
rollup endpoint mara separately asked for — exactly the kind of
stopgap-on-a-stopgap her comment is calling out, since the endpoint
that should serve it doesn't exist on main yet.

Removes jobqNodesState, refreshJobqGraph(), the rebuild_queue_changed
SSE subscription, and the banner's render block from swarm.js/tabs.js
entirely — swarm.js now reads no job-queue state of any kind, fully
satisfying "swarm.js should not need to pull in the jobq to do its
job." The banner comes back once the rollup endpoint
(hyperhive#2985's follow-up) exists, reading that directly instead of
the full graph. Until then the per-agent transient pills still show
what's actually running on each card; only the hive-wide "N running /
M queued" summary line is temporarily gone.

CSS classes for the banner (.queue-summary/.queue-summary-link) kept
in dashboard.css rather than deleted-then-restored — commented as
currently unused, expected to come back unchanged.

docs/web-ui/dashboard.md updated to match (Container-row pending-
badge section, the removed Build-queue-summary-banner section, and
the BU1LDS-page note that used to describe SW4RM's now-removed
parallel fetch).
2026-08-03 20:03:49 +02:00
iris
7b05656e17 tabs.js: fix stale comment — rebuild_queue_changed feeds the queue-summary banner only 2026-08-03 20:03:49 +02:00
iris
17f61d1da6 swarm.js: drop per-agent pending-badge fallback, transients-only now
mara, on review: "swarm.js should not need to pull in the jobq to do
its job" followed by "remove the per agent pending stuff - only show
what is running."

Deletes queuedOpsByAgent() entirely — no more per-agent badge derived
from Pending-state job-queue nodes. A card's pending badges are now
driven exclusively by transientsState (i.e. actually-running work);
queued-but-not-started work shows nothing on the card until a node
starts. jobqNodesState + refreshJobqGraph() stay, now feeding only
the queue-summary banner (a separate, still-open question — mara
separately asked for a dedicated rollup endpoint for that, tracked
apart from this PR).

Collapses the now-always-coincident `pending`/`pending-running` row
classes into one (`pending-running`) — there's no more queued-only
row state to visually distinguish it from.

docs/web-ui/dashboard.md's Container-row section rewritten to match:
the two-store priority-fallback description is gone, replaced with
"transients only."
2026-08-03 20:03:49 +02:00
iris
45710ab739 swarm.js: migrate pending-row fallback + queue-summary banner off DagView
hyperhive#2822/PR#3026 moved swarm.js's per-agent in-flight status off
the rebuild queue. Two other reads of the same rebuild_queue field
survived that PR by design (a different feature, atlas flagged it on
#2985) and are the last DagView/NodeView consumers on the frontend:
queuedOpsByAgent()'s pending-row fallback and the SW4RM queue-summary
banner. Both now read GET /api/jobq/graph (hive-jobq-wire's generic
GraphNode shape) instead, matching the pattern builds.js already
established for <hive-jobq-graph>.

Along the way: DagView no longer carries state/kind fields (removed
in an earlier refactor that pushed roll-up derivation client-side),
so both migrated functions were silently reading undefined fields and
had become permanent no-ops — the pending-badge fallback never lit
and the queue-summary banner never rendered. This restores real
behavior rather than porting broken logic forward.

The queue-summary banner's node-count-vs-group-count question (flagged
on hyperhive#3028 as needing a decision) resolves cleanly: a GraphNode
group root (parent: null) is an ordinary node whose own state already
IS the group's roll-up per hive-jobq-wire's contract, so counting
roots by state is a direct filter, not a parent-chain walk or a
client-side rollup calculation.

Verified the derivation logic against constructed GraphNode fixtures
(multi-step chains, settled history that must not count, Finishing
roots, multi-agent single-DAG groups) before wiring it in — 13/13
checks passed.

docs/web-ui/dashboard.md's Container-row + BU1LDS sections updated to
match.
2026-08-03 20:03:49 +02:00
4 changed files with 105 additions and 148 deletions

View file

@ -209,8 +209,12 @@ Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**,
**BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads **BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads
`/api/state` and subscribes to `/api/dashboard/stream` for `/api/state` and subscribes to `/api/dashboard/stream` for
`rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`. `rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`.
The dashboard tab keeps the rebuild-queue *state* for the SW4RM The SW4RM tab (`/dashboard.html`) does **not** read this endpoint (or
card badges without rendering these panels. any job-queue state) at all — mara, on review: "swarm.js should not
need to pull in the jobq to do its job." Its per-agent pending badges
are transient-only, and its queue-summary banner is removed for now
(see Container row, below), pending a dedicated rollup endpoint it
can read directly instead of the full graph.
**R3BU1LD QU3U3** — pending, in-flight, and recently-settled container **R3BU1LD QU3U3** — pending, in-flight, and recently-settled container
operations: rebuilds, meta-update cascades, and first-spawns. One operations: rebuilds, meta-update cascades, and first-spawns. One
@ -222,9 +226,11 @@ listens for its `hive-jobq-graph-update` event to drive the two things
below it that the generic view doesn't show. The component owns below it that the generic view doesn't show. The component owns
fetching, cold and live: `GET /api/jobq/graph` on mount, and fetching, cold and live: `GET /api/jobq/graph` on mount, and
`.refresh()` on every `rebuild_queue_changed` SSE tick (that event `.refresh()` on every `rebuild_queue_changed` SSE tick (that event
still carries its own `Vec<QueueEntry>` payload on the wire — the still carries its own `Vec<QueueEntry>` payload — `DagView`-shaped,
SW4RM tab still consumes it for the badges, untouched — this page just also read by `hivectl`'s own wait/progress loop, a separate migration
ignores the payload and treats the tick as a refetch trigger). — on the wire, but neither dashboard page reads it anymore; both
treat the tick as a pure refetch trigger against the generic
endpoint).
Each row is one root graph node (`parent: null`); a multi-step op's Each row is one root graph node (`parent: null`); a multi-step op's
per-agent subgraphs and sub-steps render as nodes within that one per-agent subgraphs and sub-steps render as nodes within that one
@ -879,22 +885,19 @@ fetch entirely.
independent badge rather than being collapsed into one label, independent badge rather than being collapsed into one label,
matching the existing multi-badge convention this line already uses matching the existing multi-badge convention this line already uses
for `paused`/`needs_update`/model/ctx. for `paused`/`needs_update`/model/ctx.
The row visual splits queued vs running: a **queued** entry (no Any pending badge means the row is actually **running** something
transient yet, see below) shows only the pending-state pill (no row right now — there is no separate queued-but-not-started row state
tint, so a long queue doesn't paint half the tab amber); a to visually distinguish it from (see **Pending-badge derivation**
**running** entry (at least one transient) keeps the amber row tint below), so every row carrying ≥1 badge keeps the amber row tint AND
AND draws a **rotating amber ring** around the agent icon, so it's draws a **rotating amber ring** around the agent icon.
obvious at a glance which container is actually moving.
**Pending-badge derivation:** two separate stores, but no longer a **Pending-badge derivation:** transients only (`transientsState`,
priority *order* between them — the second only ever applies when keyed `agent -> Map<kind, since_unix>`) — a transient is **derived
the first has nothing to say. (1) **Transients** from a job-queue node currently `Running`** against that agent, not
(`transientsState`, keyed `agent -> Map<kind, since_unix>`) — a declared per request, so its label follows the operation as it
transient is **derived from a job-queue node currently `Running`** progresses (a rebuild reads `stop_for_update`, then `swap`, then
against that agent, not declared per request, so its label follows `reconcile` rather than one constant `rebuilding` for its whole
the operation as it progresses (a rebuild reads `stop_for_update`, life). Two consequences for anything rendering it:
then `swap`, then `reconcile` rather than one constant `rebuilding`
for its whole life). Two consequences for anything rendering it:
- The label vocabulary is **open** — it is the node's own wire tag - The label vocabulary is **open** — it is the node's own wire tag
(`NodeKind::as_str`, the same strings `NodeView.kind` carries), (`NodeKind::as_str`, the same strings `NodeView.kind` carries),
@ -912,20 +915,16 @@ fetch entirely.
their own label directly via `TransientSet`/`TransientCleared` their own label directly via `TransientSet`/`TransientCleared`
events carrying no backing node at all. events carrying no backing node at all.
(2) The **rebuild-queue fallback** (`rebuildQueueState`) only **Queued (not-yet-started) work shows nothing on the card, and
fires when an agent has **zero** transients — since (1) now covers `swarm.js` reads no job-queue state at all.** An earlier version of
every `Running` node unconditionally, a `Running` rebuild-queue this page had a job-queue-backed fallback badge for the `Pending`
entry can never usefully reach this fallback by the time it's case (`queuedOpsByAgent()`, reading a `GET /api/jobq/graph` fetch);
consulted; the fallback exists purely for the **`Pending` removed per mara, on review of hyperhive#3028's PR: *"swarm.js
(queued, not yet started)** case, which `running_transients()`'s should not need to pull in the jobq to do its job,"* followed by
Running-only test cannot represent. `queuedOpsByAgent()` (swarm.js) *"remove the per agent pending stuff - only show what is running."*
reflects this: it only ever looks at `Pending`-state queue nodes, The queue-summary banner below was, for the same reason, also pulled
and (mara, on review) the badge shows the queue entry's raw `kind` rather than kept on that same client-side graph-fetch — see its own
string as-is — no English-phrase lookup translating it first, same entry below for why.
opaque-string treatment a transient's own label already gets.
`opRunning` (driving the `pending-running` row class + spinner) is
simply "does this agent have at least one transient" — a queued-only
entry (no transient yet) leaves it false.
An **active model badge** (`model · <name>`, blue) appears when the An **active model badge** (`model · <name>`, blue) appears when the
container is running and the harness has persisted a model name container is running and the harness has persisted a model name
(`harness/hyperhive-model`). Read by hive-c0re's `ContainerView` (`harness/hyperhive-model`). Read by hive-c0re's `ContainerView`
@ -966,13 +965,23 @@ per-agent actions and navigation links. Contents:
agent is stale. Banner pulses on each broker SSE event agent is stale. Banner pulses on each broker SSE event
(`pulseBanner` with a 4s grace timer). (`pulseBanner` with a 4s grace timer).
**Build-queue summary banner** — when the rebuild queue has any **Build-queue summary banner — currently removed, coming back on a
`queued` / `running` entries, a compact amber banner sits above the different data source.** Previously a compact amber banner above the
container list: `◐ build queue — N running · M queued — view queue →` container list (`◐ build queue — N running · M queued — view
(the link goes to the BU1LDS page's R3BU1LD QU3U3). It replaces the queue →`, linking to the BU1LDS page), derived client-side from `GET
old per-transient spinner list; the actual running node for each /api/jobq/graph` (filter to `parent: null` group roots — each an
agent is already shown on its card (transient + in-flight-queue independent operation, not a raw node count — bucket by `state`).
badges), so the top of the tab only needs the at-a-glance summary. Pulled per mara, on review of hyperhive#3028's PR: *"dont replace one
legacy thing with another. then we will have to either wait with
this pr or split it into what can and cannot be done now"* — a
client-side derivation over the generic graph was itself judged a
stopgap not worth landing, the same way the old `DagView` read it
replaced was. Comes back once a dedicated rollup endpoint (status ×
count pairs, tracked separately) exists for `swarm.js` to read
directly instead of pulling the whole graph in for a summary. Until
then, the actual running work per agent is still visible on each
card via the transient pills; only the hive-wide at-a-glance count is
missing.
### Themed dialogs ### Themed dialogs

View file

@ -347,8 +347,11 @@ hive-agent-menu {
.container-row:hover hive-agent-menu { .container-row:hover hive-agent-menu {
--menu-btn-opacity: 1; --menu-btn-opacity: 1;
} }
/* Pending state splits queued vs running. */ /* Card actions dim while a row has any pending-state badge (transient-
.container-row.pending .actions { opacity: 0.4; pointer-events: none; } driven only now see swarm.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 { opacity: 0.4; pointer-events: none; }
.container-row.pending-running { .container-row.pending-running {
border-color: var(--amber); border-color: var(--amber);
background: color-mix(in srgb, var(--amber) 5%, transparent); background: color-mix(in srgb, var(--amber) 5%, transparent);
@ -455,7 +458,11 @@ hive-agent-menu {
/* Build-queue summary banner on the SW4RM tab: one compact line /* Build-queue summary banner on the SW4RM tab: one compact line
when the rebuild queue has active work, with a link to the full queue on when the rebuild queue has active work, with a link to the full queue on
the C0R3 page. Amber to match the in-progress / "rebuilding" card tint. */ the C0R3 page. Amber to match the in-progress / "rebuilding" card tint.
Currently unused swarm.js dropped the banner pending a dedicated
rollup endpoint (see swarm.js's transients-section comment) kept
here rather than deleted-then-restored, since the markup/classes are
expected to come back unchanged once that endpoint exists. */
.queue-summary { .queue-summary {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -1,7 +1,7 @@
// SW4RM (containers) domain — extracted from tabs.js. // SW4RM (containers) domain — extracted from tabs.js.
// Agent topology, container-row rendering, selection bar, peer-hives block, // Agent topology, container-row rendering, selection bar, peer-hives block,
// and all live-update apply handlers for container-state, rebuild-queue, and // and all live-update apply handlers for container-state and transient ops.
// transient ops. See docs/web-ui.md::Container row for the rendering contract. // See docs/web-ui.md::Container row for the rendering contract.
import { import {
$, form, fmtAgeSecs, $, form, fmtAgeSecs,
@ -27,8 +27,6 @@ const CTX_CAUTION_TOKENS = 100_000; // fallback yellow threshold (~= 50% of 200k
// ─── module-level state ───────────────────────────────────────────────────── // ─── module-level state ─────────────────────────────────────────────────────
let rebuildQueueState = [];
// Keyed container row cache. Maps agent name -> { el: <li>, fingerprint }. // Keyed container row cache. Maps agent name -> { el: <li>, fingerprint }.
// Allows renderContainers to skip rebuilding rows whose displayed state // Allows renderContainers to skip rebuilding rows whose displayed state
// hasn't changed — prevents full-wipe flicker + avoids redundant async // hasn't changed — prevents full-wipe flicker + avoids redundant async
@ -52,62 +50,20 @@ const transientsState = new Map();
// tab-gated visibility). // tab-gated visibility).
const selectionState = new Set(); const selectionState = new Set();
// ─── rebuild queue ──────────────────────────────────────────────────────────
export function syncRebuildQueueFromSnapshot(s) {
rebuildQueueState = (s.rebuild_queue || []).slice();
}
export function applyRebuildQueueChanged(ev) {
rebuildQueueState = (ev.queue || []).slice();
// Re-render the SW4RM tab so newly-queued ops light up the right
// card with a "<kind> queued…" badge, and entries that drop out of
// Pending fall back to whatever transientsState (or nothing) says
// instead. Running work is *not* driven by this event — that's
// transient_set/transient_cleared's job, since every running node
// naming an agent already lights a pill by the time it gets here.
// See docs/web-ui.md::Container row for the badge taxonomy.
renderContainersFromState();
}
// Map from agent name -> the queued op's raw `kind` string, backing
// the SW4RM card's fallback pending badge. Pending only — every
// *running* node naming an agent already lights a transient pill (any
// running node, not just a curated "worth it" subset — see
// docs/web-ui.md::Container row), so a Running entry here would
// always be redundant with `transientsState` by the time this is
// consulted. Queued (not yet started) work is the one state
// transients can't represent, since `running_transients()` on the
// backend is a Running-only test.
//
// The returned `kind` is displayed as-is (mara, on review: "drop
// queuedLabelFor - just show what the backend sends") — same opaque-
// string treatment `pending`'s transient half already gets, no
// English-phrase lookup table translating it first.
//
// Agent is per-node, not per-DAG (a DAG can span agents — e.g. the
// startup sweep's MetaLock cascade, or a hive-wide restart), so this
// derives each agent's queued state from its own node(s) within the
// entry rather than the DAG's overall `state`/`kind`.
function queuedOpsByAgent() {
const out = new Map();
for (const e of rebuildQueueState) {
if (e.state !== 'Pending') continue;
// spawn ops target an agent that doesn't exist yet as a
// container — the transient store already drives the
// pending row for that case. Skip here to avoid double-
// surfacing if the spawn op happens to land in the queue
// while the row exists transiently.
if (e.kind === 'spawn') continue;
for (const n of e.nodes || []) {
if (!n.agent || n.state !== 'Pending') continue;
// First entry found wins — with only one state to consider
// (Pending), there's no priority to resolve between DAGs.
if (!out.has(n.agent)) out.set(n.agent, e.kind);
}
}
return out;
}
// ─── transients ───────────────────────────────────────────────────────────── // ─── transients ─────────────────────────────────────────────────────────────
//
// This is now the ONLY job-queue-derived state on this page — mara, on
// review of the DagView migration: "swarm.js should not need to pull in
// the jobq to do its job." An earlier version of this file also fetched
// GET /api/jobq/graph directly for two things now gone: a per-agent
// queued-but-not-running badge (`queuedOpsByAgent()`, removed per "remove
// the per agent pending stuff - only show what is running" — a card's
// pending badges are transients-only now, which already means "what is
// running") and the queue-summary banner (removed per "dont replace one
// legacy thing with another" — a client-side derivation over the generic
// graph was itself judged a stopgap not worth shipping; the banner comes
// back once the dedicated rollup endpoint exists, and reads that
// directly instead of pulling the graph in here at all).
export function syncTransientsFromSnapshot(s) { export function syncTransientsFromSnapshot(s) {
transientsState.clear(); transientsState.clear();
@ -344,9 +300,14 @@ function buildContainerLi(c, node, opts) {
askerCount, targetCount, agentQCount, askerCount, targetCount, agentQCount,
url, containerBase, forgeBase, s, url, containerBase, forgeBase, s,
} = opts; } = opts;
// A single `pending-running` class now covers the whole "has at
// least one badge" state — there's no more queued-but-not-running
// row to distinguish it from (see the pending-badge derivation
// comment in renderContainers), so the separate no-tint `pending`
// class from before that removal is gone rather than kept as a
// class that would now always co-occur with this one.
const li = el('li', { const li = el('li', {
class: 'container-row' class: 'container-row'
+ (pending.length ? ' pending' : '')
+ (opRunning ? ' pending-running' : '') + (opRunning ? ' pending-running' : '')
+ (selected ? ' selected' : ''), + (selected ? ' selected' : ''),
}); });
@ -622,27 +583,15 @@ export function renderContainers(s) {
)); ));
} }
// Queue-summary banner: when the rebuild queue has active work, // No queue-summary banner for now — the previous version derived it
// show one compact at-a-glance line + a link to the full queue on the // client-side from GET /api/jobq/graph, which mara flagged as
// C0R3 page. Replaces the old per-transient spinner list — the actual // replacing one legacy DagView-shaped hack with another rather than
// running step is already visible per-agent on each card (transient + // landing the real fix ("dont replace one legacy thing with another
// in-flight-queue badges), so the top of the tab only needs the summary. // ... split it into what can and cannot be done now"). Comes back once
const activeQueue = rebuildQueueState.filter( // the dedicated rollup endpoint exists, reading that directly. Until
(e) => e.state === 'Pending' || e.state === 'Running', // then the queue's actual state is still visible per-agent on each
); // card via the transient pills; only the hive-wide at-a-glance summary
if (activeQueue.length) { // is missing.
const running = activeQueue.filter((e) => e.state === 'Running').length;
const queued = activeQueue.length - running;
const parts = [];
if (running) parts.push(`${running} running`);
if (queued) parts.push(`${queued} queued`);
root.append(el('div', { class: 'queue-summary' },
el('span', { class: 'glyph spinner' }, '◐'), ' ',
el('strong', {}, 'build queue'), ' — ',
parts.join(' · '), ' ',
el('a', { class: 'queue-summary-link', href: '/builds.html' }, 'view queue →'),
));
}
if (!containers.length && !transientsState.size) { if (!containers.length && !transientsState.size) {
root.append(el('p', { class: 'empty' }, 'no managed containers')); root.append(el('p', { class: 'empty' }, 'no managed containers'));
@ -672,10 +621,6 @@ export function renderContainers(s) {
const forgeBase = (s && s.forge_public_url) || null; const forgeBase = (s && s.forge_public_url) || null;
const ul = existingUl ?? el('ul', { class: 'containers' }); const ul = existingUl ?? el('ul', { class: 'containers' });
const tree = buildAgentTree(containers); const tree = buildAgentTree(containers);
// Queued (not-yet-running) ops per agent name — see
// docs/web-ui.md::Container row for why this only covers the
// Pending case now (Running is fully covered by transientsState).
const queuedOps = queuedOpsByAgent();
// Build the ordered list of <li> elements, reusing cached rows // Build the ordered list of <li> elements, reusing cached rows
// whose displayed state hasn't changed. // whose displayed state hasn't changed.
@ -689,20 +634,17 @@ export function renderContainers(s) {
const containerBase = gatewayLinks const containerBase = gatewayLinks
? `/agent/${encodeURIComponent(c.name)}` ? `/agent/${encodeURIComponent(c.name)}`
: `http://${hostname}:${c.port}`; : `http://${hostname}:${c.port}`;
// Pending-badge derivation: an agent's transients win outright when // Pending-badge derivation: transients only — "what is running,"
// any exist (rendered one badge per pill — mara: "show all running // full stop (mara, on review: "remove the per agent pending stuff -
// nodes that name the agent"), the rebuild-queue's queued-only // only show what is running"; rendered one badge per pill — mara,
// fallback otherwise. See docs/web-ui.md::Container row. // earlier: "show all running nodes that name the agent"). See
// docs/web-ui.md::Container row.
const transientKindsMap = transientsState.get(c.name); const transientKindsMap = transientsState.get(c.name);
// Sorted for stable badge order across renders — Map iteration // Sorted for stable badge order across renders — Map iteration
// order is insertion order, which shifts as pills clear/re-add. // order is insertion order, which shifts as pills clear/re-add.
const transientKinds = transientKindsMap const pending = transientKindsMap
? Array.from(transientKindsMap.keys()).sort() : []; ? Array.from(transientKindsMap.keys()).sort() : [];
const queuedKind = transientKinds.length === 0 ? queuedOps.get(c.name) : null; const opRunning = pending.length > 0;
const pending = transientKinds.length > 0
? transientKinds
: (queuedKind ? [queuedKind] : []);
const opRunning = transientKinds.length > 0;
const selected = selectionState.has(c.name); const selected = selectionState.has(c.name);
// Pending questions where this agent is the asker (awaiting an // Pending questions where this agent is the asker (awaiting an
// answer) or the target (owes a reply). Derived live from // answer) or the target (owes a reply). Derived live from

View file

@ -42,8 +42,8 @@ import {
renderQuestions, activeQuestionCount, renderQuestions, activeQuestionCount,
} from './call.js'; } from './call.js';
import { import {
syncRebuildQueueFromSnapshot, syncTransientsFromSnapshot, syncTransientsFromSnapshot,
applyRebuildQueueChanged, applyContainerStateChanged, applyContainerRemoved, applyContainerStateChanged, applyContainerRemoved,
applyTransientSet, applyTransientCleared, applyTransientSet, applyTransientCleared,
renderContainers, renderContainersFromState, renderContainers, renderContainersFromState,
renderSelectionBar, renderPeerHives, renderSelectionBar, renderPeerHives,
@ -234,9 +234,6 @@ window.marked = marked;
// `transientsState` + `containersState`, not from `s.*`). // `transientsState` + `containersState`, not from `s.*`).
syncTransientsFromSnapshot(s); syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s); syncContainersFromSnapshot(s);
// Rebuild-queue state feeds the SW4RM agent-card badges
// (inFlightOpsByAgent); its own panel now lives on /core.html.
syncRebuildQueueFromSnapshot(s);
renderContainers(s); renderContainers(s);
// Sync the derived approvals + questions stores from the // Sync the derived approvals + questions stores from the
// snapshot, then render. Live `*_added` / `*_resolved` events // snapshot, then render. Live `*_added` / `*_resolved` events
@ -307,8 +304,10 @@ window.marked = marked;
container_removed: applyContainerRemoved, container_removed: applyContainerRemoved,
// tombstones_changed / meta_inputs_changed / meta_update_running are // tombstones_changed / meta_inputs_changed / meta_update_running are
// handled on /core.html now (the SYST3M panels moved there). // handled on /core.html now (the SYST3M panels moved there).
// rebuild_queue_changed stays: it refreshes the SW4RM badges. // rebuild_queue_changed: this page no longer subscribes to it at
rebuild_queue_changed: applyRebuildQueueChanged, // all (see swarm.js's transients-section comment) — the SW4RM tab
// has nothing left that reads it, unlike /builds.html which still
// does (its own separate subscription).
schedules_changed: applySchedulesChanged, schedules_changed: applySchedulesChanged,
capabilities_changed: applyCapabilitiesChanged, capabilities_changed: applyCapabilitiesChanged,
tool_groups_changed: applyToolGroupsChanged, tool_groups_changed: applyToolGroupsChanged,
@ -330,7 +329,7 @@ window.marked = marked;
const es = openStream( const es = openStream(
'/api/dashboard/stream?kinds=sent,approval_added,approval_resolved,' + '/api/dashboard/stream?kinds=sent,approval_added,approval_resolved,' +
'question_added,question_resolved,transient_set,transient_cleared,' + 'question_added,question_resolved,transient_set,transient_cleared,' +
'container_state_changed,container_removed,rebuild_queue_changed,' + 'container_state_changed,container_removed,' +
'schedules_changed,capabilities_changed,tool_groups_changed', 'schedules_changed,capabilities_changed,tool_groups_changed',
); );
es.onmessage = (e) => { es.onmessage = (e) => {