diff --git a/docs/integrations/forge.md b/docs/integrations/forge.md index 08b2ab93..9e471b80 100644 --- a/docs/integrations/forge.md +++ b/docs/integrations/forge.md @@ -127,7 +127,7 @@ Two things live in the `agent-configs` Forgejo organization: [How a hive reads the forge](#how-a-hive-reads-the-forge)). - The dashboard links each container's deployed config commit to this config repo on the swarm's forge (`swarm.forge.publicUrl`; `null` means no - link), so operators can go straight from the SW4RM tab into + link), so operators can go straight from the 4G3NTS tab into the rendered repo without an extra `git` step. The `hive-forge` CLI (separate workspace crate, see diff --git a/docs/scheduler/jobq.md b/docs/scheduler/jobq.md index 990a3362..15bca667 100644 --- a/docs/scheduler/jobq.md +++ b/docs/scheduler/jobq.md @@ -33,7 +33,7 @@ swarm controller and hive-c0re each build their own graph on it. Two views, one per graph, drawn by the same component: the swarm UI's `/jobs` page shows the swarm controller's graph, and the hive dashboard's -**BU1LDS** page (R3BU1LD QU3U3 — see +**R3BU1LD QU3U3** tab (see [`web-ui/dashboard.md`](../web-ui/dashboard.md)) shows that hive's. Each **row** is one job; the rows nested under it are that job's steps, in order (occasionally a couple run side by side). A step shows one of: diff --git a/docs/web-ui/README.md b/docs/web-ui/README.md index fdf14ca6..9c8f2fe4 100644 --- a/docs/web-ui/README.md +++ b/docs/web-ui/README.md @@ -11,21 +11,23 @@ what you actually do here. ## Where things are Everything starts at the **H0M3 hub**, served at `/` — a grid of tiles -linking to every surface (Dashboard, Flow, Logs, Builds, Stats, +linking to every surface (Dashboard, Flow, Logs, Stats, Settings, Core). Every page links back to H0M3, so you're never more than one step from the hub. The **dashboard** itself (`/dashboard.html`) is where you'll spend most -of your time. It's a single page with exactly two tabs: +of your time. It's a single page with five tabs: -- **SW4RM** — every agent, live. This is the default tab and the one +- **4G3NTS** — every agent, live. This is the default tab and the one you'll check most. +- **R3BU1LD QU3U3**, **M3T4 1NPUTS** and **BUILD L0GS** — the build + lifecycle: the rebuild queue with a live log of whatever is building, + the meta flake inputs you can bump, and the build log history. - **P3RM1SS10NS** — what tools and system-level access each agent has. Everything else lives on its own page instead, all reachable from the H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across -the whole swarm), **Stats** (`/stats.html`, swarm-wide usage stats), -**Builds** (`/builds.html`, the rebuild queue and build history), and +the whole swarm), **Stats** (`/stats.html`, swarm-wide usage stats), and **Core** (`/core.html`, tombstones and container resource use). Each agent also has its own page — a full terminal view of that @@ -35,7 +37,7 @@ if the gateway isn't in front). ## The things you'll actually do -**Check on an agent.** SW4RM shows every container as a row: name, +**Check on an agent.** 4G3NTS shows every container as a row: name, whether it's running, what it's currently doing (a live status pill — `rebuilding…`, `starting…`, or similar — while something's in flight), and quick links (stats, screen, forge profile). Select the name to open @@ -47,12 +49,12 @@ repo. Review and merge them on the forge; the merge deploys the change. See [`config-changes.md`](../agent-lifecycle/config-changes.md#config-changes). **Start, stop, restart, or rebuild an agent.** Select one or more -agents on SW4RM (select the icon) and use the selection bar, or use the +agents on 4G3NTS (select the icon) and use the selection bar, or use the per-agent `⋮` menu on a single row. Rebuilding re-applies that agent's current config; use it whenever an agent shows as "needs update." -**Watch a build.** BU1LDS shows the rebuild queue live, plus a +**Watch a build.** The dashboard's R3BU1LD QU3U3 tab shows the rebuild queue live, plus a streaming log of whatever's currently building. Useful right after merging a config change or bumping a flake input. diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index f65593e5..6cfd43f1 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -10,8 +10,8 @@ matrix accounts, the controller's job graph — are on the hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`). It has a fixed chrome header at the top and a `
` that shows exactly -one tab pane at a time. The URL hash (`#swarm`, -`#permissions`) drives which pane is +one tab pane at a time. The URL hash (`#agents`, `#rebuild`, `#meta`, +`#buildlogs`, `#permissions`) drives which pane is active; hash changes don't reload the page. FL0W and the other standalone pages are reachable from the H0M3 hub at `/`, not from the dashboard tab strip. @@ -19,17 +19,18 @@ from the dashboard tab strip. **Chrome header** (fixed, overlays the active tab pane): - **← home back-link**: top-left of the chrome, links to the H0M3 hub at `/`. Every surface links back to the hub rather than to each other. -- **Tab strip**: `◆ SW4RM ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only — +- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`, + `◆ BUILD L0GS ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only — the SYST3M panels live on the standalone **C0R3** page (`/core.html`), and FL0W / ST4TS live on their own pages too, all reachable from the - H0M3 hub (not the tab strip). Count pill on SW4RM - (container count). + H0M3 hub (not the tab strip). Count pills on 4G3NTS + (stale containers) and R3BU1LD QU3U3 (rebuilds in flight). - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) — sits below the tab strip. - **Server-warnings banner** — a generic, sticky top-of-page strip shown on **every** page (dashboard + every stand-alone page — FL0W, - H0M3, C0R3, BU1LDS, ST4TS), injected at the top + H0M3, C0R3, ST4TS), injected at the top of `` by `renderServerWarnings` in `common.js`. Driven by `state.server_warnings` — a list of `{ kind, level, message }` — and coloured by `level` (`warn` amber / `crit` red). hive-c0re owns the @@ -55,7 +56,7 @@ The FL0W page uses a slim header (a `← home` back-link + the page title) rather than the dashboard tab strip — it's a standalone surface, not a tab pane. -## SW4RM tab +## 4G3NTS tab **C0NTAINERS** — live containers as a flat list, one row per container, sorted alphabetically by agent name; every container the @@ -73,13 +74,13 @@ agent is stale. Passive / rare-interaction state, reached from the **Core** tile on the H0M3 hub (served at -`/core.html`), with the same minimal chrome as `/builds.html`: a `← home` -back-link and a single pane, **K3PT ST4T3**. The page is its own esbuild -bundle (`core.js`) that cold-loads `/api/state` and subscribes to -`/api/dashboard/stream` for `tombstones_changed`, `capabilities_changed`, -and `tool_groups_changed` (the latter two re-render the stale-perms -sub-section when permission data changes). (Rebuild queue and meta inputs -live on the BU1LDS page — see below.) +`/core.html`), with minimal chrome: a `← home` back-link and a single pane, +**K3PT ST4T3**. The page is its own esbuild bundle (`core.js`) that +cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for +`tombstones_changed`, `capabilities_changed`, and `tool_groups_changed` +(the latter two re-render the stale-perms sub-section when permission +data changes). (Rebuild queue and meta inputs are dashboard tabs — see +below.) Per-container CPU, memory and disk load isn't shown in the hive UI. The same figures go out as OTEL metrics and appear in Grafana (see @@ -111,21 +112,23 @@ autorefreshes on `capabilities_changed` + `tool_groups_changed` SSE events. Each ghost agent gets a `✕ clear perms` button (`DELETE /api/permissions/{name}`). -## BU1LDS page (`/builds.html`) +## Build tabs (`#rebuild`, `#meta`, `#buildlogs`) -The build lifecycle hub — rebuild queue, live build log, meta inputs, -and build log history on one page. Standalone page reached from the -**Builds** tile on the H0M3 hub, same minimal chrome as `/core.html`. -Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**, -**BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads -`/api/state` and subscribes to `/api/dashboard/stream` for -`rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`. -The SW4RM tab (`/dashboard.html`) does **not** read this endpoint at -all, by design — the swarm view stays independent of job-queue -internals. Its per-agent pending badges are transient-only, and -its queue-summary banner reads the much narrower `GET /api/jobq/rollup` -instead (see Container row, below) — a handful of pre-tallied counts, -not the graph. +The build lifecycle — rebuild queue, live build log, meta inputs, and +build log history — is three tabs of the dashboard: **R3BU1LD QU3U3** +(`#rebuild`), **M3T4 1NPUTS** (`#meta`) and **BUILD L0GS** +(`#buildlogs`). They live in `builds.js`, which `tabs.js` imports: the +dashboard's one tab strip, `/api/state` snapshot and +`/api/dashboard/stream` subscription serve them, with +`rebuild_queue_changed`, `meta_inputs_changed` and `meta_update_running` +on the stream alongside the dashboard's other kinds. The count pill on +R3BU1LD QU3U3 is the number of rebuilds in flight. +The 4G3NTS tab does **not** read the graph endpoint, by design — the swarm +view stays independent of job-queue internals. Its per-agent pending +badges are transient-only, and its queue-summary banner reads the much +narrower `GET /api/jobq/rollup` instead (see Container row, below) — a +handful of pre-tallied counts, not the graph. The banner's "view queue →" +link goes to the `#rebuild` tab. **R3BU1LD QU3U3** — pending, in-flight, and recently settled container operations: rebuilds, meta-update cascades, and first-spawns. One @@ -394,7 +397,7 @@ re-renders the terminal row. The operator addresses the root agent as `@root`. ## H0M3 page (`/`) The H0M3 hub is the primary landing page (served at `/` by default). A -responsive grid of link tiles — Dashboard, Flow, Builds, Stats, +responsive grid of link tiles — Dashboard, Flow, Stats, Core, API — each pointing to their respective surfaces, all unconditionally shown (no gating). The page is a pure portal with no tab-bar or SSE subscriptions. Typography + colours inherit @@ -411,7 +414,7 @@ the tooltip says "select … for bulk actions" or "deselect … (or press Esc to clear all)." The `` points at `/icon`; load failure falls back to the dimmed hyperhive mark (`/favicon.svg`). The card body sits to the right with three stacked lines -(`assets/swarm.js::renderContainers`). +(`assets/agents.js::renderContainers`). **Icon layout + load strategy:** the `` is absolutely @@ -580,10 +583,10 @@ agent is stale. Banner pulses on each broker SSE event **Build-queue summary banner** — when the job queue has any active work, a compact amber banner sits above the container list: `◐ build queue — N running · M queued — view queue →` (the link goes to the -BU1LDS page's R3BU1LD QU3U3). The shared `JobqRollup` Preact component +R3BU1LD QU3U3 tab). The shared `JobqRollup` Preact component (`@hive/shared/jobq-rollup.js` — the same one swarm-ui's `/jobs` page mounts, pointed at swarm-controller's own rollup endpoint instead), -mounted once into `#jobq-rollup-section` by `swarm.js::initJobqRollup` +mounted once into `#jobq-rollup-section` by `agents.js::initJobqRollup` and refreshed via its own handle rather than being re-rendered by `renderContainers`. Reads `GET /api/jobq/rollup` (`hive-jobq-wire::state_rollup`) — `Vec<{ state, nodes, roots }>`, @@ -645,7 +648,7 @@ Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `P4US3` / `R3SUM3` / buttons — see **Container row** above. Selecting an agent's icon toggles its selection (an in-memory `Set`); `Esc` or the bar's `✕ clear` button drops everything. The selection persists across tab -switches in-memory — the bar just hides on non-SW4RM tabs since other +switches in-memory — the bar just hides on non-4G3NTS tabs since other tabs don't show the agent cards needed to cross-reference. When the operator selects one or more agents (via the icon), a sticky @@ -711,11 +714,11 @@ below — some endpoints aren't in it yet. 404 when the row is missing (vacuum-reaped or stale id). - `GET /api/build-logs/id/{id}/stream` — live SSE stream of stdout+stderr for an in-progress build (newline-delimited JSON - frames). Used by the expanding live-row view in BU1LDS. The + frames). Used by the expanding live-row view in the BUILD L0GS tab. The stream closes when the build finishes or errors. - `GET /api/build-logs/id/{id}/raw` — raw concatenated stdout+stderr as `text/plain`. Used by the `↓ raw` / `↓ download - raw` links in BU1LDS. Works on both finished and in-progress + raw` links in the build tabs. Works on both finished and in-progress build logs. - `GET /static/marked.js` serves the vendored `marked` bundle used for markdown previews. @@ -854,10 +857,10 @@ payload): full snapshot of destroyed-but-state-kept agents. Emitted on destroy, purge, and revive. C0R3 K3PT ST4T3 tab subscribes. - `meta_inputs_changed` (seq, inputs: `Vec`) — full - snapshot of `meta/flake.lock` inputs. BU1LDS M3T4 1NPUTS tab + snapshot of `meta/flake.lock` inputs. The M3T4 1NPUTS tab subscribes; same shape as `GET /api/meta-inputs`. - `meta_update_running` (running: bool) — emitted when a - `nix flake update` ripple starts or completes. BU1LDS M3T4 1NPUTS + `nix flake update` ripple starts or completes. The M3T4 1NPUTS tab uses this to show/hide the "⏳ meta-update running" banner. diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md index 44850508..af9f4382 100644 --- a/docs/web-ui/shape.md +++ b/docs/web-ui/shape.md @@ -20,7 +20,7 @@ CSS bundle per page (`colors.css` + `theme.css` + `common.css`, loaded by every page, plus a page-specific bundle — `dashboard.css` / `flow.css` / `home.css` / - `stats.css` / `core.css` / `builds.css`); + `stats.css` / `core.css`); `common.css` inlines `@hive/shared`'s `base.css` + `terminal.css` + `tabs.css` + `chrome.css` + `pill.css` via esbuild's `@import` resolution. @@ -208,7 +208,7 @@ re-render), call `refreshState()`. State shapes live in `hive-c0re/src/dashboard/state_snapshot.rs::StateSnapshot` and `web_ui/state.rs::StateSnapshot` — when adding state fields, plumb through the snapshot struct and the -relevant domain module (`swarm.js`, `permissions.js`, etc.) render function. +relevant domain module (`agents.js`, `permissions.js`, etc.) render function. **Focus preservation:** `refreshState` checks whether `document.activeElement` sits inside one of the managed sections diff --git a/frontend/README.md b/frontend/README.md index 308451ff..8bab5d97 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -30,7 +30,7 @@ layers per-agent additions on top of the default agent dist. output is reproducible without network access at build time. - **esbuild**: `agent` and `swarm-ui` are Preact apps; `dashboard` is a vanilla-JS + custom-element MPA with a few Preact-rendered pages - (`builds.js`, `swarm.js`); `shared` holds both kinds of module. No + (`builds.js`, `agents.js`, imported by the dashboard entry); `shared` holds both kinds of module. No framework runtime beyond Preact where it's used. Each workspace's `build.mjs` is a short esbuild script. - **Single-PR migration**: see the tracked design proposal and diff --git a/frontend/packages/dashboard/build.mjs b/frontend/packages/dashboard/build.mjs index 99da2c1f..ca29bc82 100644 --- a/frontend/packages/dashboard/build.mjs +++ b/frontend/packages/dashboard/build.mjs @@ -1,5 +1,5 @@ // esbuild build for @hive/dashboard: one JS+CSS bundle per page (H0M3 at -// `/`, dashboard/flow/core/builds at their own +// `/`, dashboard/flow/core/stats at their own // `.html`), each named after its own entry point below — see the // `entryPoints`/`for` lists for the exact map, not repeated here to // avoid this comment drifting out of sync with the real build steps. @@ -38,7 +38,6 @@ await build({ src("home.js"), src("stats.js"), src("core.js"), - src("builds.js"), ], outdir: staticDir(""), bundle: true, @@ -57,7 +56,7 @@ await build({ // runs as its own esbuild invocation over different entry points. loader: { ".css": "text" }, // `@hive/shared/jobq-graph.js` resolves to a real `.jsx` file - // (`JobqGraph.jsx`), pulled in transitively by `builds.js` — esbuild + // (`JobqGraph.jsx`), pulled in transitively by `tabs.js` via `builds.js` — esbuild // already picks the `jsx` loader for `.jsx` by extension, this just // sets the transform mode to match swarm-ui's (which also authors // this file). No other entry here uses JSX today; this doesn't turn @@ -103,7 +102,6 @@ for (const entry of [ "home.css", "stats.css", "core.css", - "builds.css", ]) { await build({ entryPoints: [src(entry)], @@ -120,7 +118,6 @@ for (const html of [ "flow.html", "stats.html", "core.html", - "builds.html", ]) { copyFileSync(src(html), dist(html)); } diff --git a/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js b/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js index afa4d261..8267589f 100644 --- a/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js +++ b/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js @@ -1,6 +1,6 @@ // hive-agent-menu.js — , the per-agent "⋮" context-menu // custom element. One instance per agent card, appended after -// .card-body in the container row's flex layout (swarm.js's +// .card-body in the container row's flex layout (agents.js's // buildContainerLi via buildAgentMenu). // // Deliberately a *light-DOM* element with no shadow root of its own: all @@ -66,7 +66,7 @@ async function agentMenuPost(actionPath, name, body, flags) { class HiveAgentMenu extends HTMLElement { connectedCallback() { // Same reconnect-without-detach hazard as (see its - // connectedCallback comment) — swarm.js's row cache can move an + // connectedCallback comment) — agents.js's row cache can move an // already-built
  • subtree without a real detach. Without this // guard a reconnect would append a second on top of the // first, doubling the dropdown. diff --git a/frontend/packages/dashboard/src/swarm.js b/frontend/packages/dashboard/src/agents.js similarity index 98% rename from frontend/packages/dashboard/src/swarm.js rename to frontend/packages/dashboard/src/agents.js index 07f38285..313f6043 100644 --- a/frontend/packages/dashboard/src/swarm.js +++ b/frontend/packages/dashboard/src/agents.js @@ -1,4 +1,4 @@ -// SW4RM (containers) domain — extracted from tabs.js. +// 4G3NTS (containers) domain — extracted from tabs.js. // Agent topology, container-row rendering, selection bar, and all // live-update apply handlers for container-state, transient, and // job-queue-rollup ops. See docs/web-ui/dashboard.md::Container row for the @@ -52,7 +52,7 @@ const selectionState = new Set(); // ─── job queue rollup (queue-summary banner only) ────────────────────────── // // The ONE piece of job-queue-derived state on this page, and deliberately -// narrow — mara: "swarm.js should not need to pull in the jobq to do its +// narrow — mara: "agents.js should not need to pull in the jobq to do its // job." Rendering is owned by the shared `JobqRollup` Preact component // (same one swarm-ui's /jobs page mounts, self-fetching // GET /api/jobq/rollup); this file just calls `render(h(...))` and bumps @@ -70,7 +70,7 @@ export function initJobqRollup() { render( h(JobqRollup, { endpoint: "/api/jobq/rollup", - queueHref: "/builds.html", + queueHref: "#rebuild", refreshToken: jobqRollupToken, }), root, @@ -80,8 +80,7 @@ export function initJobqRollup() { // `rebuild_queue_changed` fires — a payload-less push trigger by // design, confirmed with atlas on the jobq-deletion tracker: the event // carries no `queue` field this page reads, same "something changed, -// go refetch" treatment builds.js already gives its own JobqGraph mount -// handle's .refresh(). +// go refetch" treatment builds.js gives the R3BU1LD QU3U3 tab's JobqGraph. export function applyRebuildQueueChanged() { jobqRollupToken += 1; initJobqRollup(); @@ -753,7 +752,7 @@ export function renderContainers(s) { } // ─── selection bar ─────────────────────────────────────────────────────────── -// Sticky-bottom strip; visible when >=1 agent selected on the SW4RM +// Sticky-bottom strip; visible when >=1 agent selected on the 4G3NTS // tab. See docs/web-ui/dashboard.md::Selection bar for the interaction model // and the per-action availability rules (disabled-with-tooltip for // actions that don't apply to the full selection). Actions POST per @@ -768,11 +767,12 @@ export function renderSelectionBar(containers) { if (!countSpan || !namesSpan || !actions) return; const selected = containers.filter((c) => selectionState.has(c.name)); - // Tab-gate: bar only renders on SW4RM (the only tab with agent + // Tab-gate: bar only renders on 4G3NTS (the only tab with agent // cards to cross-reference). Selection state lives in-memory and - // the bar reappears on return to SW4RM if still non-empty. - const onSwarmTab = (document.body.dataset.activeTab || "swarm") === "swarm"; - if (!selected.length || !onSwarmTab) { + // the bar reappears on return to 4G3NTS if still non-empty. + const onAgentsTab = + (document.body.dataset.activeTab || "agents") === "agents"; + if (!selected.length || !onAgentsTab) { bar.hidden = true; document.body.classList.remove("has-selection"); return; diff --git a/frontend/packages/dashboard/src/builds.css b/frontend/packages/dashboard/src/builds.css deleted file mode 100644 index a17106ae..00000000 --- a/frontend/packages/dashboard/src/builds.css +++ /dev/null @@ -1,139 +0,0 @@ -/* ─── /builds.html — BU1LDS page ──────────────────────────────────── - The build lifecycle hub: rebuild queue + live log, meta inputs, and - build log history on one page. - - Imports system-sections.css for rebuild queue + meta inputs + live-log - component styles; the build-logs-* rules for the BUILD L0GS list are - below. */ -@import "./system-sections.css"; -@import "@hive/shared/jobq-graph.css"; - -body.builds-shell { - margin: 0; - padding: 0; -} - -.builds-tabbar { - flex: 1; -} - -.builds-main { - padding: 1.2em 1.5em 2em; -} - -.builds-pane[hidden] { - display: none; -} - -.logs-toolbar { - display: flex; - gap: 0.6em; - align-items: center; - margin-bottom: 0.9em; - flex-wrap: wrap; -} - -/* ─── build-log list ────────────────────────────────────────────── - Scrollable list of log header rows; clicking a row expands full - stdout+stderr inline. */ -.build-logs-list { - list-style: none; - padding: 0; - margin: 0; - overflow-y: auto; - flex: 1 1 0; - min-height: 0; -} -.build-logs-item { - border-bottom: 1px solid var(--border); - padding: 0.15em 0; -} -.build-logs-row-btn { - display: flex; - flex-wrap: wrap; - gap: 0.4em; - align-items: baseline; - width: 100%; - background: none; - border: none; - color: var(--fg); - font-family: inherit; - font-size: 0.85em; - text-align: left; - padding: 0.25em 0.4em; - cursor: pointer; - border-radius: 3px; -} -.build-logs-row-btn:hover { - background: var(--bg-elev); -} -.build-logs-kind { - font-weight: 600; -} -.build-logs-age { - font-size: 0.88em; -} -.build-logs-cmdline { - color: var(--muted); - font-size: 0.82em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 40em; -} -.build-logs-detail { - padding: 0 0.4em 0.4em; -} -.build-logs-output { - margin: 0.3em 0 0; - padding: 0.4em 0.6em; - background: var(--crust); - color: var(--fg); - border: 1px solid var(--purple-dim); - font-size: 0.82em; - line-height: 1.4; - white-space: pre-wrap; - word-break: break-word; - overflow-x: auto; - max-height: 30em; - overflow-y: auto; -} -.build-logs-dl { - color: var(--muted); - font-size: 0.8em; - text-decoration: none; - border: 1px solid var(--purple-dim); - padding: 0.1em 0.4em; - border-radius: 2px; - margin-top: 0.3em; - display: none; -} -.build-logs-dl:not([hidden]) { - display: inline-block; -} -.build-logs-dl:hover { - color: var(--fg); - border-color: var(--purple-dim); -} - -.build-logs-live-badge { - margin-bottom: 0.4em; -} - -@keyframes live-pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.45; - } -} -.build-logs-live-badge.badge-running { - animation: live-pulse 1.4s ease-in-out infinite; -} - -.build-logs-runtime { - font-size: 0.85em; - color: var(--muted); -} diff --git a/frontend/packages/dashboard/src/builds.html b/frontend/packages/dashboard/src/builds.html deleted file mode 100644 index 90edfd96..00000000 --- a/frontend/packages/dashboard/src/builds.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - hyperhive // BU1LDS - - - - - - - - - - -
    - -
    -

    - pending + running rebuilds, meta-updates, and first-spawns. one runs - at a time; meta-update cascades nest under their parent. dedup: - re-enqueueing a still-queued op collapses into the existing entry. -

    -
    -

    loading…

    -
    - - -
    - - -
    -

    - select inputs to nix flake update in /meta/. - selected agents rebuild in sequence after the lock bump; the - submitting agent learns each outcome via the usual - rebuilt system event. -

    -
    -

    loading…

    -
    -
    - - -
    -

    - all-agents build log history. click a row to expand stdout + stderr. - live builds stream in real time. -

    -
    - -
    -

    loading…

    -
    -
    - - - - diff --git a/frontend/packages/dashboard/src/builds.js b/frontend/packages/dashboard/src/builds.js index 03e41e18..cd7d7857 100644 --- a/frontend/packages/dashboard/src/builds.js +++ b/frontend/packages/dashboard/src/builds.js @@ -1,23 +1,17 @@ -// BU1LDS page entry (/builds.html). The build lifecycle hub — rebuild -// queue, live build log, meta inputs, and build log history on one page. -// Three sub-tabs via (default = Rebuild Queue): -// - R3BU1LD QU3U3: pending + running rebuilds + live build log inline -// - M3T4 1NPUTS: nix flake input selector + meta-update trigger -// - BUILD L0GS: all-agents build log history (lazy-loaded on tab show) +// The build lifecycle tabs of /dashboard.html — rebuild queue, live build +// log, meta inputs, and build log history. Not an entry point: tabs.js +// imports it, owns the tab strip and the dashboard event stream, and calls +// the exports below. Three tabs, the panes' ids matching the tab ids: +// - rebuild (R3BU1LD QU3U3): pending + running rebuilds + live build log +// - meta (M3T4 1NPUTS): nix flake input selector + meta-update +// - buildlogs (BUILD L0GS): all-agents build log history, lazy-loaded +// the first time the tab is shown -import { - $, - fmtAgeSecs, - openStream, - openBuildLogStream, - initServerWarnings, -} from "./common.js"; +import { $, fmtAgeSecs, openBuildLogStream } from "./common.js"; import { el } from "@hive/shared/dom.js"; -import { bindAsyncForms } from "@hive/shared/forms.js"; import { themedConfirm } from "@hive/shared/modal.js"; import { h, render } from "preact"; import { fmtAgo, fmtDuration, truncate } from "./util.js"; -import "@hive/shared/hive-tab-strip.js"; import { JobqGraph } from "@hive/shared/jobq-graph.js"; // ─── derived state ─────────────────────────────────────────────────────────── @@ -174,7 +168,7 @@ function renderMetaInputs(s) { // the generic component's. // Split from `renderRebuildQueue` below: `replaceChildren()` only // belongs on the *first* render, to clear the static "loading…" -// placeholder `#rebuild-queue-section` starts with in `builds.html` — +// placeholder `#rebuild-queue-section` starts with in `dashboard.html` — // Preact doesn't know that placeholder is there, so a later render // call must NOT repeat `replaceChildren()` (it would wipe Preact's own // tracked children out from under its diffing rather than let it @@ -196,7 +190,6 @@ function renderRebuildQueue() { onUpdate: (nodes) => { jobqNodes = nodes || []; renderRebuildLiveLog(); - updateRebuildCount(); }, onCancel: async (id) => { const node = jobqNodes.find((n) => n.id === id); @@ -384,33 +377,18 @@ function renderRebuildLiveLog() { } // ─── rebuild-queue count pill ───────────────────────────────────────────────── -function updateRebuildCount() { - const pill = $("builds-tab-count-rebuild"); - if (!pill) return; - // Pending/Running/Finishing = in flight (Finishing = own work done, a - // sub-node still running — still counts). Root nodes only: each is one - // queue entry. - const n = jobqNodes.filter( +// The queue's in-flight count, for the R3BU1LD QU3U3 tab's pill (tabs.js +// reads it on its refresh tick). Pending/Running/Finishing = in flight +// (Finishing = own work done, a sub-node still running — still counts). +// Root nodes only: each is one queue entry. +export function rebuildCount() { + return jobqNodes.filter( (n) => n.parent == null && (n.state === "Pending" || n.state === "Running" || n.state === "Finishing"), ).length; - if (n > 0) { - pill.textContent = String(n); - pill.hidden = false; - } else { - pill.hidden = true; - } -} - -// ─── render-all (cold load + any full re-render) ────────────────────────────── -function renderAll() { - renderMetaInputs({ meta_inputs: metaInputsState }); - // Rebuild-queue rendering + the count-pill/live-log it drives all happen - // off JobqGraph's own onUpdate callback (see mountRebuildQueue) — - // nothing to render here directly. } // ─── BUILD L0GS tab ─────────────────────────────────────────────────────────── @@ -419,10 +397,7 @@ function renderAll() { // 2s) when rebuild_queue_changed fires while the BUILD L0GS tab is active. // Deep-link: ?id=N#buildlogs auto-expands the target row. -const buildList = $("build-list"); -const buildRefresh = $("build-refresh"); let buildLogsLoaded = false; -let buildTabs; // set in init function fmtTs(unixSecs) { if (!unixSecs) return ""; @@ -431,6 +406,7 @@ function fmtTs(unixSecs) { } async function fetchBuild() { + const buildList = $("build-list"); if (!buildList) return; buildList.replaceChildren(); buildList.append(el("p", { class: "meta" }, "loading…")); @@ -612,93 +588,54 @@ async function fetchBuild() { } } -if (buildRefresh) buildRefresh.addEventListener("click", fetchBuild); +// ─── entry points for tabs.js ───────────────────────────────────────────────── +// Everything below is called by tabs.js, which owns the tab strip, the +// `/api/state` snapshot and the dashboard event stream. -// ─── SSE handlers ───────────────────────────────────────────────────────────── -let buildRefreshTimer = null; -const SSE_HANDLERS = { - rebuild_queue_changed() { - // No `ev.queue` payload read anymore — this event still carries its own - // queue snapshot on the wire (tabs.js/SW4RM still consumes it for the - // swarm badges, untouched by this), this page just treats it as a - // refetch trigger. `JobqGraph` owns the actual fetch. - jobqGraphToken += 1; - renderRebuildQueue(); - // Auto-refresh build log list when the queue changes and BUILD L0GS is active. - if (buildTabs && buildTabs.active() === "buildlogs") { - if (buildRefreshTimer) clearTimeout(buildRefreshTimer); - buildRefreshTimer = setTimeout(fetchBuild, 2000); - } - }, - meta_inputs_changed(ev) { - metaInputsState = (ev.inputs || []).slice(); - renderMetaInputs({ meta_inputs: metaInputsState }); - }, - meta_update_running(ev) { - metaUpdateRunning = !!ev.running; - renderMetaInputs({ meta_inputs: metaInputsState }); - }, -}; - -// ─── boot ───────────────────────────────────────────────────────────────────── -async function refreshState() { - try { - const resp = await fetch("/api/state"); - if (resp.ok) syncFromSnapshot(await resp.json()); - } catch { - // best-effort - } - renderAll(); -} - -async function init() { - initServerWarnings(); - bindAsyncForms(() => refreshState()); - // Self-fetches on mount (JobqGraph's own effect) — no explicit initial - // fetch needed here, unlike meta inputs below. +/** Mount the rebuild queue and wire the build-log refresh button. Called + * once, after the tab strip exists. The queue self-fetches on mount + * (JobqGraph's own effect), unlike the meta inputs, which wait for the + * snapshot. */ +export function initBuilds() { + const refresh = $("build-refresh"); + if (refresh) refresh.addEventListener("click", fetchBuild); mountRebuildQueue(); +} - buildTabs = document.getElementById("builds-tabbar").configure({ - tabs: [ - { - id: "rebuild", - label: "R3BU1LD QU3U3", - badgeId: "builds-tab-count-rebuild", - }, - { id: "meta", label: "M3T4 1NPUTS" }, - { id: "buildlogs", label: "BUILD L0GS" }, - ], - defaultId: "rebuild", - onShow: (id) => { - if (id === "buildlogs" && !buildLogsLoaded) { - buildLogsLoaded = true; - fetchBuild(); - } - }, - }); +/** The meta inputs, from an `/api/state` snapshot. */ +export function syncBuildsFromSnapshot(s) { + syncFromSnapshot(s); + renderMetaInputs({ meta_inputs: metaInputsState }); +} - await refreshState(); - - // kinds= matches SSE_HANDLERS below verbatim — narrows this from all - // 13 wire kinds down to the 3 this page acts on. This page was one - // of 4 unfiltered `/api/dashboard/stream` subscribers before this - // (subscription discipline, part 1 of the dashboard-event-stream- - // split issue). - const es = openStream( - "/api/dashboard/stream?kinds=rebuild_queue_changed,meta_inputs_changed,meta_update_running", - ); - if (es) { - es.onmessage = (e) => { - let ev; - try { - ev = JSON.parse(e.data); - } catch { - return; - } - const h = SSE_HANDLERS[ev.kind]; - if (h) h(ev); - }; +/** A tab was shown: BUILD L0GS loads its list the first time. */ +export function onBuildsTabShown(tab) { + if (tab === "buildlogs" && !buildLogsLoaded) { + buildLogsLoaded = true; + fetchBuild(); } } -init(); +let buildRefreshTimer = null; + +/** `rebuild_queue_changed`: a payload-less refetch trigger — `JobqGraph` + * owns the actual fetch. */ +export function onRebuildQueueChanged() { + jobqGraphToken += 1; + renderRebuildQueue(); + // Auto-refresh the build log list while BUILD L0GS is the active tab. + if (document.body.dataset.activeTab === "buildlogs") { + if (buildRefreshTimer) clearTimeout(buildRefreshTimer); + buildRefreshTimer = setTimeout(fetchBuild, 2000); + } +} + +export function onMetaInputsChanged(ev) { + metaInputsState = (ev.inputs || []).slice(); + renderMetaInputs({ meta_inputs: metaInputsState }); +} + +export function onMetaUpdateRunning(ev) { + metaUpdateRunning = !!ev.running; + renderMetaInputs({ meta_inputs: metaInputsState }); +} diff --git a/frontend/packages/dashboard/src/common.css b/frontend/packages/dashboard/src/common.css index 5d5d57ec..307bc90e 100644 --- a/frontend/packages/dashboard/src/common.css +++ b/frontend/packages/dashboard/src/common.css @@ -84,7 +84,7 @@ code { Semantic colour variants only — shape comes from `hive-pill-sm` (shared with the agent UI's pill/chip family, @hive/shared/pill.css), carried directly in the markup alongside each of these classnames - (swarm.js/core.js/builds.js). Used on container rows and build-log + (agents.js/core.js/builds.js). Used on container rows and build-log rows. */ .badge-warn { color: var(--amber); diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index 9f7baeab..c2b55c29 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -292,7 +292,7 @@ export function openStream(url) { } // Stream a build log into a
    , returning the EventSource. Used by the
    -// BU1LDS page's BUILD L0GS tab. Appends `stdout_append` then
    +// BUILD L0GS tab. Appends `stdout_append` then
     // `stderr_append` (one `--- stderr ---` separator) frames from
     // `GET /api/build-logs/id/{id}/stream`; auto-scrolls to the bottom unless the operator scrolled up; on the terminal `done` frame
     // closes the stream and calls `onDone(status)`; on a non-transient error
    diff --git a/frontend/packages/dashboard/src/core.css b/frontend/packages/dashboard/src/core.css
    index be613a1d..f618308b 100644
    --- a/frontend/packages/dashboard/src/core.css
    +++ b/frontend/packages/dashboard/src/core.css
    @@ -1,7 +1,6 @@
     /* ─── /core.html — C0R3 page ───────────────────────────────────────
        Kept state (tombstones) and stale permission entries. Section rules
    -   imported from system-sections.css (shared with dashboard.css +
    -   builds.css). */
    +   imported from system-sections.css (shared with dashboard.css). */
     @import "./system-sections.css";
     
     body.core-shell {
    diff --git a/frontend/packages/dashboard/src/core.html b/frontend/packages/dashboard/src/core.html
    index 8728f020..95ac4964 100644
    --- a/frontend/packages/dashboard/src/core.html
    +++ b/frontend/packages/dashboard/src/core.html
    @@ -14,7 +14,8 @@
         
    +           The build lifecycle is three tabs here (builds.js): the rebuild
    +           queue with its live log, the meta flake inputs, and the build log
    +           history. -->
    +        
    +          ◆ R3BU1LD QU3U3 ◆
    +          
    +        
    +        
    +          ◆ M3T4 1NPUTS ◆
    +        
    +        
    +          ◆ BUILD L0GS ◆
    +        
             
             
     
             
    +           under 4G3NTS. See tab-pane-agents.) -->
     
             
         
         
    - + — the tab label 4G3NTS already says it. --> - + + + + + + + +