diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md
index 5aad6263..683f3528 100644
--- a/docs/web-ui/dashboard.md
+++ b/docs/web-ui/dashboard.md
@@ -986,21 +986,17 @@ agent is stale. Banner pulses on each broker SSE event
**Build-queue summary banner** — when the job queue has any active
work, a compact amber banner sits above the container list: `◐ build
queue — N running · M queued — view queue →` (the link goes to the
-BU1LDS page's R3BU1LD QU3U3). The shared `JobqRollup` Preact component
-(`@hive/shared/jobq-rollup.js` — the same one swarm-ui's `/jobs` page
-mounts, pointed at swarm-controller's own rollup endpoint instead),
-mounted once into `#jobq-rollup-section` by `swarm.js::initJobqRollup`
-and refreshed via its own handle rather than being re-rendered by
-`renderContainers`. Reads `GET /api/jobq/rollup`
-(`hive-jobq-wire::state_rollup`) — `Vec<{ state, nodes, roots }>`,
-every lifecycle state present in a fixed order, zero counts included —
-rather than the full `/api/jobq/graph` tree: `running` sums the
-`Running` and `Finishing` entries' `roots` (`Finishing` = own work
-done, subtree still going, still in flight), `queued` reads the
-`Pending` entry's `roots`. `roots` specifically, not `nodes` — the
-banner means *N whole operations*, not raw steps (one rebuild is ~7
-nodes but 1 root); `nodes` exists on the same endpoint for a consumer
-that wants step-level counts instead, unused here.
+BU1LDS page's R3BU1LD QU3U3). Reads `GET /api/jobq/rollup`
+(`hive-jobq-wire::state_rollup`, `jobqRollupState` in `swarm.js`) —
+`Vec<{ state, nodes, roots }>`, every lifecycle state present in a
+fixed order, zero counts included — rather than the full
+`/api/jobq/graph` tree: `running` sums the `Running` and `Finishing`
+entries' `roots` (`Finishing` = own work done, subtree still going,
+still in flight), `queued` reads the `Pending` entry's `roots`.
+`roots` specifically, not `nodes` — the banner means *N whole
+operations*, not raw steps (one rebuild is ~7 nodes but 1 root);
+`nodes` exists on the same endpoint for a consumer that wants
+step-level counts instead, unused here.
### Themed dialogs
diff --git a/frontend/packages/dashboard/src/builds.js b/frontend/packages/dashboard/src/builds.js
index 7e8f9358..16b37662 100644
--- a/frontend/packages/dashboard/src/builds.js
+++ b/frontend/packages/dashboard/src/builds.js
@@ -14,10 +14,9 @@ import { $, fmtAgeSecs, openStream, openBuildLogStream, initServerWarnings } fro
import { el } from '@hive/shared/dom.js';
import { bindAsyncForms } from '@hive/shared/forms.js';
import { themedConfirm } from '@hive/shared/modal.js';
-import { h, render } from 'preact';
import { fmtAgo, fmtDuration, truncate } from './util.js';
import '@hive/shared/hive-tab-strip.js';
-import { JobqGraph } from '@hive/shared/jobq-graph.js';
+import { mountJobqGraph } from '@hive/shared/jobq-graph.js';
// ─── derived state ───────────────────────────────────────────────────────────
let metaInputsState = [];
@@ -27,7 +26,7 @@ let metaUpdateRunning = false;
// the tree itself; this flat array exists only for the two things it
// doesn't render: the count-pill and the live-log panel.
let jobqNodes = [];
-let jobqGraphToken = 0;
+let jobqGraphHandle = null;
function syncFromSnapshot(s) {
metaInputsState = (s.meta_inputs || []).slice();
@@ -128,40 +127,27 @@ function renderMetaInputs(s) {
}
// ─── rebuild queue ────────────────────────────────────────────────────────────
-// R3BU1LD QU3U3 is `JobqGraph` (Preact, rendered via plain `render(h(...))`
-// — no JSX pragma needed for either call, no mount wrapper: `render` is
-// already the re-render/diff entry point, per mara on review) directly —
-// no hand-rolled tree/roll-up rendering here anymore. The component owns
-// fetching GET /api/jobq/graph and its own refetch (bump `jobqGraphToken`
-// and render again); this page just reads its `onUpdate` callback to keep
-// `jobqNodes` (the flat array) in sync for the two things the generic
-// view doesn't render itself: the count-pill and the live-log panel below.
+// R3BU1LD QU3U3 is `JobqGraph` (Preact, mounted imperatively — see
+// mountJobqGraph's own doc comment in @hive/shared/jobq-graph.js for why
+// this file has no JSX pipeline) directly — no hand-rolled tree/roll-up
+// rendering here anymore. The component owns fetching GET /api/jobq/graph
+// and its own refetch (`.refresh()` on the mount handle); this page just
+// reads its `onUpdate` callback to keep `jobqNodes` (the flat array) in
+// sync for the two things the generic view doesn't render itself: the
+// count-pill and the live-log panel below.
//
// Cancel is the one action this page *does* wire up: the `cancellable`
// prop turns on the component's own per-node cancel button, which calls
// `onCancel(id)` rather than posting anything — the endpoint
// (`/api/rebuild-queue/{id}/cancel`) is this page's domain concept, not
// the generic component's.
-// Split from `renderRebuildQueue` below: `replaceChildren()` only
-// belongs on the *first* render, to clear the static "loading…"
-// placeholder `#rebuild-queue-section` starts with in `builds.html` —
-// Preact doesn't know that placeholder is there, so a later render
-// call must NOT repeat `replaceChildren()` (it would wipe Preact's own
-// tracked children out from under its diffing rather than let it
-// update them minimally).
function mountRebuildQueue() {
const root = $('rebuild-queue-section');
if (!root) return;
root.replaceChildren();
- renderRebuildQueue();
-}
-function renderRebuildQueue() {
- const root = $('rebuild-queue-section');
- if (!root) return;
- render(h(JobqGraph, {
+ jobqGraphHandle = mountJobqGraph(root, {
endpoint: '/api/jobq/graph',
cancellable: true,
- refreshToken: jobqGraphToken,
onUpdate: (nodes) => {
jobqNodes = nodes || [];
renderRebuildLiveLog();
@@ -179,12 +165,12 @@ function renderRebuildQueue() {
if (!r.ok) throw new Error('http ' + r.status);
// No manual refresh: cancel flips node state, which fires
// rebuild_queue_changed over SSE — the existing handler below
- // already bumps jobqGraphToken and re-renders on that tick.
+ // already calls jobqGraphHandle.refresh() on that tick.
} catch (err) {
console.error('cancel failed', err);
}
},
- }), root);
+ });
}
// ─── running-rebuild live log ─────────────────────────────────────────────────
@@ -485,8 +471,7 @@ const SSE_HANDLERS = {
// queue snapshot on the wire (tabs.js/SW4RM still consumes it for the
// swarm badges, untouched by this), this page just treats it as a
// refetch trigger. `JobqGraph` owns the actual fetch.
- jobqGraphToken += 1;
- renderRebuildQueue();
+ if (jobqGraphHandle) jobqGraphHandle.refresh();
// Auto-refresh build log list when the queue changes and BUILD L0GS is active.
if (buildTabs && buildTabs.active() === 'buildlogs') {
if (buildRefreshTimer) clearTimeout(buildRefreshTimer);
diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css
index 44899b9e..251652fe 100644
--- a/frontend/packages/dashboard/src/dashboard.css
+++ b/frontend/packages/dashboard/src/dashboard.css
@@ -7,7 +7,6 @@
referenced from dashboard JS (the schedules view uses `.rqe-source*`),
so the dashboard pulls them in here. */
@import "./system-sections.css";
-@import "@hive/shared/jobq-rollup.css";
/* ─── tabbed dashboard chrome ──────────────────────────────────────
Top-of-page sticky header with banner + tab strip. SSE stays
@@ -457,9 +456,30 @@ hive-agent-menu {
Notification controls below sit between the banner and the containers. */
-/* Build-queue summary banner on the SW4RM tab moved to the shared
- `JobqRollup` Preact component (`.jqr-*` classes, imported above) —
- mounted by swarm.js::initJobqRollup into #jobq-rollup-section. */
+/* 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
+ the C0R3 page. Amber to match the in-progress / "rebuilding" card tint. */
+.queue-summary {
+ display: flex;
+ align-items: center;
+ gap: 0.4em;
+ flex-wrap: wrap;
+ background: color-mix(in srgb, var(--amber) 8%, transparent);
+ border: 1px solid color-mix(in srgb, var(--amber) 55%, transparent);
+ color: var(--amber);
+ padding: 0.45em 0.8em;
+ margin-bottom: 0.6em;
+ border-radius: 4px;
+}
+.queue-summary strong { color: var(--amber); }
+.queue-summary-link {
+ margin-left: auto;
+ color: var(--amber);
+ text-decoration: none;
+ font-weight: bold;
+ white-space: nowrap;
+}
+.queue-summary-link:hover { text-decoration: underline; }
/* .notif-row / .btn-notif moved to settings.css with the S3TT1NGS
page. */
diff --git a/frontend/packages/dashboard/src/dashboard.html b/frontend/packages/dashboard/src/dashboard.html
index b1baf418..d9bd8b53 100644
--- a/frontend/packages/dashboard/src/dashboard.html
+++ b/frontend/packages/dashboard/src/dashboard.html
@@ -97,13 +97,6 @@
-
-
diff --git a/frontend/packages/dashboard/src/swarm.js b/frontend/packages/dashboard/src/swarm.js
index 6c4d2e3a..b2966320 100644
--- a/frontend/packages/dashboard/src/swarm.js
+++ b/frontend/packages/dashboard/src/swarm.js
@@ -9,8 +9,6 @@ import {
} from './common.js';
import { el } from '@hive/shared/dom.js';
import { themedConfirm, themedToast } from '@hive/shared/modal.js';
-import { h, render } from 'preact';
-import { JobqRollup } from '@hive/shared/jobq-rollup.js';
import {
containersState, questionsState,
} from './state.js';
@@ -64,39 +62,35 @@ const selectionState = new Set();
// a card's pending badges are transients-only now, which already means
// "what is running") and this banner, which was pulled entirely per "dont
// replace one legacy thing with another" (a client-side tally over the
-// 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 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.
-//
-// 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) — rendering into a
-// section that gets wiped on every container-state tick would defeat
-// the component owning its own fetch lifecycle.
-let jobqRollupToken = 0;
+// generic graph was itself judged a stopgap). Now that the dedicated
+// rollup endpoint exists (hive-jobq-wire::state_rollup, served at
+// GET /api/jobq/rollup), the banner reads *that* instead — a handful of
+// pre-tallied counts, not the graph.
+let jobqRollupState = [];
-export function initJobqRollup() {
- const root = $('jobq-rollup-section');
- if (!root) return;
- 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
+// Fetches the rollup fresh and re-renders. Called on cold load (see
+// tabs.js's refreshState) and whenever `rebuild_queue_changed` fires
+// (applyRebuildQueueChanged below) — a payload-less push trigger by
// design, confirmed with atlas on the jobq-deletion tracker: the event
// carries no `queue` field this page reads, same "something changed,
-// go refetch" treatment builds.js already gives its own JobqGraph mount
+// go refetch" treatment builds.js already gives its JobqGraph mount
// handle's .refresh().
+// Best-effort: a failed fetch leaves the previous snapshot in place
+// rather than wiping the banner on a network blip.
+export async function refreshJobqRollup() {
+ let counts;
+ try {
+ const r = await fetch('/api/jobq/rollup');
+ if (!r.ok) return;
+ counts = await r.json();
+ } catch {
+ return;
+ }
+ jobqRollupState = counts;
+ renderContainersFromState();
+}
export function applyRebuildQueueChanged() {
- jobqRollupToken += 1;
- initJobqRollup();
+ refreshJobqRollup();
}
// ─── transients ─────────────────────────────────────────────────────────────
@@ -633,9 +627,31 @@ export function renderContainers(s) {
));
}
- // Queue-summary banner lives outside this section now — see
- // #jobq-rollup-section / initJobqRollup, mounted once rather than
- // rebuilt on every render this function does.
+ // Queue-summary banner: one compact line above the container list when
+ // the job queue has active work, linking to the full queue on the
+ // BU1LDS page. Reads GET /api/jobq/rollup's pre-tallied `roots` counts
+ // (see jobqRollupState above) rather than the full graph — `roots`
+ // because "N running / M queued" has always meant *operations*, not
+ // raw steps (one rebuild is ~7 nodes but 1 root); `nodes` exists on
+ // the same endpoint for a consumer that wants steps instead, unused
+ // here. `Finishing` counts as running (own work done, subtree still
+ // going, still in flight) — same treatment `roots` gets nowhere else,
+ // since the rollup endpoint doesn't collapse the two itself.
+ const byState = new Map(jobqRollupState.map((c) => [c.state, c]));
+ const running = (byState.get('Running')?.roots ?? 0)
+ + (byState.get('Finishing')?.roots ?? 0);
+ const queued = byState.get('Pending')?.roots ?? 0;
+ if (running || queued) {
+ 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) {
root.append(el('p', { class: 'empty' }, 'no managed containers'));
diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js
index 46d7502c..9e0fe748 100644
--- a/frontend/packages/dashboard/src/tabs.js
+++ b/frontend/packages/dashboard/src/tabs.js
@@ -42,7 +42,7 @@ import {
renderQuestions, activeQuestionCount,
} from './call.js';
import {
- initJobqRollup, syncTransientsFromSnapshot,
+ refreshJobqRollup, syncTransientsFromSnapshot,
applyRebuildQueueChanged, applyContainerStateChanged, applyContainerRemoved,
applyTransientSet, applyTransientCleared,
renderContainers, renderContainersFromState,
@@ -232,12 +232,13 @@ 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). 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();
+ // (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();
renderContainers(s);
// Sync the derived approvals + questions stores from the
// snapshot, then render. Live `*_added` / `*_resolved` events
diff --git a/frontend/packages/shared/package.json b/frontend/packages/shared/package.json
index ba195859..05d490e9 100644
--- a/frontend/packages/shared/package.json
+++ b/frontend/packages/shared/package.json
@@ -29,9 +29,7 @@
"./hive-warn.js": "./src/hive-warn/hive-warn.js",
"./side-panel.js": "./src/side-panel/hive-side-panel.js",
"./jobq-graph.js": "./src/jobq-graph/JobqGraph.tsx",
- "./jobq-graph.css": "./src/jobq-graph/jobq-graph.css",
- "./jobq-rollup.js": "./src/jobq-rollup/JobqRollup.tsx",
- "./jobq-rollup.css": "./src/jobq-rollup/jobq-rollup.css"
+ "./jobq-graph.css": "./src/jobq-graph/jobq-graph.css"
},
"files": [
"src/"
diff --git a/frontend/packages/shared/src/jobq-graph/JobqGraph.tsx b/frontend/packages/shared/src/jobq-graph/JobqGraph.tsx
index 8c335ba4..3c549f83 100644
--- a/frontend/packages/shared/src/jobq-graph/JobqGraph.tsx
+++ b/frontend/packages/shared/src/jobq-graph/JobqGraph.tsx
@@ -17,10 +17,11 @@
// needing the raw list (a count badge, a live-log panel) without its
// own parallel fetch.
//
-// JSX (swarm-ui) — ``. Or plain `render(h(JobqGraph,
-// props), container)` (dashboard/src/builds.js, no JSX pragma needed)
-// — call again with a bumped `refreshToken` to refetch.
+// Two ways to use this: JSX (swarm-ui, or any dashboard page that
+// renders it directly) — ``. Or imperative mount
+// (dashboard/src/builds.js, plain `.js` — mounting needs no JSX pragma)
+// — `mountJobqGraph(container, props)` returns `{ refresh(), update(props) }`.
//
// Styles live in `@hive/shared/jobq-graph.css`, `@import`ed from a
// page/component CSS file rather than imported here — dashboard bundles
@@ -28,6 +29,7 @@
// loader is `text` (for unrelated shadow-DOM components' CSS-as-string
// needs), and that loader is global per call, not per-module.
+import { h, render } from 'preact';
import { useState, useEffect, useCallback } from 'preact/hooks';
// Mirrors `hive_jobq_wire::StateSchema` verbatim (variant names, no
@@ -231,8 +233,8 @@ export interface JobqGraphProps {
}
// `refreshToken` is not read anywhere in the body — its only job is to
-// change identity so the effect below re-runs, giving a host an
-// explicit "refetch now" lever (bump it and re-render) without an
+// change identity so the effect below re-runs, giving a host (or
+// `mountJobqGraph`) an explicit "refetch now" lever without an
// imperative ref into this component.
export function JobqGraph({ endpoint, cancellable = false, onUpdate, onCancel, refreshToken = 0 }: JobqGraphProps) {
const [selectedStates, setSelectedStates] = useState>(
@@ -292,3 +294,19 @@ export function JobqGraph({ endpoint, cancellable = false, onUpdate, onCancel, r
);
}
+
+// Imperative mount helper for a call site that isn't itself a JSX file
+// (dashboard/src/builds.js — plain `.js`, no per-file JSX pragma to
+// write `` inline). Returns a handle: `.refresh()`
+// (re-fetch with the current props) and `.update(props)` (merge new
+// props — e.g. a different `endpoint` — and re-render).
+export function mountJobqGraph(container: Element, initialProps: JobqGraphProps) {
+ let props = initialProps;
+ let token = 0;
+ const draw = () => render(h(JobqGraph, { ...props, refreshToken: token }), container);
+ draw();
+ return {
+ refresh() { token += 1; draw(); },
+ update(next: Partial) { props = { ...props, ...next }; draw(); },
+ };
+}
diff --git a/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx b/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx
deleted file mode 100644
index 85c6dea6..00000000
--- a/frontend/packages/shared/src/jobq-rollup/JobqRollup.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-// JobqRollup.tsx — , a compact one-line banner over
-// GET .../api/jobq/rollup's pre-tallied `Vec`
-// (any endpoint serving that shape works — hive-c0re and swarm-controller
-// both do). Extracted out of dashboard/src/swarm.js's hand-rolled
-// queue-summary banner, which this component now replaces there too —
-// same "one shared component, not two parallel implementations" pattern
-// `JobqGraph` already established for the rebuild-queue tree view.
-//
-// Reads `roots` (not `nodes`) — "N running / M queued" has always meant
-// *operations*, not raw steps (one rebuild is ~7 nodes but 1 root).
-// `Finishing` counts as running (own work done, subtree still going,
-// still in flight) — the rollup endpoint doesn't collapse the two
-// itself, so this component does. Renders nothing at all when there's
-// no active work, matching the banner it replaces (an idle queue is not
-// worth a line of chrome).
-//
-// `queueHref`, when given, adds a "view queue →" link (dashboard links to
-// /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`, imported by
-// both consumers) for the "actively happening" spin — a static
-// screenshot can't tell a frozen spinner from a missing one.
-//
-// 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 { useState, useEffect } from 'preact/hooks';
-
-// Mirrors `hive_jobq_wire::StateSchema` — only the subset this banner
-// cares about, not the full union `JobqGraph.tsx` mirrors, since a
-// rollup row's `state` is read by exact string match, not rendered.
-type NodeState = 'Pending' | 'Running' | 'Finishing' | 'Done' | 'Failed' | 'Cancelled' | 'Skipped';
-
-interface StateCount {
- state: NodeState;
- nodes: number;
- roots: number;
-}
-
-export interface JobqRollupProps {
- endpoint?: string;
- queueHref?: string;
- refreshToken?: number;
-}
-
-export function JobqRollup({ endpoint, queueHref, refreshToken = 0 }: JobqRollupProps) {
- const [counts, setCounts] = useState([]);
-
- useEffect(() => {
- if (!endpoint) return undefined;
- let cancelled = false;
- // Best-effort: a failed fetch leaves the previous snapshot in
- // place rather than wiping the banner on a network blip — same
- // rule the hand-rolled version followed.
- (async () => {
- try {
- const r = await fetch(endpoint);
- if (!r.ok) return;
- const data = (await r.json()) as StateCount[];
- if (!cancelled) setCounts(data);
- } catch {
- // ignore — keep the previous snapshot
- }
- })();
- return () => { cancelled = true; };
- }, [endpoint, refreshToken]);
-
- const byState = new Map(counts.map((c) => [c.state, c]));
- const running = (byState.get('Running')?.roots ?? 0) + (byState.get('Finishing')?.roots ?? 0);
- const queued = byState.get('Pending')?.roots ?? 0;
- if (!running && !queued) return null;
-
- const parts: string[] = [];
- if (running) parts.push(`${running} running`);
- if (queued) parts.push(`${queued} queued`);
-
- return (
-
-
◐{' '}
-
build queue — {parts.join(' · ')}{' '}
- {queueHref && (
-
view queue →
- )}
-
- );
-}
diff --git a/frontend/packages/shared/src/jobq-rollup/jobq-rollup.css b/frontend/packages/shared/src/jobq-rollup/jobq-rollup.css
deleted file mode 100644
index 530c8f5a..00000000
--- a/frontend/packages/shared/src/jobq-rollup/jobq-rollup.css
+++ /dev/null
@@ -1,25 +0,0 @@
-/* JobqRollup.tsx styles — light DOM, `.jqr-*` prefixed to avoid
- colliding with a host page's own classes (same convention
- jobq-graph.css uses with `.jg-*`). Amber to match the in-progress /
- "rebuilding" card tint dashboard.css already uses elsewhere. */
-.jqr-summary {
- display: flex;
- align-items: center;
- gap: 0.4em;
- flex-wrap: wrap;
- background: color-mix(in srgb, var(--amber) 8%, transparent);
- border: 1px solid color-mix(in srgb, var(--amber) 55%, transparent);
- color: var(--amber);
- padding: 0.45em 0.8em;
- margin-bottom: 0.6em;
- border-radius: 4px;
-}
-.jqr-summary strong { color: var(--amber); }
-.jqr-link {
- margin-left: auto;
- color: var(--amber);
- text-decoration: none;
- font-weight: bold;
- white-space: nowrap;
-}
-.jqr-link:hover { text-decoration: underline; }
diff --git a/frontend/packages/swarm-ui/src/pages/JobsPage.css b/frontend/packages/swarm-ui/src/pages/JobsPage.css
index 579a8824..beb79c0a 100644
--- a/frontend/packages/swarm-ui/src/pages/JobsPage.css
+++ b/frontend/packages/swarm-ui/src/pages/JobsPage.css
@@ -1,9 +1,7 @@
-/* JobsPage — wraps the shared JobqGraph + JobqRollup components pointed
- at the swarm-controller's own /api/jobq/{graph,rollup} endpoints (same
- wire shapes hive-c0re's dashboard consumes, see hive-jobq-wire's
- README). Styles are @hive/shared's own CSS files, @import'ed here
- rather than from the component files themselves — see JobqGraph.tsx's
- own comment for why that split exists (esbuild's loader map is global
- per bundle call). */
+/* JobsPage — wraps the shared JobqGraph component pointed at the
+ swarm-controller's own /api/jobq/graph endpoint (same wire shape
+ hive-c0re's dashboard consumes, see hive-jobq-wire's README). Styles
+ are @hive/shared's jobq-graph.css, @import'ed here rather than from
+ the component file itself — see JobqGraph.tsx's own comment for why
+ that split exists (esbuild's loader map is global per bundle call). */
@import "@hive/shared/jobq-graph.css";
-@import "@hive/shared/jobq-rollup.css";
diff --git a/frontend/packages/swarm-ui/src/pages/JobsPage.tsx b/frontend/packages/swarm-ui/src/pages/JobsPage.tsx
index 3e95b00b..0645a566 100644
--- a/frontend/packages/swarm-ui/src/pages/JobsPage.tsx
+++ b/frontend/packages/swarm-ui/src/pages/JobsPage.tsx
@@ -7,19 +7,13 @@
// SwarmNodeKind/SwarmResourceKind), so this renders an empty tree today
// — the page exists so the wiring is in place before the first real
// swarm-level job (e.g. CreateAgent) lands.
-//
-// JobqRollup sits above the graph, same "N running / M queued" banner
-// the dashboard's SW4RM tab shows — no `queueHref`, since a "view
-// queue →" link back to this same page would be noise.
import { JobqGraph } from '@hive/shared/jobq-graph.js';
-import { JobqRollup } from '@hive/shared/jobq-rollup.js';
import { Panel } from '../ui/panel/Panel.js';
import './JobsPage.css';
export function JobsPage() {
return (
-
);