Commit graph hyperhive/frontend/packages/dashboard/src/dashboard.css
Author SHA1 Message Date
iris
ccc69bbc7b fix(dashboard): contain rebuild spinner within icon boundary 2026-06-29 01:05:34 +02:00
iris
2bfa5bc1a8 feat(schedules): make schedules pausable
Adds pause/resume support for scheduled prompts.

Backend:
- New paused_at_unix column on scheduled_prompts table (added via
  ALTER TABLE migration so existing databases are upgraded on first
  start). The due-rows index is dropped and recreated to also exclude
  paused rows so the worker never fires them while paused.
- Worker's due() query gains AND paused_at_unix IS NULL filter.
- New pause(id) and resume(id) methods on ScheduledPrompts; both are
  idempotent and refuse cancelled rows.
- New POST /api/schedules/{id}/pause and /api/schedules/{id}/resume
  dashboard endpoints (operator-direct, no approval gate). Both emit
  a schedules snapshot on success so the tab updates live.
- WireSchedule gains paused_at_unix: Option<i64> so the frontend can
  render the state without an extra fetch.

Frontend:
- Paused rows render with a distinct row class + muted opacity.
- The next-fire cell shows a yellow pause glyph + tooltip with the
  paused-since timestamp and the would-have-fired time.
- Actions column: pause/resume toggle button (⏸/▶) beside fire/edit/cancel.
  Fire-now is disabled while paused (resume first).
- Sort order: active → paused → cancelled (paused slot keeps schedules
  visible without mixing them into the active top section).
- pauseSchedule() / resumeSchedule() async functions POST to the new
  endpoints and refresh the table on success.
2026-06-27 20:01:16 +02:00
iris
a8fb33e2ee fix(permissions): mark stale agents and allow removing their explicit entries
The P3RM1SS10NS tab showed agents that no longer exist in the live
container roster — e.g. an agent named 'root' that was renamed or
destroyed but still had explicit entries in tool-groups.json and/or
capabilities.json. The roster-union behaviour is intentional for
temporarily-stopped agents, but stale entries from renamed/destroyed
agents are confusing.

