Commit graph hyperhive/frontend/packages/swarm-ui/src/shell
Author SHA1 Message Date
iris
4175ae86e3 icons: restore inline-SVG link/gear icons, share them across all three pages
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.
2026-08-29 11:54:21 +02:00
iris
9720bdfad0 settings: move the theme/motion panel into @hive/shared, add motion to agent
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.
2026-08-29 11:11:39 +02:00
iris
6a01850096 swarm-ui: fix inconsistent header icons + body edge gap
LinksMenu and SettingsMenu triggers mixed a full-colour emoji (link)
with a plain text glyph (gear) - different rendering paths mean
different, unfixable sizes/styles. Replace both with matching inline
SVG icons (feather/lucide gear + link glyphs), same viewBox/stroke/
size, so the two buttons finally share one rendering path.

Also: shared base.css never zeroed the default UA body margin, which
showed as a bg-coloured strip around the whole viewport edge on any
full-bleed header (swarm-uis .shell-header among them). Zeroed it in
the shared file so every consumer (dashboard, agent UI, swarm-ui) gets
the fix, not just swarm-ui.

Screenshot-verified at both desktop and phone widths.
2026-08-24 18:38:12 +02:00
iris
2c8d37d5b8 swarm-ui: animate the three header popovers on open
Part of #3591 (mara: "pop ups and menus appearing should animate").
Each popover mounts fresh on open ({open ? <div> : null}, not a state
transition), so a keyframe animation on the popover element itself is
the right tool -- same shape as Shell.css's own shell-page-enter
(fade + a slight translate/scale settle), including the identical
three-rule motion-guard (base rule, prefers-reduced-motion media
query, data-motion=reduce/allow explicit overrides).

Scope: LinksMenu, SettingsMenu, UserMenu -- the three header popovers.
Not included here (posted findings on the issue instead of guessing):
the refresh-interval picker's dropdown (native <select>, whose open
popup is OS/browser chrome outside CSS reach in current browsers --
"not themed" is a platform limitation, not a bug in this component's
own styling) and the jobs graph's node animations (JobqGraph is a
@hive/shared component consumed by both swarm-ui and the per-hive
dashboard, real design/implementation work on shared infra, not a
same-shape mechanical extension of an existing pattern).

Screenshot-verified the settled (post-animation) state renders
correctly; a static screenshot cannot show an in-flight CSS animation,
so this leans on exact structural parity with the already-shipped
Shell.css pattern for the animation's own correctness.
2026-08-24 15:50:48 +02:00
iris
7aec2e339f swarm-ui: authelia settings link should go to /settings, not the domain root
mara: "should be https://auth.constellation.darkest.space/settings in
profile pic menu" -- the UserMenu link was pointing at the plain
authelia domain root, which lands on the portal rather than the
account settings page. Appends /settings client-side, same base-URL
source as before (GET /api/links Authelia entry).
2026-08-24 12:31:07 +02:00
iris
13d7c73c12 swarm-ui: move agent creation into the agents page as a dialog
Adds a generic ui/dialog/Dialog primitive (native <dialog>, no
third-party modal lib and no shadow-DOM custom element per the esbuild
gap on those) and wires a "+ agent" button into AgentsPage that opens
the existing create-agent form inside it, content unchanged from its
former life as a standalone /create-agent route/nav item.

Removes the "new agent" top-level nav entry and the /create-agent
route entirely -- creation now only reachable from the roster that
gets populated by it. CreateAgentPage.tsx/css renamed to
CreateAgentForm.tsx/css to match its new role as a mounted component
rather than a page.

