nodeComponents() split a DAG into weakly-connected components via deps
edges, but post #2476/#2450 every agent's rebuild subgraph hangs off a
shared MetaLock node via AfterOk, so the whole meta-update cascade is one
connected component and rendered as a single wall-of-chips line.
Add a second pass (splitFanOut) that further splits a component's
topo-ordered nodes on out-degree>1 points: a node with more than one
direct dependent renders as its own one-node line, and each dependent
becomes the root of an independent line. Purely deps-structure-driven,
same as the existing WCC split - no agent-field grouping involved. A
component with no fan-out (the common single-agent case) is unaffected.
Since the boot sweep (#2450) and meta-update cascade (#2476) became
single DAGs that grow subgraphs in-place, nothing constructs the old
fan-out anchors/parents anymore:
- NodeKind::Noop (the old boot_root grouping anchor) — no constructors.
- Template::StartupSweep / Source::StartupSweep (the old fan-out parent
template + cascade-child source) — replaced by Template::Boot and
Source::AutoUpdate/MetaUpdate respectively.
Drops the three variants + their as_str arms + the Noop executor arm, and
refreshes the stale fan-out/anchor doc comments (Boot/MetaUpdate/Source
docs, coordinator.md, dashboard.md). Frontend: the queue-kind glyph moves
from the dead startup_sweep to boot (which had none), and the dead
rqe-source-startup_sweep style is dropped.
No behaviour change — pure dead-variant removal.
With the meta-update cascade (#2476) and startup sweep (#2450) folded
into single DAGs that grow per-agent subgraphs via append_subgraph,
nothing links parent/child DAGs anymore — parent_id is dead.
hive-c0re: drop parent_id from Dag/DagSpec (+ the DagView copy); delete
append_children and cancel_children (no callers); simplify trim_history
(no more terminal-parent-with-live-children guard — a one-big-DAG is
terminal only when its whole graph settles); drop the rebuild() parent_id
param; QueueDag returns just the polled DAG (no fan-out children to
gather). hive-sh4re: drop the DagView.parent_id wire field.
frontend: a multi-step op is one DAG now, so renderRebuildQueue drops the
childrenOf/orphans cross-DAG grouping and renders each entry flat; its
per-agent subgraphs render as nodes within the one row (split by deps).
Removed the dead rqe-child style + isChild plumbing.
Docs + the child-DAG queue tests updated/removed to match.
mara's review comment: the frontend shouldn't apply its own grouping
logic on top of the DAG — it should render the structure the backend
already provides. The actual structure is the nodes' deps graph, not
the incidental n.agent field.
Replace the group-by-agent heuristic with nodeComponents(): splits
entry.nodes into weakly-connected components via the deps edges
(undirected reachability), then topo-sorts each component (Kahn's
algorithm) so a chain renders in true dependency order. A DAG made of
independent per-agent subgraphs (no cross-agent deps) still comes back
as separate components — same visual result for today's templates —
but the split is now driven by what the backend actually encodes, and
naturally extends to any future non-agent-aligned branching. Agent
name is still shown as a per-component label, but purely as adjunct
info sourced from that component's own nodes, not the grouping key.
renderQueueEntry flattened entry.nodes into one arrow-joined chain
regardless of which agent each node belongs to. A multi-agent DAG
(e.g. hivectl restart --graceful with several agents) runs independent
per-agent subgraphs concurrently, no cross-agent deps, so joining them
all into one sequential-looking chain misrepresented the actual DAG
shape (mara's report: 'expected one dag that forks after the start
node into the per agent sub dags').
Group nodes by n.agent (stable, first-seen order) and render one
.rqe-nodes line per agent, with a small agent-label chip when the DAG
spans more than one. Single-agent DAGs (the common case) collapse back
to exactly the prior one-line render — no visible change there.
- builds.js NODE_KIND_LABEL: 'set wanted' (and the previously-missing
'noop') so the new head node renders with a label, not the raw kind.
- docs/coordinator.md + templates.rs module doc: the power-op DAG shapes
now show the head SetWanted node; SetWanted added to the lease-needing
list with the atomicity rationale.
Review (iris/argus): the dashboard rebuild live-log header labels one
specific node's log stream (liveNode, keyed by its build_log_id), so it
should show that node's own agent — entryAgents(running) listed every
agent in the DAG, which would mislabel a single agent's log once DAGs
span multiple. The other entryAgents() sites (row label, cancel-confirm,
fingerprint) are correct whole-DAG summaries and unchanged.
DagView no longer has a DAG-level agent, so consumers derive it from the
per-node agents:
- hivectl dag_progress.rs: dag_agents(d) helper (distinct node agents,
comma-joined) in place of d.agent.
- dashboard builds.js: entryAgents(entry) helper likewise for the
rebuild-queue card + live-log header + cancel confirms.
- docs/coordinator.md: lease prose (node-agent-keyed, global per agent),
wire shape (NodeView.agent, no DagView.agent), and dropped the removed
dedup section.
each queue card now shows its DAG's node chain (per-node state, step,
build-log link), fixing 'queue jumps don't show on the dashboard'.
live-log panel keys off the running node's log. new
services.hyperhive.c0re.buildSlots option (default 1) threads the
concurrent nix-build count into serve.json.
The QueueKind enum has graceful_stop, start, and stop variants but the
dashboard QUEUE_KIND_GLYPH map only covered rebuild/meta_update/spawn/
destroy/restart/startup_sweep/perm_change — the three missing kinds
rendered as '?' in the build queue UI.
This became visible with the deferred start-after-rebuild change: a fast-lane
Start entry now appears as a child of its Rebuild parent, but showed the
fallback '?' glyph.
Glyphs assigned: graceful_stop=⏹, start=▶, stop=■.
Also update the dashboard.md kind-glyph list to include all nine kinds and
note that deferred start-after-rebuild entries also use parent_id grouping.
New standalone page /builds.html consolidating rebuild queue, meta
inputs, and build log history into one place, with a new 'Builds' home
tile linking to it. Addresses mara's request: new sub-page with a new
home tile, all three items moved there.
Changes:
- builds.html: new page with three sub-tabs: R3BU1LD QU3U3, M3T4 1NPUTS,
BUILD L0GS. Same minimal-chrome header + createTabStrip pattern as core
and logs pages.
- builds.js: new bundle combining rebuild queue renderer (from core.js),
meta inputs renderer (from core.js), rebuild-live-log renderer (from
core.js), and build log history renderer (from logs.js). Deep-links to
/builds.html?id=N#buildlogs. Count pill id: builds-tab-count-rebuild.
BUILD L0GS tab lazy-loads on first activation.
- builds.css: @imports system-sections.css (rebuild queue + meta inputs +
live-log styles) and logs.css (build-logs-* component styles).
- build.mjs: register builds.js, builds.css, builds.html.
- core.html: remove R3BU1LD QU3U3 + M3T4 1NPUTS tabs (now on builds.html).
Default tab changes to K3PT ST4T3.
- core.js: remove renderMetaInputs, renderRebuildQueue + helpers,
renderRebuildLiveLog + live-log state, elapsed-time tickers,
updateRebuildCount, and the rebuild_queue/meta_inputs SSE handlers.
Remove openBuildLogStream + util imports no longer needed.
- core.css: remove .rebuild-live-log-* rules (moved to system-sections.css
so builds.css can share them via @import).
- system-sections.css: add .rebuild-live-log-* styles (moved from core.css);
update comment to mention builds.html.
- logs.html: remove BUILD tab + pane (moved to builds.html).
- logs.js: remove fetchBuild(), fmtTs, fmtDuration, openBuildLogStream
import, and rebuild_queue_changed SSE debounce. Default tab: 'agent'.
SSE stream retained for audit_entry_added live-appends.
- index.html: add Builds tile (🔨, rebuild queue · meta inputs · build
logs); update Core tile desc to 'kept state · container load'; update
Logs tile desc to remove 'build'.