hyperhive/frontend/packages/dashboard/src/dashboard.html
iris c5610b075a frontend: unify dashboard + agent side panel into shared hive-side-panel
The dashboard's Panel singleton and the per-agent UI's own inline Panel
IIFE each had their own near-identical implementation of the right-side
slide-in drawer used for file previews, diffs, logs, and inbox/todo
lists. Both are now thin wrappers around a new <hive-side-panel>
shadow-DOM custom element in @hive/shared, following the same house
pattern as <hive-menu>: the element owns and builds all its structural
chrome itself (backdrop, drawer, resize handle, header, title, close
button) in connectedCallback, and only the caller's opaque content node
is projected in via a default <slot> so each package's own
content-type-specific CSS keeps reaching it.

Public API is the union of both originals: open(title, content),
openNamed(name, title, content), refresh(name, title, content),
close(), and currentOwner(). Drag-to-resize + localStorage width
persistence (ported verbatim from the dashboard's original
implementation, the only one of the two that had it) is now available
to both consumers by default — a deliberate behavior widening for the
agent UI, which didn't have resize before. Along the way, fixed a
latent bug in the ported CSS: the resize handle was setting a
--side-panel-w custom property that no width rule ever consumed, so
dragging never actually resized the drawer even though it looked wired
up; the new shared stylesheet's width rule reads it properly.

Each package's own global stylesheet keeps its content-specific rules
(common.css's .side-panel-body .md, agent.css's .side-panel-body
.agent-inbox) exactly where they were — those can never be reached from
the shared element's shadow tree, same architectural floor as
<hive-menu>'s item-row styling. Each wrapper applies a plain
'side-panel-body' compatibility class to its own <hive-side-panel>
instance so those existing selectors keep matching by ordinary
light-DOM descendant matching, with the shared element itself having no
knowledge of what that class name means.

Panel.bind() is gone from both packages' public API — the shared
element wires its own listeners in connectedCallback, so there's no
bind step left to call. tabs.js's one call site (the only bind() caller
in either package) was updated to drop it.

The two original chrome CSS blocks disagreed on several purely visual
details beyond the resize-handle rules (z-index, backdrop color, drawer
border/box-shadow, title typography) — the dashboard's values (the more
feature-complete of the two) were kept as canonical, which is a small
visible style change for the agent UI's panel chrome (thinner border,
no box-shadow, no bold purple title). Flagged for visibility since nothing
in the original two implementations called this out explicitly.

Verified with a real headless-Chromium/CDP harness (bundled the actual
component + built page CSS, served statically, drove via raw CDP) for
both usage shapes: open/close, backdrop-click dismiss, Escape dismiss,
refresh() owner-matching (no-op on wrong owner, applies on matching
owner), and drag-to-resize (drawer width updates live during drag and
persists to localStorage on release).
2026-08-01 00:58:53 +02:00

