hive-jobq-graph: dispatch hive-jobq-graph-update on every render

Fetching lives in the component (mara's steer on hyperhive#2812) — a
host that needs the raw node list for something the generic tree
doesn't show (a count badge, a live-log panel keyed on a specific
node) now listens for this bubbling/composed CustomEvent instead of
doing its own parallel fetch. Fires from both the self-fetch path
(refresh()) and a host-pushed render(nodes) call, so a listener sees
every update regardless of source.

Verified via jsdom: both paths dispatch with the correct nodes in
detail.
This commit is contained in:
iris 2026-08-03 01:34:47 +02:00 committed by mara
commit 98957b48e9

View file

@ -6,24 +6,23 @@
// `payload.label` verbatim, and `payload.data` (if present) as a generic
// key/value list — this element never branches on what a label or a data
// key means, matching the "opaque payload" contract the wire type
// documents. A consumer that wants domain-specific rendering (an agent
// chip, a build-log link, ...) does its own thing on top; this is the
// generic floor every jobq gets for free.
// documents. A consumer wanting domain-specific rendering (an agent chip,
// a build-log link, ...) does its own thing on top; this is the generic
// floor every jobq gets for free.
//
// Usage: <hive-jobq-graph endpoint="/api/jobq/graph"></hive-jobq-graph>
// Self-fetches on connect. Call `.refresh()` whenever the host knows the
// graph changed (e.g. on an SSE tick) — the element does not poll or
// subscribe itself, since the right refresh trigger varies per page.
// `.render(nodes)` is also public, for a host that already has a fresh
// `Vec<GraphNode>` (e.g. riding its own SSE payload) and wants to skip
// the redundant fetch.
// Usage: <hive-jobq-graph endpoint="/api/jobq/graph"></hive-jobq-graph> —
// self-fetches on connect. `.refresh()` (public) re-fetches + re-renders;
// `.render(nodes)` (public) renders host-pushed data directly, no fetch.
// Fetching lives here, not the host page (per the issue this element was
// built for) — every render dispatches a bubbling/composed `hive-jobq-graph-update`
// event (`detail: { nodes }`) so a host needing the raw list for
// something the tree doesn't show (a count badge, a live-log panel)
// listens instead of running its own parallel fetch.
//
// Shadow DOM + own styles (not light-DOM like <hive-tab-strip>): unlike a
// tabbar, this renders a whole subtree of markup nothing else on the page
// needs to select into, so scoping is a win here rather than a cost.
// Theme custom properties (--fg, --red, ...) still apply — they pierce
// the shadow boundary by inheritance, only plain class rules need to be
// local, which is exactly what the own stylesheet is for.
// Shadow DOM + own styles, per instruction — unlike light-DOM
// <hive-tab-strip>, this renders a whole subtree nothing else needs to
// select into. Theme custom properties (--fg, --red, ...) still pierce
// the shadow boundary by inheritance; only plain class rules are local.
import { el } from '../dom.js';
import { attachShadowCss } from '../shadow-css.js';
@ -130,10 +129,15 @@ class HiveJobqGraph extends HTMLElement {
this._body.replaceChildren();
if (!nodes || !nodes.length) {
this._body.append(el('p', { class: 'jg-empty' }, 'empty'));
return;
} else {
const roots = buildTree(nodes);
for (const root of roots) this._body.append(renderNode(root));
}
const roots = buildTree(nodes);
for (const root of roots) this._body.append(renderNode(root));
this.dispatchEvent(new CustomEvent('hive-jobq-graph-update', {
detail: { nodes: nodes || [] },
bubbles: true,
composed: true,
}));
}
}
customElements.define('hive-jobq-graph', HiveJobqGraph);