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.
This commit is contained in:
iris 2026-06-06 08:39:30 +02:00 committed by mara
commit e70584b632
14 changed files with 88 additions and 38 deletions

View file

@ -1,11 +1,22 @@
# CSS custom properties (theme variables)
All colour variables are declared **once** in
`frontend/packages/shared/src/base.css` under `:root`.
`frontend/packages/shared/src/theme.css` under `:root`.
Per-page stylesheets (`common.css`, `dashboard.css`, `flow.css`, `logs.css`, `agent.css`) must reference these
names and must **not** redeclare them.
names and must **not** redeclare them. (`base.css` holds only the
shared `body` typography — it references the palette but no longer
defines it.)
## Palette (`base.css`)
`theme.css` is deliberately a **standalone** stylesheet, not `@import`ed
into the page bundles: each package re-exports it (`src/theme.css`
`@import "@hive/shared/theme.css"`) so esbuild emits its own
`dist/static/theme.css`, and every page links it **first**
(`<link rel="stylesheet" href=".../theme.css">`) ahead of the page CSS.
Because the palette lives in its own output file, a theme swap — e.g. a
stylix-generated variant carrying the same variable names — can replace
just that one file without rebuilding the rest of the frontend bundle.
## Palette (`theme.css`)
| Variable | Hex | Catppuccin Mocha | Use |
|---|---|---|---|

View file

