From d9c66de069f12870e6cbbdd5948d6c2c3668fd3c Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 8 Jun 2026 23:28:28 +0200 Subject: [PATCH] refactor(frontend): move S3TT1NGS to its own /settings.html page (#1464 step 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the operator-local preferences (browser-notification toggle) out of the dashboard tab strip into a standalone /settings.html page, reached from the H0M3 hub — same minimal-chrome pattern as /flow.html and /logs.html. The dashboard tab strip is now purely operational. - new settings.{html,css,js}; settings.js binds NOTIF + renders the shared server-warnings banner (matching the other standalone pages). - drop the S3TT1NGS tab + pane from dashboard.html; the NOTIF.bind() toggle wiring moves to settings.js. The dashboard keeps NOTIF.show() (approvals / questions) — it reads the same browser permission + localStorage mute flag the settings page sets, so firing still works. - move .notif-row / .btn-notif from dashboard.css to settings.css. - add a Settings tile to the H0M3 hub; wire the new entries into build.mjs and update the nix/frontend.nix asset manifest comment. First slice of #1464 step 2; the ST4TS page extraction follows separately. --- frontend/packages/dashboard/build.mjs | 8 ++-- frontend/packages/dashboard/src/dashboard.css | 24 +---------- .../packages/dashboard/src/dashboard.html | 37 ++++------------- frontend/packages/dashboard/src/index.html | 5 +++ frontend/packages/dashboard/src/settings.css | 40 +++++++++++++++++++ frontend/packages/dashboard/src/settings.html | 38 ++++++++++++++++++ frontend/packages/dashboard/src/settings.js | 15 +++++++ frontend/packages/dashboard/src/tabs.js | 12 ++++-- nix/frontend.nix | 7 ++-- 9 files changed, 124 insertions(+), 62 deletions(-) create mode 100644 frontend/packages/dashboard/src/settings.css create mode 100644 frontend/packages/dashboard/src/settings.html create mode 100644 frontend/packages/dashboard/src/settings.js diff --git a/frontend/packages/dashboard/build.mjs b/frontend/packages/dashboard/build.mjs index 6cdd553e..63c6f6b5 100644 --- a/frontend/packages/dashboard/build.mjs +++ b/frontend/packages/dashboard/build.mjs @@ -47,12 +47,12 @@ const staticDir = (p) => resolve(here, 'dist', 'static', p); rmSync(dist(''), { recursive: true, force: true }); mkdirSync(staticDir(''), { recursive: true }); -// Bundle all three JS entries. ES-module output, browser target, no minify +// Bundle the JS entries. ES-module output, browser target, no minify // (line-aligned source aids debugging; minification belongs in a later // follow-up once asset sizes warrant it). esbuild writes each entry // to `static/.js` based on the entryPoint basename. await build({ - entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js'), src('home.js')], + entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js'), src('home.js'), src('settings.js')], outdir: staticDir(''), bundle: true, format: 'esm', @@ -91,7 +91,7 @@ await build({ // so a swap replaces only it) + theme.css (the semantic derivation // layer) + common.css (shared typography, badges, buttons, inbox, side // panel) plus its own page-specific bundle. -for (const entry of ['colors.css', 'theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css', 'home.css']) { +for (const entry of ['colors.css', 'theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css', 'home.css', 'settings.css']) { await build({ entryPoints: [src(entry)], outfile: staticDir(entry), @@ -101,7 +101,7 @@ for (const entry of ['colors.css', 'theme.css', 'common.css', 'dashboard.css', ' }); } -for (const html of ['index.html', 'dashboard.html', 'flow.html', 'logs.html']) { +for (const html of ['index.html', 'dashboard.html', 'flow.html', 'logs.html', 'settings.html']) { copyFileSync(src(html), dist(html)); } diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index 77a8f95c..3b1f0229 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -434,28 +434,8 @@ body.dashboard-shell { } .port-conflict strong { color: var(--red); } -.notif-row { - display: flex; - gap: 0.5em; - align-items: center; - margin: 0.5em 0; - font-size: 0.85em; -} -.btn-notif { - font-family: inherit; - font-size: 0.85em; - background: transparent; - color: var(--cyan); - border: 1px solid var(--cyan); - padding: 0.2em 0.7em; - border-radius: 999px; - cursor: pointer; - text-shadow: 0 0 4px currentColor; -} -.btn-notif:hover { - background: color-mix(in srgb, var(--cyan) 10%, transparent); - box-shadow: 0 0 10px -2px currentColor; -} +/* .notif-row / .btn-notif moved to settings.css with the S3TT1NGS + page. */ .pending-state { color: var(--amber); diff --git a/frontend/packages/dashboard/src/dashboard.html b/frontend/packages/dashboard/src/dashboard.html index 5214b75e..e1f20160 100644 --- a/frontend/packages/dashboard/src/dashboard.html +++ b/frontend/packages/dashboard/src/dashboard.html @@ -78,15 +78,9 @@ into the log viewer (an agent's logs, a build entry's log) still live inside the relevant dashboard content. --> - - - ◆ S3TT1NGS ◆ - + @@ -282,26 +276,11 @@ tabs.js boots on this page the corresponding renderers no-op silently (each guard is `if (!el) return`). --> - - + diff --git a/frontend/packages/dashboard/src/index.html b/frontend/packages/dashboard/src/index.html index 63ebb5d1..6fb86101 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -39,6 +39,11 @@ build · agent · system logs + + Settings + operator-local prefs · browser notifications + + diff --git a/frontend/packages/dashboard/src/settings.css b/frontend/packages/dashboard/src/settings.css new file mode 100644 index 00000000..bf2218d7 --- /dev/null +++ b/frontend/packages/dashboard/src/settings.css @@ -0,0 +1,40 @@ +/* ─── /settings.html — operator-local preferences ────────────────── + Extracted from the dashboard S3TT1NGS tab. Same + minimal-chrome pattern as /flow.html and /logs.html: the back-link + header + title (.logs-header / .logs-back / .logs-title) live in + common.css; only the page body padding + the notif-toggle row are + page-specific and live here. */ + +body.settings-shell { + margin: 0; + padding: 0; +} + +.settings-main { + padding: 1.2em 1.5em 2em; +} + +/* Browser-notification toggle row (moved verbatim from dashboard.css — + the buttons + status that NOTIF.bind() in common.js wires up). */ +.notif-row { + display: flex; + gap: 0.5em; + align-items: center; + margin: 0.5em 0; + font-size: 0.85em; +} +.btn-notif { + font-family: inherit; + font-size: 0.85em; + background: transparent; + color: var(--cyan); + border: 1px solid var(--cyan); + padding: 0.2em 0.7em; + border-radius: 999px; + cursor: pointer; + text-shadow: 0 0 4px currentColor; +} +.btn-notif:hover { + background: color-mix(in srgb, var(--cyan) 10%, transparent); + box-shadow: 0 0 10px -2px currentColor; +} diff --git a/frontend/packages/dashboard/src/settings.html b/frontend/packages/dashboard/src/settings.html new file mode 100644 index 00000000..d568f397 --- /dev/null +++ b/frontend/packages/dashboard/src/settings.html @@ -0,0 +1,38 @@ + + + + + + hyperhive // S3TT1NGS + + + + + + + + + +
+ ← home + 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.

+
+ + + + +
+
+ + + + diff --git a/frontend/packages/dashboard/src/settings.js b/frontend/packages/dashboard/src/settings.js new file mode 100644 index 00000000..c6d5fc29 --- /dev/null +++ b/frontend/packages/dashboard/src/settings.js @@ -0,0 +1,15 @@ +// /settings.html — operator-local preferences page. +// +// Extracted from the dashboard S3TT1NGS tab. Today the only setting is +// the browser-notification toggle: NOTIF.bind() wires the enable / mute / +// unmute buttons and persists state to localStorage. The dashboard's +// NOTIF.show() calls (approvals / questions) read that same localStorage +// state + the browser-level permission, so firing still works from the +// dashboard even though the toggle UI now lives on its own page. +// +// initServerWarnings() renders the shared top-of-page warnings banner, +// matching the other stand-alone pages (FL0W / L0GS / H0M3). +import { NOTIF, initServerWarnings } from './common.js'; + +initServerWarnings(); +NOTIF.bind(); diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index f773987e..333268cf 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -3979,7 +3979,10 @@ window.marked = marked; // title reflect unread agent→operator messages immediately, before the // operator opens the tab. Live updates arrive via the broker stream. refreshOperatorInbox(); - NOTIF.bind(); + // NOTIF.bind() (the enable/mute/unmute toggle UI) moved to /settings.html + // with the S3TT1NGS page. The dashboard keeps NOTIF.show() for approval/ + // question notifications — it reads the browser permission + localStorage + // mute flag the settings page sets, so no bind() is needed here. Panel.bind(); // ─── live updates: dashboard event stream ────────────────────────────── @@ -4053,9 +4056,10 @@ window.marked = marked; // ─── tab routing ─────────────────────────────────────────────────────── // 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 + // `#permissions` / `#stats` activate the matching pane on the + // dashboard. Empty hash defaults to SW4RM. FL0W and S3TT1NGS are NOT + // tabs — they're separate pages (`/flow.html`, `/settings.html`) + // reached from the H0M3 hub. 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). // The shared hash-routed tab strip (@hive/shared/tabs.js) owns the diff --git a/nix/frontend.nix b/nix/frontend.nix index 0d50b929..dcaf687a 100644 --- a/nix/frontend.nix +++ b/nix/frontend.nix @@ -16,9 +16,10 @@ # $out/dashboard/ the hive-c0re dashboard assets (full layout in # frontend/packages/dashboard/build.mjs): # index.html (H0M3 hub, served at /) dashboard.html (operator SPA, -# served at /dashboard.html) flow.html logs.html favicon.svg -# static/{home,tabs,flow,logs,stream-worker}.js{,.map} -# static/{colors,theme,common,home,dashboard,flow,logs}.css +# served at /dashboard.html) flow.html logs.html settings.html +# favicon.svg +# static/{home,tabs,flow,logs,settings,stream-worker}.js{,.map} +# static/{colors,theme,common,home,dashboard,flow,logs,settings}.css # $out/agent/ the per-agent default UI (layered with # hyperhive.frontend.extraFiles at activation time) # index.html stats.html screen.html