hyperhive/frontend/packages/dashboard/src/flow.html
iris 6cf169d324 dashboard: new S3TT1NGS tab housing the notification toggle (closes #763)
Adds a fifth tab (◆ S3TT1NGS ◆) at the end of the dashboard tab
strip; moves the browser-notification controls out of the chrome
header into the new pane.

### Why

The chrome was carrying the notification controls as 'always-on
ergonomics' but the surface they belong on is operator preferences,
not navigation. With #589 / #701 likely adding more per-browser
preferences later (theme / density / etc.), having a dedicated
S3TT1NGS pane gives them a home so the chrome strip doesn't keep
accreting controls.

### Shape

- index.html: new `#tab-pane-settings` section with H2 + intro +
  H3 'browser notifications' group; same notif-row DOM (same IDs)
  migrated from the chrome
- flow.html: drops the duplicated notif-row from its chrome (FL0W
  reused the dashboard's strip); adds a `◆ S3TT1NGS ◆ →`
  cross-page link to `/#settings`. Notifications still fire on
  FL0W when enabled — NOTIF.show() reads
  Notification.permission + the hyperhive.notify.muted localStorage
  key directly, no buttons needed in the page DOM.
- tabs.js: adds 'settings' to TABS array so hash routing activates
  the pane
- dashboard.css: drops the chrome-anchoring `.tabbar #notif-row`
  rule and the flow-page reuse comment, both obsolete; the general
  `.notif-row` styling stays
- docs/web-ui.md: new `### S3TT1NGS tab` section; Chrome-header
  bullet drops 'Notification controls' + adds S3TT1NGS; Browser
  notifications section pointers at the new pane

### No JS or backend changes

Same IDs (notif-row, notif-enable, notif-mute, notif-unmute,
notif-status) so `NOTIF.bind()` in common.js wires the new pane
unchanged. NOTIF.show() dispatch is DOM-independent.

### Validation

`npm run --workspace @hive/dashboard build` clean (tabs.js 167.8kb,
flow.js 37.4kb, dashboard.css 50.2kb).
2026-05-31 17:55:30 +02:00

117 lines
5.3 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. The full-viewport
terminal has no normal-flow footer position so the dashboard's
slug 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 / SCH3DUL3S 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" href="/#schedules" role="tab" data-tab="schedules">
<span class="tab-label">◆ SCH3DUL3S ◆</span>
<span class="tab-count" id="tab-count-schedules" 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>
<!-- S3TT1NGS cross-links to the dashboard tab — notification
toggle + future preferences live there now. `NOTIF.show()`
in common.js doesn't depend on the buttons existing here,
so notifications still fire on this page when they're
enabled (or muted) in dashboard settings. -->
<a class="tab tab-link" href="/#settings"
title="open S3TT1NGS on the dashboard">
<span class="tab-label">◆ S3TT1NGS ◆ →</span>
</a>
</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 header pill pattern. -->
<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 flow.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. Contains the broker terminal init, the
operator-inbox derived store, the inbox pill flyout, and the
@-mention composer. Tab renderers etc. live in
`/static/tabs.js` which /flow.html doesn't load. -->
<script type="module" src="/static/flow.js" defer></script>
</body>
</html>