diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx
index 3a34ebdb..d18c7546 100644
--- a/frontend/packages/agent/src/Root.tsx
+++ b/frontend/packages/agent/src/Root.tsx
@@ -11,6 +11,7 @@ import { useApplyThemeOverride } from '@hive/shared/theme-apply.js';
import { useApplyMotionOverride } from '@hive/shared/motion-apply.js';
import { Header } from './components/Header.js';
import { MetaNav } from './components/MetaNav.js';
+import { ExpandDetailsSetting } from './components/ExpandDetailsSetting.js';
import { StatusChips } from './components/StatusChips.js';
import { LiveStream, type LiveStreamHandle } from './components/LiveStream.js';
import { HeaderPill } from './components/HeaderPill.js';
@@ -113,7 +114,9 @@ export function Root() {
dashboardBase={resolveDashboardBase(state.dashboard_port)}
/>
) : null}
-
+
+
+
>
);
// Kept mounted regardless of `openPanel` (open/closed toggles just the
diff --git a/frontend/packages/agent/src/components/ExpandDetailsSetting.tsx b/frontend/packages/agent/src/components/ExpandDetailsSetting.tsx
new file mode 100644
index 00000000..3a7a939b
--- /dev/null
+++ b/frontend/packages/agent/src/components/ExpandDetailsSetting.tsx
@@ -0,0 +1,35 @@
+// Agent-terminal-only settings-menu row: "expand tool output panels".
+// Rendered as a `` child (`@hive/shared/settings-menu.js`)
+// rather than a prop on that shared component — `SettingsMenu` is also
+// used by swarm-ui, which has no terminal to apply this to, and a prior
+// attempt to gate it behind a boolean prop (`showExpandDetails`) on the
+// shared component 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." This file owns
+// its own state/storage entirely; `SettingsMenu` just renders it as a
+// child, no different from any other consumer's markup.
+import { useState } from 'preact/hooks';
+import { getExpandDetailsPref, setExpandDetailsPref } from '@hive/shared/prefs.js';
+
+export function ExpandDetailsSetting() {
+ // Plain localStorage read, not a hook-managed override like
+ // theme/motion — `getExpandDetailsPref`/`setExpandDetailsPref`
+ // (`@hive/shared/prefs.js`) are the existing shared-storage-key pair
+ // the terminal itself already reads live (no round-trip needed here
+ // beyond re-rendering this row's own checkbox state on toggle).
+ const [expandDetails, setExpandDetailsState] = useState(() => getExpandDetailsPref());
+ return (
+
+ );
+}
diff --git a/frontend/packages/shared/src/settings/SettingsMenu.tsx b/frontend/packages/shared/src/settings/SettingsMenu.tsx
index c5711e67..233d5677 100644
--- a/frontend/packages/shared/src/settings/SettingsMenu.tsx
+++ b/frontend/packages/shared/src/settings/SettingsMenu.tsx
@@ -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(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
))}
- {showExpandDetails ? (
-
- ) : null}
+ {children}
) : null}