diff --git a/docs/web-ui/README.md b/docs/web-ui/README.md index d3b95f21..265171b6 100644 --- a/docs/web-ui/README.md +++ b/docs/web-ui/README.md @@ -26,11 +26,12 @@ of your time. It's a single page with exactly four tabs: Everything else lives on its own page instead, all reachable from the H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across the whole swarm), **Logs** (`/logs.html`, per-agent and host -journals), **Stats** (`/stats.html`, swarm-wide usage stats), **Settings** -(`/settings.html`, your local browser preferences), **Builds** (`/builds.html`, the rebuild -queue and build history), **Core** (`/core.html`, tombstones and -container resource use), and **Credentials** (`/credentials.html`, -provisioning matrix/GitHub/forge accounts per agent). +journals), **Stats** (`/stats.html`, swarm-wide usage stats), +**Builds** (`/builds.html`, the rebuild queue and build history), +**Core** (`/core.html`, tombstones and container resource use), and +**Credentials** (`/credentials.html`, provisioning matrix/GitHub/forge +accounts per agent). Your local browser preferences (notifications) +live in the dashboard's Y3R C4LL tab now, not a separate page. Each agent also has its own page — a full terminal view of that agent's session, reachable by clicking its name anywhere in the diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index b0f84fb8..bae45de0 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -7,7 +7,7 @@ The dashboard is served at `/dashboard.html` (with the home page at `/`). It has a fixed chrome header at the top and a `
` that shows exactly one tab pane at a time. The URL hash (`#swarm`, `#call`, `#system`, -`#permissions`, `#schedules`, `#settings`) drives which pane is +`#permissions`, `#schedules`) drives which pane is active; hash changes don't reload the page. FL0W, L0GS, and the optional M4TR1X client are separate pages reachable from the H0M3 hub at `/`, not from the dashboard tab strip. @@ -18,16 +18,15 @@ from the dashboard tab strip. - **Tab strip**: `◆ SW4RM ◆`, `◆ Y3R C4LL ◆`, `◆ P3RM1SS10NS ◆`, `◆ SCH3DUL3S ◆`. In-page tabs only — the SYST3M panels moved to the standalone **C0R3** page (`/core.html`), and FL0W / L0GS / ST4TS / - S3TT1NGS / M4TR1X live on their own pages too, all reachable from the + M4TR1X live on their own pages too, all reachable from the H0M3 hub (not the tab strip). Count pills on SW4RM (container count), Y3R C4LL (pending approvals + unread - operator messages), and SCH3DUL3S (active schedules); S3TT1NGS has no - count. + operator messages), and SCH3DUL3S (active schedules). - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) — sits below the tab strip. - **Server-warnings banner** — a generic, sticky top-of-page strip shown on **every** page (dashboard + every stand-alone page — FL0W, L0GS, - H0M3, C0R3, BU1LDS, CR3D3NTIALS, ST4TS, S3TT1NGS), injected at the top + H0M3, C0R3, BU1LDS, CR3D3NTIALS, ST4TS), injected at the top of `` by `renderServerWarnings` in `common.js`. Driven by `state.server_warnings` — a list of `{ kind, level, message }` — and coloured by `level` (`warn` amber / `crit` red). The backend owns the @@ -100,6 +99,27 @@ messages are visible from any tab even while inactive. Backed by `GET /api/operator-inbox` → `{ messages: [...] }` (id, from, body, at, in_reply_to, file_refs). +**◆ PR3F3R3NC3S ◆** — operator-local preferences. State lives in the +browser's `localStorage` — preferences do NOT sync between devices and +do NOT survive a profile wipe. One section today (browser +notifications); future preferences land here as sibling `

` blocks +in `dashboard.html`. Was its own S3TT1NGS page/tile (`/settings.html`) +until mara moved it in here — a standalone page for one toggle didn't +earn its own tile. + +**◇ 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 "expand tool output panels" toggle lives on the per-agent page's +own settings popover (`SettingsMenu`, `@hive/shared/settings-menu.js`) +instead — it's agent-terminal-only, unrelated to this section. + ## C0R3 page (`/core.html`) Passive / rare-interaction state. No longer a dashboard tab — it's a @@ -595,32 +615,10 @@ the model id, longest match wins) mapping to `{ input, output, cache_read, cache_write }` USD-per-million-token prices. Models not covered fall back to hive-c0re's built-in estimate. -## 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. One section today (browser -notifications); future preferences land here as sibling `

