From 6cf169d32427b50f2c0d0ea2ff1477b74fbabba6 Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 17:53:03 +0200 Subject: [PATCH] dashboard: new S3TT1NGS tab housing the notification toggle (closes #763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- docs/web-ui.md | 59 +++++++++++++++---- frontend/packages/dashboard/src/dashboard.css | 13 ---- frontend/packages/dashboard/src/flow.html | 20 +++---- frontend/packages/dashboard/src/index.html | 38 +++++++++--- frontend/packages/dashboard/src/tabs.js | 14 ++--- 5 files changed, 92 insertions(+), 52 deletions(-) diff --git a/docs/web-ui.md b/docs/web-ui.md index ed703b47..e3829f9f 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -284,20 +284,22 @@ cross-page link (`◆ FL0W ◆ →`), not a pane swap. **Chrome header** (fixed, overlays the active tab pane): - **Tab strip**: `◆ SW4RM ◆`, `◆ Y3R C4LL ◆`, `◆ SYST3M ◆`, `◆ SCH3DUL3S ◆`, `◆ M4TR1X ◆ →` (optional page link, see below), - and `◆ FL0W ◆ →` (page link). 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). The M4TR1X → entry is hidden when + `◆ 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); S3TT1NGS has 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 `state.matrix_gui_enabled` from `/api/state`. -- **Notification controls**: `🔔 enable notifications` when - permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted. - Always visible in the chrome regardless of active tab. - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) — sits below the tab strip. +The FL0W page reuses the same chrome strip but its `◆ S3TT1NGS ◆ →` +entry is a cross-page link back to the dashboard +(`/#settings`) since the settings pane only lives there. + ### SW4RM tab **C0NTAINERS** — live containers rendered as a depth-first @@ -445,6 +447,32 @@ button hard-deletes (`POST /cancel-reminder/{id}`) and a Lives in the SCH3DUL3S tab alongside operator schedules so the operator has one place for everything time-fired. +### S3TT1NGS tab + +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 +under the same `
`. + +**◇ browser notifications** — `🔔 enable notifications` button when +permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted +(mute silences the dispatch without revoking the OS-level +permission). On unsupported origins (non-secure context, or +browsers without the `Notification` API) the controls hide and a +single status line explains why. See `### Browser notifications` +below for the dispatch model + the three signals the dashboard +emits OS notifications on. + +The FL0W page does NOT host this pane — its tab-strip +`◆ S3TT1NGS ◆ →` entry is a cross-page link to the dashboard's +`#settings` route. Notifications still fire on the FL0W page when +they're enabled here, because `NOTIF.show()` in +`common.js` depends on `Notification.permission` + the +`hyperhive.notify.muted` localStorage key, not on the buttons +existing in the page DOM. + ### M4TR1X page (`/matrix/`, optional) A static matrix web client (default `pkgs.fluffychat-web` rebuilt @@ -755,6 +783,10 @@ Pure frontend (`Notification` API). Three signals trigger them: - new pending operator question (per id) - new broker message sent `to: "operator"` (live via SSE) +The toggle controls live in the `S3TT1NGS` tab (`#settings`); see +that section above for the user-facing shape. Dispatch logic lives +in `common.js::NOTIF`. + First `/api/state` after page load seeds "seen" sets without firing — only items that arrive while the page is open count. Per-event tags (`hyperhive:approval:`, `hyperhive:question:`, @@ -762,12 +794,13 @@ Per-event tags (`hyperhive:approval:`, `hyperhive:question:`, notification center instead of overwriting each other. `console.debug` logs at every block point (unsupported, permission ungranted, muted) for in-browser debugging. Click -focuses the dashboard tab. localStorage-backed mute toggle -silences without revoking the OS permission. Requires a secure -context (HTTPS or localhost); on other origins the controls hide -themselves. Browsers typically suppress notifications while the -originating tab is focused — that's a browser-level decision, -not ours. +focuses the dashboard tab. The localStorage key +`hyperhive.notify.muted` (`"1"` = muted, absent = unmuted) backs +the toggle and silences dispatch without revoking the OS +permission. Requires a secure context (HTTPS or localhost); on +other origins the controls hide themselves. Browsers typically +suppress notifications while the originating tab is focused — +that's a browser-level decision, not ours. ### Dashboard endpoints diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index 2e806146..2c7872b4 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -98,15 +98,6 @@ body.dashboard-shell { color: var(--red); } -/* Notification controls cohabit with the tabs (always-on chrome). */ -.tabbar #notif-row { - margin-left: auto; - display: flex; - gap: 0.5em; - align-items: center; - padding-right: 0.5em; -} - /* Tab pane visibility — show only the active one. The .tab-pane-active class is set by tabs.js based on the URL hash; default (no hash) resolves to SW4RM. */ @@ -1582,10 +1573,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); } -/* `.notif-row` styling lives under the shared `.tabbar #notif-row` - selector earlier in the file — the flow page reuses the dashboard - tab strip rather than carrying its own title/hint/back chrome. */ - /* 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. */ diff --git a/frontend/packages/dashboard/src/flow.html b/frontend/packages/dashboard/src/flow.html index 9ad25da7..a68185bc 100644 --- a/frontend/packages/dashboard/src/flow.html +++ b/frontend/packages/dashboard/src/flow.html @@ -38,17 +38,15 @@ ◆ FL0W ◆ - - -
- - - - -
+ + + ◆ S3TT1NGS ◆ → + diff --git a/frontend/packages/dashboard/src/index.html b/frontend/packages/dashboard/src/index.html index 9c20d32a..3fc47edf 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -69,14 +69,15 @@ - -
- - - - -
+ + + ◆ S3TT1NGS ◆ + @@ -178,6 +179,27 @@ tabs.js boots on this page the corresponding renderers no-op silently (each guard is `if (!el) return`). --> + +
+

◆ S3TT1NGS ◆

+
══════════════════════════════════════════════════════════════
+

operator-local preferences. these live in the browser's localStorage — they do not sync between devices and do not survive a profile wipe.

+ +

◇ browser notifications

+

desktop notifications for new approvals, new operator questions, and broker messages addressed to you. requires a secure context (https or localhost). mute silences the notifications without revoking the OS-level permission.

+
+ + + + +
+
+