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).
This commit is contained in:
parent
80dd4babd4
commit
6cf169d324
5 changed files with 92 additions and 52 deletions
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -38,17 +38,15 @@
|
|||
<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 the shared NOTIF binding
|
||||
(from common.js, imported by both tabs.js and flow.js)
|
||||
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>
|
||||
<!-- 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>
|
||||
|
||||
|
|
|
|||
|
|
@ -69,14 +69,15 @@
|
|||
<span class="tab-count" id="tab-count-flow" hidden></span>
|
||||
</a>
|
||||
|
||||
<!-- Notification controls live in the chrome (always-on
|
||||
ergonomics; not tab-specific). -->
|
||||
<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>
|
||||
<!-- S3TT1NGS: operator-local preferences (browser notification
|
||||
toggle today; future: theme, density). Sits at the end of
|
||||
the strip because it's rarely-touched and not a primary
|
||||
surface. See docs/web-ui.md::S3TT1NGS tab. -->
|
||||
<a class="tab" id="tab-settings" href="#settings" role="tab"
|
||||
aria-controls="tab-pane-settings"
|
||||
data-tab="settings">
|
||||
<span class="tab-label">◆ S3TT1NGS ◆</span>
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
|
@ -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. Today this is just the
|
||||
browser-notification toggle (per-browser localStorage state).
|
||||
The same IDs that used to live in the tab strip migrated
|
||||
here unchanged so `NOTIF.bind()` in common.js wires them
|
||||
without any JS change. -->
|
||||
<section class="tab-pane" id="tab-pane-settings"
|
||||
role="tabpanel" aria-labelledby="tab-settings">
|
||||
<h2>◆ S3TT1NGS ◆</h2>
|
||||
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
||||
<p class="meta">operator-local preferences. these live in the browser's localStorage — they do not sync between devices and do not survive a profile wipe.</p>
|
||||
|
||||
<h3>◇ browser notifications</h3>
|
||||
<p class="meta">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.</p>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
|
|
|||
|
|
@ -3219,13 +3219,13 @@ window.marked = marked;
|
|||
})();
|
||||
|
||||
// ─── tab routing ───────────────────────────────────────────────────────
|
||||
// Hash-based: `#swarm` / `#call` / `#system` activate the matching
|
||||
// pane on the dashboard. Empty hash defaults to SW4RM. FL0W is NOT
|
||||
// a tab — it's a separate page (`/flow.html`) reached via the
|
||||
// tab-strip link. Tab routing only applies when the tab DOM is
|
||||
// present (e.g. not on the flow page itself, where these elements
|
||||
// don't exist and the loop no-ops).
|
||||
const TABS = ['swarm', 'call', 'system', 'schedules'];
|
||||
// Hash-based: `#swarm` / `#call` / `#system` / `#schedules` /
|
||||
// `#settings` activate the matching pane on the dashboard. Empty
|
||||
// hash defaults to SW4RM. FL0W is NOT a tab — it's a separate page
|
||||
// (`/flow.html`) reached via the tab-strip link. Tab routing only
|
||||
// applies when the tab DOM is present (e.g. not on the flow page
|
||||
// itself, where these elements don't exist and the loop no-ops).
|
||||
const TABS = ['swarm', 'call', 'system', 'schedules', 'settings'];
|
||||
function activateTab(name) {
|
||||
const target = TABS.includes(name) ? name : TABS[0];
|
||||
for (const t of TABS) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue