Operator: 'option A (tabs)' (#369#issuecomment-3434) + 'yes terminal can be a separate page' (#369#issuecomment-3437). ## Tab framework `index.html` becomes a 3-tab dashboard with a sticky chrome header: - `◆ SW4RM ◆` — containers list (the central thing) - `◆ Y3R C4LL ◆` — pending approvals + operator-targeted questions - `◆ SYST3M ◆` — meta inputs + rebuild queue + reminders + tombstones Hash routing: `#swarm` / `#call` / `#system` (empty → SW4RM). F5-reloadable + back-button-aware without a router framework. SSE stays alive across tab switches — count pills on inactive tabs update live so the operator never loses pulse on what's happening elsewhere: - SW4RM: containers with needs_update - Y3R C4LL: approvals.pending + questions.pending (attn-coloured pill) - SYST3M: rebuild_queue entries in Queued|Running Pills hidden when count is zero. setInterval(1s) polls the existing state stores (cheap, no per-renderer hookup needed). ## FL0W as its own page The all-agents chat moves to /flow.html — full-viewport vibec0re layout mirroring the per-agent live page (#362): - Fixed-overlay frosted-glass header at top (back link + title + notif controls), backdrop-filter blur shows the scrolled chat text behind. - Full-viewport terminal, scroll-padded for the floating chrome so first/last rows stay reachable. - Fixed-overlay frosted composer at the bottom. - Operator inbox surfaces via a pill (📬 inbox · N) in the upper right — click opens the side-panel flyout with the message list. In the dashboard tab strip, FL0W is the right-most entry but renders as a `<a class="tab tab-link" href="/flow.html">` — clicking navigates to the page rather than swapping a pane. Same pattern back from flow.html via the `← d4shb04rd` link. ## Implementation notes - New `/flow.html` page rendered by the same bundled `app.js` — the flow page just doesn't have the dashboard-chrome DOM, so the matching renderers no-op silently (each `if (!el) return`). Avoids splitting the bundle for v1; can extract later if size becomes a concern. - `Panel` module gains `openNamed(name, …)` + `refresh(name, …)` — the legacy untyped `open(title, content)` calls clear the owner, so file-preview / diff / log drill-ins behave unchanged. `refresh` is no-op when a different view owns the panel, so live message events re-render the inbox flyout only when it's actually open. - `renderInbox` updates BOTH the dashboard's inline `#inbox-section` (now living on the flow page) AND the flow page's pill count + side-panel refresh. The dashboard's empty FL0W tab is removed — inbox + message flow + compose box only exist in flow.html. - Banner shrinks to a thin Catppuccin gradient strip at the top of the dashboard chrome (dropped the multi-line ASCII art — affectionate but pure chrome budget in a tabbed layout). - `build.mjs` copies both `index.html` + `flow.html` into dist. ## Validation `npm run build` clean. Dashboard bundle deltas: app.js 150kb → 152kb (tab routing + count pills + named-Panel) dashboard.css 33kb → 38kb (tab chrome + flow page layout) + dist/flow.html 4.4kb Browser smoke test isn't possible from inside iris's container (no JS engine) — drafting as a PR for operator visual review on next deploy. Worth eyeballing: - Tab switching feels right; counts update live across SSE events - FL0W page reads like the agent live page (frosted header + composer) - Inbox pill opens flyout; live message arrivals refresh it - Back link from flow → dashboard returns to last tab via the URL hash (browser remembers the hash across page nav) Closes #369.
165 lines
8.6 KiB
HTML
165 lines
8.6 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>hyperhive // h1ve-c0re</title>
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<link rel="stylesheet" href="/static/dashboard.css">
|
|
</head>
|
|
<body class="dashboard-shell">
|
|
|
|
<!-- Sticky chrome — banner + tab strip. Tabs route via the URL
|
|
hash so F5 / back-button / shared links keep you on the same
|
|
view. JS owns the actual show/hide; this is just the menu. -->
|
|
<header class="dashboard-chrome">
|
|
<pre class="banner banner-thin">░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░</pre>
|
|
|
|
<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>
|
|
<a class="tab" id="tab-system" href="#system" role="tab"
|
|
aria-controls="tab-pane-system"
|
|
data-tab="system">
|
|
<span class="tab-label">◆ SYST3M ◆</span>
|
|
<span class="tab-count" id="tab-count-system" hidden></span>
|
|
</a>
|
|
|
|
<!-- FL0W is its own page (`/flow.html`), not a tab — per
|
|
operator @ #369#issuecomment-3437 ("yes terminal can be a
|
|
separate page"). The link lives in the tab strip so it
|
|
reads as a peer surface; clicking navigates rather than
|
|
swapping panes in place. Count pill mirrors the dashboard's
|
|
operator-inbox length and is hidden when zero. -->
|
|
<a class="tab tab-link" id="tab-flow" href="/flow.html"
|
|
title="open the all-agents chat in a dedicated full-page terminal">
|
|
<span class="tab-label">◆ FL0W ◆ →</span>
|
|
<span class="tab-count" id="tab-count-flow" hidden></span>
|
|
</a>
|
|
|
|
<!-- Notification controls live in the chrome (always-on
|
|
ergonomics; not tab-specific). -->
|
|
<div id="notif-row" class="notif-row">
|
|
<button type="button" id="notif-enable" class="btn btn-notif" hidden>🔔 enable notifications</button>
|
|
<button type="button" id="notif-mute" class="btn btn-notif" hidden>🔕 mute</button>
|
|
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button>
|
|
<span id="notif-status" class="meta" hidden></span>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<!-- Tab panes. Exactly one is `.tab-pane-active` at a time;
|
|
JS toggles based on the URL hash + `hashchange` events. -->
|
|
<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. -->
|
|
<section class="tab-pane" id="tab-pane-swarm"
|
|
role="tabpanel" aria-labelledby="tab-swarm">
|
|
<h2>◆ C0NTAINERS ◆</h2>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<div id="containers-section">
|
|
<p class="meta">loading…</p>
|
|
</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"
|
|
role="tabpanel" aria-labelledby="tab-call">
|
|
<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: passive / rare-interaction state. Meta inputs (lock
|
|
bumps), rebuild queue (watch only), queued reminders, kept
|
|
state from previous tombstoned agents. Headings stay; the
|
|
per-section content auto-compresses to a one-line summary
|
|
when empty (separate JS toggle). -->
|
|
<section class="tab-pane" id="tab-pane-system"
|
|
role="tabpanel" aria-labelledby="tab-system">
|
|
<h2>◆ M3T4 1NPUTS ◆</h2>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<p class="meta">select inputs to <code>nix flake update</code> in <code>/meta/</code>. selected agents rebuild in sequence after the lock bump; manager learns each outcome via the usual <code>rebuilt</code> system event.</p>
|
|
<div id="meta-inputs-section">
|
|
<p class="meta">loading…</p>
|
|
</div>
|
|
|
|
<h2>◆ R3BU1LD QU3U3 ◆</h2>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<p class="meta">pending + running rebuilds, meta-updates, and first-spawns. one runs at a time; meta-update cascades nest under their parent. dedup: re-enqueueing a still-queued op collapses into the existing entry.</p>
|
|
<div id="rebuild-queue-section">
|
|
<p class="meta">loading…</p>
|
|
</div>
|
|
|
|
<h2>◆ QU3U3D R3M1ND3RS ◆</h2>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<p class="meta">reminders agents have queued for themselves but not yet delivered. cancel to drop a stuck or unwanted entry.</p>
|
|
<div id="reminders-section">
|
|
<p class="meta">loading…</p>
|
|
</div>
|
|
|
|
<h2>◆ K3PT ST4T3 ◆</h2>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<div id="tombstones-section">
|
|
<p class="meta">loading…</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- FL0W: lives on its own page now (`/flow.html`). The
|
|
message-flow + inbox + compose DOM only exists there — when
|
|
app.js boots on this page the corresponding renderers
|
|
no-op silently (each guard is `if (!el) return`). -->
|
|
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
|
<p>▲△▲ <a href="https://git.berlin.ccc.de/vinzenz/hyperhive">hyperhive</a> ▲△▲ hive-c0re on this host ▲△▲</p>
|
|
</footer>
|
|
|
|
<!-- Slide-in detail panel. Long content (clicked file previews,
|
|
approval diffs, journald logs, applied config) opens here
|
|
instead of expanding inline. Singleton — JS swaps the title +
|
|
body and toggles `.open`. Lives outside the tab panes so it
|
|
overlays any active tab. -->
|
|
<div id="side-panel" class="side-panel" aria-hidden="true">
|
|
<div class="side-panel-backdrop" id="side-panel-backdrop"></div>
|
|
<aside class="side-panel-drawer" role="dialog" aria-modal="true"
|
|
aria-labelledby="side-panel-title">
|
|
<header class="side-panel-head">
|
|
<span class="side-panel-title" id="side-panel-title"></span>
|
|
<button type="button" class="side-panel-close" id="side-panel-close"
|
|
title="close (esc)">✕</button>
|
|
</header>
|
|
<div class="side-panel-body" id="side-panel-body"></div>
|
|
</aside>
|
|
</div>
|
|
|
|
<!-- Single bundled entry. esbuild folds @hive/shared/terminal.js and
|
|
the marked npm package into app.js; load order is preserved by
|
|
the module bundler. -->
|
|
<script type="module" src="/static/app.js" defer></script>
|
|
</body>
|
|
</html>
|