From e949129e7504934e357394939e766b462726b02c Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 7 Jun 2026 19:42:23 +0200 Subject: [PATCH] refactor(flow): drop operator-inbox UI, now lives on Y3R C4LL (#1483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operator inbox moved to the dashboard's Y3R C4LL tab in #1469 (◆ 1NB0X ◆ section, with per-message + mark-all read). FL0W goes back to being the pure event firehose, so remove its now-redundant inbox UI: - flow.js: drop the operatorInbox store, inboxAppendFromEvent, buildInboxListNode, renderInbox, the inbox-pill click wiring, and the onAnyEvent hook that fed them. The side panel was only used for the inbox flyout on this page, so drop Panel.bind() + the Panel import too. - flow.html: remove the inbox pill, the offscreen inbox-section div, and the side-panel markup. - flow.css: remove the .flow-pill* and .flow-inbox-headless rules. - home.html: Flow tile desc → "live all-agents message firehose". - docs/web-ui/dashboard.md: drop the FL0W 0PER4T0R 1NB0X section, point at Y3R C4LL, and fix the count-pill + /op-send descriptions. The agent filter, sent→delivered collapse, compose box, and OS notifications on operator-bound traffic are unaffected. --- docs/web-ui/dashboard.md | 24 +++--- frontend/packages/dashboard/src/flow.css | 53 +------------ frontend/packages/dashboard/src/flow.html | 39 +--------- frontend/packages/dashboard/src/flow.js | 92 +++-------------------- frontend/packages/dashboard/src/home.html | 2 +- 5 files changed, 27 insertions(+), 183 deletions(-) diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 3baa5a59..1d19c2c3 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -18,9 +18,9 @@ swap. `◆ P33RS ◆` (hidden when `swarm.peers` is empty), `◆ M4TR1X ◆ →` (optional page link, see below), `◆ FL0W ◆ →` (page link), and `◆ S3TT1NGS ◆`. Count pills on SW4RM (container - count), Y3R C4LL (pending approvals + questions), and SCH3DUL3S - (active schedules); FL0W pill mirrors the operator inbox length - (hidden when zero); P33RS and S3TT1NGS have no count. The M4TR1X → + count), Y3R C4LL (pending approvals + questions + unread operator + messages), and SCH3DUL3S (active schedules); P33RS and S3TT1NGS + have no count. The M4TR1X → entry is hidden when `services.hyperhive.matrix.gui.enable` is off (defaults to `matrix.enable`) so operators without the matrix GUI on don't see a dead link — tabs.js gates the `hidden` attribute on @@ -368,7 +368,7 @@ Operator-local preferences. State lives in the browser's `localStorage` — preferences do NOT sync between devices and do NOT survive a profile wipe. Today the tab holds one section (browser notifications); future preferences (theme, density, -inbox-pill threshold, etc.) land here as sibling `

` blocks +etc.) land here as sibling `

` blocks under the same `
`. **◇ browser notifications** — `🔔 enable notifications` button when @@ -415,11 +415,9 @@ as the dashboard so the tab strip remains visible; SW4RM / Y3R C4LL / SYST3M / SCH3DUL3S are cross-page links back to `/#`, and the FL0W entry is marked active (`aria-current="page"`). -**0PER4T0R 1NB0X** — recent messages addressed to `operator`, -derived client-side from the dashboard event stream. Cold load -seeds from `/dashboard/history`'s 200-message backfill; subsequent -`sent` events with `to == "operator"` are appended live. Cap 50, -newest-first. +The operator inbox is **not** on this page — it lives on the +dashboard's Y3R C4LL tab (◆ 1NB0X ◆ section, with per-message and +mark-all read). FL0W stays the pure event firehose. **MESS4GE FL0W** — live broker tail wrapped in a `.terminal-wrap`. Cold load backfills the last ~200 messages from `/dashboard/history`; @@ -437,8 +435,7 @@ terminal-style compose box: `@name` picks the recipient (sticky via localStorage; auto-complete from the live container list, Tab/Enter to confirm; `@*` broadcasts). `POST /op-send` drops `{from:"operator", to, body}` into the broker; the resulting SSE -frame re-renders both the terminal row and the inbox section. -Manager is addressed as `@root`. +frame re-renders the terminal row. Manager is addressed as `@root`. ## L0GS page (`/logs.html`) @@ -805,9 +802,8 @@ that's a browser-level decision, not ours. - `POST /op-send` (`to=`, `body=`) — drop an operator-authored message into ``'s inbox. `to=*` fans out to every registered agent. Returns 200; the broker - `Sent` event re-renders both the message-flow terminal and - the operator inbox without a snapshot refetch. Used by the - compose textbox under MESS4GE FL0W. + `Sent` event re-renders the message-flow terminal without a + snapshot refetch. Used by the compose textbox under MESS4GE FL0W. - `GET /api/build-logs/{agent}?limit=N` — most-recent build log headers for one agent, newest first. Returns `Vec` (JSON): `id`, `agent`, `kind`, `cmdline`, diff --git a/frontend/packages/dashboard/src/flow.css b/frontend/packages/dashboard/src/flow.css index 7818c530..954691b9 100644 --- a/frontend/packages/dashboard/src/flow.css +++ b/frontend/packages/dashboard/src/flow.css @@ -1,8 +1,7 @@ /* ─── /flow.html — full-page chat ───────────────────────────────── - See docs/web-ui.md::FL0W page for the page-vs-pane rationale - and the inbox-flyout-as-pill ergonomics. Shape mirrors the - per-agent live page (frosted-glass header + composer, full- - viewport terminal). */ + See docs/web-ui.md::FL0W page for the page-vs-pane rationale. + Shape mirrors the per-agent live page (frosted-glass header + + composer, full-viewport terminal). */ :root { /* Approximate height of the flow chrome (tabbar + dashboard-chrome @@ -59,47 +58,6 @@ body.flow-shell .tabbar .tab.active.tab-link { border-color: var(--purple-dim); box-shadow: 0 -2px 12px -4px rgba(203, 166, 247, 0.4); } -/* Inbox pill — operator inbox flyout trigger. Sits right under the - header so it stays in the operator's gaze without crowding the - chat. Same shape as the agent page's header pills. */ -.flow-pill { - position: fixed; - top: calc(var(--flow-header-h) + 0.8em); - right: 1em; - z-index: 25; - background: var(--bg-elev); - border: 1px solid var(--purple-dim); - color: var(--fg); - font-family: inherit; - font-size: 0.85em; - letter-spacing: 0.04em; - border-radius: 999px; - padding: 0.3em 0.8em; - display: inline-flex; - align-items: center; - gap: 0.5em; - cursor: pointer; - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35); - transition: border-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease; -} -.flow-pill:hover { - border-color: var(--purple); - color: var(--purple); - box-shadow: 0 0 12px -2px var(--purple); -} -.flow-pill-icon { font-size: 1.05em; line-height: 1; } -.flow-pill-label { color: var(--muted); } -.flow-pill-count { - background: rgba(250, 179, 135, 0.18); - color: var(--amber); - border-radius: 999px; - padding: 0 0.5em; - min-width: 1.6em; - text-align: center; - font-weight: bold; - font-variant-numeric: tabular-nums; -} - .flow-main { position: absolute; top: 0; @@ -159,11 +117,6 @@ body.flow-shell .flow-main-slim { padding: 0.4em 1em; } -/* Hidden inbox section on the flow page — the renderInbox path - wants `#inbox-section` in the DOM (legacy contract), but we - surface the messages via the pill/flyout instead. */ -.flow-inbox-headless { display: none !important; } - /* Agent filter (#1473): a select in the FL0W header narrows the timeline to messages involving one agent. Non-matching rows get `.flow-hidden`; the select reuses the journal-unit control look. */ diff --git a/frontend/packages/dashboard/src/flow.html b/frontend/packages/dashboard/src/flow.html index c98f3960..8535b569 100644 --- a/frontend/packages/dashboard/src/flow.html +++ b/frontend/packages/dashboard/src/flow.html @@ -23,16 +23,6 @@ - - - @@ -55,34 +45,7 @@ - - - - - - - diff --git a/frontend/packages/dashboard/src/flow.js b/frontend/packages/dashboard/src/flow.js index 969972cf..cdb46664 100644 --- a/frontend/packages/dashboard/src/flow.js +++ b/frontend/packages/dashboard/src/flow.js @@ -1,8 +1,9 @@ -// /flow.html entry point. Owns the full-page broker terminal, the -// operator-inbox derived store (populated from the broker stream), -// the inbox pill flyout, and the @-mention compose box. Pulls shared -// infrastructure (DOM helpers, side panel, OS notifications, path -// linkification) from `./common.js`. +// /flow.html entry point. Owns the full-page broker terminal and the +// @-mention compose box. Pulls shared infrastructure (DOM helpers, +// side panel, OS notifications, path linkification) from `./common.js`. +// +// The operator inbox lives on the dashboard's Y3R C4LL tab now; FL0W +// stays the pure event firehose. // // Does NOT contain the dashboard's tab renderers, mutation-event // dispatchers, or refreshState — that's `./tabs.js`, loaded only by @@ -13,74 +14,14 @@ import { create as termCreate } from '@hive/shared/terminal.js'; import { $, el, - Panel, NOTIF, + NOTIF, appendLinkified, openStream, } from './common.js'; (() => { - Panel.bind(); NOTIF.bind(); - // ─── operator inbox (derived from the broker message stream) ─────────── - // No longer shipped on `/api/state.operator_inbox`. The broker - // terminal feeds this via `onAnyEvent` — backfill from - // `/dashboard/history` populates on load, live SSE keeps it current. - // Newest-first to match the previous behaviour. - const INBOX_LIMIT = 50; - const operatorInbox = []; - function inboxAppendFromEvent(ev) { - if (ev.kind !== 'sent' || ev.to !== 'operator') return false; - operatorInbox.unshift({ - from: ev.from, - body: ev.body, - at: ev.at, - file_refs: ev.file_refs || [], - }); - if (operatorInbox.length > INBOX_LIMIT) operatorInbox.length = INBOX_LIMIT; - return true; - } - function buildInboxListNode() { - if (!operatorInbox.length) return el('p', { class: 'empty' }, 'no messages'); - const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19); - const ul = el('ul', { class: 'inbox' }); - for (const m of operatorInbox) { - const li = el('li'); - const body = el('span', { class: 'msg-body' }); - appendLinkified(body, m.body, m.file_refs); - li.append( - el('span', { class: 'msg-ts' }, fmt(m.at)), ' ', - el('span', { class: 'msg-from' }, m.from), ' ', - el('span', { class: 'msg-sep' }, '→ '), - body, - ); - ul.append(li); - } - return ul; - } - function renderInbox() { - // Flow page surfaces inbox as a pill that opens the side-panel - // flyout. Pill is hidden when empty; click handler below opens - // the panel with the freshest list. If the panel is already - // showing the inbox view, refresh its body in place so live - // messages land without a re-open. - const pill = $('inbox-pill'); - const pillCount = $('inbox-pill-count'); - if (pillCount) pillCount.textContent = String(operatorInbox.length); - if (pill) pill.hidden = operatorInbox.length === 0; - Panel.refresh('inbox', 'inbox · ' + operatorInbox.length, buildInboxListNode()); - } - - // Wire the inbox pill to open the side-panel flyout with the - // operator inbox. - const inboxPill = $('inbox-pill'); - if (inboxPill) { - inboxPill.addEventListener('click', () => { - Panel.openNamed('inbox', 'inbox · ' + operatorInbox.length, - buildInboxListNode()); - }); - } - // ─── local containers cache (for compose autocomplete) ────────────────── // The compose box's @-mention completion suggests known agent names. // /index.html (tabs.js) maintains the canonical `containersState` @@ -144,8 +85,8 @@ import { // ─── message flow: shared terminal pane ──────────────────────────────── // Scroll, pill, backfill + SSE plumbing live in @hive/shared/terminal. // What stays here is the broker-message renderer + the page-local - // side effects (banner pulse, inbox refresh on operator-bound - // traffic, OS notifications). + // side effects (banner pulse, OS notifications on operator-bound + // traffic). (() => { const flow = $('msgflow'); if (!flow) return; @@ -310,19 +251,10 @@ import { // tabs handle these on /index.html via tabs.js. _default: () => {}, }, - // Both history backfill and live frames flow through here, so the - // inbox section ends up populated correctly on first paint and - // updated thereafter — no /api/state refetch needed for inbox - // freshness. - onAnyEvent: (ev /* , { fromHistory } */) => { - if (inboxAppendFromEvent(ev)) renderInbox(); - }, // Re-sync the local containers cache on every SSE (re)connect. // Live mutation events that fired during a disconnect window // are never replayed, so without this the compose autocomplete - // could drift stale. We don't try to recover missed broker rows - // here — operator inbox briefly stales on reconnect; the - // history-replay covers the next page load. + // could drift stale. onStreamOpen: () => { fetch('/api/state').then((r) => r.ok ? r.json() : null).then((s) => { if (!s || !Array.isArray(s.containers)) return; @@ -448,8 +380,8 @@ import { input.disabled = true; try { // /op-send returns 200. The SSE channel carries the resulting - // MessageEvent → the terminal renders the sent row + the - // inbox updates on its own; no /api/state refetch needed. + // MessageEvent → the terminal renders the sent row on its own; + // no /api/state refetch needed. const resp = await fetch('/op-send', { method: 'POST', body: new URLSearchParams(fd), diff --git a/frontend/packages/dashboard/src/home.html b/frontend/packages/dashboard/src/home.html index 10fe7695..ace213d4 100644 --- a/frontend/packages/dashboard/src/home.html +++ b/frontend/packages/dashboard/src/home.html @@ -31,7 +31,7 @@ Flow - all-agents chat + operator inbox + live all-agents message firehose