Commit graph hyperhive/frontend
Author SHA1 Message Date
iris
6c74c6dafa feat(agent): model quick-picker in overflow menu
Adds three one-click model shortcuts (haiku / sonnet / opus) at the
bottom of the agent overflow menu (⋯), below a separator and a small
'model' section label.

- The active model alias is highlighted with .active (purple border +
  tint) so the operator can see at a glance which model is current.
  Matching tolerates both short aliases (haiku) and full API names
  (claude-3-5-haiku-20241022) via an endsWith check.
- Clicking the active model is a no-op (closes menu, no POST).
- Clicking a different alias calls postModel() and closes the menu.
- renderModelChip() keeps the picker buttons in sync on every
  /api/state refresh without rebuilding the menu.

Before this change the operator had to type /model <name> in the
compose box. The overflow menu provides a discoverability path for
operators who don't remember slash commands.
2026-06-05 13:14:17 +02:00
iris
e24cfba124 feat(dashboard): preserve call-count prefix on hive-identity title update
On refreshState, the hive-identity title update (fired once when
hive_name/swarm_name are configured) now reads any existing (N) prefix
and preserves it, so the title doesn't briefly drop back to the bare
identity string before refreshTabCounts re-applies the prefix.
2026-06-05 13:12:33 +02:00
iris
fa62417328 feat(dashboard): prefix browser tab title with pending call count
When approvals or questions are waiting, document.title is prefixed
with (N) so the operator can see activity in a background browser
tab without switching windows. Clears to the base title when zero.

Implemented in refreshTabCounts (1s tick): strips any existing
prefix before re-applying so the hive-identity title update (once
on state load) composes cleanly.
2026-06-05 13:12:33 +02:00
iris
2a5444fdc8 feat(dashboard): live-tick agent status-age chip via data-set-at + 30s interval
Stamp data-set-at on .status-age spans so the '(set N ago)' label
stays accurate as time passes. Previously the age was computed once
when the async dashboard-state fetch completed and never updated.

With the keyed container row cache, rows persist much longer between
rebuilds (no more full rebuild on every SSE event), making stale
status-age labels more noticeable. The 30s ticker fixes this without
requiring a full row rebuild or re-fetch.
2026-06-05 13:08:53 +02:00
iris
f011638b5e perf(dashboard): keyed container row cache — skip rebuild + async fetch for unchanged rows
Maintain a module-level containerRowCache (Map<name, {el, fingerprint}>)
that preserves <li> elements across renderContainers calls. Each row's
fingerprint encodes everything that affects its rendered output:
container running/login/update/reminder state, derived pending/opRunning
labels, tree position (depth, isLast, ancestorIsLast), selection, agent
question counts, and link-base context.

When the fingerprint is unchanged the existing DOM node is reused:
- no replaceChildren wipe for stable rows
- the async dashboard-state fetch (nav strip, ctx badge, status text)
  is skipped — previously-fetched data stays in place
- DOM order is reconciled via insertBefore with zero layout work for
  in-place nodes

Before this change every SSE event (container_changed, rebuild_queue_
changed, transient_set/cleared, question_added/resolved) caused a full
wipe + rebuild of the entire container list, triggering N concurrent
/api/dashboard-state fetches where N is the number of running agents.

After this change only the rows whose state actually changed are
rebuilt; the rest survive intact across re-renders.
2026-06-05 13:08:53 +02:00
iris
59798f72f1 fix(dashboard): auto-reset stale per-agent questions filter on re-render
If the operator had an agent:foo filter active and all of foo's questions
were resolved, foo's chip disappears from the filter row on the next
render — but the stored filter value is still agent:foo. The section
then shows "no questions match this filter" with no active chip visible,
leaving the operator confused.