Backend (dashboard/permissions.rs):
- New DELETE /api/permissions/{agent} handler that bypasses the live-
  roster guard (intentionally — that's the point). Calls
  tool_groups::remove_agent + capabilities::remove_agent to clear both
  JSON files, then emits live SSE snapshots so the tab updates without
  a page reload. Format-checks the agent name but does not require it to
  be in the containers snapshot.

Frontend (permissions.js):
- renderCapabilities / renderToolGroups now cross-reference agentNames
  against containersState (the live roster, already imported). Agents
  not in the live roster get an isStale flag.
- Stale rows get a '(not running)' label and a '✕ remove' button that
  calls clearStaleAgent() — a new async helper that DELETEs the stale
  entry and re-fetches both perm tables.
- Non-stale agents without explicit assignments still get '(default)'.

CSS (dashboard.css):
- .perm-row-stale (reduced opacity), .perm-stale-label (muted small
  text), .perm-remove-btn (small red-bordered button) + disabled state.
2026-06-27 13:44:11 +02:00
iris
5ae5657ca4 fix(dashboard): extract themed-dialog styles into an importable modal.css
The themed dialog component (modal.js: themedConfirm / themedPrompt /
themedToast) is raised from common.js's data-async / data-confirm
handler, which every page loads — but its .tc-* styles lived only in
dashboard.css (the main tabbed dashboard's stylesheet). So on any
standalone page (core/C0R3, settings, flow, logs) a dialog rendered
completely unstyled: the build-queue rebuild-cancel confirm came up as
raw text.

Extract the .tc-* rules into a dedicated modal.css component (paired
with modal.js) and @import it from common.css, so the styles load
wherever a dialog can fire — not just the main dashboard. esbuild
inlines the @import into the common.css bundle, so there's no extra
request and the main dashboard is visually unchanged (it loads
common.css too). The stale 'themedConfirm() in common.js' comment is
gone with the move. Fixes #1910.
2026-06-22 22:24:42 +02:00
iris
08afa49c96 dashboard: themedPrompt is a textarea — Enter submits, Shift+Enter newline
The approval deny-reason prompt (and every themedPrompt dialog) used a
single-line input. Make themedPrompt always a resizable <textarea> with
chat-box keys: Enter submits (clicks the confirm button), Shift+Enter inserts
a newline. Short answers stay one keystroke; multi-line reasons (e.g. a deny
note) are now possible. No single-line variant — themedPrompt is only used by
the data-async data-prompt path, so all those dialogs get the textarea. Closes #1840.
2026-06-21 23:55:15 +02:00
iris
6bb976ec72 dashboard: replace agent-tab transient list with build-queue summary banner
The SW4RM tab rendered a per-transient spinner list above the container
list, duplicating the running-step badge already shown on each agent card.
Replace it with a single compact amber banner that appears when the rebuild
queue has active (queued/running) entries — 'build queue — N running / M
queued — view queue →', linking to the full queue on /core.html. Per-agent
detail stays on the cards; the top of the tab just gives the at-a-glance
summary + a jump to the queue. Closes #1817.
2026-06-21 22:07:14 +02:00
iris
7c2de690ec dashboard: theme all remaining dialogs (replace native confirm/alert/prompt)
Follow-up to the themed-modal component: route every remaining native
browser dialog through modal.js so nothing falls back to the OS chrome.

- modal.js: add themedPrompt (input dialog) + themedToast (non-blocking
  transient notification, info/error/ok) alongside openDialog/themedConfirm.
- Migrate call sites: bindAsyncForms confirm/prompt/alerts (common.js),
  the answer-validation alert (call.js), the M0V3 reparent confirm + action
  toasts (tabs.js), and the schedule form/cancel/fire confirms + validation
  and error alerts (schedules.js).
- UX: blocking modal for confirms/prompts; non-blocking toast for transient
  errors + validation. Destructive confirms keep the danger styling.
- CSS for the toast stack + prompt input.

common.js <-> modal.js is a safe deferred import cycle (usage is call-time
only); esbuild bundles it clean.
2026-06-19 02:10:18 +02:00
iris
03b10d22cd dashboard(swarm): themed stop confirm modal with graceful checkbox
Replace the browser-native confirm() on the agent ST0P actions (per-agent
menu + bulk) with an in-theme modal (themedConfirm in common.js), carrying a
'stop gracefully' checkbox. Checked sends POST /kill/<name>?graceful=1 (the
quiesce path); unchecked is today's immediate hard stop, unchanged. The modal
also covers the other destructive menu actions (restart / rebuild / destroy /
purge) so they no longer fall back to the OS dialog.
2026-06-19 00:20:05 +02:00
iris
74196ac582 dashboard(swarm): wrap agent status to two lines, always show set-time
The agent self-reported status rendered on one line with
white-space:nowrap + text-overflow:ellipsis. Because the "(set N ago)"
stamp trailed the text in the same clipped flow, a long status clipped
the set-time away entirely. Wrap the icon + message in a .status-msg
span clamped to two lines and make the age a non-shrinking flex sibling,
so the message wraps to two lines (then ellipsizes) and the set-time is
always shown.
2026-06-18 13:02:11 +02:00
iris
4297436d94 dashboard: single save-all button for the permissions page
Replace the per-row save buttons on the P3RM1SS10NS tab with one
page-level "save all (N agents)" button covering both the capabilities
and tool-groups matrices.

Toggling cells stages edits in the DOM; each checkbox carries its
render-time baseline (data-baseline). On save we diff against the
baseline and POST only the perm-types that actually changed per agent
to the new combined endpoint:

  POST /api/permissions { changes: [ { agent, tool_groups?, capabilities? } ] }

An omitted field leaves that perm-type untouched (no commit, no diff);
an included array fully replaces it. The backend coalesces caps + groups
for one agent into a single rebuild — no more double-rebuild when an
operator changes both for the same agent. The batch is atomic: on a
clean 200 the bar shows queued -> rebuilding and re-fetches (resetting
baselines); on any validation error nothing is applied and an error note
is shown.

Live capabilities_changed / tool_groups_changed re-renders are skipped
while the section has unsaved edits so a half-finished edit set is not
clobbered; the tab-activation and post-save re-fetches are the recovery
paths.

Pairs with the hive-c0re combined-PermPayload half (damocles).
2026-06-17 18:58:56 +02:00
iris
06c5d68071 refactor(dashboard): move SYST3M section CSS into shared system-sections.css
The C0R3 page (/core.html) pulled the four SYST3M panel styles (rebuild
queue, meta inputs, container load) by @importing the whole ~39kb
dashboard.css, dragging the entire dashboard stylesheet into the C0R3
CSS bundle.

Extract those section-internal rules into a new system-sections.css and
@import it from both core.css and dashboard.css. core.html now loads only
the small shared file (its CSS bundle drops from ~40kb to 5.3kb) while
the dashboard keeps the rules — a couple are still referenced from
dashboard JS (the schedules view uses .rqe-source*). The container-load
table's .hive-stats-table already lives in common.css, linked directly
by core.html, so it is unaffected.

Behaviour-preserving: identical rules, same custom properties; the moved
selectors are uniquely named to these panels and not redefined
elsewhere, so the cascade is unchanged. Verified the built bundles:
core.css carries the section rules and no dashboard chrome; dashboard.css
still carries them via the import.
2026-06-15 09:54:55 +02:00
iris
d7bba48176 fix(frontend): stack the warning banner + tab bar in one sticky wrapper (#1587)
Per mara's review: instead of measuring the banner height in JS and
offsetting the chrome's sticky top, put the warning banner and the page
chrome in the same sticky div so they stack naturally.

common.js builds the wrapper: ensureStickyTop() wraps the page's chrome
(.dashboard-chrome / .page-header) in a single .sticky-top container and
injects the warning banner as its first child. The banner and the chrome
are no longer individually sticky — .sticky-top owns the stickiness, so
they pin together in one context instead of two top:0 stickies colliding
(the banner used to overlay the tab bar). Pages without a chrome (the
H0M3 hub) get a banner-only sticky region. No per-page markup needed; no
JS height measurement. Build green.
2026-06-10 01:05:55 +02:00
iris
0dc85e8261 fix(frontend): flag gone-agent columns in the schedules table (#1586)
The schedules table builds a column per target, including agents that
have since been destroyed (their past schedules linger). Those columns
showed identically to live agents, so the operator couldn't tell a
schedule still targets something that no longer exists.

Flag them: renderSchedulesTableHead now checks each agent column against
the live roster (containersState) and, for any that isn't there (and
isn't 'operator', which is always valid), greys + strikes through the
column label and titles it 'no longer exists (gone)'. The rows stay
visible + cancellable — the operator can see the stale targets and clear
them rather than be surprised by phantom columns. Build green.
2026-06-10 00:59:07 +02:00
iris
5ef6a8b683 fix(frontend): put the dashboard home link and tab strip on one row (#1576)
.dashboard-chrome was a plain block, so the inline-block home back-link
and the block-level .tabbar stacked on two separate rows. Make the chrome
a flex row (align-items: center) so the '← home' link and the tab strip
sit side by side. The chrome now owns the horizontal gutter and the
single full-width bottom divider; .dash-home-back and .tabbar drop their
own side padding / border accordingly. CSS-only — the markup already had
both as siblings of the chrome header.
2026-06-09 13:37:07 +02:00
iris
bb2a031135 refactor(frontend): unify dashboard tabbar with the minimal pill chrome (#1464 step 3b)
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.
2026-06-09 11:25:58 +02:00
iris
49a0a0d15f refactor(frontend): extract the page-chrome into @hive/shared (#1464 step 3)
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.
2026-06-09 09:13:35 +02:00
iris
293e608ca4 refactor(frontend): invert the dashboard + H0M3 gutter (#1537)
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.
2026-06-09 00:18:01 +02:00
iris
09cb705738 refactor(frontend): move ST4TS to its own /stats.html page (#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.
2026-06-09 00:11:43 +02:00
iris
d9c66de069 refactor(frontend): move S3TT1NGS to its own /settings.html page (#1464 step 2)
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.
2026-06-08 23:44:22 +02:00
iris
2ec06f07ed refactor(frontend): migrate dashboard tabbar to createTabStrip + drop overflow (#1464 step 1)
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.
2026-06-08 23:17:13 +02:00
iris
742d4a5c6d fix(frontend): var-derive hardcoded palette colours for theme-swap safety
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.
2026-06-08 21:24:07 +02:00
iris
8ddab401f9 feat(dashboard): serve H0M3 at /, relocate dashboard to /dashboard.html (#1464)
Step 2 of the nav restructure: make the H0M3 menu hub the landing page
at / and move the dashboard SPA to /dashboard.html, with every surface
linking back to the hub.

Mechanism (frontend-only, no host-side change — confirmed against the
ServeDir route table): the dashboard's ServeDir fallback serves
index.html at / via append_index_html_on_directories, and there is no
SPA path-routing catch-all to break. The dashboard SPA is served as the
plain file dashboard.html so it never shadows the exact-match
/dashboard/stream + /dashboard/history SSE routes registered before the
fallback.

- Swap the HTML entry files: the H0M3 page becomes index.html (loads
  home.js → served at /), and the dashboard SPA becomes dashboard.html
  (loads tabs.js → served at /dashboard.html). build.mjs copies the new
  set; JS bundle names are unchanged (referenced by absolute /static/
  paths, independent of the HTML filename).
- H0M3 Dashboard tile now points at /dashboard.html.
- The dashboard gains a "← home" back-link in its chrome; flow.html and
  logs.html relabel their back-link from "← dashboard" to "← home"
  (href stays / — which is the hub now). Pages link to the hub, not to
  each other.
- Agent page (app.js + stats.js): the "↑ dashboard" link now targets
  /dashboard.html. The API base (rebuild / answer-question /
  mark-all-read POSTs) stays the origin root, unchanged.
- Comment-only: tabs.js / flow.js / common.js references to the
  dashboard's old index.html filename updated to dashboard.html.

Note for review: git renders the file swap as a deleted home.html + an
added dashboard.html + a heavily-modified index.html, because index.html
exists on both sides with swapped content. It's a content swap, not a
rewrite — the built dist/ is verified (index.html→home.js, dashboard.html
→tabs.js).

Deferred to a follow-up: removing the FL0W / L0GS / M4TR1X "→" page-links
from the dashboard tab strip (touches tabs.js gating/overflow), and the
shared reusable chrome component (a later step).
2026-06-07 21:00:34 +02:00
iris
0a7d66c19e cleanup(dashboard): remove dead CSS selectors
Four selectors defined in dashboard.css but referenced nowhere in the
repo (verified against all html/js/rs, including dynamically-built class
names):

- .role-m1nd / .role-ag3nt — old per-role colouring; only `.role-pending`
  is still applied (`'role role-pending'`), and there's no `'role-' +`
  builder, so the m1nd/ag3nt variants are unreachable.
- .journal-body — the journald viewer is now a link to the dedicated
  logs.html page (`menuLink('journal logs →', …)`), not an inline panel.
- .talkform (+ its select/input/placeholder/focus rules) — old operator
  T4LK form styling, no longer emitted.

Part of #1450. Pure dead-code removal, no behaviour change; dashboard
bundle still builds (dashboard.css 36.9kb → 36.1kb).
2026-06-06 08:10:12 +02:00
iris
03ea6d1bda feat(stats): per-container cpu/mem load (#1424 p3)
C0NT41N3R L04D on the SYST3M tab + GET /api/container-resources.

Backend (hive-c0re/src/container_stats.rs): reads cgroup v2 cpu.stat +
memory.{current,peak,max} for each running agent machine
(machine-h\x2d<name>.scope under machine.slice), read-only/world-
readable so no hive-priv. CPU is a two-sample (~200ms) host-normalised
percentage; one shared sleep covers all agents. Skips agents whose
scope dir is absent (= not running). Network omitted: agents share the
host netns, no per-container counter.

Frontend: a polled C0NT41N3R L04D table on SYST3M (agent / cpu / mem /
peak / limit with meter bars), reusing the ST4TS table style. Polls
/api/container-resources every 5s only while the tab is active.

Backend reviewed-in-principle by damocles (path escaping + cpu delta
math); ping for the on-host sign-off.
2026-06-05 23:06:33 +02:00
iris
8e74dda4c5 feat(dashboard): ST4TS tab — hive-wide stats view
Builds the dashboard UI on the /api/stats-hive endpoint: a new ST4TS
tab showing swarm totals (active agents, turns, tokens, labelled est
cost), a busiest-first per-agent table, and a model-mix bar list.
Plain tables/CSS bars — no chart lib in the dashboard bundle; per-agent
trend charts stay on each agent's own /stats page. Fetched on tab
activation + window change (pull-only, no SSE).

Also adds conn.busy_timeout(500ms) in hive_stats read_agent per review:
turn_stats is rollback-journal, so a read landing mid-INSERT would hit
SQLITE_BUSY and silently drop that active agent — wait the write out.
2026-06-05 22:51:11 +02:00
iris
70832fcbf1 fix(#1293): override global li padding on agent menu separator
common.css sets `li { padding: 0.5em 0 }` globally. The .agent-menu-sep
li inherits this, bloating the 1px separator into a ~16px thick bar.

Add `padding: 0` to .agent-menu-sep to reset it.
2026-06-04 20:06:18 +02:00
iris
b13a6911b7 feat(#1098): replace deployed:hash chip with config link in three-dot menu
Remove the deployed sha plain-text badge from the card head.
Add a forge-linked menu item to the three-dot menu instead:
'deployed:abc123def012 ↗' opens agent-configs/<name>/commit/<sha>
on the hive forge in a new tab.

Only shown when both c.deployed_sha and forgeBase are present.
buildAgentMenu now takes forgeBase as second arg; call site passes
the same forgeBase computed for nav-strip links.

CSS: add text-decoration: none + box-sizing to .agent-menu-item
so <a> link items render identically to the <button> items.
2026-06-03 01:11:56 +02:00
iris
5da7f6cd3a feat(#1097): overhaul schedule table style and layout
- Wrap table in .schedules-table-wrap (overflow-x: auto) so it
  never exceeds the page width on narrow viewports
- Add .schedules-table-next-col (width: 8em) and
  .schedules-table-every-col (width: 7em) with white-space: nowrap
  to stop those columns from stealing space from the body column
- Align visual style with the perm page tables: unified border on
  all cells (was: border-top row separator only), bg-elev thead,
  font-size 0.82em (was 0.9em), min-width: 100% on the table itself
2026-06-02 22:50:25 +02:00
iris
ea5115c3a6 feat(dashboard): split monolithic CSS into common + per-page bundles
dashboard.css was 2500+ lines covering every page. Split into four
separate esbuild entry points:

- common.css  — @hive/shared imports + shared typography, badges,
                buttons, inbox/compose, side panel, diff panel,
                path linkification, logs-header chrome (shared by
                flow + logs)
- dashboard.css — dashboard-only styles (tabs, container rows,
                  approvals, questions, permissions, schedules, etc.)
- flow.css    — flow page chrome (frosted header, composer, inbox pill)
- logs.css    — log viewer sub-tabs (journal-*, build-logs-*)

Each HTML file now loads common.css + its own page-specific bundle.
build.mjs updated to produce all four CSS outputs. All builds pass.

Closes #1056
2026-06-02 11:19:32 +02:00
iris
c7f02993d0 chore(#1055): address argus review nits
- rename cap-cap-col → cap-col; add CSS rules for .cap-col and .cap-save-col
- drop zero-width-space replace in capability header cells (nowrap makes it a no-op)
- add Capability::ALL to hive-sh4re; validate incoming cap strings in post_capabilities
2026-06-02 11:15:33 +02:00
iris
013e8740bd feat(#1053): permissions tab — capabilities UI + move tool-groups
Add a new P3RM1SS10NS tab to the dashboard that consolidates all
per-agent permission configuration:

Backend:
- GET /api/capabilities returns { caps: [...], assignments: {...} }
  driven by Capability::ALL variants (manage_root_agent,
  read_host_journal, query_agent_state)
- POST /api/capabilities/{agent} writes capabilities.json and queues
  a rebuild so HIVE_CAPABILITIES takes effect

Frontend:
- New 'permissions' entry in TABS, placed after 'system'
- P3RM1SS10NS tab pane with two sections:
  C4P4B1L1T13S — agents × capabilities checkbox matrix (.cap-*)
  T00L GR0UPS — agents × tool-groups checkbox matrix (.tg-*) moved
    from SYST3M tab
- activateTab('permissions') fetches both tables; neither has an SSE
  channel so they re-fetch on each activation to stay fresh
- CSS for .cap-* mirrors the .tg-* layout (scrollable, Catppuccin)
2026-06-02 01:11:07 +02:00
iris
2a62a561c4 chore: frontend cleanup — replaceChildren, missing source badge, http case
- Replace innerHTML = '' with replaceChildren() throughout tabs.js and
  app.js (12 + 7 sites). paintAtomic already used replaceChildren; now
  the direct-clear sites are consistent with it.
- Add missing .rqe-source-startup_sweep CSS rule (startup_sweep is a
  valid QueueSource variant but had no badge style, falling through to
  the base muted appearance with no explicit intent).
- Lowercase the one uppercase 'HTTP ' in the fetchAndRenderToolGroups
  error path to match every other fetch error in the file.
2026-06-01 22:48:21 +02:00
iris
9efe9ca175 fix(#1029): prevent tab labels from wrapping to next line
Add white-space: nowrap to .tab-label so text like '◆ Y3R C4LL ◆'
never breaks at spaces. Add flex-shrink: 0 to .tabbar .tab so tabs
keep their natural width rather than compressing — overflow detection
then correctly moves tabs that don't fit into the ⋮ dropdown.
2026-06-01 22:30:46 +02:00
iris
912f9c5ed2 feat(#1007): select-as-action — picking a parent directly triggers M0V3
Selecting any option in the M0V3 dropdown now fires confirm+POST
immediately; no separate button needed. On cancel or after the
request completes, the select resets to the placeholder. Removes
the now-redundant btn-move button and its CSS rule.
2026-06-01 20:31:35 +02:00
iris
a7d31046a6 refactor(#1005): rename capabilities → tool-groups throughout UI
Section heading, element id, CSS classes, and JS functions all renamed
from 'capabilities'/'cap-*' to 'tool-groups'/'tg-*' to accurately
describe what the UI manages (tool-group permissions, not a capabilities
system).
2026-06-01 20:27:28 +02:00
iris
86a1591cfc feat(#1005): capabilities UI — per-agent tool-group table in SYST3M tab
Backend (hive-c0re/src/dashboard.rs):
  GET /api/tool-groups  — returns { groups: [...], assignments: {...} };
    groups list comes from ToolGroup::ALL so the UI needs no change when
    a new group is added (satisfies the 'no extend ui' requirement)
  POST /api/tool-groups/{agent} — accepts { groups: [...] }, calls
    set_groups() then enqueues a rebuild so the new HIVE_TOOL_GROUPS
    env var takes effect immediately

hive-sh4re/src/lib.rs:
  Added ToolGroup::ALL const (ordered slice of every group)
  Added ToolGroup::as_str() — snake_case wire name, matches serde

Frontend:
  SYST3M tab: new C4P4B1L1T13S section above K3PT ST4T3 with
    #capabilities-section placeholder
  tabs.js: fetchAndRenderCapabilities() + renderCapabilities() —
    columns are built from the groups array returned by the API;
    each row has one checkbox per group and a save button that POSTs
    and re-fetches after 800ms; agents without explicit assignments
    show a (default) label; triggered on each SYST3M tab activation
  dashboard.css: .cap-table-wrap/.cap-table/.cap-row/.cap-agent-*
    styles for the scrollable matrix table
2026-06-01 20:19:11 +02:00
iris
e8d391be13 fix(#1001): replace var(--mauve) with var(--purple) throughout dashboard.css
--mauve is not declared in base.css; the palette exposes the Catppuccin
Mocha mauve colour as --purple (#cba6f7). Affected rules:
  .btn-move colour + border
  .logs-back colour (back-link on logs page)
  .logs-tab.logs-tab-active text colour (issue #1001 — tab shows no state)
  border colour on a schedule input

Combined with the --surface1/2 fix from the previous commit, the active
log tab now shows a solid background (--border) with purple text (--purple).
2026-06-01 19:52:21 +02:00
iris
e045a29508 fix(#994,#996): replace undefined CSS vars; guard overflow rebuild when open
dashboard.css used var(--surface0/1/2) and var(--text) throughout the
agent context menu, tabbar overflow dropdown, and logs toolbar — none
of which are declared in base.css (the palette only defines --bg-elev,
--border, --purple-dim, --fg etc). This caused all three dropdowns to
render with a transparent background (issue #996).

Replacements:
  --surface0  → --bg-elev   (dropdown background)
  --surface1  → --border    (hover / active state)
  --surface2  → --purple-dim (border / separator)
  --text      → --fg        (label colour)

Also addresses two argus yellows from the PR #997 review:
- Skip updateTabbarOverflow() early when the dropdown is open; avoids
  DOM flicker from the 1s badge-update tick while the menu is visible.
- Fall back to 32px when overflowBtn.offsetWidth === 0 (hidden on the
  first render) so the initial width measurement is not off.
2026-06-01 19:49:43 +02:00
iris
e9d22ef2d3 feat(#994): tabbar overflow menu — settings and logs in ⋮ by default
Add a ⋮ overflow button at the right end of the dashboard tab strip.

SETTINGS and LOGS always live in the overflow dropdown (marked
data-overflow="default" in index.html — never rendered in the main bar).

Dynamic overflow: when the bar is too narrow to fit all remaining tabs,
rightmost tabs spill into the dropdown right-to-left. Implemented via
JS measurement + ResizeObserver; no CSS-only relayout trick needed.

The ⋮ button:
- hidden when the dropdown is empty (wide screens with only the default
  tabs overflowed, which are always there anyway → button always shown)
- gets .tabbar-overflow-active when the current hash tab is inside
- dropdown items clone the tab label + count badge from the original
- closes on outside click / Escape

MutationObserver on the tabbar catches P33RS/M4TR1X hidden-attribute
changes so the overflow recalculates when those tabs are shown/hidden.
2026-06-01 19:39:17 +02:00
iris
ee61eac663 fix(989): correct four live build-log viewer bugs in logs.js
- live detection: use !h.status (null while running) not === 'running'
- SSE: replace named addEventListener('chunk'/'done') with onmessage +
  frame.done check — backend sends generic message events, no named type
- SSE payload: use frame.stdout_append/stderr_append not frame.text
- timestamps: started_at/finished_at are unix seconds, multiply by 1000
  was already done implicitly — actually use unix arithmetic directly
  to avoid new Date() confusion entirely

Also move the misplaced .flow-main-slim rule from the logs section to
the flow-shell section of dashboard.css.
2026-06-01 19:20:54 +02:00
iris
0b15cad93f feat(#986): dedicated logs page with build/agent/system sub-tabs
Add /logs.html as a standalone page (same back-link pattern as flow.html):
- BUILD tab: all-agents build log history via new GET /api/build-logs endpoint
- AGENT tab: per-container journald viewer with agent selector + unit filter
- SYSTEM tab: host-side hive-c0re.service logs via new GET /api/journal-host endpoint

Remove inline log drill-ins from SW4RM container rows (buildJournalTrigger
and buildBuildLogsTrigger) — log viewing now lives on the dedicated page.

flow.html: strip the full dashboard tabbar, replace with a simple back link
matching the new logs page chrome.

index.html: add L0GS tab link to /logs.html in the tab strip.

Backend additions:
- build_logs::list_recent_all — cross-agent query (newest first, cap 100)
- GET /api/build-logs — all-agents variant backed by list_recent_all
- GET /api/journal-host — host journald (no -M container flag), restricted
  to allow-listed units (hive-c0re.service)
2026-06-01 19:20:54 +02:00
iris
ba43b5f869 fix(988): hide inapplicable actions in agent context menu
Per mara feedback: don't show disabled actions at all.
- restart/stop only rendered when agent is running
- start only rendered when agent is stopped
- removed .agent-menu-item:disabled CSS rule (no longer needed)

Also switch Escape handler to stopImmediatePropagation so the
selection-clear keydown listener doesn't co-fire when closing a menu.
2026-06-01 19:18:18 +02:00
iris
28e0d1b7fa feat(987): add per-agent three-dot context menu on SW4RM tab
Each agent card in the SW4RM tab now has a ⋮ button that reveals a
dropdown with single-agent lifecycle actions: R3ST4RT, ST0P, ST4RT,
R3BU1LD, DESTR0Y, PURG3. Items are state-aware (disabled when action
doesn't apply). The button is CSS-invisible until the row is hovered
or the menu is open — quiet rows stay clean. DESTR0Y and PURG3 are
hidden for the manager container.

Click-outside and Escape close the menu. A tree-rebuild (on any SSE
state update) also closes it to avoid stale DOM references.
2026-06-01 19:18:18 +02:00
iris
050e130eba build-logs: SSE live streaming + raw download (#726 phase 4)
Backend (hive-c0re):
- build_logs.rs: add tokio::sync::broadcast::Sender<i64> to BuildLogs;
  append() and finish() notify subscribers after each sqlite write.
  Add BuildLogProgress struct and get_progress(id, stdout_cursor,
  stderr_cursor) for incremental delta reads.
- dashboard.rs: two new endpoints —
    GET /api/build-logs/id/{id}/stream  SSE; streams BuildLogFrame
      {stdout_append, stderr_append, status?, done} deltas until the
      build finishes or the browser disconnects. Backed by an mpsc
      channel task that watches the per-build broadcast notifications.
    GET /api/build-logs/id/{id}/raw    text/plain download with
      Content-Disposition: attachment; filename build-log-{agent}-{id}.txt

Frontend (dashboard):
- tabs.js: running builds (status === null) connect an EventSource to
  /stream and append lines live; "live" badge pulses amber while active,
  flips to ok/fail on done. Finished builds still use the JSON fetch path.
  Collapsing a running panel closes the EventSource; re-expanding
  reconnects. Adds a "⬇ raw" download link to every expanded row.
- dashboard.css: .build-logs-dl inline download link; .build-logs-live
  live pulse @keyframes animation.

Docs: web-ui.md updated for all three new endpoints + behaviour.
2026-05-31 21:48:31 +02:00
iris
c4ce91b61f build-logs viewer: add --crust var + aria-expanded on row buttons (argus nits) 2026-05-31 21:48:31 +02:00
iris
cc6227ccb5 dashboard: build-log side-panel viewer in agent card (#726 phase 3)
Adds a `↳ build logs · {agent}` drill-in to every agent card that
opens the side panel and fetches the last 10 build-log headers from
GET /api/build-logs/{agent}. Each row shows a status chip (ok/fail/
running), kind, age, and truncated cmdline. Clicking a row lazily
fetches the full stdout+stderr from GET /api/build-logs/id/{id} and
expands it inline as a scrollable pre.

CSS additions: .build-logs-{panel,toolbar,list,item,row-btn,...} plus
.badge-ok/.badge-fail/.badge-running status chips shared with future
uses. docs/web-ui.md updated with the new drill-in and the new badge
classes.
2026-05-31 21:48:31 +02:00
iris
65f385a7a0 fix(#804): spinner arc needs border-radius: 50% to travel a circle
The ::after spinner ring used border-radius: 11px on a ~90px element,
making it a rounded rectangle. The amber arc swept around a rectangular
path, reading as a spinning border side. border-radius: 50% makes it a
true circle and the arc sweeps smoothly like a classic loading spinner.
2026-05-31 21:47:03 +02:00
iris
6d7ec90843 dashboard: revise rebuild spinner — transparent ring, single rotating arc (#804 follow-up)
PR #807 fix (faint amber full ring + brighter top arc) still reads
as 'a border around the icon, with a brighter top' rather than as
a loading spinner — mara reports it's still broken.

This iteration drops the full ring entirely. The ::after now has
transparent borders on three sides plus a bright amber top arc, so
only the rotating arc itself is visible — no static ring frame
around the icon. Same shape every desktop OS uses for circular
progress indicators (Material, Apple's spinner, etc.).

Other tweaks:
- 3px stroke (was 2px) so the arc carries enough visual weight
- inset: -5px + border-radius: 11px to keep the arc hugging the
  icon's 6px-radius square at the new stroke width
- 0.9s rotation period (was 1s) — slightly snappier read

Same DOM, same .pending-running class trigger; pure CSS swap.
2026-05-31 18:00:06 +02:00
iris
6cf169d324 dashboard: new S3TT1NGS tab housing the notification toggle (closes #763)
Adds a fifth tab (◆ S3TT1NGS ◆) at the end of the dashboard tab
strip; moves the browser-notification controls out of the chrome
header into the new pane.

### Why

The chrome was carrying the notification controls as 'always-on
ergonomics' but the surface they belong on is operator preferences,
not navigation. With #589 / #701 likely adding more per-browser
preferences later (theme / density / etc.), having a dedicated
S3TT1NGS pane gives them a home so the chrome strip doesn't keep
accreting controls.

### Shape

- index.html: new `#tab-pane-settings` section with H2 + intro +
  H3 'browser notifications' group; same notif-row DOM (same IDs)
  migrated from the chrome
- flow.html: drops the duplicated notif-row from its chrome (FL0W
  reused the dashboard's strip); adds a `◆ S3TT1NGS ◆ →`
  cross-page link to `/#settings`. Notifications still fire on
  FL0W when enabled — NOTIF.show() reads
  Notification.permission + the hyperhive.notify.muted localStorage
  key directly, no buttons needed in the page DOM.
- tabs.js: adds 'settings' to TABS array so hash routing activates
  the pane
- dashboard.css: drops the chrome-anchoring `.tabbar #notif-row`
  rule and the flow-page reuse comment, both obsolete; the general
  `.notif-row` styling stays
- docs/web-ui.md: new `### S3TT1NGS tab` section; Chrome-header
  bullet drops 'Notification controls' + adds S3TT1NGS; Browser
  notifications section pointers at the new pane

### No JS or backend changes

Same IDs (notif-row, notif-enable, notif-mute, notif-unmute,
notif-status) so `NOTIF.bind()` in common.js wires the new pane
unchanged. NOTIF.show() dispatch is DOM-independent.

### Validation

`npm run --workspace @hive/dashboard build` clean (tabs.js 167.8kb,
flow.js 37.4kb, dashboard.css 50.2kb).
2026-05-31 17:55:30 +02:00
iris
dc8c71c687 dashboard: fix rebuild spinner shape (#804)
Mara reported the rebuild spinner rendered as a rotating L-corner
('_|' / '⌐') instead of a smooth orbiting arc. Two issues:

1. The previous version coloured TWO adjacent border sides amber
   (border-top + border-right). With the rest of the border
   transparent, this paints a sharp L-shape at the icon corner —
   which when rotated reads as a spinning border-corner, not a
   loading spinner.

2. @keyframes had only `to` defined. Safer to include explicit
   `from { transform: rotate(0deg) }` so any browser that doesn't
   default cleanly still picks up the rotation.

Fix is the classic CSS spinner shape: faint amber ring around the
full icon perimeter (`border: 2px solid rgba(250, 179, 135, 0.2)`)
with one brighter top arc (`border-top-color: var(--amber)`) that
rotates. Reads unambiguously as a loading indicator.

Closes #804.
2026-05-31 15:36:38 +02:00