settings menu: replace showExpandDetails prop with a children slot

mara, reviewing PR#3780's showExpandDetails boolean prop: "you cannot
just add it like this - if we have more and more options there in
different places we will keep accumulating cruft in the shared
component." SettingsMenu now exposes a plain children slot instead;
the agent page owns its expand-tool-output row entirely (state,
storage, markup) in its own ExpandDetailsSetting component and passes
it in, so the shared component never learns that setting exists.
This commit is contained in:
iris 2026-08-30 15:55:23 +02:00 committed by mara
commit 33fafc8c19
3 changed files with 52 additions and 31 deletions

View file

@ -24,11 +24,11 @@
// component, so the two stay in sync without this component owning any
// page-specific naming decision.
import { useEffect, useRef, useState } from 'preact/hooks';
import type { ComponentChildren } from 'preact';
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'];
@ -38,28 +38,24 @@ 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.
* Consumer-owned extra row(s), rendered inside the popover after the
* built-in theme/motion rows. Not a named boolean prop per setting
* a `showExpandDetails?: boolean` attempt drew mara's review: "you
* cannot just add it like this - if we have more and more options
* there in different places we will keep accumulating cruft in the
* shared component." A caller owns its row(s) entirely (state,
* storage key, markup); this component never learns they exist. Use
* `settings-menu-row` (this file's CSS) on each row's outer element
* to match spacing/typography see `Root.tsx` for a worked example.
*/
showExpandDetails?: boolean;
children?: ComponentChildren;
}
export function SettingsMenu({ themeKey, motionKey, showExpandDetails }: SettingsMenuProps) {
export function SettingsMenu({ themeKey, motionKey, children }: 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(() => {
@ -115,20 +111,7 @@ export function SettingsMenu({ themeKey, motionKey, showExpandDetails }: Setting
))}
</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}
{children}
</div>
) : null}
</div>