swarm-ui: a hide debug output toggle in the header's settings menu
The row moves from the per-agent page into @hive/shared so both pages mount the same one. The terminal's debug rows now follow the preference as it changes, so rows already on screen hide or show at once instead of only the rows that arrive after the toggle.
This commit is contained in:
parent
f2e2ff82e9
commit
be6036b216
9 changed files with 88 additions and 36 deletions
|
|
@ -35,6 +35,12 @@ An agent's terminal opens with its last 100 rows from the past 24 hours, then
|
||||||
follows new ones; a reconnect picks up after the last row it showed. A subagent
|
follows new ones; a reconnect picks up after the last row it showed. A subagent
|
||||||
terminal shows rows published while it's open, and has no turn-state badges.
|
terminal shows rows published while it's open, and has no turn-state badges.
|
||||||
|
|
||||||
|
The settings menu in the header has a **hide debug output** toggle. It drops
|
||||||
|
the muted debug rows (thinking, coalesced ticks, harness chatter) from every
|
||||||
|
terminal, the preview and the tabs, and rows already on screen hide or show as
|
||||||
|
you flip it. The browser keeps the choice for the swarm UI, apart from the
|
||||||
|
per-agent pages'.
|
||||||
|
|
||||||
A terminal's own tab takes the agent's icon as its browser-tab icon, so tabs of
|
A terminal's own tab takes the agent's icon as its browser-tab icon, so tabs of
|
||||||
different agents tell apart. A subagent's tab shows its agent's icon. An agent
|
different agents tell apart. A subagent's tab shows its agent's icon. An agent
|
||||||
with no icon keeps the hyperhive mark.
|
with no icon keeps the hyperhive mark.
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,11 @@ that motivated the change.
|
||||||
|
|
||||||
The settings menu's "hide debug output" toggle (`getHideDebugPref()`,
|
The settings menu's "hide debug output" toggle (`getHideDebugPref()`,
|
||||||
`@hive/shared/prefs.js`) skips `debug`-level rows entirely rather than
|
`@hive/shared/prefs.js`) skips `debug`-level rows entirely rather than
|
||||||
muting them further — client-side only, same live-read-no-reload shape
|
muting them further — client-side only, no reload. The per-agent page and
|
||||||
as the expand-tool-output preference above.
|
the swarm UI both have the toggle, as one shared row
|
||||||
|
(`@hive/shared/hide-debug-setting.js`). Each browser origin keeps its own
|
||||||
|
setting. A `debug` row follows the toggle as it changes, so rows already
|
||||||
|
on screen hide or show at once, not only the rows that arrive after it.
|
||||||
|
|
||||||
Per-tool icon + summary formatting (what a `Read`/`Edit`/`send` call's row
|
Per-tool icon + summary formatting (what a `Read`/`Edit`/`send` call's row
|
||||||
actually says) lives in `stream_enrich.rs`'s `fmt_tool_use()` family —
|
actually says) lives in `stream_enrich.rs`'s `fmt_tool_use()` family —
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { useApplyMotionOverride } from "@hive/shared/motion-apply.js";
|
||||||
import { Header } from "./components/Header.js";
|
import { Header } from "./components/Header.js";
|
||||||
import { MetaNav } from "./components/MetaNav.js";
|
import { MetaNav } from "./components/MetaNav.js";
|
||||||
import { ExpandDetailsSetting } from "./components/ExpandDetailsSetting.js";
|
import { ExpandDetailsSetting } from "./components/ExpandDetailsSetting.js";
|
||||||
import { HideDebugSetting } from "./components/HideDebugSetting.js";
|
import { HideDebugSetting } from "@hive/shared/hide-debug-setting.js";
|
||||||
import { StatusChips } from "./components/StatusChips.js";
|
import { StatusChips } from "./components/StatusChips.js";
|
||||||
import { LiveStream, type LiveStreamHandle } from "./components/LiveStream.js";
|
import { LiveStream, type LiveStreamHandle } from "./components/LiveStream.js";
|
||||||
import { HeaderPill } from "./components/HeaderPill.js";
|
import { HeaderPill } from "./components/HeaderPill.js";
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
// Agent-terminal-only settings-menu row: "hide debug output". Same shape
|
|
||||||
// as `ExpandDetailsSetting` (its own file's header comment explains why
|
|
||||||
// this lives here rather than as a `SettingsMenu` prop) — mara: "add a
|
|
||||||
// setting to not display verbose output... like the grey colored debug
|
|
||||||
// stuff."
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { getHideDebugPref, setHideDebugPref } from "@hive/shared/prefs.js";
|
|
||||||
|
|
||||||
export function HideDebugSetting() {
|
|
||||||
const [hideDebug, setHideDebugState] = useState(() => getHideDebugPref());
|
|
||||||
return (
|
|
||||||
<label class="settings-menu-row">
|
|
||||||
<span>hide debug output</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={hideDebug}
|
|
||||||
onChange={(e) => {
|
|
||||||
const v = (e.target as HTMLInputElement).checked;
|
|
||||||
setHideDebugPref(v);
|
|
||||||
setHideDebugState(v);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -52,6 +52,7 @@
|
||||||
"./theme-apply.js": "./src/settings/theme-apply.ts",
|
"./theme-apply.js": "./src/settings/theme-apply.ts",
|
||||||
"./motion-apply.js": "./src/settings/motion-apply.ts",
|
"./motion-apply.js": "./src/settings/motion-apply.ts",
|
||||||
"./settings-menu.js": "./src/settings/SettingsMenu.tsx",
|
"./settings-menu.js": "./src/settings/SettingsMenu.tsx",
|
||||||
|
"./hide-debug-setting.js": "./src/settings/HideDebugSetting.tsx",
|
||||||
"./settings-menu.css": "./src/settings/SettingsMenu.css"
|
"./settings-menu.css": "./src/settings/SettingsMenu.css"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
// so a future second consumer (e.g. swarm-ui's own terminal) gets the
|
// so a future second consumer (e.g. swarm-ui's own terminal) gets the
|
||||||
// exact same key name for free via the browser's shared per-origin
|
// exact same key name for free via the browser's shared per-origin
|
||||||
// storage, rather than an independent copy that could drift.
|
// storage, rather than an independent copy that could drift.
|
||||||
// `ExpandDetailsSetting` / `HideDebugSetting` (the per-agent page's own
|
// `ExpandDetailsSetting` / `HideDebugSetting` (the settings popover rows
|
||||||
// settings popover rows) are the only writers today; `Row.tsx` is the
|
// of the per-agent page and of the swarm UI) are the only writers today;
|
||||||
// only reader of either.
|
// `Row.tsx` is the only reader of either.
|
||||||
|
|
||||||
const EXPAND_DETAILS_KEY = "hive-agent-expand-details";
|
const EXPAND_DETAILS_KEY = "hive-agent-expand-details";
|
||||||
|
|
||||||
|
|
@ -53,4 +53,24 @@ export function setHideDebugPref(v: boolean): void {
|
||||||
} catch {
|
} catch {
|
||||||
/* localStorage unavailable — preference is session-only */
|
/* localStorage unavailable — preference is session-only */
|
||||||
}
|
}
|
||||||
|
for (const listener of hideDebugListeners) listener();
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideDebugListeners = new Set<() => void>();
|
||||||
|
|
||||||
|
// Calls `listener` whenever the preference changes: in this tab through
|
||||||
|
// `setHideDebugPref`, in another tab of the same origin through the
|
||||||
|
// browser's `storage` event. Returns the unsubscribe. This is what lets a
|
||||||
|
// terminal that is already showing rows follow the toggle, instead of only
|
||||||
|
// the rows that arrive after it.
|
||||||
|
export function subscribeHideDebugPref(listener: () => void): () => void {
|
||||||
|
hideDebugListeners.add(listener);
|
||||||
|
const onStorage = (e: StorageEvent) => {
|
||||||
|
if (e.key === HIDE_DEBUG_KEY || e.key === null) listener();
|
||||||
|
};
|
||||||
|
window.addEventListener("storage", onStorage);
|
||||||
|
return () => {
|
||||||
|
hideDebugListeners.delete(listener);
|
||||||
|
window.removeEventListener("storage", onStorage);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
frontend/packages/shared/src/settings/HideDebugSetting.tsx
Normal file
26
frontend/packages/shared/src/settings/HideDebugSetting.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Settings-menu row: "hide debug output". Rendered as a `<SettingsMenu>`
|
||||||
|
// child (`./SettingsMenu.js`) by each page that has a terminal, the
|
||||||
|
// per-agent page and the swarm UI, rather than as a prop on the menu: the
|
||||||
|
// caller owns its row. Both read the same preference (`../prefs.js`), which
|
||||||
|
// `../terminal/Row.tsx` follows as it changes — mara: "add a setting to not
|
||||||
|
// display verbose output... like the grey colored debug stuff."
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { getHideDebugPref, setHideDebugPref } from "../prefs.js";
|
||||||
|
|
||||||
|
export function HideDebugSetting() {
|
||||||
|
const [hideDebug, setHideDebugState] = useState(() => getHideDebugPref());
|
||||||
|
return (
|
||||||
|
<label class="settings-menu-row">
|
||||||
|
<span>hide debug output</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={hideDebug}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = (e.target as HTMLInputElement).checked;
|
||||||
|
setHideDebugPref(v);
|
||||||
|
setHideDebugState(v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,15 @@
|
||||||
// Shared between `@hive/agent`'s local stream and `@hive/swarm-ui`'s
|
// Shared between `@hive/agent`'s local stream and `@hive/swarm-ui`'s
|
||||||
// relay consumer (see `./termMsg.js`'s doc) — both hand this the same
|
// relay consumer (see `./termMsg.js`'s doc) — both hand this the same
|
||||||
// `TermRow` shape regardless of which wire framing they read it off of.
|
// `TermRow` shape regardless of which wire framing they read it off of.
|
||||||
import { useEffect, useRef } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import type { TermRow } from "./termMsg.js";
|
import type { TermRow } from "./termMsg.js";
|
||||||
import { linkifyToNodes } from "./linkify.js";
|
import { linkifyToNodes } from "./linkify.js";
|
||||||
import { renderMarkdown } from "./markdown.js";
|
import { renderMarkdown } from "./markdown.js";
|
||||||
import { getExpandDetailsPref, getHideDebugPref } from "../prefs.js";
|
import {
|
||||||
|
getExpandDetailsPref,
|
||||||
|
getHideDebugPref,
|
||||||
|
subscribeHideDebugPref,
|
||||||
|
} from "../prefs.js";
|
||||||
|
|
||||||
function MarkdownBody({ text }: { text: string }) {
|
function MarkdownBody({ text }: { text: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -54,13 +58,27 @@ function DiffBody({ text }: { text: string }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Row({ row }: { row: TermRow }) {
|
// A `debug` row, which follows the "hide debug output" preference as it
|
||||||
|
// changes. Only debug rows subscribe, so a long terminal does not hold a
|
||||||
|
// listener per row.
|
||||||
|
function DebugRow({ row }: { row: TermRow }) {
|
||||||
|
const [hidden, setHidden] = useState(() => getHideDebugPref());
|
||||||
|
useEffect(
|
||||||
|
() => subscribeHideDebugPref(() => setHidden(getHideDebugPref())),
|
||||||
|
[],
|
||||||
|
);
|
||||||
// "hide debug output" is a skip, not a dimmer — a hidden debug row
|
// "hide debug output" is a skip, not a dimmer — a hidden debug row
|
||||||
// still occupies no DOM node at all, same as it never streamed, rather
|
// still occupies no DOM node at all, same as it never streamed, rather
|
||||||
// than a CSS `display: none` that would keep it in the layout/DOM for
|
// than a CSS `display: none` that would keep it in the layout/DOM for
|
||||||
// no benefit.
|
// no benefit.
|
||||||
if (row.level === "debug" && getHideDebugPref()) return null;
|
return hidden ? null : <RowBody row={row} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Row({ row }: { row: TermRow }) {
|
||||||
|
return row.level === "debug" ? <DebugRow row={row} /> : <RowBody row={row} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RowBody({ row }: { row: TermRow }) {
|
||||||
const cssClass = "level-" + row.level;
|
const cssClass = "level-" + row.level;
|
||||||
const icon = row.icon != null && row.icon !== "" && (
|
const icon = row.icon != null && row.icon !== "" && (
|
||||||
<span className="row-glyph">{row.icon}</span>
|
<span className="row-glyph">{row.icon}</span>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { Link, useLocation } from "wouter-preact";
|
||||||
import { LinksMenu } from "./LinksMenu.js";
|
import { LinksMenu } from "./LinksMenu.js";
|
||||||
import { UserMenu } from "./UserMenu.js";
|
import { UserMenu } from "./UserMenu.js";
|
||||||
import { SettingsMenu } from "@hive/shared/settings-menu.js";
|
import { SettingsMenu } from "@hive/shared/settings-menu.js";
|
||||||
|
import { HideDebugSetting } from "@hive/shared/hide-debug-setting.js";
|
||||||
import { useApplyThemeOverride } from "@hive/shared/theme-apply.js";
|
import { useApplyThemeOverride } from "@hive/shared/theme-apply.js";
|
||||||
import { useApplyMotionOverride } from "@hive/shared/motion-apply.js";
|
import { useApplyMotionOverride } from "@hive/shared/motion-apply.js";
|
||||||
import { useDynamicTabs } from "./useDynamicTabs.js";
|
import { useDynamicTabs } from "./useDynamicTabs.js";
|
||||||
|
|
@ -444,7 +445,9 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
||||||
`margin-left: auto` split the available space between them
|
`margin-left: auto` split the available space between them
|
||||||
instead of sitting flush together at the right edge. */}
|
instead of sitting flush together at the right edge. */}
|
||||||
<div class="shell-header-actions">
|
<div class="shell-header-actions">
|
||||||
<SettingsMenu themeKey={THEME_KEY} motionKey={MOTION_KEY} />
|
<SettingsMenu themeKey={THEME_KEY} motionKey={MOTION_KEY}>
|
||||||
|
<HideDebugSetting />
|
||||||
|
</SettingsMenu>
|
||||||
<LinksMenu />
|
<LinksMenu />
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue