builds: mount <hive-jobq-graph> directly, drop the hand-rolled queue renderer

Per mara's explicit steer on hyperhive#2812 ("also replace the build
queue tab with this component" + "graph fetching should live in the
component, not build.js" + "dont replicate the grouping by dag"):
R3BU1LD QU3U3 is now a mounted <hive-jobq-graph endpoint="/api/jobq/graph">
element. builds.js no longer renders the queue itself, does its own
fetch, or hand-rolls a per-root tree/roll-up/cancel-button — all of
buildNodeTree/topoSort/entryFingerprint/renderQueueEntry/
firstFailedNode/rebuildQueueRowCache/QUEUE_STATE_GLYPH/rollupState is
gone.

builds.js's remaining job is listening for the component's
hive-jobq-graph-update event (added to the component in the prior
commit) to keep a flat jobqNodes array in sync, and using that for the
two things the generic view doesn't render: the count-pill and the
live-log panel. On the rebuild_queue_changed SSE tick, calls the
mounted element's .refresh() instead of doing its own fetch — that
event still carries its own queue payload on the wire (tabs.js/SW4RM
still reads it for the badges, untouched), this page just ignores it
now.

Also removed, now genuinely dead: the two elapsed/finished-time
tickers (nothing produces the .rqe-when spans they targeted anymore),
stateSlug and isoToSecs (no callers left), fmtElapsed import (no
callers left).

New @hive/shared/jobq-graph.js export entry in packages/shared's
package.json, alongside the existing hive-tab-strip.js/hive-menu.js/
etc. pattern.

docs/web-ui/dashboard.md's R3BU1LD QU3U3 section rewritten to match:
mounted-component shape, no source/reason/cancel-button/deep-link on
rows (generic component has none), settled entries show their full
step tree (Done nodes aren't filtered off this wire, unlike the old
DagView projection).

Verified against real production data again (this hive's own live
/api/jobq/graph, now settled — no in-flight build at test time) plus
a synthetic running-build case to exercise findLiveBuild's happy path:
correct live-node detection (build_log_id gate), correct in-flight
root count. Confirmed the built dist bundle actually registers
customElements.define("hive-jobq-graph", ...) — the new shared
package export resolves correctly through esbuild.

