The hive convention is prose, not tracker tags, in code, but it was
enforced only at review time — three PRs this session needed
request-changes purely for stray tags in new comments.
Add scripts/check-issue-refs.sh: scans tracked source (rust, nix, js,
ts, css, html; markdown exempt) for a hash followed by an issue number
and emits a CI warning annotation per hit. The pattern is a hash, 2-5
digits, then a non-hex char or end-of-line, so it skips CSS hex
colours (letter-bearing or six/eight-digit) while catching tags; a
pure-numeric short hex is the only residual false positive (dodge with
the six-digit form).
Wire it into the CI workflow as a fast pre-check before nix flake
check. It runs in warn mode (exit 0) so it does not block while the
legacy backlog is cleaned up; the script takes a warn|deny arg so the
later flip to a hard gate is a one-word change, not a rewrite — the
same rollout shape as tightening a clippy lint.
The dashboard tabbar was the styling outlier: a folder-tab metaphor
(top-rounded corners, transparent side borders overlapping the strip
border, and a box-shadow lift on the active tab) while every standalone
page's sub-tabs use the flat shared .hive-tab pill. Restyle .tabbar .tab
to the same pill family — drop the lift shadow, folder border and
top-only radius; active tab now uses the filled var(--border) background
like .hive-tab--active.
Kept a touch more weight than the bare sub-tab (larger 0.92em font, bold
labels, count pills) since this is the dashboard's primary nav, per the
operator's 'less fancy but not too minimal' steer. CSS-only, no markup
or behaviour change.
Part of the tracker-tag cleanup: the hive convention is prose, not
issue-tracker tags, in code. Reword the 21 tags in the nix tree
(flake.nix + the hive-c0re/ci/gateway/network modules) to describe
the thing they pointed at, preserving the context without the tag.
Comment-only — no eval or logic change. Validated with nix fmt
(no reformatting) and nix flake check --no-build (all checks
evaluate clean); the full build check was skipped locally because
the shared remote builder is degraded, so CI will exercise the
build derivations once the runner recovers.
The standalone pages (/flow, /logs, /stats, /settings) all share the same
sticky back-link header, but its CSS lived in the dashboard's common.css
under the misnomer `.logs-*` (the comment even noted it was used by more
than logs). Promote it to a first-class shared component.
- new @hive/shared/chrome.css with neutral `.page-header` / `.page-back` /
`.page-title` (cohering with the `.page-content` gutter wrapper);
exported from the shared package and @imported into common.css next to
base/terminal/tabs.css.
- rename the four pages' header markup `.logs-*` → `.page-*`.
- drop the old `.logs-*` chrome block from common.css; refresh the stale
doc comments in settings.css / stats.css / dashboard.css.
CSS-only, behaviour/visual-neutral (same rules, new names + home). The
dashboard keeps its own richer sticky `.dashboard-chrome`; unifying the
per-agent UI's `.stats-nav` chrome is a larger follow-up, deferred.
Part of #1464 step 3.
Follow-up from #1532. Previously `body.dashboard-shell` / `body.home-shell`
carried the 1.5em horizontal gutter, and full-width chrome broke out of it
with negative margins (`.dashboard-chrome { margin: 0 -1.5em }`, the
`#server-warnings { margin: 0 -1.5em }` override).
Invert it: `<body>` is now full-bleed, and the padded page content lives in
a single inner `.page-content` wrapper that carries the gutter. The
server-warnings banner, the sticky tab strip, and the footer then span the
full width for free — so the `-1.5em` breakout hacks are gone.
- `.page-content { padding: 0 1.5em }` is a shared primitive in common.css
(the dashboard + H0M3 both opt in by wrapping their content; FL0W / L0GS /
ST4TS / S3TT1NGS stay full-bleed with their own `.<page>-main` padding).
- dashboard.html / index.html wrap their content in `.page-content`.
- dashboard.css / home.css: drop the body horizontal gutter (keep
`padding-bottom` for foot breathing room); `.dashboard-chrome` margin
`0 -1.5em 1em` → `0 0 1em`.
- common.css: drop the `#server-warnings` breakout override.
Behaviour/visual-neutral by intent (content gutter unchanged; chrome +
banner already rendered full-width via the old breakout). Worth a gui
screenshot-diff to confirm — esp. the now-full-width footer divider.
Last fold-in of #1464 step 2.
Extracts the hive-wide turn-stats rollup out of the dashboard tab strip
into a standalone /stats.html page, reached from the H0M3 hub — same
minimal-chrome pattern as /flow.html and /logs.html. The dashboard tab
strip is now purely operational.
- new stats.{html,css,js}; stats.js holds the moved render JS and
fetches /api/stats-hive on load + window change.
- migrate the window selector (#hive-stats-windows) from a bespoke
data-w/.active toggle to the shared createTabStrip — now hash-routed
(#1h / #24h / …) and deep-linkable, matching the per-agent /stats page.
- drop the ST4TS tab + pane from dashboard.html and the hive-stats render
block + the stats->refreshHiveStats lazy-load from tabs.js.
- move the shared .hive-stats-table to common.css (the dashboard SYST3M >
C0NT41N3R L04D table still uses it); the ST4TS-only window/chip/bar
styles go to stats.css.
- add a Stats tile to the H0M3 hub (and drop the now-stale "stats" from
the Dashboard tile desc); wire build.mjs + the nix/frontend.nix manifest.
Second slice of #1464 step 2; follows the /settings.html extraction.
The gateway container starts alongside every hyperhive deployment, so
gating it behind a separate enable flag was a footgun: an operator who
set it false lost the only thing exposed to the outside while the
agent containers kept running. Re-gate the gateway config on the
top-level services.hyperhive.enable instead.
- hive-gateway.nix: drop the gateway.enable mkOption; gate the config
block on config.services.hyperhive.enable.
- hive-forge.nix: behindGateway now defaults to services.hyperhive.enable;
remove the behindGateway-requires-gateway assertion (now vacuous).
- hive-network.nix: remove both gateway.enable assertions (vacuous).
- hive-c0re.nix: drop the firewall.allowedTCPPortRanges 8100-8999
fallback that opened agent ports when the gateway was off (the
gateway is now the sole entry point); HIVE_GATEWAY_ENABLED is always
set since the gateway always runs.
- nix/docs/default.nix: remove the gateway.enable = mkForce false stub
(would be an eval error against the removed option; the gateway is
already re-gated on hyperhive.enable, which docs force false).
- hive-matrix.nix, dashboard.rs: comment/prose updates only.
BREAKING: operators relying on services.hyperhive.gateway.enable = false
to suppress the gateway must instead point their own reverse proxy at
the gateway's port. NixOS errors clearly on the now-unknown option.
Extracts the operator-local preferences (browser-notification toggle)
out of the dashboard tab strip into a standalone /settings.html page,
reached from the H0M3 hub — same minimal-chrome pattern as /flow.html
and /logs.html. The dashboard tab strip is now purely operational.
- new settings.{html,css,js}; settings.js binds NOTIF + renders the
shared server-warnings banner (matching the other standalone pages).
- drop the S3TT1NGS tab + pane from dashboard.html; the NOTIF.bind()
toggle wiring moves to settings.js. The dashboard keeps NOTIF.show()
(approvals / questions) — it reads the same browser permission +
localStorage mute flag the settings page sets, so firing still works.
- move .notif-row / .btn-notif from dashboard.css to settings.css.
- add a Settings tile to the H0M3 hub; wire the new entries into
build.mjs and update the nix/frontend.nix asset manifest comment.
First slice of #1464 step 2; the ST4TS page extraction follows separately.
Builds on the merged dashboard theming: the themed-frontend overlay now
swaps colors.css in both the dashboard/ and agent/ dist subtrees, and the
agent frontend dir (HIVE_AGENT_FRONTEND_DIR, which the gateway serves agent
static files from) points at the themed tree too. So when stylix is enabled
on the host, both the dashboard and the gateway-served agent UIs re-theme
from the host palette — still zero-op auto-detect, still a no-op (both dirs
on the unthemed dist) when stylix isn't imported.
Not covered: an agent reached directly on its own harness web server (no
gateway) serves from its per-agent mergedDist, built in the agent's own
nixosSystem with no host stylix access — that path needs the base16 palette
forwarded host->agent and is tracked separately.
The dashboard's tab strip now runs on the shared createTabStrip
(@hive/shared/tabs.js) — the third and final consumer of #1464 step 1
(after logs + the agent window selector).
- activateTab is reduced to the per-tab side-effects only
(body.dataset.activeTab, the selection-bar gate, the lazy-loads);
createTabStrip owns the active tab/pane toggle + aria-selected + hash
routing, calling activateTab via onShow. Panes switch via the `hidden`
attribute (was a `.tab-pane-active` class) — the markup carries `hidden`
so there's no flash before the script runs.
- the responsive overflow menu is DROPPED per the design call (move panes
to their own pages rather than hide them in a dropdown). Removes ~150
lines: updateTabbarOverflow, closeOverflowMenu, the click/keydown
handlers, the ResizeObserver + MutationObserver, and syncTabFromHash.
S3TT1NGS is a regular strip tab now (no data-overflow).
- CSS: .tab.active becomes .tab.hive-tab--active; pane visibility is now
.tab-pane[hidden] { display: none }; the overflow dropdown styles are
gone. Count pills (setTabCount/refreshTabCounts) are unchanged.
Behaviour-preserving except the deliberate overflow removal; aria-selected
is now standardised on the tabs.
Replaces the agent stats page's bespoke window-tab click handler with the
shared createTabStrip (@hive/shared/tabs.js). The selector is now
hash-routed — #1h / #24h / … become deep-linkable and survive
back/forward navigation.
- window buttons use data-tab (was data-w); the strip resolves them by
that convention and toggles .hive-tab--active + aria-selected.
- onShow(w) sets currentWindow + reloads stats; the strip's initial show
fires it once, so the explicit loadStats() in DOMContentLoaded is
dropped (avoids a double fetch).
- the selector keeps its distinct pill look — agent.css now targets
.window-tabs button.hive-tab--active (was .active), not the shared
.hive-tab text-tab base. It's a pure data-selector with no panes;
createTabStrip skips the absent [data-tab-pane] gracefully.
Behaviour-preserving; aria-selected is now standardised on the buttons.
The system prompt enumerated every hive-forge verb inline — which drifted
(it named pr-reviews but not pr-status), so agents skimming it for how to
check CI didn't see pr-status and reached for curl against the forge,
breaking the no-curl rule (hit multiple agents). The enumeration also
auto-loads into every turn for every agent, costing context for a list
that's better discovered live.
Replace it with a short description + 'discover verbs/signatures via
hive-forge --help / hive-forge <verb> --help', keeping only the few
conventions --help won't surface: never curl the forge (check CI with
pr-status, not curl); --body-file - HEREDOC bodies; pr-create --push; and
forge notifications arriving via the message daemon. docs/tools/forge.md
remains the full reference.
Removes the standalone P33RS tab + pane. Peer hives now render as a
headline section at the bottom of the SW4RM pane; the swarm/hive name
moves off the cramped strip above the tabbar into a headline at the top
of SW4RM. Both are quiet when nothing's configured:
- swarm/hive identity → `#swarm-identity` h2 in the SW4RM pane (hidden
until hive_name/swarm_name are set), replacing the `#hive-identity`
banner-thin line above the tabbar.
- peer hives → `#peers-block` headline + `#peers-section` cards in the
SW4RM pane. When no peers are federated the headline block is hidden
entirely and a grey "no peer hives configured" note shows in its place.
- drops `peers` from the tab list + the per-refresh peers-tab gating;
renderPeerHives now toggles its own headline.
No backend change — still reads `state.peer_hives` + `hive_name` /
`swarm_name`. Build green.
Adds @hive/shared/tabs.js — createTabStrip(tabbar, {defaultId, onShow}):
a hash-routed tab strip that resolves controls by `[data-tab="<id>"]`
inside the passed container and panels by `[data-tab-pane="<id>"]`,
toggling `.hive-tab--active` + aria-selected + the panel's `hidden`, and
firing onShow(id) for per-tab side-effects. Always hash-routed (deep-
linkable + back/forward), no element-resolver callbacks (convention over
config), per the #1464 design review.
Wires it up: @hive/shared exports `./tabs.js` + `./tabs.css`, and
common.css @imports tabs.css (component structure, not a swap target, so
inlining is fine — unlike theme.css).
Migrates the logs sub-tabs as the first consumer: markup uses
`.hive-tab`/`data-tab`/`data-tab-pane`, logs.css drops the duplicated
base styles (keeps only its `flex:1` layout delta), and logs.js swaps its
activeTab/showTab/hashchange for createTabStrip (onShow lazy-loads the
SYSTEM tab). Behaviour-preserving. aria-selected is now standardised.
First piece of the generic tabs component: @hive/shared/tabs.css carries
the base .hive-tabbar / .hive-tab / .hive-tab--active styles (lifted from
the logs sub-tab pattern, the most generic of the three tab UIs). Colours
use the semantic theme vars so it inherits theme-swap safety.
Not yet consumed — the createTabStrip JS helper + the per-call-site
migrations (logs, agent window-tabs, dashboard tabbar) follow. Count
pills + the dashboard's responsive overflow menu will layer on top in
the per-page CSS rather than the shared base.
When the operator's host config has stylix enabled, generate a base16
colors.css from its palette and overlay it onto the bundled frontend dist,
then serve that themed tree as the dashboard's HIVE_STATIC_DIR. Zero-op
auto-detect — no flake input, no operator action, no npm/esbuild rebuild
(a pure file-copy over the prebuilt dist). colors.css is the whole swap
contract; theme.css derives the semantic vars from the 16 base16 slots.
Guarded access (config.lib.stylix / config.stylix.enable via 'or' fallbacks)
makes it a clean no-op when stylix isn't imported — HIVE_STATIC_DIR stays
on the unthemed dist, which is what every non-stylix deployment + CI gets.
Dashboard surface only for now; the agent surface needs the base16 palette
forwarded host->agent (the per-agent harness builds its own dist) and is a
separate follow-up.
The banner is prepended as a direct child of <body>, so on pages that
pad the body gutter (the dashboard + H0M3 use a 1.5em horizontal gutter)
it inherited 1.5em gaps left and right. Break out of that padding with
negative margins matching the gutter — the same trick .dashboard-chrome
uses — scoped to the dashboard + home shells. FL0W + L0GS are full-bleed
already (no body padding), so they're unchanged.
The systemd ExecStart carried every host-level setting as its own flag —
nine of them, including two escaped JSON blobs (the context-window map and
the model-price table). Collapse them into a single `--config <file>` JSON.
- Reuse the existing HiveEnv as the container-injected config shape (add
Deserialize + Default), and add a ServeConfig wrapper = flattened HiveEnv
plus the hive-c0re-local model_prices table (kept out of HiveEnv since it
is never injected into containers). serde(default) lets any field be
omitted and fall back to its canonical default.
- clap: add --config; the per-setting flags become optional overrides
(config file is the base, explicit flags win — preserves hivectl/debug
ergonomics and bare `hive-c0re serve`).
- Coordinator::open and cmd_serve now take the bundled HiveEnv, which drops
their too_many_arguments clippy allows. cmd_serve keeps a single
too_many_lines allow (inherent daemon-boot orchestration, not arg-driven).
- nix: write the config as JSON to the store + pass --config, so ExecStart
is one short line.
- Add a round-trip test proving the flatten + per-field defaults work.
Closes the ExecStart-length issue.
Split the palette into two standalone stylesheets:
- colors.css — the 16 base16 slots (--base00..--base0F). This is the
entire theme swap contract; a generator (e.g. a stylix base16 scheme,
which is natively base00-base0F) replaces only this file.
- theme.css — the semantic layer (--bg, --purple, …) derived from the
base16 slots via var()/color-mix. Never changes on a swap.
Every page links colors.css then theme.css; theme.css does NOT @import
colors.css (that would re-bake the slots into it) — they're separate
dist outputs so a swap touches just colors.css. Pixel-identical refactor:
base16 defaults are Catppuccin Mocha and the three off-slot vars
(--crust, --muted, --subtext0) derive via color-mix reproducing their
exact prior hexes.
Wires colors.css through both build.mjs CSS entry lists, the
@hive/shared exports map, and all 7 page templates. css-vars.md + the
frontend.nix output-list comment updated.
Replace ~92 hardcoded Catppuccin rgba literals across the dashboard,
agent, and shared terminal stylesheets with color-mix() over the theme
:root vars, so a stylix-generated theme.css recolours the whole UI, not
just the elements that already referenced the vars. Pixel-identical
under the default palette (same RGB + alpha; color-mix(in srgb, C N%,
transparent) == rgba(C, N/100)).
Also fixes four var(--mauve, #cba6f7) usages: --mauve is undefined (the
var is --purple), so they were silently falling back to the hardcoded
hex and would not have recoloured on a theme swap. The bare crust hex in
terminal.css and the checkerboard-gradient hexes in common.css are
var-derived too.
Pure black/white drop-shadow/frost scrims are left as literals (not
theme colours). Two off-palette surface tints (dim-purple schedule rows,
warm-amber inline-button hover) are also left, with a comment, pending a
dedicated named var. Correct-var fallbacks like var(--red, #f38ba8) are
left as-is since they already recolour via the var.
Per operator request: instead of a disk-specific alert, surface a generic
server-warnings banner at the very top of every page, so new system
warnings can be added backend-side with no frontend change.
- hive-c0re `host_stats`: `server_warnings() -> Vec<ServerWarning>`
(`{ kind, level, message }`). The threshold logic lives server-side; the
host disk-pressure check (a `statvfs` probe of `/nix`: ≥85% used → warn,
≥95% → crit) is the first and only producer today. No new deps (libc).
- `/api/state` carries `server_warnings` (replaces the disk-specific
field). Empty when all clear.
- frontend: `renderServerWarnings` / `initServerWarnings` in `common.js`
inject a sticky top-of-<body> banner and render the list, coloured by
`level`. Wired on every page — dashboard (live, via refreshState),
FL0W, L0GS, H0M3. No per-warning frontend code; adding a warning kind
is a pure backend change.
cargo check/clippy/fmt + npm run build green. Closes#1518.
Swarm-level companion to the per-agent favorite-tools doughnut (#1433).
Aggregates each agent's bash_commands(ts, head) table (written by
hive-bash-mcp) across the whole hive and surfaces the top-10 most-run
command heads on the dashboard ST4TS tab, alongside the existing model
mix.
- hive_stats.rs: AgentAgg gains a `bash` head→count map, filled by a new
guarded `read_bash_heads()` that reuses read_agent's read-only
connection. A missing `bash_commands` table (capture hasn't run for
that agent) or any read error yields an empty map — isolated from
read_agent's error path so it never drops an agent from the rollup.
HiveStats gains `bash_mix: Vec<KeyCount>` (busiest-first, top 10).
Unit tests cover the per-head tally + window cutoff and the
missing-table degrade-to-empty path (in-memory sqlite).
- dashboard: a "favorite tools (bash commands across the swarm)" CSS-bar
list on the ST4TS pane, mirroring the model-mix bars. Header + list
stay hidden until bash_mix has data, so a fresh hive shows no empty
block. (Dashboard ships no chart lib — bars, not a doughnut.)
- docs: dashboard.md ST4TS section documents the new rollup.
Closes#1449. Inert until the hive-bash-mcp capture (#1448, merged) has
recorded data across agents.
Two small follow-ups after the modelPrices PR (#1442):
- nix/modules/hive-c0re.nix: the `modelPrices` option `example` still
showed the old 5-minute sonnet `cache_write = 3.75`; the default is now
the 1-hour TTL `6.0`. Bump the example to match so it doesn't mislead.
- frontend/packages/agent/agent.css: remove the dead `.stats-empty-note`
selector (no element uses it — the stats empty state paints on canvas).
Per operator request on the PR: the built-in/default prices were the old
Claude 3 numbers (opus 15/75, etc.). Update opus + haiku to the current
Claude 4.x family list pricing (cache_write = the default 5-minute cache
TTL); sonnet was already correct:
- opus: input 5, output 25, cache_read 0.5, cache_write 6.25
- sonnet: input 3, output 15, cache_read 0.3, cache_write 3.75 (unchanged)
- haiku: input 1, output 5, cache_read 0.1, cache_write 1.25
Updated in both builtin_prices (hive_stats.rs) and the nix modelPrices
default (hive-c0re.nix), with cross-reference "keep in sync" notes on
both sides. Also addresses the earlier reviewer note: dropped the
over-strong "single source of truth" wording in the --model-prices arg
doc (the nix default does mirror the numbers in production).
- modelPrices submodule fields use lib.types.numbers.nonnegative
instead of lib.types.float: accepts bare ints (15) as well as floats
(15.0) and rejects negative prices for free.
- Collapse the triple-sourced default: hive-c0re serve --model-prices
now defaults to "{}" so builtin_prices() is the single in-code
fallback. The nix option default still carries the full
opus/sonnet/haiku table to self-document prices for operators.