extract build-queue rollup as a shared Preact component

New @hive/shared/jobq-rollup.js (JobqRollup.tsx + jobq-rollup.css),
mirroring JobqGraph's shape exactly: JSX use plus an imperative
mountJobqRollup(container, props) for a plain-.js call site. Fetches
Vec<hive_jobq_wire::StateCount> off `endpoint`, sums Running+Finishing
roots as "running" and Pending roots as "queued", renders nothing when
both are zero. Optional `queueHref` adds a "view queue -> " link.

Swapped dashboard's hand-rolled queue-summary banner (swarm.js) over to
this component instead of keeping two parallel implementations - same
"one shared component" pattern JobqGraph already set for the rebuild
queue tree view. Mounted once into a new #jobq-rollup-section, kept as
a sibling of (not inside) #containers-section since that section gets
replaceChildren()-wiped on every container-state render, which would
tear down and remount a Preact tree on every tick. Refreshed via the
mount handle's .refresh() on rebuild_queue_changed, same as builds.js's
JobqGraph handle.

Also mounted in swarm-ui's /jobs page, above JobqGraph, with no
queueHref (a link back to the page you're already on is noise) - the
literal ask on hyperhive#3364.

Verified: npm run build (whole workspace) and swarm-ui typecheck both
clean, comment-block + issue-ref lints run manually, headless-chromium
screenshots of both the dashboard SW4RM tab and swarm-ui's /jobs page
against mocked /api/jobq/rollup payloads - banner renders identically
in both, with and without the queue link as expected.
This commit is contained in:
iris 2026-08-16 21:21:12 +02:00 committed by mara
commit 1111577c91
10 changed files with 208 additions and 99 deletions

View file

@ -42,7 +42,7 @@ import {
renderQuestions, activeQuestionCount,
} from './call.js';
import {
refreshJobqRollup, syncTransientsFromSnapshot,
initJobqRollup, syncTransientsFromSnapshot,
applyRebuildQueueChanged, applyContainerStateChanged, applyContainerRemoved,
applyTransientSet, applyTransientCleared,
renderContainers, renderContainersFromState,
@ -232,13 +232,12 @@ window.marked = marked;
syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s);
// Job-queue rollup feeds only the SW4RM queue-summary banner
// (per-agent card badges are transient-only — see swarm.js).
// Self-fetches GET /api/jobq/rollup — not read off `s` (this
// page's snapshot carries no jobq field) — fire-and-forget:
// renderContainers below runs off whatever jobqRollupState
// already holds, and refreshJobqRollup's own re-render catches
// up once the fetch resolves.
refreshJobqRollup();
// (per-agent card badges are transient-only — see swarm.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
// mount's own effect handles the actual fetch.
initJobqRollup();
renderContainers(s);
// Sync the derived approvals + questions stores from the
// snapshot, then render. Live `*_added` / `*_resolved` events