dashboard: tab-bar restructure + extract FL0W to /flow.html (#369)
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.
This commit is contained in:
parent
8c7bc850f3
commit
9666cb8c3f
5 changed files with 628 additions and 87 deletions
93
frontend/packages/dashboard/src/flow.html
Normal file
93
frontend/packages/dashboard/src/flow.html
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>hyperhive // FL0W</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/dashboard.css">
|
||||
</head>
|
||||
<body class="flow-shell">
|
||||
|
||||
<!-- Fixed-overlay header. Frosted glass over the message flow —
|
||||
backdrop-filter blur shows the scrolled chat text behind. Mirrors
|
||||
the agent live page's overlay layout (#362). -->
|
||||
<header class="flow-header" id="flow-header">
|
||||
<a href="/" class="flow-back" title="back to dashboard">← d4shb04rd</a>
|
||||
<h2 class="flow-title">◆ FL0W ◆</h2>
|
||||
<p class="flow-hint">live broker tail · newest at the top · <code>@name</code> picks a recipient (sticky); <code>tab</code> completes</p>
|
||||
<!-- Notif controls cohabit here (no other chrome on this page).
|
||||
Same IDs as on the dashboard so app.js's NOTIF binding picks
|
||||
them up unchanged. -->
|
||||
<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>
|
||||
</header>
|
||||
|
||||
<!-- Operator inbox flyout trigger — count + click → side panel
|
||||
(singleton, declared below). Hidden until the inbox is non-
|
||||
empty. Mirrors the agent page's pill pattern (#362). -->
|
||||
<button type="button" id="inbox-pill" class="flow-pill" hidden
|
||||
title="open operator inbox">
|
||||
<span class="flow-pill-icon" aria-hidden="true">📬</span>
|
||||
<span class="flow-pill-label">inbox</span>
|
||||
<span class="flow-pill-count" id="inbox-pill-count">0</span>
|
||||
</button>
|
||||
|
||||
<!-- Main content: the full-viewport terminal. Padded for the
|
||||
overlay header + composer so the first/last rows stay
|
||||
reachable. -->
|
||||
<main class="flow-main">
|
||||
<div class="terminal-wrap">
|
||||
<div id="msgflow" class="live terminal"><div class="meta">connecting…</div></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Fixed-overlay composer at the bottom. Same frosted treatment
|
||||
as the header — symmetric framing, terminal goes edge-to-edge
|
||||
between them. -->
|
||||
<footer class="flow-composer">
|
||||
<div id="op-compose" class="op-compose">
|
||||
<span id="op-compose-prompt" class="op-compose-prompt">@—></span>
|
||||
<textarea id="op-compose-input" class="op-compose-input"
|
||||
placeholder="@agent message… (enter sends, shift+enter newline, tab completes @-mention)"
|
||||
rows="1" autocomplete="off"></textarea>
|
||||
<div id="op-compose-suggest" class="op-compose-suggest" hidden></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Inbox rendered offscreen — kept in the DOM so app.js's
|
||||
renderInbox keeps working unchanged. The pill click handler
|
||||
opens the side panel which displays a clone of the list. The
|
||||
legacy section heading would otherwise be visible; hidden
|
||||
here. -->
|
||||
<div id="inbox-section" class="flow-inbox-headless" hidden>
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
|
||||
<!-- Slide-in side panel. Singleton — JS swaps the title + body
|
||||
and toggles `.open`. On this page only used to surface the
|
||||
operator inbox flyout; the dashboard's other panel uses
|
||||
(approval diffs, file previews, logs) don't apply here. -->
|
||||
<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>
|
||||
|
||||
<!-- Same bundled entry as the dashboard. Renderers whose target
|
||||
DOM doesn't exist on this page no-op silently. SSE +
|
||||
/api/state fetching still run; only the chat-relevant chunks
|
||||
have anywhere to render to. -->
|
||||
<script type="module" src="/static/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue