From 099118d31e7b1ac8b8eb109b2aab426d7e7e3db3 Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 1 Nov 2026 13:35:42 +0100 Subject: [PATCH] swarm ui: a solid backgrounds setting turns the frosted surfaces opaque A gear-menu toggle, off by default: the header bar, badges and terminal panes lose their see-through fill and blur. --- docs/swarm/ui.md | 4 ++ frontend/packages/shared/package.json | 1 + .../shared/src/settings/solid-backgrounds.css | 20 ++++++++++ .../shared/src/settings/solid-backgrounds.tsx | 39 +++++++++++++++++++ .../packages/swarm-ui/src/shell/Shell.tsx | 7 ++++ 5 files changed, 71 insertions(+) create mode 100644 frontend/packages/shared/src/settings/solid-backgrounds.css create mode 100644 frontend/packages/shared/src/settings/solid-backgrounds.tsx diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index b80b3232..1263d6e7 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -129,6 +129,10 @@ you flip it. It also hides the **components** tab, a page for developers, which still answers at `/components`. The browser keeps the choice for the swarm UI, apart from the per-agent pages'. +The menu also has a **solid backgrounds** toggle. It turns the frosted, see-through +surfaces opaque: the header bar, the badges and the terminal panes. It is off +until you turn it on, and the browser keeps the choice. + The menu also has an **expand tool output** toggle. It opens tool-output rows expanded; rows already on screen keep their state, and rows that arrive after you flip it follow it. diff --git a/frontend/packages/shared/package.json b/frontend/packages/shared/package.json index 0d5718e3..4d004381 100644 --- a/frontend/packages/shared/package.json +++ b/frontend/packages/shared/package.json @@ -55,6 +55,7 @@ "./hide-debug-setting.js": "./src/settings/HideDebugSetting.tsx", "./expand-details-setting.js": "./src/settings/ExpandDetailsSetting.tsx", "./settings-menu.css": "./src/settings/SettingsMenu.css", + "./solid-backgrounds.js": "./src/settings/solid-backgrounds.tsx", "./user-menu.js": "./src/user-menu/UserMenu.tsx", "./user-menu.css": "./src/user-menu/UserMenu.css", "./links-menu.js": "./src/links-menu/LinksMenu.tsx", diff --git a/frontend/packages/shared/src/settings/solid-backgrounds.css b/frontend/packages/shared/src/settings/solid-backgrounds.css new file mode 100644 index 00000000..eaf63c47 --- /dev/null +++ b/frontend/packages/shared/src/settings/solid-backgrounds.css @@ -0,0 +1,20 @@ +/* The frosted surfaces, made opaque while `data-solid="true"` is on `` + (`solid-backgrounds.tsx`). Each rule names the same surface as the rule that + makes it see-through, with `html:root` in front so it outranks that rule's + theme variants whatever order the stylesheets load in. The dialog scrim is + left alone: it dims the page on purpose. */ +html:root[data-solid="true"] .shell-header { + background: var(--bg-elev); + -webkit-backdrop-filter: none; + backdrop-filter: none; +} +html:root[data-solid="true"] .ui-badge:not(.ui-badge-quiet) { + background: var(--purple-dim); + -webkit-backdrop-filter: none; + backdrop-filter: none; +} +html:root[data-solid="true"] .terminal-wrap { + background: var(--crust); + -webkit-backdrop-filter: none; + backdrop-filter: none; +} diff --git a/frontend/packages/shared/src/settings/solid-backgrounds.tsx b/frontend/packages/shared/src/settings/solid-backgrounds.tsx new file mode 100644 index 00000000..c7ce9ea0 --- /dev/null +++ b/frontend/packages/shared/src/settings/solid-backgrounds.tsx @@ -0,0 +1,39 @@ +// "Solid backgrounds" — a gear-menu toggle that turns the frosted, see-through +// surfaces opaque (mara: "add setting to disable transparency", e.g. the top +// bar and badge backgrounds). Off by default, so the look is unchanged until +// someone turns it on. +// +// The setting sets `data-solid="true"` on ``, and `./solid-backgrounds.css` +// answers it. The caller owns the storage key, as with the theme and motion +// overrides, and mounts `useApplySolidBackgrounds` once so the choice applies +// before the menu is ever opened; `SolidBackgroundsSetting` is the menu's row. +import { useEffect } from "preact/hooks"; +import { useLocalSetting } from "./settings-storage.js"; +import "./solid-backgrounds.css"; + +export function useApplySolidBackgrounds(key: string): void { + const [solid] = useLocalSetting(key, false); + useEffect(() => { + const root = document.documentElement; + if (solid) root.dataset.solid = "true"; + else delete root.dataset.solid; + }, [solid]); +} + +export function SolidBackgroundsSetting({ + storageKey, +}: { + storageKey: string; +}) { + const [solid, setSolid] = useLocalSetting(storageKey, false); + return ( + + ); +} diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx index 4d59b5d8..b3dc6038 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.tsx +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -37,6 +37,10 @@ import { HideDebugSetting } from "@hive/shared/hide-debug-setting.js"; import { ExpandDetailsSetting } from "@hive/shared/expand-details-setting.js"; import { useApplyThemeOverride } from "@hive/shared/theme-apply.js"; import { useApplyMotionOverride } from "@hive/shared/motion-apply.js"; +import { + SolidBackgroundsSetting, + useApplySolidBackgrounds, +} from "@hive/shared/solid-backgrounds.js"; import { isFullBleedRoute, visibleNavItems } from "./routes.js"; import { useDynamicTabs } from "./useDynamicTabs.js"; import { useSwarmName } from "./useSwarmName.js"; @@ -49,6 +53,7 @@ import "./Shell.css"; // not `'system'` — see `@hive/shared/theme-apply.js`'s file comment. const THEME_KEY = "swarm-ui:theme-override"; const MOTION_KEY = "swarm-ui:motion-override"; +const SOLID_KEY = "swarm-ui:solid-backgrounds"; const NAV_ITEMS: { href: string; label: string; accent: string }[] = [ { href: "/agents", label: "agents", accent: "var(--green)" }, @@ -220,6 +225,7 @@ export function Shell({ children }: { children: ComponentChildren }) { // needing a reference to the other. useApplyThemeOverride(THEME_KEY); useApplyMotionOverride(MOTION_KEY); + useApplySolidBackgrounds(SOLID_KEY); // Measures nav item `index`'s rect relative to `.shell-nav`. // `getBoundingClientRect()` on both elements at the same tick keeps @@ -424,6 +430,7 @@ export function Shell({ children }: { children: ComponentChildren }) { +