The largest of these moves: sixteen references spelled through `let`
aliases across eight modules, plus eight more spelled as a path, plus
five documentation pages.
authelia is also the clearest case for why the two namespaces exist.
`swarm.authelia.url` is needed by *every* hive in the swarm — it says
where to send a browser to authenticate — while running the container is
the business of exactly one host. The client half and the server half
were sharing a namespace whose whole contract is "identical everywhere",
and only one of them could honour it.
`swarm.authelia.oidc.clients` stays where it is for the same reason:
several modules register a client there, gated on authelia running here,
and the registry itself is what the service *is* rather than a decision
about this machine.
One sweep note worth recording: a grep for `swarm.authelia.enable` misses
`swarmCfg.authelia.enable`, because the prefix is whatever the reading
file bound. Grepping the suffix `.authelia.enable` finds both, and found
a reference in swarm.nix that the path-shaped pattern did not.
One commit rather than two because they are not independent: the UI's
`enable` had the controller's as its literal default, so moving the
controller alone would leave the UI's default naming an option that no
longer exists.
The UI keeps that derivation in its new home — it is a view onto the
controller's state and reaches it over that daemon's unix socket, so the
host running the controller is the host that can serve it.
Three spellings had to move together for the UI, not one: the `default`,
the `defaultText` shown in the options doc, and the description prose
that names the old path in words. A grep for the option path finds the
first two.
The sweep also reached outside nix: `swarm-controller`'s crate README and
its `//!` module doc both named the option, as did this repo's own
CLAUDE.md and four pages under docs/. An option's name is API, and its
documentation lives wherever someone thought to write it down.
Same move as grafana, and the three belong together: they derive from one
switch and a store with no UI is as useless as a UI with no store.
`victorialogs` is the case that shows why the option-path sweep is not
enough on its own. It has **zero** references spelled
`swarm.victorialogs.enable` anywhere in the tree, and four spelled
through `let` aliases (`vlCfg.enable` in the collector, `cfg.enable` in
its own module). A sweep for the path would have reported nothing to do
and left every reader broken.
Prose moved with the code rather than being left behind: the comments in
swarm-required-services.nix that explained why the pair derives together
now sit above the assignments that do it, instead of above the gap where
they used to be.
`ssoLocal` in the grafana module read `cfg.enable`, where `cfg` is the
local binding for `services.hyperhive.swarm.grafana` — the option set the
previous commit removed `enable` from. Evaluation fails on the missing
attribute.
The sweep that missed it looked for the option *path*
(`grafana.enable`). This reference is spelled through a `let` alias, so
no grep for the path can find it, and a parse check cannot either: it is
syntactically valid and only wrong once the module system resolves the
attribute.
The check that does find it: for each file, extract the local bindings
that point at the option set being changed, then grep for
`<alias>.<removed-attr>`. Run against this tree it reports nothing for
grafana and sixteen references for authelia, which is the next module to
move.
`services.hyperhive.swarm.*` is meant to be identical on every host in a
swarm — it describes the swarm, and every hive needs all of it to be a
client. But it also carried the `enable` toggles, which are precisely the
values that must differ per machine. The namespace that should be the
same everywhere held the one thing that cannot be.
Adds `services.hyperhive.deploy.*` for a host's deployment decisions, and
moves the first of them (`swarm.grafana.enable` -> `deploy.grafana`) as
the pattern for the rest. Flat and named for the thing deployed rather
than grouped under a "swarm services" attribute: from the deploy side it
does not matter what kind of thing each one is, and a grouping by service
kind would re-encode the service-side taxonomy into a layer that does not
care about it.
Behaviour is unchanged. The move is a rename in the strict sense — same
type, same meaning, new path — so `mkRenamedOptionModule` carries it and
existing configs keep evaluating with one warning naming both paths. The
renames live in the new module rather than the service modules, so the
whole migration has a single home and a single file to delete when the
deprecation window closes.
Same layering rule as the previous commit, one construct over. The
remediation text explained how nix wires the var and pointed at
`nix/assets.nix` -- a path that does not exist either, so it carried
both defects this branch is about.
What a developer hitting this panic needs is the layout to point at,
which is a fact about this crate.
Per review: the rust code should not explain nix stuff.
The original doc comment named nix modules as the setters of
HIVE_ASSETS_DIR, and the previous commit here made that worse -- it
replaced two stale nix paths with four accurate ones, entrenching a
cross-layer explanation instead of removing it.
What this crate actually depends on is the env var and the layout it
points at. Who sets it belongs to the layer that sets it.
harness-base.nix has never existed in this tree. Four comments named it,
or a `harness-base` module, as the place to look:
- weston-vnc.nix: the agent user is declared and home-chowned by
nix/agent-modules/user.nix
- hive-ci.nix: the sandbox-fallback reasoning lives in
nix/agent-modules/default.nix -- which the very next comment block in
the same file already cites correctly
- packages/default.nix: the per-bin consumer is
nix/agent-modules/packages.nix
- hive-sh4re/src/assets.rs: HIVE_ASSETS_DIR is set by
hive-c0re/environment.nix and agent-modules/default.nix +
agent-service.nix, and the package is built by nix/packages/assets.nix
-- not the equally nonexistent nix/assets.nix
assets.rs was twice declared out of scope on the sibling PR because it
names a module rather than a file. That distinction was real and
irrelevant: neither the module nor the nix/assets.nix path it points at
exists. Reading the wording is not checking the reference.
Every replacement path was verified to exist, with a deliberately bogus
path as a control.
The network-isolation doc comments carried prose docs/network.md
already owns, and three of them named `harness-base.nix` — a file
that does not exist. The `hyperhive-isolated-dns` oneshot lives in
nix/agent-modules/network.nix, which the doc gets right.
That is #3749's argument reproducing itself: the same fact written
in two places goes stale in the copy nobody reads. Linking removes
the class, not just the instance — a link cannot name a nonexistent
file without the doc noticing first.
Trap and measurement comments stay put, per the issue's scope: the
load-bearing HOST_ADDRESS default-route note, the unquoted
$EXTRA_NSPAWN_FLAGS expansion, and the "isolation is the only mode"
invariants are facts about this code, not about the subsystem.
argus (PR review): the thinking-flip useEffect left the button stuck
disabled if the /api/cancel POST itself failed while the turn was
still genuinely thinking, since nothing re-fires the effect. Switch
onCancelTurn's signature to () => Promise<void> and reset cancelBusy
in a .finally() on that promise instead — fires on success or
failure alike, matching app.js's original
postCancelTurn().finally(() => { btn.disabled = false; }).
Root-caused mara's 'no interrupt button on agent term anymore' report:
the pre-rewrite app.js had a visible <button id=cancel-btn> (class
btn-cancel-turn, already-styled CSS still in agent.css but orphaned)
shown only while a turn was in flight, wired to /api/cancel. The
Preact rewrite (agent terminal Preact rewrite) ported the /cancel
slash command but never re-added the visible button, so interrupting
a turn now requires typing a command instead of clicking.
Adds thinking/onCancelTurn props to StatusChips, rendering the same
btn-cancel-turn markup only while turn_state === thinking, wired to
the existing postCancelTurn() (termActions.ts) + refresh(). Verified
with real headless-chromium screenshots against a mock /api/state:
button renders while thinking, is absent while idle.
pauseAction.ts submitted a real <form> POST, so clicking pause/resume
navigated the whole page to hive-c0re's plain-text "ok" response body
instead of staying on the agent terminal. Switch to a fetch with
mode: 'no-cors' (still cross-origin-safe, no CORS headers needed from
hive-c0re) + credentials: 'include' to match the form's cookie
behavior, then refresh() the agent state afterward, matching the
existing postModel/postEffort pattern right next to it in Root.tsx.
The inline comment above write_bridge_dns_marker still said the marker
is 'written on isolate, removed otherwise, so the same shared container
toplevel behaves correctly in both modes'. There is one mode now.
Caught because argus pointed out that reading every changed function's
doc comment does not cover comments at the call sites -- the complete
form is to read every comment in the context around each hunk, which is
what git diff -U15 shows.
set_nspawn_flags said PRIVATE_NETWORK was forced 0 for web-UI
reachability; it is now unconditionally 1 and the UI is reached over the
bridge. write_bridge_dns_marker said it writes or removes the marker;
the removal path went with the host-netns case.
Neither was reachable by the sweeps that found the earlier ones: this
prose describes the *value in words* ('forced 0'), not the code's
literal PRIVATE_NETWORK=0, and neither names the env var. Found by
reading the doc comment of every function whose body this branch
changed.
argus caught docs/network.md still listing HIVE_NETWORK_ISOLATION as a
live c0re signal and saying hive-c0re reads it. Neither is true after
this branch. conventions.md had the same shape one file over: it
described write_dropins as writing PRIVATE_NETWORK=0.
docs/gotchas.md has the same staleness and is deliberately untouched --
#3723 already rewrites that passage, and editing it here would conflict
with a PR that has been reviewed five times.
git grep now reports no HIVE_NETWORK_ISOLATION anywhere in tracked
files.
Per mara on #3725: the on/off toggle is removed, and required env vars
unset lead to a crash. HIVE_NETWORK_ISOLATION is gone from
hive-network.nix -- it was the toggle.
Validation happens once at daemon startup rather than per container.
The variables are process-global, so a bad value breaks every container
rather than one: failing at boot gives a single diagnostic naming the
bad value, and cannot reach a state where some containers were
configured before it was noticed.
Option<NetworkIsolation> collapses to NetworkIsolation through the wire
type, client and helper, which deletes the branch instead of leaving it
unreachable. serde(default) is dropped on that field deliberately: a
request omitting isolation is now rejected rather than defaulting to a
container sharing the host's network namespace.
What this replaces was a silent security downgrade. Of the four ways
into the old fallback, two logged nothing at all -- a container came up
without isolation and the journal agreed it was fine.
Doc comments that still described the removed branch are updated
(argus's note on #3723 scoped that to this issue). The hive-priv one is
a minimal edit inside the block #3723 rewrites; de-splicing is that
PR's job.
argus review: the two :root[data-theme='...'] glass-override rules
have specificity 0-3-0 (:root + [data-theme] + .ui-badge), beating
.ui-badge-quiet's 0-1-0 — so whenever a user has an explicit theme
override set (not just relying on prefers-color-scheme), these rules
reintroduced a fill on quiet badges, regressing the settings/links
trigger fix. Scope both overrides with :not(.ui-badge-quiet).
Verified with a real repro (same synthetic striped-bg test page,
before/after) rather than just the specificity arithmetic.
Translucent color-mix background + backdrop-filter blur/saturate,
same recipe as the header chrome/terminal glass (chrome.css,
terminal.css). Dark (mocha) palette only: applies the glass look to
the default filled badges, keeps light (latte) mode's plain solid
fill unchanged since a bright/busy background behind a badge would
lose legibility from the same transparency that helps on a dark
background. variant="quiet" badges are untouched (they already have
no permanent fill to frost).
Verified with a synthetic striped-background test page (raw CSS
against colors.css/theme.css/Badge.css, not the bundled app) to make
the blur/transparency visually obvious, plus real headless-chromium
screenshots of the built agent + swarm-ui dist to confirm no
regression against real content — the effect is real but subtle
there since the chrome background is flat, which is expected.
Badge's default look is a filled pill, right for status/picker chips
but wrong for an icon-only header button like the settings gear or
agent links trigger, which should read as chrome. Add variant='quiet'
(same naming/shape as Button's ButtonVariant) that drops the idle
background, keeping the existing hover/expanded feedback.
Apply it to the two icon-only Badge triggers: shared SettingsMenu
(used by both swarm-ui and the agent page) and the agent page's own
MetaNav links trigger. Add a components-page showcase sample so the
variant has a visible regression check going forward.
Fixes a regression + a longer-standing inconsistency (mara: "agent
page link icons is different from swarm ui, settings icon looks weird
since component extract").
- The `SettingsMenu` shared-component extraction swapped swarm-ui's
original inline-SVG gear trigger for a plain `⚙` text glyph (matching
agent's `MetaNav`, which was itself still on the `🔗` emoji at the
time). An emoji/text glyph is rendered by the OS/browser's own font
at that font's fixed metrics — it can never match a neighbouring
icon in size or weight, and some codepoints (the gear included)
aren't even reliably covered by every font.
- New `@hive/shared/icons.js` (`GearIcon`, `LinkIcon`) — the exact SVG
markup that used to live only in swarm-ui's `SettingsMenu`/
`LinksMenu` as two separate inline copies, now the one shared source.
- `SettingsMenu` (shared) uses `GearIcon`; agent's `MetaNav` and
swarm-ui's `LinksMenu` both use `LinkIcon` — three consumers, one
rendering path, matching stroke/viewBox/size everywhere.
Verified with real screenshots on both agent and swarm-ui — both
trigger icons render as the same crisp line-icon style now.
Review feedback on this PR (mara): "i think the component should be
shared. motion setting is missing." Both addressed:
- `settings-storage.ts` (generic localStorage hook), `theme-apply.ts`,
`motion-apply.ts`, and `SettingsMenu.tsx`/`.css` all move from
swarm-ui's `lib/`/`shell/` into `@hive/shared/src/settings/` —
agent's previous local copies are deleted outright rather than kept
as a second implementation. One component, `Badge` trigger
everywhere (already used elsewhere in swarm-ui, so not a new visual
language there either) — storage keys stay caller-owned (`themeKey`/
`motionKey` props + matching `useApplyThemeOverride`/
`useApplyMotionOverride` calls at each package's single mount point)
so agent and swarm-ui keep fully independent, non-colliding
persisted settings.
- Agent's settings menu now includes the motion row, matching
swarm-ui's. No animation in the agent package is gated behind
`data-motion` yet — same as when swarm-ui first built this plumbing
ahead of having a consumer — so it's currently inert there, ready for
whenever agent grows a motion-guarded animation.
- swarm-ui's own theme default flips to `'dark'` as part of this move
(`theme-apply.ts`'s new default), superseding PR #3715 — that PR
becomes redundant once this lands and will be closed rather than
merged, to avoid the two colliding on the same file.
Verified end-to-end with real screenshots on both pages: shared
component renders identically (Badge trigger, theme+motion rows, dark
default) on agent's mock server and a static rebuild of swarm-ui's
dist.
Ports swarm-ui's `SettingsMenu` (mara: "agent terminal page should get
the settings panel from swarm ui as well") — same shape as `MetaNav`
already in this header: a `Badge` icon trigger ("⚙"), popover, close on
outside-click/Escape.
`theme-apply.ts` + `settings-storage.ts` are near-verbatim ports of
swarm-ui's own (duplicated rather than moved into `@hive/shared` for
this pass — lower risk than reworking swarm-ui's imports in the same
change). Defaults the stored override to 'dark', not 'system', for the
same reason as swarm-ui's own default flip: `prefers-color-scheme` has
no real "unset" value, so 'system' silently reads as light for anyone
who's never touched an OS dark-mode toggle.
Motion NOT ported — agent has no animation gated behind `data-motion`
yet, so that plumbing would have nothing to control.
Verified end-to-end: default dark on a fresh load, and an explicit
localStorage override to 'light' correctly re-themes the whole page via
the existing `colors.css` `:root[data-theme='light']` block (already
shipped, previously only reachable from swarm-ui).
--muted is color-mix()'d toward --base00/--bg — correct for dimmed text
on the page background, wrong for dimmed text sitting on the elevated
--purple-dim fill (badge/dropdown-item backgrounds). Badge's label/caret
used bare --muted there and mara measured it at ~1.3:1 contrast against
her theme's badge fill, essentially invisible.
--muted-on-dim mixes toward --purple-dim instead, same contrast-floor
technique as --muted itself, just anchored to the surface it's actually
used on. Applied to .ui-badge-label/.ui-badge-caret and to Dropdown's
active-item description (same bug: its row bg is --purple-dim too).
Ceiling note: even plain --fg only reaches ~3.4:1 against --purple-dim
in mara's theme, short of formal 4.5:1 AA — that's the theme's own
limit, not something the anchor choice can fix on its own. 90% gets
close to that ceiling (~3:1, more than double the old ~1.3:1) while
keeping a hint of the label/value visual distinction.