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 ◆
-
-
-
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.