262 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive // h1ve-c0re</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/static/colors.css">
<link rel="stylesheet" href="/static/theme.css">
<link rel="stylesheet" href="/static/common.css">
<link rel="stylesheet" href="/static/dashboard.css">
</head>
<body class="dashboard-shell">
<!-- Sticky chrome — just the tab strip. The "WE ARE THE WIRED"
slug lives at the page footer below `<main>`; chrome is
navigation only. Tabs route via the URL hash so F5 / back-
button / shared links keep you on the same view. JS owns
the show/hide. -->
<header class="dashboard-chrome">
<!-- Back to the H0M3 menu hub (served at /). Sub-pages and the
dashboard all link back to the landing page rather than to each
other — H0M3 is the single navigation hub. -->
<a class="dash-home-back" href="/" title="back to the H0M3 menu">← home</a>
<nav class="tabbar" id="tabbar" role="tablist">
<a class="tab" id="tab-swarm" href="#swarm" role="tab"
aria-controls="tab-pane-swarm"
data-tab="swarm">
<span class="tab-label">◆ SW4RM ◆</span>
<span class="tab-count" id="tab-count-swarm" hidden></span>
</a>
<a class="tab" id="tab-call" href="#call" role="tab"
aria-controls="tab-pane-call"
data-tab="call">
<span class="tab-label">◆ Y3R C4LL ◆</span>
<span class="tab-count tab-count-attn" id="tab-count-call" hidden></span>
</a>
<!-- SYST3M moved to its own standalone page (/core.html, "C0R3"),
reached from the H0M3 hub — keeps the dashboard tab strip lean.
The rebuild-queue STATE still lives in tabs.js (it drives the
"building…" badges on SW4RM agent cards); only the SYST3M tab +
its section renderers moved out. -->
<!-- P3RM1SS10NS: per-agent capability grants and tool-group
assignments. Both tables are fetched on tab activation. -->
<a class="tab" id="tab-permissions" href="#permissions" role="tab"
aria-controls="tab-pane-permissions"
data-tab="permissions">
<span class="tab-label">◆ P3RM1SS10NS ◆</span>
</a>
<!-- SCH3DUL3S: scheduled-prompts surface. List of queued
schedules + an operator-direct creation form. Count pill
mirrors the active (non-cancelled) schedule count; hidden
when zero. -->
<a class="tab" id="tab-schedules" href="#schedules" role="tab"
aria-controls="tab-pane-schedules"
data-tab="schedules">
<span class="tab-label">◆ SCH3DUL3S ◆</span>
<span class="tab-count" id="tab-count-schedules" hidden></span>
</a>
<!-- ST4TS lives on its own page now (`/stats.html`), reached from
the H0M3 hub — the hive-wide rollup is a standalone read-only
view, not part of the operational tab strip. -->
<!-- (Peer hives are no longer a tab — they render as a headline
under SW4RM. See tab-pane-swarm.) -->
<!-- FL0W, L0GS, and M4TR1X are separate pages reachable from the
H0M3 hub (served at /), not from the dashboard tab strip — the
strip holds only real in-page tabs now. Contextual deep-links
into the log viewer (an agent's logs, a build entry's log)
still live inside the relevant dashboard content. -->
<!-- S3TT1NGS lives on its own page now (`/settings.html`), reached
from the H0M3 hub — operator-local prefs don't need a slot in
the operational tab strip. -->
</nav>
</header>
<!-- Tab panes. createTabStrip (@hive/shared/tabs.js) keeps exactly one
visible by toggling the `hidden` attribute, resolved from the URL
hash (default SW4RM). Panes start `hidden` to avoid a flash before
the script runs. -->
<!-- Padded content wrapper: carries the 1.5em side gutter
(.page-content, common.css) while <body> stays full-bleed so the
sticky chrome above + the footer below span the full width. -->
<div class="page-content">
<main class="dashboard-main">
<!-- SW4RM: the swarm itself. Container cards (the central thing
the operator looks at) and rebuild queue / cascade visualisation
that drives them. No inline `C0NTAINERS` h2 heading + divider
— the tab label SW4RM already says it. -->
<section class="tab-pane" id="tab-pane-swarm" data-tab-pane="swarm" hidden
role="tabpanel" aria-labelledby="tab-swarm">
<!-- Swarm / hive identity headline. Populated by refreshState from
hive_name + swarm_name; stays hidden when neither is set. -->
<h2 id="swarm-identity" hidden></h2>
<div id="containers-section">
<p class="meta">loading…</p>
</div>
<!-- Peer hives (federated swarms). The headline block is hidden
entirely when nothing is federated; renderPeerHives then drops
a quiet "no peer hives configured" note into #peers-section. -->
<div id="peers-block" hidden>
<h2>◆ P33R H1V3S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">peer hives in this swarm. click a card to open that hive's dashboard.</p>
</div>
<div id="peers-section"></div>
</section>
<!-- Y3R C4LL: things blocked on operator decision. Approvals +
questions read as the same concept ("something is waiting on
you"); both surface their full bodies inline so the operator
can decide without leaving the pane. -->
<section class="tab-pane" id="tab-pane-call" data-tab-pane="call" hidden
role="tabpanel" aria-labelledby="tab-call">
<!-- 1NB0X: unread agent→operator messages. Fetched on
tab activation + cold load, appended live from the broker
stream, cleared via "mark all read". Count folds into the
Y3R C4LL pill so messages aren't missed. -->
<h2>◆ 1NB0X ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="operator-inbox-section">
<p class="meta">loading…</p>
</div>
<h2>◆ P3NDING APPR0VALS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="approvals-section">
<p class="meta">loading…</p>
</div>
<h2>◆ M1ND H4S QU3STI0NS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="questions-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- SYST3M pane moved to /core.html (the standalone "C0R3" page):
meta inputs, rebuild queue, kept state, container load. Reached
from the H0M3 hub. -->
<!-- P3RM1SS10NS: per-agent capability grants + tool-group
assignments. Both tables are column-driven from the backend
(GET /api/capabilities, GET /api/tool-groups) so new entries
require no UI change. Edits stage in-browser across both
matrices; the single page-level "save all" button POSTs every
changed agent to /api/permissions in one atomic batch (caps +
groups coalesce into one rebuild per agent). -->
<section class="tab-pane" id="tab-pane-permissions" data-tab-pane="permissions" hidden
role="tabpanel" aria-labelledby="tab-permissions">
<h2>◆ C4P4B1L1T13S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">per-agent capability grants. capabilities unlock gated MCP tools and system access. toggle any cells across both tables, then hit "save all" at the bottom — each changed agent rebuilds exactly once.</p>
<div id="capabilities-section">
<p class="meta">loading…</p>
</div>
<!-- T00L GR0UPS: per-agent tool-group permission matrix. Rows = agents,
cols = tool groups fetched from GET /api/tool-groups.
Checking / unchecking is staged in the UI; the single page-level
"save all" button POSTs every changed agent to
/api/permissions in one atomic batch, coalescing caps + groups
per agent into one rebuild each.
Absent agents default to the role default (shown in parens). -->
<h2>◆ T00L GR0UPS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">per-agent tool-group permissions. columns are filled from the backend — adding a new group requires no UI change. agents without an explicit entry use the role default (agents: messaging, meta, inbox, execution).</p>
<div id="tool-groups-section">
<p class="meta">loading…</p>
</div>
<!-- Page-level save bar (save-all issue): one button for the whole
permissions page. Staged checkbox edits across BOTH matrices are
diffed against their render-time baselines and POSTed together to
/api/permissions; caps + groups for one agent coalesce into a
single rebuild. Disabled until something is dirty. The atomic
batch either fully lands (queued ✓ → rebuilding) or fully fails
(error note, nothing applied). -->
<div class="perm-save-bar">
<button type="button" id="perm-save-all" class="btn btn-approve" disabled>save all</button>
<span id="perm-save-note" class="meta"></span>
</div>
</section>
<!-- SCH3DUL3S: scheduled prompts. Creation + edit are folded
into the same table — empty bottom row is the create form
(fill cells, click ), inline-edit-row expands on the `✎`
toggle for existing schedules. Schedules list driven by
GET /api/schedules; POST /api/schedules to create, PATCH
/api/schedules/{id} to edit, POST /api/schedules/{id}/cancel
for per-target / whole-row cancel. Live updates via
`schedules_changed` SSE; tab activation re-fetches as a
safety net for disconnect windows.
See docs/web-ui.md::SCH3DUL3S tab. -->
<section class="tab-pane" id="tab-pane-schedules" data-tab-pane="schedules" hidden
role="tabpanel" aria-labelledby="tab-schedules">
<h2>◆ SCH3DUL3S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">all schedules currently in the table. fill the bottom row to queue a new schedule (operator-direct, no approval gate; recurring when an interval is set). cancel a single target with the row button or the whole schedule with <code>✕ cancel all</code>.</p>
<div id="schedules-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- ST4TS: hive-wide turn-stats aggregate lives on its own page now
(`/stats.html`), reached from the H0M3 hub. The markup + the
hive-stats render JS moved there; when tabs.js boots on the
dashboard the renderers are simply gone (no stats tab to
activate). -->
<!-- FL0W: lives on its own page now (`/flow.html`). The
message-flow + inbox + compose DOM only exists there — when
tabs.js boots on this page the corresponding renderers
no-op silently (each guard is `if (!el) return`). -->
<!-- S3TT1NGS: operator-local preferences (browser-notification
toggle) live on their own page now (`/settings.html`), reached
from the H0M3 hub. The notif buttons moved there verbatim;
`NOTIF.show()` on this page still fires (it reads the same
localStorage + browser permission the settings page sets). -->
</main>
</div><!-- /.page-content -->
<footer>
<pre class="banner banner-thin">░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░</pre>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p>▲△▲ <a href="https://forge.darkest.space/hyperhive/hyperhive">hyperhive</a> ▲△▲ hive-c0re on this host ▲△▲</p>
</footer>
<!-- Slide-in detail panel (clicked file previews, approval diffs,
journald logs, applied config) is a <hive-side-panel> element
(@hive/shared/side-panel.js) — the Panel singleton in common.js
creates + appends it to <body> lazily on first use, so nothing
needs to be pre-declared here. -->
<!-- Selection action bar. Sticky-bottom strip that slides into
view when one or more agent cards is selected (click the icon
to toggle). See docs/web-ui.md::Selection bar for the bulk
action gating + clear semantics. -->
<div id="selection-bar" class="selection-bar" hidden role="toolbar"
aria-label="bulk agent actions">
<span class="selection-count" id="selection-count"></span>
<span class="selection-names" id="selection-names"></span>
<span class="selection-actions" id="selection-actions"></span>
<button type="button" class="btn selection-clear" id="selection-clear"
title="clear selection (esc)">✕ clear</button>
</div>
<!-- Single bundled entry — tabs.js is the dashboard tabs surface;
flow.html has its own flow.js bundle. esbuild folds
@hive/shared/terminal.js and the marked npm package into
tabs.js. -->
<script type="module" src="/static/tabs.js" defer></script>
</body>
</html>