diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index e6c3ce37..71258581 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -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 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 different agents tell apart. A subagent's tab shows its agent's icon. An agent with no icon keeps the hyperhive mark. diff --git a/docs/web-ui/terminal-rendering.md b/docs/web-ui/terminal-rendering.md index 1eb32c31..aae8b9af 100644 --- a/docs/web-ui/terminal-rendering.md +++ b/docs/web-ui/terminal-rendering.md @@ -53,8 +53,11 @@ that motivated the change. The settings menu's "hide debug output" toggle (`getHideDebugPref()`, `@hive/shared/prefs.js`) skips `debug`-level rows entirely rather than -muting them further — client-side only, same live-read-no-reload shape -as the expand-tool-output preference above. +muting them further — client-side only, no reload. The per-agent page and +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 actually says) lives in `stream_enrich.rs`'s `fmt_tool_use()` family — diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index bf98c97c..f4eec760 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -12,7 +12,7 @@ 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 { HideDebugSetting } from "./components/HideDebugSetting.js"; +import { HideDebugSetting } from "@hive/shared/hide-debug-setting.js"; import { StatusChips } from "./components/StatusChips.js"; import { LiveStream, type LiveStreamHandle } from "./components/LiveStream.js"; import { HeaderPill } from "./components/HeaderPill.js"; diff --git a/frontend/packages/agent/src/components/HideDebugSetting.tsx b/frontend/packages/agent/src/components/HideDebugSetting.tsx deleted file mode 100644 index bff063ae..00000000 --- a/frontend/packages/agent/src/components/HideDebugSetting.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/frontend/packages/shared/package.json b/frontend/packages/shared/package.json index 2e98b15a..47a8ba34 100644 --- a/frontend/packages/shared/package.json +++ b/frontend/packages/shared/package.json @@ -52,6 +52,7 @@ "./theme-apply.js": "./src/settings/theme-apply.ts", "./motion-apply.js": "./src/settings/motion-apply.ts", "./settings-menu.js": "./src/settings/SettingsMenu.tsx", + "./hide-debug-setting.js": "./src/settings/HideDebugSetting.tsx", "./settings-menu.css": "./src/settings/SettingsMenu.css" }, "files": [ diff --git a/frontend/packages/shared/src/prefs.ts b/frontend/packages/shared/src/prefs.ts index 4ba17362..c542e1df 100644 --- a/frontend/packages/shared/src/prefs.ts +++ b/frontend/packages/shared/src/prefs.ts @@ -2,9 +2,9 @@ // 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 // storage, rather than an independent copy that could drift. -// `ExpandDetailsSetting` / `HideDebugSetting` (the per-agent page's own -// settings popover rows) are the only writers today; `Row.tsx` is the -// only reader of either. +// `ExpandDetailsSetting` / `HideDebugSetting` (the settings popover rows +// of the per-agent page and of the swarm UI) are the only writers today; +// `Row.tsx` is the only reader of either. const EXPAND_DETAILS_KEY = "hive-agent-expand-details"; @@ -53,4 +53,24 @@ export function setHideDebugPref(v: boolean): void { } catch { /* 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); + }; } diff --git a/frontend/packages/shared/src/settings/HideDebugSetting.tsx b/frontend/packages/shared/src/settings/HideDebugSetting.tsx new file mode 100644 index 00000000..b0c98025 --- /dev/null +++ b/frontend/packages/shared/src/settings/HideDebugSetting.tsx @@ -0,0 +1,26 @@ +// Settings-menu row: "hide debug output". Rendered as a `` +// 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 ( + + ); +} diff --git a/frontend/packages/shared/src/terminal/Row.tsx b/frontend/packages/shared/src/terminal/Row.tsx index 8109213e..a199365d 100644 --- a/frontend/packages/shared/src/terminal/Row.tsx +++ b/frontend/packages/shared/src/terminal/Row.tsx @@ -10,11 +10,15 @@ // Shared between `@hive/agent`'s local stream and `@hive/swarm-ui`'s // relay consumer (see `./termMsg.js`'s doc) — both hand this the same // `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 { linkifyToNodes } from "./linkify.js"; import { renderMarkdown } from "./markdown.js"; -import { getExpandDetailsPref, getHideDebugPref } from "../prefs.js"; +import { + getExpandDetailsPref, + getHideDebugPref, + subscribeHideDebugPref, +} from "../prefs.js"; function MarkdownBody({ text }: { text: string }) { const ref = useRef(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 // 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 // no benefit. - if (row.level === "debug" && getHideDebugPref()) return null; + return hidden ? null : ; +} +export function Row({ row }: { row: TermRow }) { + return row.level === "debug" ? : ; +} + +function RowBody({ row }: { row: TermRow }) { const cssClass = "level-" + row.level; const icon = row.icon != null && row.icon !== "" && ( {row.icon} diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx index 324d7ad9..7cab64c1 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.tsx +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -32,6 +32,7 @@ import { Link, useLocation } from "wouter-preact"; import { LinksMenu } from "./LinksMenu.js"; import { UserMenu } from "./UserMenu.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 { useApplyMotionOverride } from "@hive/shared/motion-apply.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 instead of sitting flush together at the right edge. */}
- + + +