feat(dashboard): show jobq node tree in build queue

Add NodeView::parent to the wire (hive-sh4re + hive-c0re dag_view), then
render the recursive parent/child tree in the dashboard build queue instead
of the previous flat chain/fan-out layout.

Wire change (hive-sh4re, hive-c0re):
- NodeView gains parent: Option<NodeId> (skip_serializing_if = None)
- dag_view() projects node.parent, filtering out the Dag container id
  (top-level work nodes become parent: None on the wire)

Frontend (builds.js):
- Replace nodeComponents + splitFanOut with buildNodeTree (uses parent
  edges directly) + topoSort helper (orders siblings by deps)
- renderTreeNode walks the tree depth-first, rendering indented rows
  with └─/├─ connectors and agent label per chip
- Flat chains and fan-out heuristics are gone; structure comes straight
  from the scheduler's parent axis

Closes: none (parent issue tracked in forge)
This commit is contained in:
iris 2026-07-25 20:16:20 +02:00 committed by mara
commit cb936fe2fe
3 changed files with 94 additions and 142 deletions

View file

@ -807,6 +807,15 @@ impl QueueInner {
Vec::new()
};
let has_log = self.node_rt.get(&id).and_then(|r| r.build_log_id).is_some();
// `node.parent` is the structural jobq parent. Top-level nodes
// have `parent == Some(container)` (direct children of the Dag
// container); those become `parent: None` on the wire since the
// container itself is not part of the work-node payload. Sub-nodes
// carry the id of their containing parent work-node.
let parent = node
.parent
.filter(|&p| p != container)
.map(hive_jobq::NodeId::get);
nodes.push(NodeView {
id: id.get(),
agent: node.payload.agent().to_owned(),
@ -819,6 +828,7 @@ impl QueueInner {
approval_id,
inputs,
has_log,
parent,
});
}
if nodes.is_empty() {