diff --git a/frontend/packages/dashboard/src/swarm.js b/frontend/packages/dashboard/src/swarm.js
index d2692923..6c4d2e3a 100644
--- a/frontend/packages/dashboard/src/swarm.js
+++ b/frontend/packages/dashboard/src/swarm.js
@@ -9,7 +9,8 @@ import {
} from './common.js';
import { el } from '@hive/shared/dom.js';
import { themedConfirm, themedToast } from '@hive/shared/modal.js';
-import { mountJobqRollup } from '@hive/shared/jobq-rollup.js';
+import { h, render } from 'preact';
+import { JobqRollup } from '@hive/shared/jobq-rollup.js';
import {
containersState, questionsState,
} from './state.js';
@@ -66,27 +67,26 @@ const selectionState = new Set();
// generic graph was itself judged a stopgap). Rendering itself later
// moved out to the shared `JobqRollup` Preact component (same one
// swarm-ui's /jobs page mounts), which owns its own fetch of
-// GET /api/jobq/rollup — this file just mounts it once and bumps its
-// refresh handle, mirroring builds.js's JobqGraph mount exactly.
+// GET /api/jobq/rollup — this file just calls Preact's own
+// `render(h(...))` directly (no `mountX()` wrapper: `render` is
+// already the re-render/diff entry point, per mara on review) and
+// bumps a refresh token to force a refetch.
//
-// Mounted into #jobq-rollup-section, a sibling of #containers-section
+// Rendered into #jobq-rollup-section, a sibling of #containers-section
// kept OUTSIDE that section's per-render `replaceChildren()` wipe (see
-// dashboard.html's comment on the mount div) — re-mounting a fresh
-// Preact tree on every container-state tick would work but is wasteful
-// and defeats the component owning its own fetch lifecycle.
-let jobqRollupHandle = null;
+// dashboard.html's comment on the mount div) — rendering into a
+// section that gets wiped on every container-state tick would defeat
+// the component owning its own fetch lifecycle.
+let jobqRollupToken = 0;
-// Mounts once; a second call is a no-op (idempotent — matches
-// `initCall`/`initPermissions`'s "safe to call from cold-load every
-// time" shape elsewhere in this bundle).
export function initJobqRollup() {
- if (jobqRollupHandle) return;
const root = $('jobq-rollup-section');
if (!root) return;
- jobqRollupHandle = mountJobqRollup(root, {
+ render(h(JobqRollup, {
endpoint: '/api/jobq/rollup',
queueHref: '/builds.html',
- });
+ refreshToken: jobqRollupToken,
+ }), root);
}
// Refetches on cold load (tabs.js's refreshState) and whenever
// `rebuild_queue_changed` fires — a payload-less push trigger by
@@ -95,8 +95,8 @@ export function initJobqRollup() {
// go refetch" treatment builds.js already gives its own JobqGraph mount
// handle's .refresh().
export function applyRebuildQueueChanged() {
+ jobqRollupToken += 1;
initJobqRollup();
- jobqRollupHandle?.refresh();
}
// ─── transients ─────────────────────────────────────────────────────────────
diff --git a/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx b/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx
index a84e8a1e..85c6dea6 100644
--- a/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx
+++ b/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx
@@ -18,17 +18,16 @@
// /builds.html; swarm-ui's /jobs page omits it — a link to the page
// you're already on is noise).
//
-// The glyph carries `.spinner` (`@hive/shared/base.css`, already
-// imported by both consumers) for the "actively happening" spin —
-// not redeclared in jobq-rollup.css, since a static screenshot can't
-// tell a frozen spinner from a missing one; caught by argus's review.
+// The glyph carries `.spinner` (`@hive/shared/base.css`, imported by
+// both consumers) for the "actively happening" spin — a static
+// screenshot can't tell a frozen spinner from a missing one.
//
-// Two ways to use this, same shape as JobqGraph: JSX (swarm-ui) —
-// ``. Or imperative mount
-// (dashboard/src/swarm.js, plain `.js`) — `mountJobqRollup(container,
-// props)` returns `{ refresh(), update(props) }`.
+// JSX (swarm-ui) — ``. Or
+// plain `render(h(JobqRollup, props), container)` (dashboard/src/
+// swarm.js, no JSX pragma needed) — call again with a bumped
+// `refreshToken` to force a refetch. No mount wrapper: `render` is
+// already the re-render/diff entry point.
-import { h, render } from 'preact';
import { useState, useEffect } from 'preact/hooks';
// Mirrors `hive_jobq_wire::StateSchema` — only the subset this banner
@@ -89,17 +88,3 @@ export function JobqRollup({ endpoint, queueHref, refreshToken = 0 }: JobqRollup
);
}
-
-// Imperative mount helper — same shape as `mountJobqGraph`. Returns
-// `.refresh()` (bump the refresh token, re-fetch) and `.update(props)`
-// (merge new props and re-render).
-export function mountJobqRollup(container: Element, initialProps: JobqRollupProps) {
- let props = initialProps;
- let token = 0;
- const draw = () => render(h(JobqRollup, { ...props, refreshToken: token }), container);
- draw();
- return {
- refresh() { token += 1; draw(); },
- update(next: Partial) { props = { ...props, ...next }; draw(); },
- };
-}