@ -43,15 +43,19 @@ await build({
logLevel: 'info',
});
// Bundle the CSS — the @import lines pull in shared/base.css and
// shared/terminal.css from the @hive/shared workspace dep.
await build({
entryPoints: [src('agent.css')],
outfile: staticDir('agent.css'),
bundle: true,
loader: { '.css': 'css' },
logLevel: 'info',
});
// Bundle the CSS. `theme.css` re-exports the standalone Catppuccin
// palette (kept its own output file so a theme swap replaces only it,
// no bundle rebuild); `agent.css`'s @import lines pull in shared
// base.css + terminal.css from the @hive/shared workspace dep.
for (const entry of ['theme.css', 'agent.css']) {
await build({
entryPoints: [src(entry)],
outfile: staticDir(entry),
bundle: true,
loader: { '.css': 'css' },
logLevel: 'info',
});
}
for (const html of ['index.html', 'stats.html', 'screen.html']) {
copyFileSync(src(html), dist(html));

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive agent</title>
<link rel="icon" type="image/svg+xml" href="icon">
<link rel="stylesheet" href="static/theme.css">
<link rel="stylesheet" href="static/agent.css">
</head>
<body class="agent-shell">

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>screen</title>
<link rel="icon" type="image/svg+xml" href="icon">
<link rel="stylesheet" href="static/theme.css">
<link rel="stylesheet" href="static/agent.css">
</head>
<body class="screen-shell">

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive agent — stats</title>
<link rel="icon" type="image/svg+xml" href="icon">
<link rel="stylesheet" href="static/theme.css">
<link rel="stylesheet" href="static/agent.css">
</head>
<body>

View file

@ -0,0 +1,6 @@
/* Standalone theme bundle re-exports the shared Catppuccin palette so
esbuild emits it as its own `dist/static/theme.css`, linked first by
every agent page. Kept separate from the page bundles so a theme swap
(e.g. stylix) replaces only this file. See @hive/shared/theme.css +
docs/web-ui/css-vars.md. */
@import "@hive/shared/theme.css";

View file

@ -77,10 +77,11 @@ await build({
});
// Bundle CSS — one entry per page. esbuild resolves @import including
// the package re-exports from @hive/shared. Each page loads common.css
// (shared typography, badges, buttons, inbox, side panel) plus its own
// page-specific bundle.
for (const entry of ['common.css', 'dashboard.css', 'flow.css', 'logs.css']) {
// 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),

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive // FL0W</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/static/theme.css">
<link rel="stylesheet" href="/static/common.css">
<link rel="stylesheet" href="/static/flow.css">
</head>

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive // h1ve-c0re</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/static/theme.css">
<link rel="stylesheet" href="/static/common.css">
<link rel="stylesheet" href="/static/dashboard.css">
</head>

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive // LOGS</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/static/theme.css">
<link rel="stylesheet" href="/static/common.css">
<link rel="stylesheet" href="/static/logs.css">
</head>

View file

@ -0,0 +1,6 @@
/* Standalone theme bundle re-exports the shared Catppuccin palette so
esbuild emits it as its own `dist/static/theme.css`, linked first by
every dashboard page. Kept separate from the page bundles so a theme
swap (e.g. stylix) replaces only this file. See @hive/shared/theme.css
+ docs/web-ui/css-vars.md. */
@import "@hive/shared/theme.css";

View file

@ -8,6 +8,7 @@
"exports": {
".": "./src/index.js",
"./terminal.js": "./src/terminal.js",
"./theme.css": "./src/theme.css",
"./base.css": "./src/base.css",
"./terminal.css": "./src/terminal.css"
},

View file

@ -1,25 +1,10 @@
/* Base palette + typography shared by the hive-c0re dashboard and the
hive-ag3nt web UI. Catppuccin Mocha. Per-page stylesheets append on
top of this and must NOT redeclare the colour variables the whole
point of pulling them out is one source of truth. */
:root {
--bg: #1e1e2e; /* base */
--bg-elev: #181825; /* mantle */
--crust: #11111b; /* crust — terminal background */
--fg: #cdd6f4; /* text */
--muted: #7f849c; /* overlay1 */
--purple: #cba6f7; /* mauve */
--purple-dim: #45475a;/* surface1 */
--cyan: #89dceb; /* sky */
--blue: #89b4fa; /* blue */
--pink: #f5c2e7; /* pink */
--amber: #fab387; /* peach */
--yellow: #f9e2af; /* yellow */
--green: #a6e3a1; /* green */
--red: #f38ba8; /* red */
--border: #313244; /* surface0 */
--subtext0: #a6adc8; /* subtext0 — secondary/toolbar text, dimmer than --fg but lighter than --muted */
}
/* Base typography shared by the hive-c0re dashboard and the hive-ag3nt
web UI. The colour variables it references (`--bg`, `--fg`, ) live in
the standalone `theme.css` (Catppuccin Mocha), linked separately by
every page BEFORE this file so the `:root` vars resolve see
`theme.css` + docs/web-ui/css-vars.md. Per-page stylesheets append on
top of this and must NOT redeclare the colour variables; `theme.css`
is the one source of truth. */
body {
background: var(--bg);
color: var(--fg);

View file

@ -0,0 +1,30 @@
/* Theme colour variables (Catppuccin Mocha) the single source of
truth for the hive UI palette. Kept in a standalone file, linked as
its own `<link rel="stylesheet" href=".../theme.css">` by every page
BEFORE the page CSS, so the `:root` vars are defined for everything
that references them (base typography, terminal, dashboard, agent).
Why standalone rather than `@import`ed into the page bundles: esbuild
inlines `@import` at build time, which would bake the palette into
every CSS bundle. Emitting `theme.css` as its own output file lets a
theme swap (e.g. a stylix-generated variant carrying the same var
names) replace just this one file without rebuilding the rest of the
frontend. See docs/web-ui/css-vars.md. */
:root {
--bg: #1e1e2e; /* base */
--bg-elev: #181825; /* mantle */
--crust: #11111b; /* crust — terminal background */
--fg: #cdd6f4; /* text */
--muted: #7f849c; /* overlay1 */
--purple: #cba6f7; /* mauve */
--purple-dim: #45475a;/* surface1 */
--cyan: #89dceb; /* sky */
--blue: #89b4fa; /* blue */
--pink: #f5c2e7; /* pink */
--amber: #fab387; /* peach */
--yellow: #f9e2af; /* yellow */
--green: #a6e3a1; /* green */
--red: #f38ba8; /* red */
--border: #313244; /* surface0 */
--subtext0: #a6adc8; /* subtext0 — secondary/toolbar text, dimmer than --fg but lighter than --muted */
}