frontend: cut over Rust binaries to ServeDir; delete legacy assets

Phase 4 of #273 — the actual switch. Both axum routers now serve
their static surface via `tower_http::services::ServeDir` mounted
as a fallback service, reading the dist path from `HIVE_STATIC_DIR`
(set by Phase 3's NixOS module wiring).

Deletes:
- `hive-c0re/assets/{index.html, app.js, dashboard.css}`
- `hive-ag3nt/assets/{index.html, app.js, agent.css, stats.html,
   stats.js, screen.html}`
- The whole `hive-fr0nt/` crate (workspace member dropped, both
  hive-c0re and hive-ag3nt drop their `hive-fr0nt.workspace = true`
  dep). Its contents now live as `@hive/shared` under
  `frontend/packages/shared/`.

Rust changes:
- `hive-c0re/src/dashboard.rs`: remove `serve_index`, `serve_css`,
  `serve_app_js`, `serve_shared_js`, `serve_marked_js`,
  `serve_favicon` (all six `include_str!` handlers); replace their
  routes with a single `.fallback_service(ServeDir::new(static_dir))`
  on the router. Fail closed (anyhow::bail) if `HIVE_STATIC_DIR` is
  unset or not a directory at startup.
- `hive-ag3nt/src/web_ui.rs`: remove `serve_index`, `serve_css`,
  `serve_app_js`, `serve_shared_js`, `serve_marked_js`,
  `serve_stats`, `serve_stats_js`, `serve_screen`; same
  `fallback_service` pattern. `serve_icon` stays (consumes
  `/etc/hyperhive/icon.svg` + `branding/hyperhive.svg` fallback,
  neither of which lives under the frontend dist).
- `AgentLink` URLs for stats/screen switched from `/stats` / `/screen`
  to `/stats.html` / `/screen.html` since ServeDir doesn't auto-
  append the extension and the on-disk filename is the natural URL
  post-cutover.
- `Cargo.toml` (workspace): drop `hive-fr0nt` member + workspace
  dep, add `tower-http = { version = "0.6", features = ["fs"] }`.
- `hive-c0re/Cargo.toml` + `hive-ag3nt/Cargo.toml`: drop the
  `hive-fr0nt.workspace = true` dep, add `tower-http.workspace =
  true`.

Docs updated:
- `CLAUDE.md`: file map reflects `frontend/` (was `hive-fr0nt/` +
  `assets/`) and the ServeDir/HIVE_STATIC_DIR shape.
- `docs/web-ui.md` 'Shape (shared by both)' section: describes the
  ServeDir fallback + bundled-by-esbuild surface, no more
  `include_str!` references.
- `docs/terminal-rendering.md`: src paths point at
  `frontend/packages/{agent,shared}/src/`; marked is the npm dep,
  not vendored UMD.

Validation:
- `cargo check --workspace` — clean (5 warnings, all pre-existing
  in `rebuild_queue.rs`, none on changed files).
- `cargo clippy --workspace --all-targets` — clean (11 warnings,
  same pre-existing source).
- `cd frontend && npm run build` from the prior commit's lockfile
  produces the dist directories the new routers consume:
    dashboard: `dist/{index.html, static/{app.js, dashboard.css}}`
    agent:     `dist/{index.html, stats.html, screen.html,
                       static/{app.js, stats.js, agent.css}}`
  (favicon.svg lands in dashboard/ during the nix build —
  `nix/frontend.nix` install phase copies `branding/hyperhive.svg`
  there, since it's outside the npm tree.)

Refs #273.
This commit is contained in:
iris 2026-05-23 13:21:37 +02:00 committed by Mara
parent 2ecf15bb6f
commit 229c4292e9
24 changed files with 143 additions and 10122 deletions

View file

@ -2,11 +2,11 @@
Snapshot of how the per-agent web UI's live pane renders each
event kind today. Source of truth lives in
`hive-ag3nt/assets/app.js` (`renderStream`, `fmtToolUse`,
`frontend/packages/agent/src/app.js` (`renderStream`, `fmtToolUse`,
`renderRichToolUse`, `renderToolResult`, `renderTaskEvent`,
`mdNode`, `detailsOpenMd`, `fmtArgsGeneric`) +
`hive-fr0nt/assets/terminal.css` (the shared `.live .<class>`
styling) + `hive-fr0nt/assets/marked.umd.js` (markdown).
`frontend/packages/shared/src/terminal.css` (the shared
`.live .<class>` styling) + the `marked` npm package (markdown).
## Layout contract
@ -82,8 +82,8 @@ parent's negative pull.
## Markdown
`mdNode(text)` wraps `window.marked.parse(text)` (vendored
v4.0.2 UMD via `hive-fr0nt::MARKED_JS`) in a `<div
`mdNode(text)` wraps `marked.parse(text)` (the `marked` v4.x npm
dep, bundled by esbuild into the page's `app.js`) in a `<div
class="md">`. CSS in `terminal.css` scopes paragraph / code /
list / blockquote / link styling under `.live .row .md` so
the markdown body doesn't bleed into the row's own

View file

@ -7,22 +7,32 @@ and the per-agent UIs (manager on :8000, sub-agents on a hashed
## Shape (shared by both)
- `GET /``assets/index.html` (placeholders for state-driven
sections, shipped via `include_str!` so the binary has no runtime
file dependency).
- `GET /static/*.css` + `GET /static/*.js` → static assets. Both
pages prepend `hive_fr0nt::BASE_CSS` + `TERMINAL_CSS` to their
per-page stylesheet, and `GET /static/hive-fr0nt.js` serves the
shared `window.HiveTerminal.create` runtime. The dashboard's
- `GET /``index.html` from the bundled frontend dist (see
`frontend/`). Both binaries' routers declare their dynamic
endpoints first and then `fallback_service(ServeDir::new(...))`
pointed at `HIVE_STATIC_DIR` — anything not matched by an API or
action route is served from the dist. Dashboard dist lives at
`${frontend}/dashboard`; per-agent dist is the merged
`hyperhive.frontend.mergedDist` (default agent dist + per-agent
`extraFiles` overlay).
- `GET /static/*` → bundled CSS + JS produced by esbuild
(`frontend/packages/{dashboard,agent}/build.mjs`). Both pages
pull the shared terminal pane + Catppuccin palette + typography
from `@hive/shared` (was `hive-fr0nt`); the CSS bundle inlines
`base.css` + `terminal.css` via esbuild's `@import` resolution.
`terminal.js` exports `{ create, linkify }` as ES module
members (no more `window.HiveTerminal` global outside the
back-compat shim the IIFE bodies still use). The dashboard's
`#msgflow` and the per-agent `#live` log are both backed by
this terminal — sticky-bottom auto-scroll, "↓ N new" pill,
history backfill, SSE plumbing all live there. Each page
registers a kind→renderer map; unknown kinds fall through to
a JSON-dump note row. Bare `http(s)://` URLs in row text are
turned into clickable new-tab links by `HiveTerminal.linkify`
(text-node based, no `innerHTML` — XSS-safe); markdown bodies
get the same treatment via `marked`'s autolink, with the
rendered `<a>`s rewritten to `target="_blank"` (issue #233).
turned into clickable new-tab links by `linkify` (text-node
based, no `innerHTML` — XSS-safe); markdown bodies get the
same treatment via `marked`'s autolink (npm dep, replacing the
vendored UMD bundle), with the rendered `<a>`s rewritten to
`target="_blank"` (issue #233).
- `GET /api/state` → JSON snapshot the JS app renders into the
DOM. Includes a top-level `seq` (the dashboard event channel's
high-water mark at the moment the snapshot was assembled);