From ee5c057d8f2a254b68ca9348dfff5f18fd85214d Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 14 Oct 2026 20:18:16 +0200 Subject: [PATCH] dashboard: the build pages are tabs of the dashboard The build lifecycle used to be its own page with three sub-tabs. They are now dashboard tabs (rebuild, meta, buildlogs) beside SW4RM and P3RM1SS10NS: builds.js is a module tabs.js imports, so the dashboard's one tab strip, /api/state snapshot and event stream (plus the meta_inputs_changed and meta_update_running kinds) serve them, and the page, its bundle entry, stylesheet and home tile are gone. The SW4RM banner's "view queue" link points at the #rebuild tab, and the rebuild tab carries a count pill of the rebuilds in flight. An empty tab-count pill drew a "0": its display beat the UA [hidden] rule. --- docs/scheduler/jobq.md | 2 +- docs/web-ui/README.md | 12 +- docs/web-ui/dashboard.md | 69 +++---- docs/web-ui/shape.md | 2 +- frontend/README.md | 2 +- frontend/packages/dashboard/build.mjs | 7 +- frontend/packages/dashboard/src/builds.css | 139 ------------- frontend/packages/dashboard/src/builds.html | 101 ---------- frontend/packages/dashboard/src/builds.js | 185 ++++++------------ frontend/packages/dashboard/src/common.js | 2 +- frontend/packages/dashboard/src/core.css | 3 +- frontend/packages/dashboard/src/core.html | 3 +- frontend/packages/dashboard/src/core.js | 3 +- frontend/packages/dashboard/src/dashboard.css | 123 ++++++++++++ .../packages/dashboard/src/dashboard.html | 111 ++++++++++- frontend/packages/dashboard/src/index.html | 12 +- frontend/packages/dashboard/src/swarm.js | 5 +- .../dashboard/src/system-sections.css | 10 +- frontend/packages/dashboard/src/tabs.js | 37 +++- frontend/packages/shared/src/chrome.css | 6 +- .../shared/src/jobq-rollup/JobqRollup.tsx | 2 +- .../shared/src/tabs/hive-tab-strip.js | 4 +- 22 files changed, 385 insertions(+), 455 deletions(-) delete mode 100644 frontend/packages/dashboard/src/builds.css delete mode 100644 frontend/packages/dashboard/src/builds.html 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..242e2706 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 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 @@ -52,7 +54,7 @@ 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..6578f458 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 (`#swarm`, `#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**: `◆ SW4RM ◆`, `◆ 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 SW4RM + (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 @@ -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 SW4RM 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 @@ -580,7 +583,7 @@ 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` @@ -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..41ef2fa3 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. diff --git a/frontend/README.md b/frontend/README.md index 308451ff..9717204c 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`, `swarm.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/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.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 ◆
+        
         
         
         
 
-        
+        
+        
+
+        
+        
+
+        
+