Fix: compute the set of valid filter values (all, operator, peer, plus
one agent:<name> per current participant) before rendering. If the stored
value is not in the set, silently reset it to 'all'. Write directly to
localStorage rather than via setQuestionsFilter() to avoid a re-entrant
renderQuestions() call.
2026-06-05 13:06:07 +02:00
iris
6c6d6f67e7 fix(dashboard): preserve q-history details open state on SSE-triggered renderQuestions
When a question_added or question_resolved SSE event fires, the handler
calls renderQuestions() directly — bypassing the snapshotOpenDetails /
restoreOpenDetails pair that refreshState wraps around it. This caused
the answered-history <details> (data-restore-key="q-history") to
collapse every time a question arrived or was resolved while the
operator had it open.

Fix: call snapshotOpenDetails() at the start of renderQuestions() and
restoreOpenDetails() at the end. The double-restore in the refreshState
path is harmless — re-opening an already-open <details> is idempotent,
and restoreOpenDetails is a no-op when the snapshot set is empty.
2026-06-05 13:05:33 +02:00
iris
defcb5df0b fix(agent): use gateway-relative dashboard URL when behind hive-gateway
When the agent page is accessed via hive-gateway (path is /agent/<name>/)
the dashboard lives at the same origin's root (/), not at the direct
TCP port. Both app.js (overflow menu / setHeader) and stats.js
(dashboard-link anchor) previously hardcoded http://hostname:port which
is wrong or unreachable behind HTTPS TLS termination.

Detection: if location.pathname starts with '/agent/', use
location.origin + '/' as the dashboard base. Otherwise fall back to the
original direct-TCP construction for non-gateway deploys.
2026-06-05 13:05:01 +02:00
iris
f87a674e14 fix(flow): flashError was inserting at top of terminal (invisible)
The compose box's send-error path used:
  flow.insertBefore(row, flow.firstChild)

This inserts the error before the first (oldest) child — at the top of
the terminal's scroll area — which is completely out of view since the
operator is at the bottom watching new messages.

Changed to flow.append(row) + scroll the wrap element to the bottom so
the error appears where the operator is looking. Also prefixed the
message with '✗ ' so it's visually distinct from regular flow rows.
2026-06-05 13:02:48 +02:00
iris
435be4302e fix(dashboard): init_config kind label in approval history + remove dead ticker loop
Two small fixes in tabs.js:

1. renderApprovalHistory showed 'spawn' for init_config approvals.
   The pending-approvals path already handles the three kinds correctly
   (apply / init / spawn); history used a two-branch ternary that fell
   through to 'spawn' for init_config. Fixed to match the pending path.

2. The 1s interval that ticks rebuild-queue elapsed badges contained a
   second loop polling '.build-logs-runtime[data-bl-elapsed]'. No code
   in tabs.js ever sets that attribute — the logs page manages its own
   elapsed timers inside logs.js. Removed the dead loop.
2026-06-05 13:02:33 +02:00
iris
5fe750eaad feat(logs): auto-scroll live build output, elapsed-time ticker, SSE-driven list refresh
Three UX improvements for the live build log viewer:

1. Auto-scroll (sticky-bottom): live build output now scrolls to the
   bottom as new lines arrive. Stops auto-scrolling when the operator
   manually scrolls up; resumes when they scroll back to the bottom.
   Same intent-tracking pattern used by the terminal pane.

2. Elapsed-time ticker: running builds show a live seconds/minutes
   counter in the row header that ticks every second. Stops and shows
   the final duration when the build finishes (done frame received)
   or errors out.

3. SSE-driven list refresh: subscribes to /dashboard/stream and
   debounces a fetchBuild() call (2s) whenever rebuild_queue_changed
   fires while the BUILD tab is active. New log entries and status
   changes appear without a manual refresh.
2026-06-05 12:06:47 +02:00
iris
f174c5d32c fix(dashboard): guard capabilities/tool-groups SSE handlers against in-progress edits
- Add 'capabilities-section' and 'tool-groups-section' to
  MANAGED_SECTION_IDS so operatorIsTyping() covers them too
