From 198db326b31ca162a981f55754808710fcf0baad Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 2 Aug 2026 18:25:45 +0200 Subject: [PATCH] agent web UI: add terminal verbosity setting (expand tool output by default) Adds a browser-local (localStorage only, no backend field) toggle in the per-agent overflow menu's new settings section: whether otherwise- collapsed
rows in the live terminal (long tool-results, Write/Edit diffs, ...) default open. Message-bearing rows that already default open (send/ask/answer/recv) are unaffected either way. The shared terminal factory (frontend/packages/shared/src/terminal/terminal.js) gains an optional expandDetails option (boolean or zero-arg function), read live on every details()/detailsDiff() call rather than captured once, so flipping the toggle mid-session applies to the next rendered row without a reload. Unused by the dashboard's own terminal pane, so its default-closed behaviour is unchanged. Closes #2961. --- docs/web-ui/agent.md | 10 ++++ docs/web-ui/shape.md | 6 ++- frontend/packages/agent/src/agent.css | 13 +++++ frontend/packages/agent/src/app.js | 53 +++++++++++++++++++ .../packages/shared/src/terminal/terminal.js | 13 +++++ 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/web-ui/agent.md b/docs/web-ui/agent.md index ff51a5d9..ffd82a32 100644 --- a/docs/web-ui/agent.md +++ b/docs/web-ui/agent.md @@ -101,6 +101,16 @@ through. Three flex columns: the `/effort ` slash command). The active level's button is highlighted; `renderEffortChip` keeps the picker in sync with `StateSnapshot.effort` from the cold-load snapshot. + A third separator + **settings** section holds a single + `role="menuitemcheckbox"` toggle, `☐/☑ expand tool output` — flips + whether otherwise-collapsed `
` rows in the terminal (long + tool-results, Write/Edit diffs, …) default open. Pure client-side: + persisted to this browser's `localStorage` only (no backend field, + no `/api/*` call), read live by the shared terminal factory's + `expandDetails` option (see docs/web-ui/shape.md::Shared terminal + pane) so toggling mid-session applies to the next rendered row + without a reload. Rows that already default open regardless (send / + ask / answer / recv) are unaffected either way. The popover's display rules are scoped to `:not([hidden])` so the `[hidden]` HTML attribute's UA `display: none` isn't overridden by the author CSS's `display: flex` — the popover stays hidden until diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md index bd657f15..0de8445c 100644 --- a/docs/web-ui/shape.md +++ b/docs/web-ui/shape.md @@ -115,7 +115,11 @@ event picture), `onBackfillDone?(count)` (one-shot after replay; `count=0` on failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource (re)connect — use to re-sync snapshot-derived state after a reconnect gap), `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 operator's intent: true means "keep snapping to bottom on every diff --git a/frontend/packages/agent/src/agent.css b/frontend/packages/agent/src/agent.css index 79f32625..4818b3a4 100644 --- a/frontend/packages/agent/src/agent.css +++ b/frontend/packages/agent/src/agent.css @@ -299,6 +299,19 @@ h2, h3 { border-color: var(--purple-dim); background: color-mix(in srgb, var(--purple) 6%, transparent); } +.overflow-item-verbosity { + color: var(--muted); +} +.overflow-item-verbosity:hover { + color: var(--fg); + background: color-mix(in srgb, var(--purple) 8%, transparent); + border-color: var(--purple-dim); +} +.overflow-item-verbosity.active { + color: var(--purple); + border-color: var(--purple-dim); + background: color-mix(in srgb, var(--purple) 6%, transparent); +} /* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */ .header-pill { diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 33e0af8c..6b2cc320 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -26,6 +26,22 @@ window.marked = marked; ({ '&':'&', '<':'<', '>':'>', '"':'"' }[c]) ); + // "Expand tool output by default" terminal-verbosity preference — + // pure client-side, browser-local (localStorage only, no backend + // field). See the overflow-menu settings toggle below + the + // `expandDetails` option passed to HiveTerminal.create(). + const EXPAND_DETAILS_KEY = 'hive-agent-expand-details'; + function getExpandDetailsPref() { + try { return localStorage.getItem(EXPAND_DETAILS_KEY) === '1'; } + catch { return false; } + } + 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 */ } + } + // Base URL of the host dashboard (core backend). Set once the first // /api/state lands. Operator-authority actions (answering a question // as the operator) POST here rather than to this agent's own socket — @@ -251,6 +267,40 @@ window.marked = marked; menu.append(btn); } + // ─── settings: expand tool output by default ─────────────────── + // Pure client-side, browser-local preference (no backend involved) — + // see docs/web-ui/agent.md::Overflow button. Controls the *default* + // open state of otherwise-collapsed `
` rows in the live + // terminal (long tool-results, Write/Edit diffs, …); rows that + // already default open (send/ask/answer/recv) are unaffected. + // Read live by the shared terminal factory (HiveTerminal.create's + // `expandDetails` option below), so toggling mid-session applies to + // the next rendered row without a reload. + const settingsSep = el('div', { class: 'overflow-sep', 'aria-hidden': 'true' }); + const settingsLabel = el('div', { class: 'overflow-section-label' }, 'settings'); + menu.append(settingsSep, settingsLabel); + const expandIcon = el('span', { class: 'overflow-item-icon', 'aria-hidden': 'true' }, + getExpandDetailsPref() ? '☑' : '☐'); + const expandBtn = el('button', { + type: 'button', + class: 'overflow-item overflow-item-verbosity' + (getExpandDetailsPref() ? ' active' : ''), + role: 'menuitemcheckbox', + 'aria-checked': String(getExpandDetailsPref()), + title: 'expand tool output panels by default in this terminal (persisted to this browser only)', + id: 'expand-details-btn', + }, + expandIcon, + 'expand tool output', + ); + expandBtn.addEventListener('click', () => { + const next = !getExpandDetailsPref(); + setExpandDetailsPref(next); + expandBtn.classList.toggle('active', next); + expandBtn.setAttribute('aria-checked', String(next)); + expandIcon.textContent = next ? '☑' : '☐'; + }); + menu.append(expandBtn); + // ─── effort quick-picker ─────────────────────────────────────── // One-click shortcuts for each reasoning-effort level the backend // declares in `availableEfforts` (from `state.available_efforts`). @@ -1613,6 +1663,9 @@ window.marked = marked; // (e.g. /agent//) still hits the right SSE upstream. historyUrl: 'events/history', 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: { turn_start(ev, api) { if (api.fromHistory) openTurnsFromHistory += 1; diff --git a/frontend/packages/shared/src/terminal/terminal.js b/frontend/packages/shared/src/terminal/terminal.js index e00a55cd..5830e0cb 100644 --- a/frontend/packages/shared/src/terminal/terminal.js +++ b/frontend/packages/shared/src/terminal/terminal.js @@ -231,11 +231,23 @@ export function create(opts) { afterAppend(wasNearBottom); return [e, tn]; } + // `opts.expandDetails` (boolean or zero-arg function returning one) lets + // the caller default every otherwise-collapsed `
` 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) { clearPlaceholder(); const wasNearBottom = isNearBottom(); const d = document.createElement('details'); d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); + if (wantsExpandedDefault()) d.open = true; d.appendChild(buildSummary(summary, icon)); const pre = document.createElement('pre'); pre.className = 'tool-body'; @@ -250,6 +262,7 @@ export function create(opts) { const wasNearBottom = isNearBottom(); const d = document.createElement('details'); d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); + if (wantsExpandedDefault()) d.open = true; d.appendChild(buildSummary(summary, icon)); const pre = document.createElement('pre'); pre.className = 'tool-body diff-body';