Branch reused per mara's explicit "dont rework #3000 - continue
working on _this_ pr [#2996], it already has the component that
replaces 90% of build.js" — this ships as part of PR #2996, not a
separate PR.
This commit is contained in:
iris 2026-08-03 01:45:33 +02:00 committed by mara
commit aa149a7a62
3 changed files with 104 additions and 393 deletions

View file

@ -201,48 +201,52 @@ Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**,
The dashboard tab keeps the rebuild-queue *state* for the SW4RM
card badges without rendering these panels.
**R3BU1LD QU3U3** — pending and recently-completed container
operations: rebuilds, meta-update cascades, and first-spawns.
One operation runs at a time; the worker drains FIFO. Each row
shows a state glyph (`⏸` queued / `▶` running / `✔` done /
`✖` failed / `⊘` cancelled), kind glyph + verb (`↻ rebuild`,
`◆ meta_update`, `✨ spawn`, `🗑 destroy`, `↺ restart`,
`⚡ boot`, `🔑 perm_change`, `⏹ graceful_stop`, `▶ start`,
`■ stop`), agent name,
source chip (`manual | meta_update | auto_update | crash_recover | approval`
— green for operator-approved config changes), timing, and an
optional reason / error. A multi-step op is a single DAG: its
per-agent subgraphs and sub-steps (a meta-update cascade's per-agent
rebuilds, a `Reconcile`'s deferred `Start`/`Stop`) render as nodes
within the one entry — split into subgraphs by the backend `deps`
edges — not as nested child entries. Dedup: re-enqueueing a still-queued
op for the same agent collapses into the existing entry. All timing
labels stay live: running entries tick elapsed seconds every second;
queued and terminal ("done N ago" / "failed N ago") labels tick every
30s so keyed rows never show stale timestamps as they persist across
`rebuild_queue_changed` snapshots. The in-flight phase is the running
**node's kind** in the node chain — there is no separate sub-step
label (the old cyan `↳ <step>` sub-line went away with the DAG queue:
each phase is its own node now).
Queued entries carry a `✗` cancel button on the right edge;
running / done / failed / cancelled entries don't show it — the
backend refuses cancellation for non-`Queued` rows anyway
(`POST /api/rebuild-queue/{id}/cancel`). Successful
cancel flips the row to `⊘ cancelled` via the next
`rebuild_queue_changed` snapshot.
Cold-loaded from `/api/state.rebuild_queue`; live updates via
`rebuild_queue_changed` snapshot event.
**R3BU1LD QU3U3** — pending, in-flight, and recently-settled container
operations: rebuilds, meta-update cascades, and first-spawns. One
operation runs at a time; the worker drains FIFO. **Is a mounted
`<hive-jobq-graph endpoint="/api/jobq/graph">`** (the shared generic
graph-viewer component, `@hive/shared/jobq-graph.js`) — `builds.js`
does not render the queue itself; it just mounts the element and
listens for its `hive-jobq-graph-update` event to drive the two things
below it that the generic view doesn't show. The component owns
fetching, cold and live: `GET /api/jobq/graph` on mount, and
`.refresh()` on every `rebuild_queue_changed` SSE tick (that event
still carries its own `Vec<QueueEntry>` payload on the wire — the
SW4RM tab still consumes it for the badges, untouched — this page just
ignores the payload and treats the tick as a refetch trigger).
Each row is one root graph node (`parent: null`); a multi-step op's
per-agent subgraphs and sub-steps render as nodes within that one
entry (structural `parent` edges define the tree, `deps` edges order
siblings). A row shows a state glyph (`⏸` pending / `▶` running /
`◐` finishing — own work done, a sub-node still running / `✔` done /
`✖` failed / `⊘` cancelled / `·` skipped) and each step's own
label/agent. **No source chip, kind label, cancel button, timing, or
build-log deep-link on rows** — the generic component has no
per-node action affordances or entry-level metadata (no equivalent of
the old `DagView`'s `source`/`reason`/`created_at`, which were
`NodeKind::Dag`-specific fields the generic wire doesn't carry); per
mara's steer on hyperhive#2812 ("dont feel constrained by what the ui
does currently"), the first cut presents what the endpoint actually
gives rather than reconstructing the old per-row chrome. Settled
entries render their **full step tree**, not just a bare summary —
unlike the old `DagView` projection, this wire does not filter `Done`
nodes out.
Below the queue, a **live build-log panel** (`#rebuild-live-log`,
`renderRebuildLiveLog`) streams the currently-running rebuild's output
inline — collapsible, with a live/ok/fail badge and a `↓ raw` download.
It's keyed to the running entry's `build_log_id` and opens one
`EventSource` to `GET /api/build-logs/id/{id}/stream` (the same stream
the BUILD L0GS tab uses; the stream replays accumulated output on
connect). It lives in its own container outside `#rebuild-queue-section`
so the queue's per-row re-render (rows rebuild as nodes advance)
never tears down the open stream; it hides when nothing is building and
each row keeps its `logs →` link out to the full build log history.
`renderRebuildLiveLog`) shows the currently-running rebuild's output
inline — collapsible, with a live/ok/fail badge and a `↓ raw`
download. It's keyed to the first `Running` node (in wire order)
whose `payload.data.build_log_id` is set — read from the
`hive-jobq-graph-update` event's node list, same source as the count
pill, no separate fetch — and **polls** `GET /api/build-log/{id}`
every 2s (`fetchAndRenderLiveLog` / `liveLogPollTimer`); not an
`EventSource` (that's the BUILD L0GS tab's own per-row expand view
below — `GET /api/build-logs/id/{id}/stream`, real SSE, replays
accumulated output on connect — a separate mechanism). The live-log
panel lives in its own container outside `#rebuild-queue-section` so
the mounted `<hive-jobq-graph>`'s own re-renders never disturb the
open poll; it hides when nothing is building.
**M3T4 1NPUTS** — inputs in `meta/flake.lock` the operator can
selectively `nix flake update`, rendered as an indented tree: