Closes #363 (frontend half of milestone #361). Consumes the `ContainerView.parent: Option<String>` field landing in damocles' backend slice — when present, the container list renders depth-first with sibling-position tree glyphs (├─ / └─ joints + │ continuation columns). When absent (pre-#361 state — every container's `parent` is None) the tree collapses to a flat list with no glyphs and no indent — bit-identical to the legacy render. ## Tree shape - Roots: containers whose `parent` is None OR whose parent name isn't in the current container set (orphan tolerance). - Sibling ordering: alphabetical by name within each level (matches damocles' wire spec at #363#issuecomment-3356). - Cycle safety: any container not reached via the root-walk gets emitted as a root at the end — no agent ever silently disappears from the list when the topology is malformed. - Tree glyphs: ancestor at depth d contributes a │ continuation column when that ancestor has more siblings below; otherwise a 3-space gap. The joint is ├─ for non-last siblings, └─ for the last child. - Depth-0 ancestor column is suppressed: roots already separate visually as top-level rows, no need for a column 0 vertical line. ## DOM / CSS - New `buildAgentTree(containers)` + `treePrefix(node)` helpers in app.js. The render loop walks the tree-ordered list instead of the legacy alphabetical containers array. - Each container row gets `data-depth=N` (only when N > 0) and a `<span class="tree-prefix">…</span>` prepended (absolute-positioned into the row's left margin so the existing flex icon/body layout isn't disrupted). - CSS: per-depth `margin-left` step rules for depths 1-6 (hardcoded rather than typed-attr() because CSS Values 5 is Chromium-only as of 2026). 6 depths cover any reasonable hive topology with headroom; deeper agents render at depth 6 indent without further step — visually clamps gracefully. - `.tree-prefix` rendered with `var(--purple-dim)` so the structural lines read as supporting chrome, not as content. ## Validation - `npm run build` clean. Bundle deltas: dashboard app.js +1.8kb (tree builder + treePrefix + render-loop tweak), dashboard.css +0.4kb (tree-prefix + per-depth indent rules). - The render is a no-op until `ContainerView.parent` is populated — validation in production deferred to once damocles' meta-topology field lands. The pre-#361 path (every parent=None) is exercised by every existing dashboard load. - Forward-compatible with damocles' design pivot at #364 (topology source moved from agent.nix to meta/topology.json). The wire shape on ContainerView is unchanged from the frontend's perspective — the field is just sourced from a different backend store. |
||
|---|---|---|
| .. | ||
| packages | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
hyperhive frontend
npm workspaces project for the hyperhive browser-facing assets:
packages/shared/— shared modules used by both surfaces (terminal pane, Catppuccin palette + body typography).packages/dashboard/— the hive-c0re dashboard SPA.packages/agent/— the per-container web UI (default agent page, stats, screen).
Build
npm install # one-off; uses the checked-in package-lock.json
npm run build # builds every workspace into packages/*/dist/
The Rust binaries serve packages/dashboard/dist/ and
packages/agent/dist/ via tower_http::ServeDir at runtime; the
build derivation is wired up in nix/modules/frontend.nix. Per-agent
additions are layered on top of the default agent dist via the
hyperhive.frontend.extraFiles option in agent.nix.
Why npm + esbuild
- Hermetic: dependencies vendored via the checked-in lockfile;
buildNpmPackagein nix uses it as the source-of-truth so the output is reproducible without network access at build time. - esbuild: vanilla-JS bundler, no framework runtime overhead.
Each workspace's
build.mjsis ~30 lines. - Single-PR migration: see issue #273 for the design proposal and the four-commit shape (npm scaffold → nix derivations → container plumbing → Rust cutover).