- applyCapabilitiesChanged and applyToolGroupsChanged skip re-render
  when the operator has focus inside the section, preventing the
  table from being torn down under an in-progress checkbox edit.
  Tab-activation re-fetch is the recovery path for any missed event.
2026-06-05 12:06:32 +02:00
iris
13b0844c41 fix(dashboard): SYST3M tab count was always 0 (PascalCase vs snake_case)
refreshTabCounts() compared entry.state against 'Queued' / 'Running'
(PascalCase) but QueueState serialises as snake_case per
#[serde(rename_all = "snake_case")] — wire values are 'queued' /
'running'. Tab badge was always 0 regardless of rebuild-queue depth.
2026-06-05 12:06:32 +02:00
iris
2080fd3866 feat: live SSE updates for P3RM1SS10NS tab (capabilities + tool groups)
Add CapabilitiesChanged and ToolGroupsChanged DashboardEvent variants
so the P3RM1SS10NS tab reflects perm changes without the operator
navigating away and back.

Backend:
- DashboardEvent::CapabilitiesChanged { seq, caps, descriptions,
  assignments } — same payload shape as GET /api/capabilities
- DashboardEvent::ToolGroupsChanged { seq, groups, descriptions,
  assignments } — same payload shape as GET /api/tool-groups
- Coordinator::emit_capabilities_snapshot() and
  emit_tool_groups_snapshot() — read from the JSON files and broadcast
- rebuild_queue.rs PermChange worker: emit after each successful
  commit_capabilities / commit_tool_groups call

Frontend:
- applyCapabilitiesChanged(ev): calls renderCapabilities(root, ev)
- applyToolGroupsChanged(ev): calls renderToolGroups(root, ev)
- Both registered in MUTATION_HANDLERS
- activateTab comment updated (SSE now covers perm changes)

Docs: dashboard.md and CLAUDE.md updated.

This completes SSE coverage for all dashboard sections: SW4RM,
Y3R C4LL, SYST3M, SCH3DUL3S/reminders, and P3RM1SS10NS all
derive live updates from /dashboard/stream.
2026-06-05 12:06:32 +02:00
iris
d0b038e17d fix(dashboard): refresh reminders on SCH3DUL3S tab activation + update stale comments
- activateTab('schedules') now calls both refreshSchedules() and
  refreshReminders() since both sections live on the same tab.
  (The previous SYST3M/system target was wrong.)
- Update index.html comment to reflect schedules_changed SSE coverage
- Update index.html reminders comment to mention reminders_changed SSE
- Update tabs.js reminders section comment to reflect SSE coverage
2026-06-05 12:06:32 +02:00
iris
a1e46e2b3d feat: live SSE updates for the SYST3M reminders section
Add RemindersChanged SSE event so the pending-reminders list in the
SYST3M tab updates live without polling.

Backend emission sites (every path that mutates the reminders table):
- agent_server: store_remind (remind MCP call)
- dashboard.rs: post_cancel_reminder, post_retry_reminder
- questions.rs: cancel_loose_end Reminder kind
- reminder_scheduler: after each delivery batch (any_delivered)

Coordinator gets emit_reminders_snapshot() mirroring the existing
emit_schedules_snapshot() pattern: lists PendingReminder rows from the
broker and emits DashboardEvent::RemindersChanged.

Frontend: applyRemindersChanged(ev) calls renderReminders(ev.reminders)
and is registered as reminders_changed in MUTATION_HANDLERS.

Docs: dashboard.md reminders_changed entry; CLAUDE.md file map updated.
2026-06-05 12:06:32 +02:00
iris
76c4a67b1c feat: live SSE updates for the SCH3DUL3S tab
Add `SchedulesChanged` to the dashboard event channel so the
operator's schedule list updates in real time without requiring a
tab-activation or form-submit refresh.

Backend:
- `dashboard_events.rs`: new `SchedulesChanged { seq, schedules }`
  variant carrying a full `Vec<WireSchedule>` snapshot (same
  snapshot-over-diff rationale as `RebuildQueueChanged`).
