Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da3fc9bf95 | ||
|
|
3b299375e3 | ||
|
|
198db326b3 |
8 changed files with 100 additions and 11 deletions
|
|
@ -622,10 +622,9 @@ frontend reads.
|
||||||
|
|
||||||
Operator-local preferences. State lives in the browser's
|
Operator-local preferences. State lives in the browser's
|
||||||
`localStorage` — preferences do NOT sync between devices and
|
`localStorage` — preferences do NOT sync between devices and
|
||||||
do NOT survive a profile wipe. Today the tab holds one section
|
do NOT survive a profile wipe. Two sections today (browser
|
||||||
(browser notifications); future preferences (theme, density,
|
notifications, agent terminal); future preferences (theme, density,
|
||||||
etc.) land here as sibling `<h3>` blocks
|
etc.) land here as sibling `<h3>` blocks in `settings.html`.
|
||||||
under the same `<section id="tab-pane-settings">`.
|
|
||||||
|
|
||||||
**◇ browser notifications** — `🔔 enable notifications` button when
|
**◇ browser notifications** — `🔔 enable notifications` button when
|
||||||
permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted
|
permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted
|
||||||
|
|
@ -636,6 +635,19 @@ single status line explains why. See `### Browser notifications`
|
||||||
below for the dispatch model + the three signals the dashboard
|
below for the dispatch model + the three signals the dashboard
|
||||||
emits OS notifications on.
|
emits OS notifications on.
|
||||||
|
|
||||||
|
**◇ agent terminal** — a single `☐/☑ expand tool output panels`
|
||||||
|
toggle button (`role="switch"`, live `aria-checked`). Controls whether
|
||||||
|
every per-agent page's terminal defaults otherwise-collapsed
|
||||||
|
`<details>` rows (long tool-results, Write/Edit diffs, …) open; rows
|
||||||
|
that already default open regardless (send/ask/answer/recv) are
|
||||||
|
unaffected either way. Pure client-side — the key + get/set live in
|
||||||
|
`@hive/shared/prefs.js` (`getExpandDetailsPref`/`setExpandDetailsPref`)
|
||||||
|
so this page and every agent page's `app.js` read/write the exact same
|
||||||
|
`localStorage` key without a backend field; the shared terminal
|
||||||
|
factory reads it live via its `expandDetails` option (see
|
||||||
|
docs/web-ui/shape.md::Shared terminal pane), so a change here applies
|
||||||
|
to any already-open agent tab's next rendered row without a reload.
|
||||||
|
|
||||||
The FL0W page does NOT host this pane — settings live only on the
|
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`
|
dashboard's S3TT1NGS tab (reach it via the FL0W page's `← home`
|
||||||
back-link → Dashboard). Notifications still fire on the FL0W page when
|
back-link → Dashboard). Notifications still fire on the FL0W page when
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,11 @@ event picture), `onBackfillDone?(count)` (one-shot after replay; `count=0` on
|
||||||
failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource
|
failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource
|
||||||
(re)connect — use to re-sync snapshot-derived state after a reconnect gap),
|
(re)connect — use to re-sync snapshot-derived state after a reconnect gap),
|
||||||
`pillAnchor?` (parent element for the "↓ N new" pill; defaults to
|
`pillAnchor?` (parent element for the "↓ N new" pill; defaults to
|
||||||
`logEl.parentElement`).
|
`logEl.parentElement`), `expandDetails?` (boolean or zero-arg function
|
||||||
|
returning one, re-read on every `api.details`/`api.detailsDiff` call rather
|
||||||
|
than captured once — lets a page default otherwise-collapsed panels open
|
||||||
|
per a live browser-local preference; renderers that force a row open
|
||||||
|
regardless, e.g. message-bearing tool_use, are unaffected either way).
|
||||||
|
|
||||||
**Sticky-bottom + snap animation.** `stickToBottom` is the
|
**Sticky-bottom + snap animation.** `stickToBottom` is the
|
||||||
operator's intent: true means "keep snapping to bottom on every
|
operator's intent: true means "keep snapping to bottom on every
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { create as termCreate, linkify as termLinkify } from '@hive/shared/termi
|
||||||
import { asyncBtn, bindAsyncForms } from '@hive/shared/forms.js';
|
import { asyncBtn, bindAsyncForms } from '@hive/shared/forms.js';
|
||||||
import { themedConfirm } from '@hive/shared/modal.js';
|
import { themedConfirm } from '@hive/shared/modal.js';
|
||||||
import { el } from '@hive/shared/dom.js';
|
import { el } from '@hive/shared/dom.js';
|
||||||
|
import { getExpandDetailsPref } from '@hive/shared/prefs.js';
|
||||||
import '@hive/shared/side-panel.js'; // registers <hive-side-panel> — side-effect import
|
import '@hive/shared/side-panel.js'; // registers <hive-side-panel> — side-effect import
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
|
@ -1613,6 +1614,9 @@ window.marked = marked;
|
||||||
// (e.g. /agent/<name>/) still hits the right SSE upstream.
|
// (e.g. /agent/<name>/) still hits the right SSE upstream.
|
||||||
historyUrl: 'events/history',
|
historyUrl: 'events/history',
|
||||||
streamUrl: 'events/stream',
|
streamUrl: 'events/stream',
|
||||||
|
// Re-read live (not captured once) so flipping the overflow-menu
|
||||||
|
// toggle mid-session affects the next rendered row immediately.
|
||||||
|
expandDetails: () => getExpandDetailsPref(),
|
||||||
renderers: {
|
renderers: {
|
||||||
turn_start(ev, api) {
|
turn_start(ev, api) {
|
||||||
if (api.fromHistory) openTurnsFromHistory += 1;
|
if (api.fromHistory) openTurnsFromHistory += 1;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@
|
||||||
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button>
|
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button>
|
||||||
<span id="notif-status" class="meta" hidden></span>
|
<span id="notif-status" class="meta" hidden></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>◇ agent terminal</h3>
|
||||||
|
<p class="meta">applies to every agent's terminal viewed in this browser — whether otherwise-collapsed tool-output panels (long results, Write/Edit diffs, …) default open. rows that already default open (send/ask/answer/recv) are unaffected either way.</p>
|
||||||
|
<div id="verbosity-row" class="notif-row">
|
||||||
|
<button type="button" id="expand-details-toggle" class="btn btn-notif" role="switch"></button>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script type="module" src="/static/settings.js" defer></script>
|
<script type="module" src="/static/settings.js" defer></script>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,37 @@
|
||||||
// /settings.html — operator-local preferences page.
|
// /settings.html — operator-local preferences page.
|
||||||
//
|
//
|
||||||
// Extracted from the dashboard S3TT1NGS tab. Today the only setting is
|
// Extracted from the dashboard S3TT1NGS tab. The browser-notification
|
||||||
// the browser-notification toggle: NOTIF.bind() wires the enable / mute /
|
// toggle: NOTIF.bind() wires the enable / mute / unmute buttons and
|
||||||
// unmute buttons and persists state to localStorage. The dashboard's
|
// persists state to localStorage. The dashboard's NOTIF.show() calls
|
||||||
// NOTIF.show() calls (approvals / questions) read that same localStorage
|
// (approvals / questions) read that same localStorage state + the
|
||||||
// state + the browser-level permission, so firing still works from the
|
// browser-level permission, so firing still works from the dashboard
|
||||||
// dashboard even though the toggle UI now lives on its own page.
|
// even though the toggle UI now lives on its own page.
|
||||||
|
//
|
||||||
|
// The agent-terminal-verbosity toggle below is a second, unrelated
|
||||||
|
// browser-local preference — @hive/shared/prefs.js owns the key name +
|
||||||
|
// get/set so this page and every per-agent page's app.js agree on it
|
||||||
|
// without a backend field. Both preferences are per-origin localStorage,
|
||||||
|
// so they only apply within this browser (per the page's own copy).
|
||||||
//
|
//
|
||||||
// initServerWarnings() renders the shared top-of-page warnings banner,
|
// initServerWarnings() renders the shared top-of-page warnings banner,
|
||||||
// matching the other stand-alone pages (FL0W / L0GS / H0M3).
|
// matching the other stand-alone pages (FL0W / L0GS / H0M3).
|
||||||
import { NOTIF, initServerWarnings } from './common.js';
|
import { NOTIF, initServerWarnings } from './common.js';
|
||||||
|
import { getExpandDetailsPref, setExpandDetailsPref } from '@hive/shared/prefs.js';
|
||||||
|
|
||||||
initServerWarnings();
|
initServerWarnings();
|
||||||
NOTIF.bind();
|
NOTIF.bind();
|
||||||
|
|
||||||
|
(function bindExpandDetailsToggle() {
|
||||||
|
const btn = document.getElementById('expand-details-toggle');
|
||||||
|
if (!btn) return;
|
||||||
|
function render() {
|
||||||
|
const on = getExpandDetailsPref();
|
||||||
|
btn.textContent = on ? '☑ expand tool output panels' : '☐ expand tool output panels';
|
||||||
|
btn.setAttribute('aria-checked', String(on));
|
||||||
|
}
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setExpandDetailsPref(!getExpandDetailsPref());
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
render();
|
||||||
|
})();
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"./terminal.css": "./src/terminal/terminal.css",
|
"./terminal.css": "./src/terminal/terminal.css",
|
||||||
"./chrome.css": "./src/chrome.css",
|
"./chrome.css": "./src/chrome.css",
|
||||||
"./forms.js": "./src/forms.js",
|
"./forms.js": "./src/forms.js",
|
||||||
|
"./prefs.js": "./src/prefs.js",
|
||||||
"./dom.js": "./src/dom.js",
|
"./dom.js": "./src/dom.js",
|
||||||
"./modal.js": "./src/modal.js",
|
"./modal.js": "./src/modal.js",
|
||||||
"./shadow-css.js": "./src/shadow-css.js",
|
"./shadow-css.js": "./src/shadow-css.js",
|
||||||
|
|
|
||||||
27
frontend/packages/shared/src/prefs.js
Normal file
27
frontend/packages/shared/src/prefs.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
const EXPAND_DETAILS_KEY = 'hive-agent-expand-details';
|
||||||
|
|
||||||
|
// Whether a per-agent terminal's otherwise-collapsed `<details>` panels
|
||||||
|
// (long tool-results, Write/Edit diffs, …) should default open. Pure
|
||||||
|
// client-side — no backend field, nothing round-trips through
|
||||||
|
// `/api/*`. Read live (not cached) by the shared terminal factory's
|
||||||
|
// `expandDetails` option — see docs/web-ui/shape.md::Shared terminal
|
||||||
|
// pane — so a preference change on /settings.html applies to the next
|
||||||
|
// rendered row in any already-open agent tab without a reload.
|
||||||
|
export function getExpandDetailsPref() {
|
||||||
|
try { return localStorage.getItem(EXPAND_DETAILS_KEY) === '1'; }
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
export function setExpandDetailsPref(v) {
|
||||||
|
try {
|
||||||
|
if (v) localStorage.setItem(EXPAND_DETAILS_KEY, '1');
|
||||||
|
else localStorage.removeItem(EXPAND_DETAILS_KEY);
|
||||||
|
} catch { /* localStorage unavailable — preference is session-only */ }
|
||||||
|
}
|
||||||
|
|
@ -231,11 +231,23 @@ export function create(opts) {
|
||||||
afterAppend(wasNearBottom);
|
afterAppend(wasNearBottom);
|
||||||
return [e, tn];
|
return [e, tn];
|
||||||
}
|
}
|
||||||
|
// `opts.expandDetails` (boolean or zero-arg function returning one) lets
|
||||||
|
// the caller default every otherwise-collapsed `<details>` row open —
|
||||||
|
// e.g. an "expand panels by default" browser-local preference. Read
|
||||||
|
// live (not captured once) so a mid-session preference flip takes
|
||||||
|
// effect on the next row without recreating the terminal. Renderers
|
||||||
|
// that need a row open regardless (message-bearing tool_use/tool_result)
|
||||||
|
// already set `d.open = true` themselves after calling this — this only
|
||||||
|
// changes the *default* for panels that would otherwise start closed.
|
||||||
|
function wantsExpandedDefault() {
|
||||||
|
return typeof opts.expandDetails === 'function' ? !!opts.expandDetails() : !!opts.expandDetails;
|
||||||
|
}
|
||||||
function details(cls, summary, body, icon) {
|
function details(cls, summary, body, icon) {
|
||||||
clearPlaceholder();
|
clearPlaceholder();
|
||||||
const wasNearBottom = isNearBottom();
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
|
if (wantsExpandedDefault()) d.open = true;
|
||||||
d.appendChild(buildSummary(summary, icon));
|
d.appendChild(buildSummary(summary, icon));
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
pre.className = 'tool-body';
|
pre.className = 'tool-body';
|
||||||
|
|
@ -250,6 +262,7 @@ export function create(opts) {
|
||||||
const wasNearBottom = isNearBottom();
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
|
if (wantsExpandedDefault()) d.open = true;
|
||||||
d.appendChild(buildSummary(summary, icon));
|
d.appendChild(buildSummary(summary, icon));
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
pre.className = 'tool-body diff-body';
|
pre.className = 'tool-body diff-body';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue