From 5c9d89dc64b943641a8f87ec60c7a0969574f03c Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 16 Aug 2026 21:47:11 +0200 Subject: [PATCH] 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). --- frontend/packages/dashboard/src/swarm.js | 30 +++++++++--------- .../shared/src/jobq-rollup/JobqRollup.tsx | 31 +++++-------------- 2 files changed, 23 insertions(+), 38 deletions(-) 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(); }, - }; -}