- `coordinator.rs`: `emit_schedules_snapshot()` helper — queries the
  scheduled_prompts list, converts to wire shape, broadcasts the event.
- `dashboard.rs`: call `emit_schedules_snapshot()` at the end of each
  operator API handler that mutates a schedule:
  `post_schedule_new`, `post_schedule_fire_now`,
  `patch_schedule`, `post_schedule_cancel`.
- `scheduled_prompts_worker.rs`: call `emit_schedules_snapshot()`
  after each tick that fires schedules, so `last_fired_at_unix`,
  `next_fire_at_unix`, and reaped one-shots surface live.

Frontend:
- `tabs.js`: add `applySchedulesChanged(ev)` — replaces
  `schedulesState` from the snapshot and calls `renderSchedulesList()`.
  Registered in `MUTATION_HANDLERS` as `schedules_changed`.
  Tab-activation re-fetch kept as safety net for approval-path
  inserts and disconnect windows; comment updated to reflect this.

Docs:
- `docs/web-ui/dashboard.md`: document `schedules_changed` event.
- `CLAUDE.md`: add `SchedulesChanged` to the file-map entry.
2026-06-05 12:06:32 +02:00
iris
0d32048abe feat(stats): surface reminder_stats in the summary chip row
The backend already fetches ReminderStats from the broker RPC and
includes it in the /api/stats snapshot, but the frontend was not
rendering it. Add three chips to the summary row when the field is
present: reminders scheduled / delivered / pending.
2026-06-05 01:14:45 +02:00
damocles
cf6479b3a9 fix(#1307) add appearance:none to .btn to strip browser UA button chrome 2026-06-04 21:04:45 +02:00
damocles
9f8694e3cf fix(#1249): break sticky-bottom lock when user scrolls up during snap animation 2026-06-04 20:10:37 +02:00
iris
0723737d89 fix(#1029): use getBoundingClientRect for tab overflow cutoff
The old measurement used tabbar.clientWidth which includes horizontal
padding (~2em / ~30px total), causing an over-allocation of the same
amount. Flex gap between tabs (0.2em) was also not counted in the
accumulated offsetWidth, adding another ~20px of error. Together these
caused the algorithm to leave too many tabs visible, overflowing the
bar and pushing the ⋮ button off the right edge of the screen.

Replace the clientWidth + cumulative-offsetWidth approach with
getBoundingClientRect: the cutoff is the tabbar's actual right edge
minus its right padding, minus a reserve for the overflow wrapper.
Individual tabs are overflowed once their right edge exceeds the
cutoff; subsequent tabs are overflowed unconditionally to keep the
visible set contiguous and left-anchored.
2026-06-04 20:10:19 +02:00
iris
0f77fc3598 fix(#1297): prevent scroll jump on loadMore prepend
Two cooperating fixes:

1. overflow-anchor: none on .live — disables browser scroll anchoring
   so the manual scrollTop compensation in loadMore() is the sole
   mechanism. Without this the browser auto-adjusts scrollTop when
   rows are prepended above the viewport, then our += delta doubles
   the compensation, causing the erratic jump.

2. updateLoadMoreBtn() moved before the beforeH baseline capture —
   if histHasMore becomes false the load-more button is removed here
   rather than after the delta is applied, so the button height is
   already baked into beforeH and doesn't shift the viewport post-
   compensation.
2026-06-04 20:08:29 +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
e0ea22f3ee refactor(#1292): remove agent-owned fields from ContainerView
Now that the dashboard fetches agent-owned state directly from
GET /api/dashboard-state (via gateway), hive-c0re no longer needs
to read those fields from disk on the agent's behalf.

Removed from ContainerView:
  ctx_tokens, context_window_tokens, rate_limited,
  extra_links, status_text, status_set_at

Removed from container_view.rs:
  DashboardLink struct, build_nav_links, read_dashboard_links,
  read_status, is_rate_limited, read_last_turn, resolve_ctx_window
  (and the resolve_ctx_window unit tests)

Removed from dashboard.rs:
  GET /api/agent/{name}/links route + get_agent_links handler

dashboard JS (tabs.js):
  Merged rate_limited, ctx-window badge, and status-text rendering
  into the async dashboard-state fetch block. c0re still provides
  needs_login (auth sentinel on host), needs_update, pending_reminders,
  running, deployed_sha, parent — all genuinely host-side fields.
2026-06-04 19:39:06 +02:00
iris
40845c21cd feat(#1292): add GET /api/dashboard-state to hive-ag3nt
hive-ag3nt now exposes a lean DashboardState endpoint that returns the
agent-owned fields the dashboard card needs:

  - status_text / status_set_at  (from hyperhive-status on disk)
  - ctx_tokens / context_window_tokens  (from Bus)
  - rate_limited  (from Bus)
  - links  (from agent_links — includes screen link for GUI agents,
            which c0re's disk-based fallback cannot determine)

The dashboard fetches `${containerBase}/api/dashboard-state` instead of
the previous c0re proxy `/api/agent/{name}/links`. For gateway deployments
this is a same-origin call to the agent via the gateway's unix-socket
upstream; for direct TCP it hits the agent port directly. Both paths fail
gracefully (empty strip) when the agent is starting up.

The main behaviour fix: GUI agents now show the screen link in the dashboard
nav strip. c0re's build_nav_links reads /etc/hyperhive/gui.json from outside
the container (not possible), so it always omitted the screen link.
2026-06-04 19:16:40 +02:00
iris
750f64b7f0 feat(#1290): show per-agent loose-end question count in dashboard
Add a ' N' badge to each container row showing the count of pending
questions where the agent is the asker (awaiting an answer) or the
target (owes a reply). Derived live from questionsState — no backend
field needed.

Details:
- New .badge-loose-ends CSS class (purple, matching Q33R1ES section colour)
- Badge tooltip breaks down the count: 'N asked, N to answer'
- renderContainersFromState() called on QuestionAdded/QuestionResolved
  so the badge updates instantly when question state changes
- Existing  reminders badge kept separate (different signal)
2026-06-04 19:06:56 +02:00
iris
bddeb5478e fix(#1286): pretty-print system events in the agent terminal
The catch-all renderer was dumping raw JSON (in loud orange) for
claude's system events like api_retry — producing noise like:
  ! {"attempt":1,"error":"unknown","subtype":"api_retry",...}

Replace the single 'init' filter with a full system-event block:
- api_retry → muted note: '⚠ api retry 1/10 · unknown · 502ms'
- api_error → amber note:  '✗ api error · <message>'
- other subtypes (context_window_exceeded, etc.) → '⚙ <subtype>'

The loud orange sys catch-all is now reserved for truly unrecognised
top-level event types, not routine operational noise.
2026-06-04 18:54:33 +02:00
iris
0894030754 fix(#1285): handle restart queue kind in dashboard container row
The pending-label derivation had no case for op.kind === 'restart', so
it fell through to the rebuild/rebuild-queued else branch — showing a
misleading 'rebuild pending' badge when the operator issued a restart.

Add explicit 'restarting' / 'restart queued' cases alongside the
existing meta_update and destroy cases.

Also add restart / startup_sweep / perm_change to QUEUE_KIND_GLYPH so
they render with a meaningful glyph (↺ /  / 🔑) instead of '?' in
the rebuild-queue panel.
2026-06-04 18:54:28 +02:00
damocles
b5503cde2c fix(logs): add ?id= deep-link to logs viewer, fix queue entry link target 2026-06-04 17:39:31 +02:00
damocles
7118c5efdd feat(queue): link rebuild queue entries to build log rows for live streaming 2026-06-04 17:39:31 +02:00
damocles
7ec0a36d7a fix(#1218): bulk topology move uses one git commit via new set-parent-bulk endpoint 2026-06-03 23:24:55 +02:00
damocles
f5351eb59c fix(#1194): update Execution tool names + frontend + docs 2026-06-03 21:20:49 +02:00
iris
c278398d1a feat(terminal): auto-load older history on scroll-to-top
Instead of requiring a click on the load-more pill, trigger loadMore()
automatically when the user scrolls within LOAD_MORE_SCROLL_PX (80px)
of the top of the log. The pill stays as a visual indicator that more
history is available.
2026-06-03 21:07:53 +02:00
iris
62841a549d feat(#1187): dynamic terminal scrollback with paginated history 2026-06-03 21:07:53 +02:00
iris
6e670c8a72 fix(#1157): add viewport meta to all pages; use clientWidth for side panel
Without a viewport meta tag, some browsers (notably Firefox with
Fingerprinting Protection active) internally read screen.availWidth /
screen.availHeight to compute the default viewport size, producing the
console warning 'Fingerprinting Protection is altering screen.availWidth
and screen.availHeight'.

Fix:
- Add <meta name="viewport" content="width=device-width, initial-scale=1">
  to all five pages that were missing it (dashboard index/flow/logs, agent
  index/stats). screen.html already had it.
- Replace window.innerWidth with document.documentElement.clientWidth in
  the side-panel drag-resize code in common.js. clientWidth returns the
  actual CSS layout viewport width and is not rounded by Firefox's
  Fingerprinting Protection, making the drag calculation correct even
  with privacy.resistFingerprinting enabled.
2026-06-03 18:23:37 +02:00
iris
7767493428 feat(#1161): show full bash commands in terminal as expandable details 2026-06-03 18:14:52 +02:00
damocles
79265e04f8 cleanup(#1014): remove manager pinning from frontend — no special treatment for ruth 2026-06-03 16:44:29 +02:00
damocles
7a035911ef fix: use empty string as initial managerNameState default 2026-06-03 16:44:29 +02:00
damocles
6feab54882 cleanup(#1014): remove is_manager from ContainerView, add manager_name to StateSnapshot 2026-06-03 16:44:29 +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
a389ead6de fix(#1096): remove container name/port chip from dashboard card
Drop the 'h-sock :8421' meta span from the agent card head.
ContainerView.container is now #[serde(skip)] since no frontend
code reads it; the field stays in Rust for internal use.
c.port remains serialized — used for fallback agent UI URLs.
2026-06-02 22:48:41 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
iris
2368bec634 feat(#1082): add description() to ToolGroup + Capability; expose in API + UI tooltips
- hive-sh4re: ToolGroup::description() and Capability::description() return
  short human-readable strings for each variant
- hive-c0re: ToolGroupsSnapshot and CapabilitiesSnapshot now include a
  `descriptions` map (name → description); get_capabilities now iterates
  Capability::ALL instead of hardcoding the list
- tabs.js: renderToolGroups + renderCapabilities use descriptions[name] as
  the column header title attribute (native browser tooltip on hover)
2026-06-02 12:53:50 +02:00
iris
ae695931ce chore(agent): move screen.html inline styles into agent.css; add --blue/--yellow/--subtext0 to palette 2026-06-02 12:43:11 +02:00
iris
cd6cbf9997 chore: fix stale build.mjs comments + CLAUDE.md dashboard package map 2026-06-02 11:19:32 +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
f1103b1dbf chore(agent): move last inline style from stats.html into agent.css 2026-06-02 11:17:59 +02:00
iris
e350e20145 chore(agent): move stats page inline styles into agent.css
stats.html had a <style> block with all its page-specific CSS inline.
Move to agent.css under a '--- /stats page ---' section, renaming
the generic .grid/.card/.empty-note classes to .stats-grid/.stats-card/
.stats-empty-note to avoid future collisions with other agent pages.
2026-06-02 11:17:59 +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