` -blocks in `settings.html`. - -**◇ 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 "expand tool output panels" toggle used to live here too; it -moved into the per-agent page's own settings popover (`SettingsMenu`, -`@hive/shared/settings-menu.js`) since it's agent-terminal-only and -this tab is shared with swarm-ui — see that component's file comment. - -The FL0W page does NOT host this pane — settings live only on the -dashboard's S3TT1NGS tab (reach it via the FL0W page's `← home` +The FL0W page does NOT host the PR3F3R3NC3S pane — it lives only on the +dashboard's Y3R C4LL tab (reach it via the FL0W page's `← home` back-link → Dashboard). Notifications still fire on the FL0W page when -they're enabled here, because `NOTIF.show()` in +they're enabled there, 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. @@ -1091,9 +1089,9 @@ Pure frontend (`Notification` API). Two signals trigger them: - new pending approval (per id, delta on `/api/state`) - 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`. +The toggle controls live in the Y3R C4LL tab's ◆ PR3F3R3NC3S ◆ +section; 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. diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md index 3a12b496..c1f3d409 100644 --- a/docs/web-ui/shape.md +++ b/docs/web-ui/shape.md @@ -20,7 +20,7 @@ CSS bundle per page (`colors.css` + `theme.css` + `common.css`, loaded by every page, plus a page-specific bundle — `dashboard.css` / `flow.css` / `logs.css` / `home.css` / - `settings.css` / `stats.css` / `core.css` / `builds.css` / + `stats.css` / `core.css` / `builds.css` / `credentials.css`); `common.css` inlines `@hive/shared`'s `base.css` + `terminal.css` + `tabs.css` + `chrome.css` + `pill.css` via esbuild's `@import` resolution. diff --git a/frontend/packages/dashboard/build.mjs b/frontend/packages/dashboard/build.mjs index f4e16c3b..d2844aa5 100644 --- a/frontend/packages/dashboard/build.mjs +++ b/frontend/packages/dashboard/build.mjs @@ -66,7 +66,7 @@ mkdirSync(staticDir(''), { recursive: true }); // 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'), src('settings.js'), src('stats.js'), src('core.js'), src('builds.js'), src('credentials.js')], + entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js'), src('home.js'), src('stats.js'), src('core.js'), src('builds.js'), src('credentials.js')], outdir: staticDir(''), bundle: true, format: 'esm', @@ -122,7 +122,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', 'settings.css', 'stats.css', 'core.css', 'builds.css', 'credentials.css']) { +for (const entry of ['colors.css', 'theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css', 'home.css', 'stats.css', 'core.css', 'builds.css', 'credentials.css']) { await build({ entryPoints: [src(entry)], outfile: staticDir(entry), @@ -132,7 +132,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', 'settings.html', 'stats.html', 'core.html', 'builds.html', 'credentials.html']) { +for (const html of ['index.html', 'dashboard.html', 'flow.html', 'logs.html', 'stats.html', 'core.html', 'builds.html', 'credentials.html']) { copyFileSync(src(html), dist(html)); } diff --git a/frontend/packages/dashboard/src/common.css b/frontend/packages/dashboard/src/common.css index f3c5df99..a5db50a0 100644 --- a/frontend/packages/dashboard/src/common.css +++ b/frontend/packages/dashboard/src/common.css @@ -434,7 +434,7 @@ hive-side-panel .md td { server-warnings banner, the footer) then spans edge-to-edge for free, with no negative-margin breakout hacks. Only matches on pages that opt in by adding the wrapper (dashboard.html, index.html); FL0W / L0GS / - ST4TS / S3TT1NGS are full-bleed with their own `.-main` padding. */ + ST4TS are full-bleed with their own `.-main` padding. */ .page-content { padding: 0 1.5em; } /* ─── sticky top region ─────────────────────────────────────────────── diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index 476663dd..49c880f3 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -461,8 +461,32 @@ hive-agent-menu { `JobqRollup` Preact component (`.jqr-*` classes, imported above) — mounted by swarm.js::initJobqRollup into #jobq-rollup-section. */ -/* .notif-row / .btn-notif moved to settings.css with the S3TT1NGS - page. */ +/* Browser-notification toggle row, now living in the Y3R C4LL tab's + ◆ PR3F3R3NC3S ◆ section (was its own S3TT1NGS page/tile; mara: a + standalone page for one toggle didn't earn its own tile). Buttons + wired by NOTIF.bind() in common.js. */ +.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; +} .pending-state { color: var(--amber); diff --git a/frontend/packages/dashboard/src/dashboard.html b/frontend/packages/dashboard/src/dashboard.html index d8a6112f..990dc9c6 100644 --- a/frontend/packages/dashboard/src/dashboard.html +++ b/frontend/packages/dashboard/src/dashboard.html @@ -72,9 +72,6 @@ into the log viewer (an agent's logs, a build entry's log) still live inside the relevant dashboard content. --> - @@ -129,6 +126,24 @@

loading…

+ + +

◆ PR3F3R3NC3S ◆

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

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 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/index.html b/frontend/packages/dashboard/src/index.html index 3aa97541..044c884b 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -76,14 +76,6 @@ hive-wide turn stats · cost · model mix - - - - Settings - - operator-local prefs · browser notifications - - diff --git a/frontend/packages/dashboard/src/settings.css b/frontend/packages/dashboard/src/settings.css deleted file mode 100644 index def1aa9a..00000000 --- a/frontend/packages/dashboard/src/settings.css +++ /dev/null @@ -1,40 +0,0 @@ -/* ─── /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 (.page-header / .page-back / .page-title) live in - @hive/shared/chrome.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 deleted file mode 100644 index a7ea4da1..00000000 --- a/frontend/packages/dashboard/src/settings.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - hyperhive // 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 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 deleted file mode 100644 index 298378d1..00000000 --- a/frontend/packages/dashboard/src/settings.js +++ /dev/null @@ -1,15 +0,0 @@ -// /settings.html — operator-local preferences page. -// -// Extracted from the dashboard S3TT1NGS tab. The browser-notification -// toggle: NOTIF.bind() wires the enable / mute / unmute buttons and -// persists state to localStorage. The dashboard's NOTIF.show() calls -// (approvals) 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 4be7010d..3d6cd2a9 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -257,12 +257,14 @@ 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() (the enable/mute/unmute toggle UI) moved to /settings.html - // with the S3TT1NGS page. The dashboard keeps NOTIF.show() for approval - // notifications — it reads the browser permission + localStorage - // mute flag the settings page sets, so no bind() is needed here. The - // side panel () wires its own internals in - // connectedCallback — no bind() step needed either. + // NOTIF.bind() wires the enable/mute/unmute toggle buttons that now + // live in the Y3R C4LL tab's ◆ PR3F3R3NC3S ◆ section. NOTIF.show() + // (approval/inbox notifications) reads the same browser permission + + // localStorage mute flag regardless of which page fires it — bind() + // only needs to run once, here, where the buttons live. The side + // panel () wires its own internals in + // connectedCallback — no bind() step needed for it. + NOTIF.bind(); // ─── live updates: dashboard event stream ────────────────────────────── // The dashboard subscribes to /dashboard/stream for live mutation @@ -349,9 +351,9 @@ window.marked = marked; // ─── tab routing ─────────────────────────────────────────────────────── // Hash-based: `#swarm` / `#call` / `#system` / `#permissions` / // `#schedules` activate the matching pane on the dashboard. Empty - // hash defaults to SW4RM. FL0W, S3TT1NGS, and ST4TS are NOT tabs — - // they're separate pages (`/flow.html`, `/settings.html`, - // `/stats.html`) reached from the H0M3 hub. Tab routing only + // hash defaults to SW4RM. FL0W and ST4TS are NOT tabs — + // they're separate pages (`/flow.html`, `/stats.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/frontend/packages/shared/src/prefs.ts b/frontend/packages/shared/src/prefs.ts index de4fa5bf..ebfb5369 100644 --- a/frontend/packages/shared/src/prefs.ts +++ b/frontend/packages/shared/src/prefs.ts @@ -1,10 +1,9 @@ -// Tiny browser-local (localStorage) preference helpers shared by the -// dashboard's /settings.html and the per-agent page — both need the -// exact same key name to actually talk to each other via the browser's -// shared per-origin storage (settings.html is where the operator sets -// the preference; every per-agent page's terminal reads it), so the -// get/set pair — and the key itself — live here once rather than as -// independent copies in each package that could drift out of sync. +// Tiny browser-local (localStorage) preference helper, in `@hive/shared` +// so a future second consumer (e.g. swarm-ui's own terminal) gets the +// exact same key name for free via the browser's shared per-origin +// storage, rather than an independent copy that could drift. +// `ExpandDetailsSetting` (the per-agent page's own settings popover) is +// the only writer today; `Row.tsx` is the only reader. const EXPAND_DETAILS_KEY = 'hive-agent-expand-details'; diff --git a/nix/packages/frontend.nix b/nix/packages/frontend.nix index 406065bd..a8e9b41d 100644 --- a/nix/packages/frontend.nix +++ b/nix/packages/frontend.nix @@ -16,10 +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 settings.html +# served at /dashboard.html) flow.html logs.html # stats.html favicon.svg -# static/{home,tabs,flow,logs,settings,stats,stream-worker}.js{,.map} -# static/{colors,theme,common,home,dashboard,flow,logs,settings,stats}.css +# static/{home,tabs,flow,logs,stats,stream-worker}.js{,.map} +# static/{colors,theme,common,home,dashboard,flow,logs,stats}.css # $out/agent/ the per-agent default UI (layered with # hyperhive.frontend.extraFiles at activation time) # index.html stats.html screen.html