agent term: move expand-tool-output setting into its own settings popup

mara, #3778: the toggle lived on the separate hive-dashboard's
/settings.html even though it's agent-terminal-only. Moved it into
SettingsMenu (shared with swarm-ui) behind an opt-in showExpandDetails
prop — only the agent page passes it, matching her caution that the
component is shared but this setting isn't.

Also found and fixed a real regression while touching this: the
Preact rewrite's classifyEvent.ts hardcodes defaultOpen: true only for
the always-open markdown-bearing rows (send/ask/answer/recv) and never
reads the preference at all for the rows it's actually meant to
control (diffs, plain tool output, long errors) — so the toggle
currently has zero effect on the live page. Wired
getExpandDetailsPref() into those four sites; the always-open rows are
untouched, matching the documented pre-rewrite behavior.

Converted prefs.js to prefs.ts (TS couldn't resolve types for a plain
.js import) — same public @hive/shared/prefs.js export path, matching
how badge.js/icons.js etc. already map a .js export name to a
.tsx/.ts source file.
This commit is contained in:
iris 2026-08-30 15:41:07 +02:00 committed by mara
commit 385d4b6fd7
7 changed files with 70 additions and 43 deletions

View file

@ -13,15 +13,20 @@ const EXPAND_DETAILS_KEY = 'hive-agent-expand-details';
// 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; }
// pane — so a preference change applies to the next rendered row in
// any already-open agent tab without a reload.
export function getExpandDetailsPref(): boolean {
try {
return localStorage.getItem(EXPAND_DETAILS_KEY) === '1';
} catch {
return false;
}
}
export function setExpandDetailsPref(v) {
export function setExpandDetailsPref(v: boolean): void {
try {
if (v) localStorage.setItem(EXPAND_DETAILS_KEY, '1');
else localStorage.removeItem(EXPAND_DETAILS_KEY);
} catch { /* localStorage unavailable — preference is session-only */ }
} catch {
/* localStorage unavailable — preference is session-only */
}
}

View file

@ -28,6 +28,7 @@ import { Badge } from '../badge/Badge.js';
import { GearIcon } from '../icons.js';
import { useThemeOverride, type ThemeOverride } from './theme-apply.js';
import { useMotionOverride, type MotionOverride } from './motion-apply.js';
import { getExpandDetailsPref, setExpandDetailsPref } from '../prefs.js';
import './SettingsMenu.css';
const THEME_OPTIONS: ThemeOverride[] = ['system', 'light', 'dark'];
@ -36,13 +37,29 @@ const MOTION_OPTIONS: MotionOverride[] = ['system', 'allow', 'reduce'];
export interface SettingsMenuProps {
themeKey: string;
motionKey: string;
/**
* Show the "expand tool output panels" toggle agent-terminal-only,
* formerly its own section on the (separate) hive dashboard's
* `/settings.html`. `SettingsMenu` is shared with swarm-ui too, which
* has no terminal to apply this to (mara: "settigs dropdown is
* shared component, but the setting is only in the agent term") so
* it's opt-in per consumer rather than always rendered. Only the
* agent page passes this; swarm-ui's `Shell` doesn't.
*/
showExpandDetails?: boolean;
}
export function SettingsMenu({ themeKey, motionKey }: SettingsMenuProps) {
export function SettingsMenu({ themeKey, motionKey, showExpandDetails }: SettingsMenuProps) {
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const [theme, setTheme] = useThemeOverride(themeKey);
const [motion, setMotion] = useMotionOverride(motionKey);
// Plain localStorage read, not a hook-managed override like theme/motion
// above — `getExpandDetailsPref`/`setExpandDetailsPref` (`../prefs.js`)
// are the existing shared-storage-key pair the terminal itself already
// reads live (no round-trip needed here beyond re-rendering this menu's
// own checkbox state on toggle).
const [expandDetails, setExpandDetailsState] = useState(() => getExpandDetailsPref());
// Close on an outside click or Escape — only listens while open.
useEffect(() => {
@ -98,6 +115,20 @@ export function SettingsMenu({ themeKey, motionKey }: SettingsMenuProps) {
))}
</select>
</label>
{showExpandDetails ? (
<label class="settings-menu-row">
<span>expand tool output</span>
<input
type="checkbox"
checked={expandDetails}
onChange={(e) => {
const v = (e.target as HTMLInputElement).checked;
setExpandDetailsPref(v);
setExpandDetailsState(v);
}}
/>
</label>
) : null}
</div>
) : null}
</div>