hyperhive/frontend/packages/dashboard/build.mjs
iris e70584b632 feat(frontend): split theme CSS vars into a standalone theme.css
Phase 1 of stylix integration (#1457): extract the Catppuccin palette
into a dedicated, separately-linked stylesheet so a theme swap can
replace just that file without rebuilding the rest of the frontend.

- shared/src/theme.css (new): the `:root` palette, moved out of
  base.css (which now holds only the body typography it references).
- shared/package.json: export `./theme.css`.
- dashboard/src/theme.css + agent/src/theme.css (new): one-line
  re-exports of @hive/shared/theme.css so each package's esbuild emits
  its own standalone `dist/static/theme.css` (palette is NOT inlined
  into the page bundles).
- both build.mjs: add theme.css to the CSS build list.
- every page (dashboard index/flow/logs, agent index/stats/screen):
  link `theme.css` first, ahead of the page CSS, so the `:root` vars
  resolve for everything.
- docs/web-ui/css-vars.md: document the split + the no-rebuild rationale.

Behaviour-neutral — same colours, just relocated. Verified both
`npm run build` outputs: theme.css emits standalone (383b) with the
palette; no `--*` palette defs duplicated into common.css/agent.css.

Phase 2 (nix derivation that swaps theme.css from stylix colours) is a
follow-up; touches nix/frontend.nix, coordinating with damocles.

Part of #1457.
2026-06-06 08:46:45 +02:00

98 lines
4.2 KiB
JavaScript

// esbuild build for @hive/dashboard. Output layout (`dist/`):
//
// dist/index.html served by the Rust router at GET /
// dist/flow.html served at GET /flow.html
// dist/logs.html served at GET /logs.html
// dist/static/tabs.js /index.html entry — tab renderers +
// tab routing + refreshState
// dist/static/flow.js /flow.html entry — broker terminal +
// operator inbox + @-mention composer
// dist/static/logs.js /logs.html entry — build/agent/system
// log viewer sub-tabs
// dist/static/{tabs,flow,logs}.js.map source map siblings
// dist/static/common.css loaded by every page (@hive/shared
// imports + shared typography/badges/
// buttons/inbox/side-panel)
// dist/static/dashboard.css /index.html only (dashboard-specific)
// dist/static/flow.css /flow.html only (flow chrome + composer)
// dist/static/logs.css /logs.html only (log viewer sub-tabs)
//
// All three JS entries bundle `./common.js` (DOM helpers, Panel singleton,
// NOTIF, path linkification) independently — esbuild inlines the shared
// module into each bundle rather than emitting a shared chunk (no
// `splitting: true`). The Rust binary mounts `dist/` as a
// `tower_http::ServeDir` fallback; the layout above keeps every URL
// the HTML files reference reachable without rewriting paths in the
// HTML.
import { build } from 'esbuild';
import { mkdirSync, copyFileSync, rmSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const src = (p) => resolve(here, 'src', p);
const dist = (p) => resolve(here, 'dist', p);
const staticDir = (p) => resolve(here, 'dist', 'static', p);
rmSync(dist(''), { recursive: true, force: true });
mkdirSync(staticDir(''), { recursive: true });
// Bundle all three JS entries. ES-module output, browser target, no minify
// (line-aligned source aids debugging; minification belongs in a later
// follow-up once asset sizes warrant it). esbuild writes each entry
// to `static/<name>.js` based on the entryPoint basename.
await build({
entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js')],
outdir: staticDir(''),
bundle: true,
format: 'esm',
platform: 'browser',
target: ['es2022'],
sourcemap: true,
logLevel: 'info',
});
// Stream-worker entry (#448). Lives in a separate bundle: SharedWorker
// scripts run in a different global (`self` is the worker scope, no
// `window`) so they can't be inlined into tabs.js / flow.js. Output is
// at `static/stream-worker.js`; common.js's `openStream` references
// `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'`
// matches the classic-script load (`new SharedWorker(url, name)` with
// no `{ type: 'module' }`); Firefox is the #448 target and module
// SharedWorker support there is patchy, so keeping the worker as a
// classic script + IIFE bundle is the compatible default. argus nit
// on #453: if a future contributor adds an `import` to this bundle,
// the IIFE format will surface it as a build error rather than
// silently shipping broken code.
await build({
entryPoints: [src('stream-worker.js')],
outdir: staticDir(''),
bundle: true,
format: 'iife',
platform: 'browser',
target: ['es2022'],
sourcemap: true,
logLevel: 'info',
});
// Bundle CSS — one entry per page. esbuild resolves @import including
// the package re-exports from @hive/shared. Each page loads theme.css
// (the standalone Catppuccin palette — kept its own file so a theme
// swap replaces only it) + common.css (shared typography, badges,
// buttons, inbox, side panel) plus its own page-specific bundle.
for (const entry of ['theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css']) {
await build({
entryPoints: [src(entry)],
outfile: staticDir(entry),
bundle: true,
loader: { '.css': 'css' },
logLevel: 'info',
});
}
for (const html of ['index.html', 'flow.html', 'logs.html']) {
copyFileSync(src(html), dist(html));
}
console.log('dashboard build ok →', dist(''));