drop the mountJobqRollup wrapper, render(h(...)) directly

mara, on review: expected the plain Preact pattern (render(h(Widget,
props), container), call again to update) rather than a custom
mountX() returning {refresh(), update()}. Preact's own render is
already the re-render/diff entry point, so the wrapper was indirection
this component didn't need - swarm.js (plain .js, no JSX pragma
required for h()/render() either) now calls render(h(JobqRollup,
{...refreshToken}), root) directly, bumping a module-level token to
force a refetch instead of holding a mount handle.

JobqGraph/mountJobqGraph (a separate, already-merged component) is
untouched - out of scope for this PR, flagged as a possible follow-up
if she wants the same simplification there.

Re-verified: npm run build (whole workspace) + swarm-ui typecheck
clean, comment-block + issue-ref lints clean, re-screenshotted the
dashboard SW4RM tab against the same mocked payload - identical
render, spinner now visibly mid-rotation in the frame (confirms the
animation is live, not just present in markup).
This commit is contained in:
iris 2026-08-16 21:47:11 +02:00 committed by mara
commit 5c9d89dc64
2 changed files with 23 additions and 38 deletions

View file

@ -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 ─────────────────────────────────────────────────────────────