Screenshot-verified the dialog open/closed states against a mock
server.
2026-08-24 00:28:34 +02:00
iris
dabd0fc823 swarm-ui: header profile menu — initials avatar, authelia settings + logout (#3570)
Adds an "/api/whoami" same-origin nginx proxy to authelia's own
GET /api/user/info (session-cookie authenticated, no swarm-controller
code needed) and a new UserMenu header component: a generated initials
avatar (first letter of display name, coloured from the same seven
base16 chromatic slots the nav accent already cycles through) opening a
popover with the signed-in name, a link to authelia settings, and log
out — both reusing the existing "Authelia" entry from GET /api/links
rather than a second source of the domain.

Per mara's call on the open avatar-mechanism question: initials now,
a real uploaded photo (authelia's settings UI implies pics are
settable) is an explicit future item, not blocking this.
2026-08-24 00:01:30 +02:00
iris
0f52cbe40a swarm-ui: agent roster page with per-agent config-PR status
New AgentsPage at /agents: fetches GET /api/agents (roster names) and
GET /api/config-prs (bulk config-PR status) and merges them into one
table, one row per agent. Reuses the existing Panel/Table/StatusChip/
RefreshIntervalPicker components exactly as HivesPage does — the roster
page and the config-PR panel turned out to be the same page rather than
two separate pieces of UI.

Adds a nav entry (green accent, the next unused base16 chromatic slot)
between hives and new agent.
2026-08-23 22:53:32 +02:00
iris
6d9a2fa9f6 swarm-ui: fix underline not showing on the initial page load
Root cause (found via a zoomed pixel-level screenshot check, not
guessed): the indicator span always exists in the DOM starting from
left:0/width:0/transparent, and the very first real position landed
via a CSS-transitioned change from that fallback rather than a snap —
so the underline visibly grew in from nothing over 140ms instead of
being there immediately, reading as entirely absent on a fast/slow
first paint alike depending on timing. Every subsequent navigation was
unaffected (always transitioning between two already-visible states).

Fixed with a one-render-only transition suppression
(indicatorSettledOnce, gated one tick behind the indicator's first
non-null commit) so the first placement snaps instead of animating in,
while every later hop still animates normally. Verified against the
exact repro: a fresh page load at the same short virtual-time-budget
that previously showed no underline now shows it immediately.
2026-08-19 19:54:52 +02:00
iris
daabb4fe6a swarm-ui: brand text follows the nav accent too
The brand/logo text now rides the same accent value the nav underline
uses, including mid-sweep, so the header reads as one accent changing
rather than the underline alone — per mara's review comment.
2026-08-19 19:54:52 +02:00
iris
a5584d0fcd swarm-ui: plain fade + multi-hop underline sweep, per review
Content entrance was a scale+overshoot pop; mara didn't like it on
review, swapped for a plain fade. The nav underline now hops through
every nav item it passes over on its way to the new active one
(position and colour together) instead of tweening directly between
the two endpoints, matching what she actually meant by "move through
color wheel" — a->c visibly touches b's colour along the way, not a
smooth continuous hue rotation.
2026-08-19 19:54:52 +02:00
iris
6be1bbd4f7 swarm-ui: page-switch animation (pop entrance + nav underline travel)
Shell's page content now remounts on every navigation and plays a
scale+fade pop-in entrance. The active nav link's underline is now a
single shared element that slides to its new position instead of
snapping, re-colouring to a per-tab accent — a discrete cycle through
the existing base16 chromatic slots, not a continuous hue rotation.
Both are gated on the data-motion/prefers-reduced-motion plumbing
lib/motion-apply.ts already had wired and waiting for a first real
consumer.
2026-08-19 19:54:52 +02:00
iris
a08aacfdf6 swarm-ui: client-local settings surface (theme + reduced-motion overrides)
Adds the settings surface + storage plumbing swarm-ui has been missing:
nowhere to put a client-local preference and no shared code for one to
build on. Scoped small per explicit direction ("small thing somewhere",
localStorage, theme and motion in scope for now) rather than a full
/settings route + nav entry for two toggles.

- frontend/packages/swarm-ui/src/lib/settings-storage.ts: generic
  useLocalSetting<T>(key, fallback) hook - read once, write through,
  stay in sync with other same-tab consumers of the same key via a
  small module-level pub/sub (localStorage's own storage event only
  fires cross-tab).
- frontend/packages/swarm-ui/src/lib/theme-apply.ts: tri-state
  system/light/dark override, applied by setting the 16 base16 custom
  properties inline on <html> (an inline style always outranks a
  stylesheet rule, including a media-query-gated one) - colors.css's
  own comment on its light-mode block already named this as the
  intended mechanism for a future override.
- frontend/packages/swarm-ui/src/lib/motion-apply.ts: tri-state
  system/reduce/allow override, applied as a data-motion attribute.
  Currently inert - swarm-ui has zero CSS animations yet - included
  because the marginal cost riding alongside the theme override is
  near zero and it was named in the same scoping answer; the first
  swarm-ui animation's own CSS is what makes this do anything.
- frontend/packages/swarm-ui/src/shell/SettingsMenu.{tsx,css}: a
  header icon-button + popover holding both selects, same shape as
  LinksMenu (manages its own state, not a ui/ primitive, hence no
  ComponentsPage demo - same exception LinksMenu already established).
- Shell.tsx/.css: mounts the two override-application hooks once
  (every route renders through one Shell), and wraps SettingsMenu +
  LinksMenu in a single .shell-header-actions flex wrapper so one
  margin-left: auto pushes both to the right edge together - two
  adjacent auto-margins on separate elements split the space between
  them instead of sitting flush.

Verified the override actually outranks the media query, not just
"looks right": seeded localStorage with each override value while
forcing the opposite OS-level prefers-color-scheme via headless
chromium, both directions render the stored override, not the forced
OS preference. Typecheck and build clean.
2026-08-18 23:58:48 +02:00
iris
328ba19595 swarm-ui: fix nav underline gap argus caught in round 2
The previous fix put both the touch-target min-height and the
active-state border-bottom on the same <a>: centering the text
within a 2.75em box pushed the border ~0.7em away from it, a real
visible regression argus caught by actually rendering the CSS rather
than reasoning about the box model abstractly.

Split the two concerns onto two elements: the <a> (.shell-nav-link)
owns the full-height tappable box, an inner <span>
(.shell-nav-link-text) wrapping just the label owns the underline,
so the indicator stays directly under the text regardless of the
tappable box's height.

Verified: tsc clean, build succeeds, screenshotted close-up (600x100)
and at 320px — underline sits flush under the text in both, no gap.
2026-08-18 22:44:24 +02:00
iris
ea7cc03fd7 swarm-ui: fix .shell-nav-link touch target argus caught
The proposed scope on this issue named three things to give the
2.75em touch-target floor: nav links, buttons, and the links-menu
trigger. Only the links-menu trigger actually got it in the first
pass — the primary nav (hives/new agent/jobs/components), the
highest-traffic touch target in the whole shell, was still sitting at
padding: 0.25em 0 (~27px tall).

display: flex + min-height rather than more padding: padding alone
would have pushed the active-state border-bottom away from the text
to reach 2.75em; flex centres the text within the full-height box so
the indicator still sits directly under it.

Verified: build succeeds, screenshotted at desktop width and 320px —
active underline still sits correctly under the text at the taller
height, wrap behavior from the earlier commit unaffected.
2026-08-18 22:44:24 +02:00
iris
7c15cd46d2 swarm-ui: don't break at a narrow viewport
Closes #3447.

- Shell.css: .shell-header and .shell-nav wrap onto a second line
  below a breakpoint instead of forcing horizontal overflow — no
  hamburger/collapse menu, per mara's own 'don't break, don't
  over-invest' scoping.
- LinksMenu.css: bumped .links-menu-button and .links-menu-item to the
  same 2.75em (~44px, WCAG 2.5.5) touch-target floor the shared ui/
  kit's form controls and buttons already carry — this trigger is
  bespoke, not built from that kit, but the floor applies regardless.
- Table.tsx/.css: wrapped the table in a .ui-table-scroll container
  (overflow-x: auto) so a wide table scrolls within its own box on a
  narrow viewport instead of forcing the whole page to overflow
  horizontally — not named in the issue's two concrete spots, but the
  same 'should not break' failure mode, and fixing it at the shared
  Table primitive covers every future caller.

.create-agent-input's fixed width -> max-width was already fixed as
part of #3448's form kit.

Verified: tsc clean, build succeeds, screenshotted at 320px (hives
nav wraps, table scrolls internally, page itself doesn't overflow)
and 480px (create-agent form fields fit).
2026-08-18 22:44:24 +02:00
iris
537d6f9dac swarm-ui: rename OverviewPage/'overview' to HivesPage/'hives'
Closes #3460.

Renamed for accuracy — the page/nav item is the hive roster, not a
general dashboard overview. Nav label, page title, component name,
and file all renamed together so the internal name doesn't drift from
what's displayed (a component still called OverviewPage under a
'hives' nav label would be exactly the kind of stale prior-art that
makes the next contributor search harder, not less).
2026-08-18 21:26:52 +02:00
iris
3643eccf22 swarm-ui: show the swarm's name as the page title and top-left brand
New GET /api/swarm on swarm-controller, backed by
services.hyperhive.swarm.name (SWARM_CONTROLLER_NAME env var, same
optionalAttrs-gated-on-option-resolving shape queueEnv/forgeEnv/etc.
already use). swarm-ui's <Shell> fetches it once and sets both
document.title and the header's brand text; falls back to the
existing static "hyperhive swarm" label when the operator never set
a name or the fetch fails.

Extracted the swarm-queue connect block out of main() into its own
connect_status_reader() fn to keep main() under clippy's line-count
lint after adding the new field wiring — no behavior change, same
comments moved as-is.
2026-08-18 18:34:56 +02:00
iris
47e89c1c93 swarm-ui: move create-agent page off /agents, rename to CreateAgentPage
mara: the route should reflect creating an agent, and stay separate
from a future agent list page. Renamed AgentsPage -> CreateAgentPage
(file, component, css classes) and moved the route from /agents to
/create-agent - flat, not /agents/new, since index.html's relative
asset links only resolve correctly one path segment deep (filed
separately as a real bug, not fixed here). Leaves the bare /agents
path free for a future roster page.
2026-08-17 00:20:10 +02:00
iris
d044281040 swarm-ui: minimal agent creation page
Adds /agents: a name field that POSTs to swarm-controller's
POST /api/agents (from the CreateIdentity work), shows the queued
job node id, and links to /jobs to watch it settle. Scope matches
the issue exactly - no forge/deploy options, those aren't wired
server-side yet.
2026-08-17 00:20:10 +02:00
iris
527ed8f3e2 swarm-ui: add a jobs tab, and type JobqGraph as real TypeScript
New /jobs route in swarm-ui, reusing the shared JobqGraph component
against swarm-controller's own GET /api/jobq/graph (same wire shape
hive-c0re's dashboard already consumes, different endpoint, no fork).

Converted JobqGraph.jsx to JobqGraph.tsx with real prop/wire types
(mirrors hive_jobq_wire's GraphNode/GraphDep/State by hand) instead of
a hand-maintained ambient .d.ts at the swarm-ui consumer side — the
.d.ts would duplicate the prop list and drift from the source the
moment the component's signature changes without the declaration being
touched. Both dashboard (untyped consumer, esbuild strips types) and
swarm-ui (tsc --noEmit) build/typecheck clean off the one file.
2026-08-16 17:30:06 +02:00
iris
8542f2ca42 swarm-ui: header links menu for swarm-wide services (hyperhive#3289)
Single 🔗 button in the Shell header (direction 1 of 3 proposed on the
issue) — one fixed element regardless of link count, so it can't
clutter as the list grows, and visible on every route since Shell
wraps all of them. Opens a popover listing whatever GET /api/links
returns (icon + label, opens in a new tab); hidden entirely when the
list is empty, same "don't render a dead affordance" rule the old
dashboard's H0M3 tiles follow for Forge/Matrix.

Docs: docs/swarm/ui.md gets a short section on the feature + the
contribute-your-own-entry idiom.

Verified: npm run build + npm run typecheck clean, headless-chromium
screenshots of both the closed and (temporarily forced open for the
screenshot only) open states, pixel-sampled to confirm the popover is
actually using the dark theme vars and not just looking that way in a
downscaled preview.
2026-08-15 14:24:52 +02:00
iris
cfc8f2b22c swarm-ui: add a components-library page (hyperhive#3288)
New /components route showing every ui primitive (Panel, StatusChip,
Table) in each mode it supports: Panel with/without a title,
StatusChip's four tones, Table populated and empty. Sample data only,
no API calls, so it renders identically regardless of backend state.
Linked from the Shell nav next to "overview".

Verified with npm run build + npm run typecheck, and a headless
chromium screenshot of the built dist.
2026-08-15 12:57:57 +02:00
iris
8642d4acf6 swarm-ui: colocate component CSS as JS-side-effect imports
Per mara's review question on PR#3219 ('shouldnt the jsx files import
their css?'): each component now does its own import ('./Shell.css',
'./Panel.css', ...) instead of swarm-ui.css centrally @import-ing
every component's stylesheet. esbuild folds every .css reachable from
main.tsx's import graph into one main.css companion output next to
main.js — no separate build step, this is bundle:true's existing
behavior, just not exercised until now.

swarm-ui.css keeps only the shared base reset (@hive/shared/base.css)
since that isn't any one component's concern. Added src/css.d.ts
(ambient '*.css' module) since tsc otherwise rejects a side-effect
import of a non-JS/TS specifier.

Side benefit: a component nothing imports (yet) no longer ships its
CSS either — StatusChip/Table aren't referenced from App.tsx today,
and main.css correctly only carries Shell.css + Panel.css. The old
central-import approach shipped all four unconditionally.

npm run build + typecheck clean. Re-screenshotted the real dist —
pixel-identical to before this change.
2026-08-12 21:33:44 +02:00
iris
67eb0c660d swarm-ui: page shell + layout primitives
Structural foundation split out of hyperhive#3118 per mara's steer:
structure first so the real overview page (hive roster) and later
routes (swarm-wide agent roster) land as content changes rather than
each reinventing chrome + nav + a table/panel/chip shape.

- <Shell>: header bar (branding) + nav row, wraps every route. Route
  list lives in Shell itself (one small SPA, one place to know its
  own nav).
- ui/Panel, ui/StatusChip, ui/Table: the three primitives the
  overview page's actual scope (hive roster: name/domain/status,
  linking out to each hive's own dashboard) calls for, nothing
  speculative beyond that.

Preact-native styling (plain CSS files imported via swarm-ui.css, no
shadow DOM — this package renders into light DOM) — not
@hive/shared's chrome.css, which is the old MPA dashboard's visual
language. Same base16/Catppuccin color tokens via theme.css/colors.css
so it still reads as hyperhive.

npm run build (whole frontend workspace) + npm run typecheck both
clean. Verified with a real headless-chromium screenshot against the
built dist, not just source-reading.

Fixes hyperhive#3211
2026-08-12 21:33:44 +02:00