diff --git a/docs/web-ui.md b/docs/web-ui.md index 8248a034..be864b4b 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -425,6 +425,41 @@ body sits to the right with three stacked lines agent is stale. Banner pulses on each broker SSE event (`pulseBanner` with a 4s grace timer). +#### Topology tree + +Container rows render as a forest, not a flat list — each agent +sits indented under its declared parent. `tabs.js::buildAgentTree` +walks `ContainerView.parent` for every container in the snapshot +and produces a render order with per-row depth + sibling-position +info: + +- Top-level rows are agents with `parent = null` OR a parent that + doesn't appear in the container map (orphans get hoisted to root + so they're still visible). +- Within each level children sort alphabetically by name; roots + likewise. +- Cycle safety: any container not reached during the root-walk is + appended at the end as a root, so no agent ever silently + disappears from the list when the topology JSON is malformed. +- The pre-topology rendering shape (every container at depth 0, + flat list) collapses to the same visual today when no parent + field is set — bit-identical fallback path. + +The per-row prefix column (`.tree-prefix`) is **DOM-painted, not +text-glyph-painted**. Each indent lane is its own positioned +`` so CSS can draw full-height vertical bars that bridge the +gap between sibling rows; using text box-drawing characters +(`├─`, `└─`, `│ `) only paints one text-line tall and leaves +visible breaks between the taller-than-one-line container cards. +The bars come in two flavours: continuation (the ancestor's +subtree extends below this row → vertical line top→bottom) or +blank (ancestor was the last sibling at its level → no line +needed). The joint at the row's own depth column is `├` (more +siblings below) or `└` (last sibling at this depth — vertical +stops at the row's icon midline). + + + ### Selection bar When one or more agents are selected (via icon click), a sticky diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index a8276669..f8f7c892 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -370,20 +370,9 @@ window.marked = marked; } // ─── state rendering ──────────────────────────────────────────────────── - // ─── agent topology (#363) ────────────────────────────────────────────── - // Build a forest from `ContainerView.parent` and walk depth-first to - // produce a render order with per-row depth + sibling-position info. - // Top-level (parent = null OR parent not in the container map) are - // roots. Within each level, children are sorted alphabetically by - // name; roots likewise. Cycles in the parent graph (malformed config) - // are tolerated: any container not reached via root-walk is appended - // as a root at the end, so no agent ever disappears from the list. - // - // For the visual: each row gets a textual prefix column (`├─`, `└─`, - // continuation `│ ` or padding ` ` for ancestor columns). When - // every container has `parent = null` (today's pre-#361 state), the - // tree collapses to a flat list with no glyphs and no indent — bit- - // identical to the legacy render. + // ─── agent topology ───────────────────────────────────────────────── + // See docs/web-ui.md::Topology tree for the rendering contract + // (forest walk, alphabetical sort, orphan + cycle handling). function buildAgentTree(containers) { const byName = new Map(); for (const c of containers) byName.set(c.name, c); @@ -421,16 +410,9 @@ window.marked = marked; } return out; } - // Builds the .tree-prefix DOM for a row at the given depth. Each - // lane is its own positioned child so CSS can paint full-height - // vertical bars that bridge the gap between sibling rows — text - // box-drawing glyphs only paint one text-line tall, which left - // visible breaks between rows once we grew taller-than-one-line - // container cards (#388). Ancestor lanes are either continuation - // (vertical bar top→bottom+gap) or blank; the joint at this row's - // own depth is ├ (branch — vertical continues below) or └ (last — - // vertical stops at the row's icon midline). CSS at - // `.container-row .tree-prefix` paints the bars + horizontal stub. + // Builds the .tree-prefix DOM for a row at the given depth. + // See docs/web-ui.md::Topology tree for why this is DOM-painted + // (one positioned per lane) rather than text-glyph-painted. function treePrefixDom({ depth, ancestorIsLast, isLast }) { if (depth === 0) return null; const prefix = el('span', { class: 'tree-prefix', 'aria-hidden': 'true' });