Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/dashboard/src/flow.html
iris 06c23e0bdc dashboard: extract flow.js as separate /flow.html entry (#406 step 2)
Splits the single bundle into two: `app.js` (entry for /index.html —
tab renderers + tab routing + refreshState) and a new `flow.js`
(entry for /flow.html — operator inbox derived store + inbox pill
flyout + broker terminal + @-mention composer). Both bundles inline
`./common.js` (DOM helpers, Panel, NOTIF, path linkification).

## What `flow.js` owns

- Operator inbox derived store (`operatorInbox`, `INBOX_LIMIT`,
  `inboxAppendFromEvent`, `buildInboxListNode`, `renderInbox`) +
  inbox-pill click wiring
- Broker terminal init (`HiveTerminal.create({ logEl: msgflow, ... })`)
  with `renderMsg`, `pulseBanner`, `msgRowMap` reply-thread indicator,
  and the renderers map for `sent` / `delivered` broker rows
- @-mention composer (`#op-compose-input` IIFE — sticky recipient,
  autocomplete, parseAddressed, /op-send POST)
- A small local `flowContainers` cache for the composer's
  autocomplete, refreshed on cold load + on every SSE reconnect via
  `onStreamOpen`, and live-updated by `container_state_changed` /
  `container_removed` SSE events (the dashboard's `containersState`
  lives in `app.js` and isn't available here)

## What `app.js` no longer does

- Drops the inbox derived store, the bindFlowInboxPill IIFE, the
  broker-terminal IIFE, and the composer IIFE — all moved
- Drops the `renderInbox()` call in `refreshState` (dashboard has
  no #inbox-section element)
- Drops `setTabCount('flow', operatorInbox.length)` — the FL0W tab
  count lives in flow.js now (cross-page count broadcasting is a
  future follow-up; the slot currently stays hidden on /index.html)
- Drops the `window.HiveTerminal` global — the bare-import pattern
  in common.js / flow.js made it unused on the dashboard

## What changes for /flow.html

- `<script src>` switches from `/static/app.js` → `/static/flow.js`
- Mutation events on the dashboard stream (`approval_added`,
  `container_state_changed`, etc.) are silently ignored on /flow.html
  via a `_default: () => {}` renderer (the dashboard tabs aren't on
  this page; firing the legacy applyXxx handlers from here just
  mutated dead stores). #408 follow-up filters this at the SSE level

## Validation

- `npm run build` clean.
- Bundle deltas:
    - `app.js`: 154kb → 135kb (dropped ~19kb of flow code)
    - `flow.js`: NEW 29kb (was bundled into the old 154kb app.js)
    - `flow.html` page total: 154kb → 29kb (flow.js + inlined common,
      no tab renderers shipped)
- Source: `app.js` 2287 → 1907 lines (-380); `flow.js` 423 lines (new)
- No HTML / CSS changes besides the `<script src>` swap on /flow.html.

## Known limitations (out of scope; tracked separately)

- /index.html still has no live SSE subscription — the dashboard
  updates only on cold load + after async-form submits. Pre-existing
  behaviour; the SSE wiring also lived in the flow IIFE before.
  Step 3 of #406 (or its own bug fix) re-wires it.
- /flow.html's `_default: noop` drops mutation events; #408 fixes
  the duplicate-traffic by splitting the SSE endpoint server-side.
- The FL0W tab-strip count pill on /index.html stays hidden — the
  count source is now in flow.js. Broadcast via localStorage /
  BroadcastChannel is a small follow-up if both pages are open.

Browser smoke test isn't possible from inside iris's container.
Worth eyeballing post-deploy:
  - /flow.html: terminal renders broker rows; inbox pill shows count
    + opens flyout; composer autocomplete suggests known agents
    + sends successfully
  - /index.html: tab renderers all work; notification toggle still
    binds; side panel still opens for diffs / file previews / logs
2026-05-25 02:19:19 +02:00

115 lines
5.2 KiB
HTML

<!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 chrome — just the tab strip (#389 follow-up:
slug moved to the dashboard's page footer; the flow page is a
full-viewport terminal with no normal-flow footer position, so
the slug simply doesn't appear here). The operator can still
switch tabs from the flow page without navigating back; FL0W is
the current page, SW4RM / Y3R C4LL / SYST3M cross-link to the
dashboard with the matching hash. -->
<header class="dashboard-chrome flow-chrome" id="flow-header">
<nav class="tabbar" id="tabbar" role="tablist">
<a class="tab" href="/#swarm" role="tab" data-tab="swarm">
<span class="tab-label">◆ SW4RM ◆</span>
<span class="tab-count" id="tab-count-swarm" hidden></span>
</a>
<a class="tab" href="/#call" role="tab" 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" href="/#system" role="tab" data-tab="system">
<span class="tab-label">◆ SYST3M ◆</span>
<span class="tab-count" id="tab-count-system" hidden></span>
</a>
<a class="tab tab-link active" id="tab-flow" href="/flow.html"
aria-current="page"
title="all-agents chat — you are here">
<span class="tab-label">◆ FL0W ◆</span>
<span class="tab-count" id="tab-count-flow" hidden></span>
</a>
<!-- Notif controls cohabit with the tabs (always-on chrome).
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>
</nav>
</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">@—&gt;</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>
<!-- Flow-specific bundle (#406 step 2). Contains the broker
terminal init, the operator-inbox derived store, the inbox
pill flyout, and the @-mention composer. Tab renderers etc.
live in `/static/app.js` which /flow.html doesn't need. -->
<script type="module" src="/static/flow.js" defer></script>
</body>
</html>