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