From 8786e201db4a6e361ad07b06a9fd94ee0f13dcc3 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 16:32:43 -0700 Subject: [PATCH 01/12] checkpoint, working to change presets/bg.json => backgrounds.json, and create tab:background key --- cmd/generateschema/main-generateschema.go | 6 +- docs/docs/config.mdx | 5 +- docs/src/components/versionbadge.css | 19 ++++ docs/src/components/versionbadge.tsx | 4 + frontend/app/app-bg.tsx | 25 ++++- frontend/app/block/blockframe.tsx | 14 ++- frontend/app/monaco/schemaendpoints.ts | 12 +-- frontend/app/store/global.ts | 16 +++ frontend/app/tab/tabcontextmenu.ts | 28 +++-- frontend/app/waveenv/waveenv.ts | 1 + frontend/app/waveenv/waveenvimpl.ts | 2 + frontend/preview/mock/mockwaveenv.ts | 19 +++- frontend/types/gotypes.d.ts | 14 +++ pkg/waveobj/metaconsts.go | 1 + pkg/waveobj/wtypemeta.go | 1 + pkg/wconfig/defaultconfig/backgrounds.json | 108 +++++++++++++++++++ pkg/wconfig/defaultconfig/presets.json | 109 +------------------- pkg/wconfig/metaconsts.go | 1 + pkg/wconfig/settingsconfig.go | 25 ++--- pkg/wcore/workspace.go | 22 ++-- schema/{bgpresets.json => backgrounds.json} | 7 +- schema/settings.json | 3 + 22 files changed, 271 insertions(+), 171 deletions(-) create mode 100644 pkg/wconfig/defaultconfig/backgrounds.json rename schema/{bgpresets.json => backgrounds.json} (90%) diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go index 2b14a95781..24a3d9b106 100644 --- a/cmd/generateschema/main-generateschema.go +++ b/cmd/generateschema/main-generateschema.go @@ -20,7 +20,7 @@ const WaveSchemaSettingsFileName = "schema/settings.json" const WaveSchemaConnectionsFileName = "schema/connections.json" const WaveSchemaAiPresetsFileName = "schema/aipresets.json" const WaveSchemaWidgetsFileName = "schema/widgets.json" -const WaveSchemaBgPresetsFileName = "schema/bgpresets.json" +const WaveSchemaBackgroundsFileName = "schema/backgrounds.json" const WaveSchemaWaveAIFileName = "schema/waveai.json" // ViewNameType is a string type whose JSON Schema offers enum suggestions for the most @@ -185,8 +185,8 @@ func main() { log.Fatalf("widgets schema error: %v", err) } - bgPresetsTemplate := make(map[string]wconfig.BgPresetsType) - err = generateSchema(&bgPresetsTemplate, WaveSchemaBgPresetsFileName) + bgPresetsTemplate := make(map[string]wconfig.BackgroundConfigType) + err = generateSchema(&bgPresetsTemplate, WaveSchemaBackgroundsFileName) if err != nil { log.Fatalf("bg presets schema error: %v", err) } diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index ae83638ea5..abeca3429f 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -6,7 +6,7 @@ title: "Configuration" import { Kbd } from "@site/src/components/kbd"; import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext"; -import { VersionBadge } from "@site/src/components/versionbadge"; +import { VersionBadge, DeprecatedBadge } from "@site/src/components/versionbadge"; @@ -92,7 +92,8 @@ wsh editconfig | autoupdate:intervalms | float64 | time in milliseconds to wait between update checks (requires app restart) | | autoupdate:installonquit | bool | whether to automatically install updates on quit (requires app restart) | | autoupdate:channel | string | the auto update channel "latest" (stable builds), or "beta" (updated more frequently) (requires app restart) | -| tab:preset | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key | +| tab:preset | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key. deprecated in favor of `tab:background` | +| tab:background | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key | | tab:confirmclose | bool | if set to true, a confirmation dialog will be shown before closing a tab (defaults to false) | | widget:showhelp | bool | whether to show help/tips widgets in right sidebar | | window:transparent | bool | set to true to enable window transparency (cannot be combined with `window:blur`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/custom-window-styles#limitations)) | diff --git a/docs/src/components/versionbadge.css b/docs/src/components/versionbadge.css index 63ac0b3771..ea09d08480 100644 --- a/docs/src/components/versionbadge.css +++ b/docs/src/components/versionbadge.css @@ -20,3 +20,22 @@ background-color: var(--ifm-color-primary-dark); color: var(--ifm-background-color); } + +.deprecated-badge { + display: inline-block; + padding: 0.125rem 0.5rem; + margin-left: 0.25rem; + font-size: 0.75rem; + font-weight: 600; + line-height: 1.5; + border-radius: 0.25rem; + background-color: #9e9e9e; + color: #fff; + vertical-align: middle; + white-space: nowrap; +} + +[data-theme="dark"] .deprecated-badge { + background-color: #616161; + color: #e0e0e0; +} diff --git a/docs/src/components/versionbadge.tsx b/docs/src/components/versionbadge.tsx index 58c616440c..c4af6d479f 100644 --- a/docs/src/components/versionbadge.tsx +++ b/docs/src/components/versionbadge.tsx @@ -7,4 +7,8 @@ interface VersionBadgeProps { export function VersionBadge({ version, noLeftMargin }: VersionBadgeProps) { return {version}; +} + +export function DeprecatedBadge() { + return deprecated; } \ No newline at end of file diff --git a/frontend/app/app-bg.tsx b/frontend/app/app-bg.tsx index e2e60cb8ad..d94684334b 100644 --- a/frontend/app/app-bg.tsx +++ b/frontend/app/app-bg.tsx @@ -1,4 +1,4 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; @@ -10,11 +10,24 @@ import { debounce } from "throttle-debounce"; import { atoms, getApi, WOS } from "./store/global"; import { useWaveObjectValue } from "./store/wos"; +function resolveTabBgMeta(tabMeta: MetaType, fullConfig: FullConfigType): BackgroundConfigType { + const tabBg = tabMeta?.["tab:background"]; + if (tabBg) { + const bgMeta = fullConfig?.backgrounds?.[tabBg]; + if (bgMeta) { + return bgMeta; + } + } + return tabMeta; +} + export function AppBackground() { const bgRef = useRef(null); const tabId = useAtomValue(atoms.staticTabId); const [tabData] = useWaveObjectValue(WOS.makeORef("tab", tabId)); - const style: CSSProperties = computeBgStyleFromMeta(tabData?.meta, 0.5) ?? {}; + const fullConfig = useAtomValue(atoms.fullConfigAtom); + const resolvedMeta = resolveTabBgMeta(tabData?.meta, fullConfig); + const style: CSSProperties = computeBgStyleFromMeta(resolvedMeta, 0.5) ?? {}; const getAvgColor = useCallback( debounce(30, () => { if ( @@ -42,5 +55,11 @@ export function AppBackground() { useLayoutEffect(getAvgColor, [getAvgColor]); useResizeObserver(bgRef, getAvgColor); - return
; + return ( +
+ ); } diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 0b4abb755b..fddc2b3a67 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -107,9 +107,13 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => { const connModalOpen = jotai.useAtomValue(changeConnModalAtom); const isMagnified = jotai.useAtomValue(nodeModel.isMagnified); const isEphemeral = jotai.useAtomValue(nodeModel.isEphemeral); - const [magnifiedBlockBlurAtom] = React.useState(() => waveEnv.getSettingsKeyAtom("window:magnifiedblockblurprimarypx")); + const [magnifiedBlockBlurAtom] = React.useState(() => + waveEnv.getSettingsKeyAtom("window:magnifiedblockblurprimarypx") + ); const magnifiedBlockBlur = jotai.useAtomValue(magnifiedBlockBlurAtom); - const [magnifiedBlockOpacityAtom] = React.useState(() => waveEnv.getSettingsKeyAtom("window:magnifiedblockopacity")); + const [magnifiedBlockOpacityAtom] = React.useState(() => + waveEnv.getSettingsKeyAtom("window:magnifiedblockopacity") + ); const magnifiedBlockOpacity = jotai.useAtomValue(magnifiedBlockOpacityAtom); const connBtnRef = React.useRef(null); const connName = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "connection")); @@ -141,7 +145,11 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => { if (!util.isLocalConnName(connName)) { console.log("ensure conn", nodeModel.blockId, connName); waveEnv.rpc - .ConnEnsureCommand(TabRpcClient, { connname: connName, logblockid: nodeModel.blockId }, { timeout: 60000 }) + .ConnEnsureCommand( + TabRpcClient, + { connname: connName, logblockid: nodeModel.blockId }, + { timeout: 60000 } + ) .catch((e) => { console.log("error ensuring connection", nodeModel.blockId, connName, e); }); diff --git a/frontend/app/monaco/schemaendpoints.ts b/frontend/app/monaco/schemaendpoints.ts index 2b3134e215..5365d1c739 100644 --- a/frontend/app/monaco/schemaendpoints.ts +++ b/frontend/app/monaco/schemaendpoints.ts @@ -1,10 +1,10 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import settingsSchema from "../../../schema/settings.json"; -import connectionsSchema from "../../../schema/connections.json"; import aipresetsSchema from "../../../schema/aipresets.json"; -import bgpresetsSchema from "../../../schema/bgpresets.json"; +import backgroundsSchema from "../../../schema/backgrounds.json"; +import connectionsSchema from "../../../schema/connections.json"; +import settingsSchema from "../../../schema/settings.json"; import waveaiSchema from "../../../schema/waveai.json"; import widgetsSchema from "../../../schema/widgets.json"; @@ -31,9 +31,9 @@ const MonacoSchemas: SchemaInfo[] = [ schema: aipresetsSchema, }, { - uri: "wave://schema/bgpresets.json", - fileMatch: ["*/WAVECONFIGPATH/presets/bg.json"], - schema: bgpresetsSchema, + uri: "wave://schema/backgrounds.json", + fileMatch: ["*/WAVECONFIGPATH/backgrounds.json"], + schema: backgroundsSchema, }, { uri: "wave://schema/waveai.json", diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 92ffe7a59b..9fb8bd5ee6 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -229,6 +229,21 @@ function useSettingsKeyAtom(key: T): SettingsType[ return useAtomValue(getSettingsKeyAtom(key)); } +const configBackgroundAtomCache = new Map>(); + +function getConfigBackgroundAtom(bgKey: string): Atom { + if (isPreviewWindow()) return NullAtom as Atom; + let bgAtom = configBackgroundAtomCache.get(bgKey); + if (bgAtom == null) { + bgAtom = atom((get) => { + const fullConfig = get(atoms.fullConfigAtom); + return fullConfig.backgrounds?.[bgKey]; + }); + configBackgroundAtomCache.set(bgKey, bgAtom); + } + return bgAtom; +} + function getSettingsPrefixAtom(prefix: string): Atom { if (isPreviewWindow()) return NullAtom as Atom; let settingsPrefixAtom = settingsAtomCache.get(prefix + ":"); @@ -666,6 +681,7 @@ export { getBlockComponentModel, getBlockMetaKeyAtom, getBlockTermDurableAtom, + getConfigBackgroundAtom, getConnConfigKeyAtom, getConnStatusAtom, getFocusedBlockId, diff --git a/frontend/app/tab/tabcontextmenu.ts b/frontend/app/tab/tabcontextmenu.ts index 5f70bc9b9b..2c901d0960 100644 --- a/frontend/app/tab/tabcontextmenu.ts +++ b/frontend/app/tab/tabcontextmenu.ts @@ -75,28 +75,26 @@ export function buildTabContextMenu( ]; menu.push({ label: "Flag Tab", type: "submenu", submenu: flagSubmenu }, { type: "separator" }); const fullConfig = globalStore.get(env.atoms.fullConfigAtom); - const bgPresets: string[] = []; - for (const key in fullConfig?.presets ?? {}) { - if (key.startsWith("bg@") && fullConfig.presets[key] != null) { - bgPresets.push(key); - } - } - bgPresets.sort((a, b) => { - const aOrder = fullConfig.presets[a]["display:order"] ?? 0; - const bOrder = fullConfig.presets[b]["display:order"] ?? 0; + const backgrounds = fullConfig?.backgrounds ?? {}; + const bgKeys = Object.keys(backgrounds).filter((k) => backgrounds[k] != null); + bgKeys.sort((a, b) => { + const aOrder = backgrounds[a]["display:order"] ?? 0; + const bOrder = backgrounds[b]["display:order"] ?? 0; return aOrder - bOrder; }); - if (bgPresets.length > 0) { + if (bgKeys.length > 0) { const submenu: ContextMenuItem[] = []; const oref = makeORef("tab", id); - for (const presetName of bgPresets) { - // preset cannot be null (filtered above) - const preset = fullConfig.presets[presetName]; + for (const bgKey of bgKeys) { + const bg = backgrounds[bgKey]; submenu.push({ - label: preset["display:name"] ?? presetName, + label: bg["display:name"] ?? bgKey, click: () => fireAndForget(async () => { - await env.rpc.SetMetaCommand(TabRpcClient, { oref, meta: preset }); + await env.rpc.SetMetaCommand(TabRpcClient, { + oref, + meta: { "bg:*": true, "tab:background": bgKey }, + }); env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true }); recordTEvent("action:settabtheme"); }), diff --git a/frontend/app/waveenv/waveenv.ts b/frontend/app/waveenv/waveenv.ts index df1cb01c4a..d0134d7946 100644 --- a/frontend/app/waveenv/waveenv.ts +++ b/frontend/app/waveenv/waveenv.ts @@ -76,6 +76,7 @@ export type WaveEnv = { getSettingsKeyAtom: SettingsKeyAtomFnType; getBlockMetaKeyAtom: BlockMetaKeyAtomFnType; getConnConfigKeyAtom: ConnConfigKeyAtomFnType; + getConfigBackgroundAtom: (bgKey: string) => Atom; // the mock fields are only usable in the preview server (may be be null or throw errors in production) mockSetWaveObj: (oref: string, obj: T) => void; diff --git a/frontend/app/waveenv/waveenvimpl.ts b/frontend/app/waveenv/waveenvimpl.ts index 4f9e234eca..0847cd75b6 100644 --- a/frontend/app/waveenv/waveenvimpl.ts +++ b/frontend/app/waveenv/waveenvimpl.ts @@ -7,6 +7,7 @@ import { atoms, createBlock, getBlockMetaKeyAtom, + getConfigBackgroundAtom, getConnConfigKeyAtom, getConnStatusAtom, getLocalHostDisplayNameAtom, @@ -44,6 +45,7 @@ export function makeWaveEnvImpl(): WaveEnv { useWaveObjectValue: WOS.useWaveObjectValue, }, getBlockMetaKeyAtom, + getConfigBackgroundAtom, getConnConfigKeyAtom, mockSetWaveObj: (_oref: string, _obj: T) => { diff --git a/frontend/preview/mock/mockwaveenv.ts b/frontend/preview/mock/mockwaveenv.ts index faaf6cdde7..6f011b8e57 100644 --- a/frontend/preview/mock/mockwaveenv.ts +++ b/frontend/preview/mock/mockwaveenv.ts @@ -430,6 +430,7 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { const waveObjectDerivedAtomCache = new Map>(); const blockMetaKeyAtomCache = new Map>(); const connConfigKeyAtomCache = new Map>(); + const configBackgroundAtomCache = new Map>(); const getWaveObjectAtom = (oref: string): PrimitiveAtom => { if (!waveObjectValueAtomCache.has(oref)) { const obj = (mergedOverrides.mockWaveObjs?.[oref] ?? null) as T; @@ -461,7 +462,11 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { globalStore.set(waveObjectValueAtomCache.get(oref), obj); }, }; - const { rpc, setRpcHandler, setRpcStreamHandler } = makeMockRpc(mergedOverrides.rpc, mergedOverrides.rpcStreaming, mockWosFns); + const { rpc, setRpcHandler, setRpcStreamHandler } = makeMockRpc( + mergedOverrides.rpc, + mergedOverrides.rpcStreaming, + mockWosFns + ); const env = { isMock: true, mockEnv: mergedOverrides, @@ -562,6 +567,18 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { } return connConfigKeyAtomCache.get(cacheKey) as Atom; }, + getConfigBackgroundAtom: (bgKey: string) => { + if (!configBackgroundAtomCache.has(bgKey)) { + configBackgroundAtomCache.set( + bgKey, + atom((get) => { + const fullConfig = get(atoms.fullConfigAtom); + return fullConfig.backgrounds?.[bgKey]; + }) + ); + } + return configBackgroundAtomCache.get(bgKey); + }, services: null as any, callBackendService: (service: string, method: string, args: any[], noUIContext?: boolean) => { const fn = mergedOverrides.services?.[service]?.[method]; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 193929ef4c..baa737e007 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -108,6 +108,17 @@ declare global { iconcolor: string; }; + // wconfig.BackgroundConfigType + type BackgroundConfigType = { + bg?: string; + "bg:opacity"?: number; + "bg:blendmode"?: string; + "bg:bordercolor"?: string; + "bg:activebordercolor"?: string; + "display:name"?: string; + "display:order"?: number; + }; + // baseds.Badge type Badge = { badgeid: string; @@ -991,6 +1002,7 @@ declare global { defaultwidgets: {[key: string]: WidgetConfigType}; widgets: {[key: string]: WidgetConfigType}; presets: {[key: string]: MetaType}; + backgrounds: {[key: string]: BackgroundConfigType}; termthemes: {[key: string]: TermThemeType}; connections: {[key: string]: ConnKeywords}; bookmarks: {[key: string]: WebBookmark}; @@ -1129,6 +1141,7 @@ declare global { "graph:metrics"?: string[]; "sysinfo:type"?: string; "tab:flagcolor"?: string; + "tab:background"?: string; "bg:*"?: boolean; bg?: string; "bg:opacity"?: number; @@ -1378,6 +1391,7 @@ declare global { "preview:defaultsort"?: string; "tab:preset"?: string; "tab:confirmclose"?: boolean; + "tab:background"?: string; "widget:*"?: boolean; "widget:showhelp"?: boolean; "window:*"?: boolean; diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index 7028d050be..f41435954c 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -90,6 +90,7 @@ const ( MetaKey_SysinfoType = "sysinfo:type" MetaKey_TabFlagColor = "tab:flagcolor" + MetaKey_TabBackground = "tab:background" MetaKey_BgClear = "bg:*" MetaKey_Bg = "bg" diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index 027ff3eff2..4a36fdd46f 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -93,6 +93,7 @@ type MetaTSType struct { // for tabs TabFlagColor string `json:"tab:flagcolor,omitempty"` + TabBackground string `json:"tab:background,omitempty"` BgClear bool `json:"bg:*,omitempty"` Bg string `json:"bg,omitempty"` BgOpacity float64 `json:"bg:opacity,omitempty"` diff --git a/pkg/wconfig/defaultconfig/backgrounds.json b/pkg/wconfig/defaultconfig/backgrounds.json new file mode 100644 index 0000000000..3d7cc1ad3b --- /dev/null +++ b/pkg/wconfig/defaultconfig/backgrounds.json @@ -0,0 +1,108 @@ +{ + "bg@default": { + "display:name": "Default", + "display:order": -1, + "bg:*": true + }, + "bg@rainbow": { + "display:name": "Rainbow", + "display:order": 2.1, + "bg:*": true, + "bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )", + "bg:opacity": 0.3 + }, + "bg@green": { + "display:name": "Green", + "display:order": 1.2, + "bg:*": true, + "bg": "green", + "bg:opacity": 0.3 + }, + "bg@blue": { + "display:name": "Blue", + "display:order": 1.1, + "bg:*": true, + "bg": "blue", + "bg:opacity": 0.3, + "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" + }, + "bg@red": { + "display:name": "Red", + "display:order": 1.3, + "bg:*": true, + "bg": "red", + "bg:opacity": 0.3, + "bg:activebordercolor": "rgba(255, 0, 0, 1.0)" + }, + "bg@ocean-depths": { + "display:name": "Ocean Depths", + "display:order": 2.2, + "bg:*": true, + "bg": "linear-gradient(135deg, purple, blue, teal)", + "bg:opacity": 0.7 + }, + "bg@aqua-horizon": { + "display:name": "Aqua Horizon", + "display:order": 2.3, + "bg:*": true, + "bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)", + "bg:opacity": 0.85, + "bg:blendmode": "overlay" + }, + "bg@sunset": { + "display:name": "Sunset", + "display:order": 2.4, + "bg:*": true, + "bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))", + "bg:opacity": 0.8, + "bg:blendmode": "normal" + }, + "bg@enchantedforest": { + "display:name": "Enchanted Forest", + "display:order": 2.7, + "bg:*": true, + "bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)", + "bg:opacity": 0.8, + "bg:blendmode": "soft-light" + }, + "bg@twilight-mist": { + "display:name": "Twilight Mist", + "display:order": 2.9, + "bg:*": true, + "bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)", + "bg:opacity": 0.9, + "bg:blendmode": "soft-light" + }, + "bg@duskhorizon": { + "display:name": "Dusk Horizon", + "display:order": 3.1, + "bg:*": true, + "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", + "bg:opacity": 0.9, + "bg:blendmode": "overlay" + }, + "bg@tropical-radiance": { + "display:name": "Tropical Radiance", + "display:order": 3.3, + "bg:*": true, + "bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", + "bg:opacity": 0.9, + "bg:blendmode": "overlay" + }, + "bg@twilight-ember": { + "display:name": "Twilight Ember", + "display:order": 3.5, + "bg:*": true, + "bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)", + "bg:blendmode": "overlay", + "bg:text": "rgb(200, 200, 200)" + }, + "bg@cosmic-tide": { + "display:name": "Cosmic Tide", + "display:order": 3.6, + "bg:activebordercolor": "#ff55aa", + "bg:*": true, + "bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)", + "bg:opacity": 0.6 + } +} diff --git a/pkg/wconfig/defaultconfig/presets.json b/pkg/wconfig/defaultconfig/presets.json index 3d7cc1ad3b..0967ef424b 100644 --- a/pkg/wconfig/defaultconfig/presets.json +++ b/pkg/wconfig/defaultconfig/presets.json @@ -1,108 +1 @@ -{ - "bg@default": { - "display:name": "Default", - "display:order": -1, - "bg:*": true - }, - "bg@rainbow": { - "display:name": "Rainbow", - "display:order": 2.1, - "bg:*": true, - "bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )", - "bg:opacity": 0.3 - }, - "bg@green": { - "display:name": "Green", - "display:order": 1.2, - "bg:*": true, - "bg": "green", - "bg:opacity": 0.3 - }, - "bg@blue": { - "display:name": "Blue", - "display:order": 1.1, - "bg:*": true, - "bg": "blue", - "bg:opacity": 0.3, - "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" - }, - "bg@red": { - "display:name": "Red", - "display:order": 1.3, - "bg:*": true, - "bg": "red", - "bg:opacity": 0.3, - "bg:activebordercolor": "rgba(255, 0, 0, 1.0)" - }, - "bg@ocean-depths": { - "display:name": "Ocean Depths", - "display:order": 2.2, - "bg:*": true, - "bg": "linear-gradient(135deg, purple, blue, teal)", - "bg:opacity": 0.7 - }, - "bg@aqua-horizon": { - "display:name": "Aqua Horizon", - "display:order": 2.3, - "bg:*": true, - "bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)", - "bg:opacity": 0.85, - "bg:blendmode": "overlay" - }, - "bg@sunset": { - "display:name": "Sunset", - "display:order": 2.4, - "bg:*": true, - "bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))", - "bg:opacity": 0.8, - "bg:blendmode": "normal" - }, - "bg@enchantedforest": { - "display:name": "Enchanted Forest", - "display:order": 2.7, - "bg:*": true, - "bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)", - "bg:opacity": 0.8, - "bg:blendmode": "soft-light" - }, - "bg@twilight-mist": { - "display:name": "Twilight Mist", - "display:order": 2.9, - "bg:*": true, - "bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)", - "bg:opacity": 0.9, - "bg:blendmode": "soft-light" - }, - "bg@duskhorizon": { - "display:name": "Dusk Horizon", - "display:order": 3.1, - "bg:*": true, - "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", - "bg:opacity": 0.9, - "bg:blendmode": "overlay" - }, - "bg@tropical-radiance": { - "display:name": "Tropical Radiance", - "display:order": 3.3, - "bg:*": true, - "bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", - "bg:opacity": 0.9, - "bg:blendmode": "overlay" - }, - "bg@twilight-ember": { - "display:name": "Twilight Ember", - "display:order": 3.5, - "bg:*": true, - "bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)", - "bg:blendmode": "overlay", - "bg:text": "rgb(200, 200, 200)" - }, - "bg@cosmic-tide": { - "display:name": "Cosmic Tide", - "display:order": 3.6, - "bg:activebordercolor": "#ff55aa", - "bg:*": true, - "bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)", - "bg:opacity": 0.6 - } -} +{} diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 28f481ac16..df048b304c 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -85,6 +85,7 @@ const ( ConfigKey_TabPreset = "tab:preset" ConfigKey_TabConfirmClose = "tab:confirmclose" + ConfigKey_TabBackground = "tab:background" ConfigKey_WidgetClear = "widget:*" ConfigKey_WidgetShowHelp = "widget:showhelp" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index c52012803d..4839cecea2 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -136,6 +136,7 @@ type SettingsType struct { TabPreset string `json:"tab:preset,omitempty"` TabConfirmClose bool `json:"tab:confirmclose,omitempty"` + TabBackground string `json:"tab:background,omitempty"` WidgetClear bool `json:"widget:*,omitempty"` WidgetShowHelp *bool `json:"widget:showhelp,omitempty"` @@ -309,16 +310,17 @@ type AIModeConfigUpdate struct { } type FullConfigType struct { - Settings SettingsType `json:"settings" merge:"meta"` - MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` - DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"` - Widgets map[string]WidgetConfigType `json:"widgets"` - Presets map[string]waveobj.MetaMapType `json:"presets"` - TermThemes map[string]TermThemeType `json:"termthemes"` - Connections map[string]ConnKeywords `json:"connections"` - Bookmarks map[string]WebBookmark `json:"bookmarks"` - WaveAIModes map[string]AIModeConfigType `json:"waveai"` - ConfigErrors []ConfigError `json:"configerrors" configfile:"-"` + Settings SettingsType `json:"settings" merge:"meta"` + MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` + DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"` + Widgets map[string]WidgetConfigType `json:"widgets"` + Presets map[string]waveobj.MetaMapType `json:"presets"` + Backgrounds map[string]BackgroundConfigType `json:"backgrounds"` + TermThemes map[string]TermThemeType `json:"termthemes"` + Connections map[string]ConnKeywords `json:"connections"` + Bookmarks map[string]WebBookmark `json:"bookmarks"` + WaveAIModes map[string]AIModeConfigType `json:"waveai"` + ConfigErrors []ConfigError `json:"configerrors" configfile:"-"` } type ConnKeywords struct { @@ -849,8 +851,7 @@ type WidgetConfigType struct { BlockDef waveobj.BlockDef `json:"blockdef"` } -type BgPresetsType struct { - BgClear bool `json:"bg:*,omitempty"` +type BackgroundConfigType struct { Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"` BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"` BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"` diff --git a/pkg/wcore/workspace.go b/pkg/wcore/workspace.go index b070d31107..c01e509a13 100644 --- a/pkg/wcore/workspace.go +++ b/pkg/wcore/workspace.go @@ -187,14 +187,12 @@ func GetWorkspace(ctx context.Context, wsID string) (*waveobj.Workspace, error) return wstore.DBMustGet[*waveobj.Workspace](ctx, wsID) } -func getTabPresetMeta() (waveobj.MetaMapType, error) { - settings := wconfig.GetWatcher().GetFullConfig() - tabPreset := settings.Settings.TabPreset - if tabPreset == "" { - return nil, nil - } - presetMeta := settings.Presets[tabPreset] - return presetMeta, nil +func getTabBackground() string { + config := wconfig.GetWatcher().GetFullConfig() + if config.Settings.TabBackground != "" { + return config.Settings.TabBackground + } + return config.Settings.TabPreset } var tabNameRe = regexp.MustCompile(`^T(\d+)$`) @@ -256,12 +254,10 @@ func CreateTab(ctx context.Context, workspaceId string, tabName string, activate if err != nil { return tab.OID, fmt.Errorf("error applying new tab layout: %w", err) } - presetMeta, presetErr := getTabPresetMeta() - if presetErr != nil { - log.Printf("error getting tab preset meta: %v\n", presetErr) - } else if len(presetMeta) > 0 { + tabBg := getTabBackground() + if tabBg != "" { tabORef := waveobj.ORefFromWaveObj(tab) - wstore.UpdateObjectMeta(ctx, *tabORef, presetMeta, true) + wstore.UpdateObjectMeta(ctx, *tabORef, waveobj.MetaMapType{waveobj.MetaKey_TabBackground: tabBg}, false) } } telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NewTab: 1}, "createtab") diff --git a/schema/bgpresets.json b/schema/backgrounds.json similarity index 90% rename from schema/bgpresets.json rename to schema/backgrounds.json index 3ab0a8a433..10d77ff5ba 100644 --- a/schema/bgpresets.json +++ b/schema/backgrounds.json @@ -1,11 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { - "BgPresetsType": { + "BackgroundConfigType": { "properties": { - "bg:*": { - "type": "boolean" - }, "bg": { "type": "string", "description": "CSS background property value" @@ -40,7 +37,7 @@ } }, "additionalProperties": { - "$ref": "#/$defs/BgPresetsType" + "$ref": "#/$defs/BackgroundConfigType" }, "type": "object" } \ No newline at end of file diff --git a/schema/settings.json b/schema/settings.json index eda7dc3326..67d8f5b9d4 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -232,6 +232,9 @@ "tab:confirmclose": { "type": "boolean" }, + "tab:background": { + "type": "string" + }, "widget:*": { "type": "boolean" }, From 018bc5cd1053ad63f27891b95f04e247b19d2108 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 17:16:05 -0700 Subject: [PATCH 02/12] migration for bg.json, tabmeta atom, fix app-bg... --- .kilocode/skills/waveenv/SKILL.md | 9 +- cmd/server/main-server.go | 1 + frontend/app/app-bg.tsx | 21 +- frontend/app/block/blockenv.ts | 4 +- frontend/app/store/global.ts | 9 +- frontend/app/view/sysinfo/sysinfo.tsx | 4 +- .../app/view/waveconfig/waveconfig-model.ts | 13 +- frontend/app/view/waveconfig/waveconfig.tsx | 263 +++++++++--------- frontend/app/view/waveconfig/waveconfigenv.ts | 4 +- frontend/app/view/webview/webviewenv.ts | 4 +- frontend/app/waveenv/waveenv.ts | 9 +- frontend/app/waveenv/waveenvimpl.ts | 2 + frontend/preview/mock/defaultconfig.ts | 2 + frontend/preview/mock/mockwaveenv.ts | 37 ++- pkg/wconfig/settingsconfig.go | 43 +++ 15 files changed, 246 insertions(+), 179 deletions(-) diff --git a/.kilocode/skills/waveenv/SKILL.md b/.kilocode/skills/waveenv/SKILL.md index aabda6846d..c5d56af4f1 100644 --- a/.kilocode/skills/waveenv/SKILL.md +++ b/.kilocode/skills/waveenv/SKILL.md @@ -30,7 +30,7 @@ Create a narrowing whenever you are writing a component (or group of components) ```ts import { - BlockMetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom + MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom WaveEnv, @@ -77,12 +77,14 @@ export type MyEnv = WaveEnvSubset<{ // --- key-parameterized atom factories: enumerate the keys you use --- getSettingsKeyAtom: SettingsKeyAtomFnType<"app:focusfollowscursor" | "window:magnifiedblockopacity">; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"view" | "frame:title" | "connection">; + getBlockMetaKeyAtom: MetaKeyAtomFnType<"view" | "frame:title" | "connection">; + getTabMetaKeyAtom: MetaKeyAtomFnType<"tabid" | "name">; getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">; // --- other atom helpers: copy verbatim --- getConnStatusAtom: WaveEnv["getConnStatusAtom"]; getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"]; + getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"]; }>; ``` @@ -104,7 +106,8 @@ Every `WaveEnvSubset` automatically includes the mock fields — you never ne | `wos` | `wos: WaveEnv["wos"]` | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. | | `services` | `services: { svc: WaveEnv["services"]["svc"]; }` | List each service used; take the whole service object (no method-level narrowing). | | `getSettingsKeyAtom` | `SettingsKeyAtomFnType<"key1" \| "key2">` | Union all settings keys accessed. | -| `getBlockMetaKeyAtom` | `BlockMetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. | +| `getBlockMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. | +| `getTabMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all tab meta keys accessed. | | `getConnConfigKeyAtom` | `ConnConfigKeyAtomFnType<"key1">` | Union all conn config keys accessed. | | All other `WaveEnv` fields | `WaveEnv["fieldName"]` | Copy type verbatim. | diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 70c8b3a005..a15b05b1e2 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -560,6 +560,7 @@ func main() { createMainWshClient() sigutil.InstallShutdownSignalHandlers(doShutdown) sigutil.InstallSIGUSR1Handler() + wconfig.MigratePresetsBackgrounds() startConfigWatcher() aiusechat.InitAIModeConfigWatcher() maybeStartPprofServer() diff --git a/frontend/app/app-bg.tsx b/frontend/app/app-bg.tsx index d94684334b..04c3c3f25a 100644 --- a/frontend/app/app-bg.tsx +++ b/frontend/app/app-bg.tsx @@ -1,6 +1,7 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { MetaKeyAtomFnType, useWaveEnv, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { computeBgStyleFromMeta } from "@/util/waveutil"; import useResizeObserver from "@react-hook/resize-observer"; @@ -10,23 +11,19 @@ import { debounce } from "throttle-debounce"; import { atoms, getApi, WOS } from "./store/global"; import { useWaveObjectValue } from "./store/wos"; -function resolveTabBgMeta(tabMeta: MetaType, fullConfig: FullConfigType): BackgroundConfigType { - const tabBg = tabMeta?.["tab:background"]; - if (tabBg) { - const bgMeta = fullConfig?.backgrounds?.[tabBg]; - if (bgMeta) { - return bgMeta; - } - } - return tabMeta; -} +type AppBgEnv = WaveEnvSubset<{ + getTabMetaKeyAtom: MetaKeyAtomFnType<"tab:background">; + getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"]; +}>; export function AppBackground() { const bgRef = useRef(null); const tabId = useAtomValue(atoms.staticTabId); const [tabData] = useWaveObjectValue(WOS.makeORef("tab", tabId)); - const fullConfig = useAtomValue(atoms.fullConfigAtom); - const resolvedMeta = resolveTabBgMeta(tabData?.meta, fullConfig); + const env = useWaveEnv(); + const tabBg = useAtomValue(env.getTabMetaKeyAtom(tabId, "tab:background")); + const configBg = useAtomValue(env.getConfigBackgroundAtom(tabBg)); + const resolvedMeta: BackgroundConfigType = tabBg && configBg ? configBg : tabData?.meta; const style: CSSProperties = computeBgStyleFromMeta(resolvedMeta, 0.5) ?? {}; const getAvgColor = useCallback( debounce(30, () => { diff --git a/frontend/app/block/blockenv.ts b/frontend/app/block/blockenv.ts index 000228c014..0c115371b4 100644 --- a/frontend/app/block/blockenv.ts +++ b/frontend/app/block/blockenv.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { - BlockMetaKeyAtomFnType, ConnConfigKeyAtomFnType, + MetaKeyAtomFnType, SettingsKeyAtomFnType, WaveEnv, WaveEnvSubset, @@ -36,7 +36,7 @@ export type BlockEnv = WaveEnvSubset<{ getConnStatusAtom: WaveEnv["getConnStatusAtom"]; getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"]; getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType< + getBlockMetaKeyAtom: MetaKeyAtomFnType< | "frame:text" | "frame:activebordercolor" | "frame:bordercolor" diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 9fb8bd5ee6..acc0f4d518 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -132,6 +132,10 @@ function getBlockMetaKeyAtom(blockId: string, key: T): return metaAtom; } +function getTabMetaKeyAtom(tabId: string, key: T): Atom { + return getOrefMetaKeyAtom(WOS.makeORef("tab", tabId), key); +} + function getOrefMetaKeyAtom(oref: string, key: T): Atom { const orefCache = getSingleOrefAtomCache(oref); const metaAtomName = "#meta-" + key; @@ -231,8 +235,8 @@ function useSettingsKeyAtom(key: T): SettingsType[ const configBackgroundAtomCache = new Map>(); -function getConfigBackgroundAtom(bgKey: string): Atom { - if (isPreviewWindow()) return NullAtom as Atom; +function getConfigBackgroundAtom(bgKey: string | null): Atom { + if (isPreviewWindow() || bgKey == null) return NullAtom as Atom; let bgAtom = configBackgroundAtomCache.get(bgKey); if (bgAtom == null) { bgAtom = atom((get) => { @@ -681,6 +685,7 @@ export { getBlockComponentModel, getBlockMetaKeyAtom, getBlockTermDurableAtom, + getTabMetaKeyAtom, getConfigBackgroundAtom, getConnConfigKeyAtom, getConnStatusAtom, diff --git a/frontend/app/view/sysinfo/sysinfo.tsx b/frontend/app/view/sysinfo/sysinfo.tsx index 30feead6c8..f283096749 100644 --- a/frontend/app/view/sysinfo/sysinfo.tsx +++ b/frontend/app/view/sysinfo/sysinfo.tsx @@ -14,7 +14,7 @@ import * as React from "react"; import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions"; import { waveEventSubscribeSingle } from "@/app/store/wps"; import { TabRpcClient } from "@/app/store/wshrpcutil"; -import type { BlockMetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; +import type { MetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; export type SysinfoEnv = WaveEnvSubset<{ @@ -26,7 +26,7 @@ export type SysinfoEnv = WaveEnvSubset<{ fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"]; }; getConnStatusAtom: WaveEnv["getConnStatusAtom"]; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"graph:numpoints" | "sysinfo:type" | "connection" | "count">; + getBlockMetaKeyAtom: MetaKeyAtomFnType<"graph:numpoints" | "sysinfo:type" | "connection" | "count">; }>; const DefaultNumPoints = 120; diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index 35c2d2ea11..ac3a0d6444 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -32,16 +32,6 @@ export type ConfigFile = { export const SecretNameRegex = /^[A-Za-z][A-Za-z0-9_]*$/; -function validateBgJson(parsed: any): ValidationResult { - const keys = Object.keys(parsed); - for (const key of keys) { - if (!key.startsWith("bg@")) { - return { error: `Invalid key "${key}": all top-level keys must start with "bg@"` }; - } - } - return { success: true }; -} - function validateAiJson(parsed: any): ValidationResult { const keys = Object.keys(parsed); for (const key of keys) { @@ -101,10 +91,9 @@ function makeConfigFiles(isWindows: boolean): ConfigFile[] { }, { name: "Tab Backgrounds", - path: "presets/bg.json", + path: "backgrounds.json", language: "json", docsUrl: "https://docs.waveterm.dev/presets#background-configurations", - validator: validateBgJson, hasJsonView: true, }, { diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index 8dfa6ad25d..ef53f03332 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -10,7 +10,7 @@ import type { WaveConfigEnv } from "@/app/view/waveconfig/waveconfigenv"; import { useWaveEnv } from "@/app/waveenv/waveenv"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { cn } from "@/util/util"; -import { useAtom, useAtomValue } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; import type * as MonacoTypes from "monaco-editor"; import { memo, useCallback, useEffect } from "react"; @@ -20,7 +20,7 @@ interface ConfigSidebarProps { const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => { const selectedFile = useAtomValue(model.selectedFileAtom); - const [isMenuOpen, setIsMenuOpen] = useAtom(model.isMenuOpenAtom); + const setIsMenuOpen = useSetAtom(model.isMenuOpenAtom); const configFiles = model.getConfigFiles(); const deprecatedConfigFiles = model.getDeprecatedConfigFiles(); const configErrorFiles = useAtomValue(model.configErrorFilesAtom); @@ -164,141 +164,144 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps
- {isMenuOpen && ( -
setIsMenuOpen(false)} /> - )} -
- -
-
- {selectedFile && ( - <> -
-
- -
- {selectedFile.name} -
- {selectedFile.docsUrl && ( - - - - - - )} -
- {selectedFile.path} -
-
-
- {selectedFile.hasJsonView && ( - <> - {hasChanges && ( - - Unsaved changes - - )} - - +
+ {selectedFile.name} +
+ {selectedFile.docsUrl && ( + + - {isSaving ? "Saving..." : "Save"} - + + - - )} -
-
- {selectedFile.visualComponent && selectedFile.hasJsonView && ( -
- -
+
+ {selectedFile.hasJsonView && ( + <> + {hasChanges && ( + + Unsaved changes + + )} + + + + )} - > - Raw JSON - -
- )} - {errorMessage && ( -
- {errorMessage} - -
- )} - {validationError && ( -
- {validationError} - +
- )} -
- {isLoading ? ( -
- Loading... + {selectedFile.visualComponent && selectedFile.hasJsonView && ( +
+ +
- ) : selectedFile.visualComponent && - (!selectedFile.hasJsonView || activeTab === "visual") ? ( - (() => { - const VisualComponent = selectedFile.visualComponent; - return ; - })() - ) : ( - )} -
- - )} -
+ {errorMessage && ( +
+ {errorMessage} + +
+ )} + {validationError && ( +
+ {validationError} + +
+ )} +
+ {isLoading ? ( +
+ Loading... +
+ ) : selectedFile.visualComponent && + (!selectedFile.hasJsonView || activeTab === "visual") ? ( + (() => { + const VisualComponent = selectedFile.visualComponent; + return ; + })() + ) : ( + + )} +
+ + )} +
{configErrors?.length > 0 && (
diff --git a/frontend/app/view/waveconfig/waveconfigenv.ts b/frontend/app/view/waveconfig/waveconfigenv.ts index 5e2937c19b..c76352cbde 100644 --- a/frontend/app/view/waveconfig/waveconfigenv.ts +++ b/frontend/app/view/waveconfig/waveconfigenv.ts @@ -1,7 +1,7 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import type { BlockMetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; +import type { MetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; export type WaveConfigEnv = WaveEnvSubset<{ electron: { @@ -22,6 +22,6 @@ export type WaveConfigEnv = WaveEnvSubset<{ atoms: { fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"]; }; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"file">; + getBlockMetaKeyAtom: MetaKeyAtomFnType<"file">; isWindows: WaveEnv["isWindows"]; }>; diff --git a/frontend/app/view/webview/webviewenv.ts b/frontend/app/view/webview/webviewenv.ts index 268e4c9e5d..419b04c4eb 100644 --- a/frontend/app/view/webview/webviewenv.ts +++ b/frontend/app/view/webview/webviewenv.ts @@ -1,7 +1,7 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import type { BlockMetaKeyAtomFnType, SettingsKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; +import type { MetaKeyAtomFnType, SettingsKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv"; export type WebViewEnv = WaveEnvSubset<{ electron: { @@ -19,7 +19,7 @@ export type WebViewEnv = WaveEnvSubset<{ wos: WaveEnv["wos"]; createBlock: WaveEnv["createBlock"]; getSettingsKeyAtom: SettingsKeyAtomFnType<"web:defaulturl" | "web:defaultsearch">; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType< + getBlockMetaKeyAtom: MetaKeyAtomFnType< "web:hidenav" | "web:useragenttype" | "web:zoom" | "web:partition" >; }>; diff --git a/frontend/app/waveenv/waveenv.ts b/frontend/app/waveenv/waveenv.ts index d0134d7946..3b024d2f95 100644 --- a/frontend/app/waveenv/waveenv.ts +++ b/frontend/app/waveenv/waveenv.ts @@ -6,8 +6,8 @@ import { RpcApiType } from "@/app/store/wshclientapi"; import { Atom, PrimitiveAtom } from "jotai"; import React from "react"; -export type BlockMetaKeyAtomFnType = ( - blockId: string, +export type MetaKeyAtomFnType = ( + id: string, key: T ) => Atom; @@ -74,9 +74,10 @@ export type WaveEnv = { useWaveObjectValue: (oref: string) => [T, boolean]; }; getSettingsKeyAtom: SettingsKeyAtomFnType; - getBlockMetaKeyAtom: BlockMetaKeyAtomFnType; + getBlockMetaKeyAtom: MetaKeyAtomFnType; + getTabMetaKeyAtom: MetaKeyAtomFnType; getConnConfigKeyAtom: ConnConfigKeyAtomFnType; - getConfigBackgroundAtom: (bgKey: string) => Atom; + getConfigBackgroundAtom: (bgKey: string | null) => Atom; // the mock fields are only usable in the preview server (may be be null or throw errors in production) mockSetWaveObj: (oref: string, obj: T) => void; diff --git a/frontend/app/waveenv/waveenvimpl.ts b/frontend/app/waveenv/waveenvimpl.ts index 0847cd75b6..6abe00e574 100644 --- a/frontend/app/waveenv/waveenvimpl.ts +++ b/frontend/app/waveenv/waveenvimpl.ts @@ -12,6 +12,7 @@ import { getConnStatusAtom, getLocalHostDisplayNameAtom, getSettingsKeyAtom, + getTabMetaKeyAtom, isDev, WOS, } from "@/app/store/global"; @@ -45,6 +46,7 @@ export function makeWaveEnvImpl(): WaveEnv { useWaveObjectValue: WOS.useWaveObjectValue, }, getBlockMetaKeyAtom, + getTabMetaKeyAtom, getConfigBackgroundAtom, getConnConfigKeyAtom, diff --git a/frontend/preview/mock/defaultconfig.ts b/frontend/preview/mock/defaultconfig.ts index 0c2ac11b3a..415630b2b6 100644 --- a/frontend/preview/mock/defaultconfig.ts +++ b/frontend/preview/mock/defaultconfig.ts @@ -1,6 +1,7 @@ // Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import backgroundsJson from "../../../pkg/wconfig/defaultconfig/backgrounds.json"; import mimetypesJson from "../../../pkg/wconfig/defaultconfig/mimetypes.json"; import presetsJson from "../../../pkg/wconfig/defaultconfig/presets.json"; import settingsJson from "../../../pkg/wconfig/defaultconfig/settings.json"; @@ -18,5 +19,6 @@ export const DefaultFullConfig: FullConfigType = { connections: {}, bookmarks: {}, waveai: waveaiJson as unknown as { [key: string]: AIModeConfigType }, + backgrounds: backgroundsJson as { [key: string]: BackgroundConfigType }, configerrors: [], }; diff --git a/frontend/preview/mock/mockwaveenv.ts b/frontend/preview/mock/mockwaveenv.ts index 6f011b8e57..69827a09b4 100644 --- a/frontend/preview/mock/mockwaveenv.ts +++ b/frontend/preview/mock/mockwaveenv.ts @@ -8,6 +8,7 @@ import { handleWaveEvent } from "@/app/store/wps"; import { RpcApiType } from "@/app/store/wshclientapi"; import { WaveEnv } from "@/app/waveenv/waveenv"; import { PlatformLinux, PlatformMacOS, PlatformWindows } from "@/util/platformutil"; +import { NullAtom } from "@/util/util"; import { Atom, atom, PrimitiveAtom, useAtomValue } from "jotai"; import { showPreviewContextMenu } from "../preview-contextmenu"; import { MockSysinfoConnection } from "../previews/sysinfo.preview-util"; @@ -428,7 +429,7 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { const connStatusAtomCache = new Map>(); const waveObjectValueAtomCache = new Map>(); const waveObjectDerivedAtomCache = new Map>(); - const blockMetaKeyAtomCache = new Map>(); + const orefMetaKeyAtomCache = new Map>(); const connConfigKeyAtomCache = new Map>(); const configBackgroundAtomCache = new Map>(); const getWaveObjectAtom = (oref: string): PrimitiveAtom => { @@ -544,17 +545,36 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { }, }, getBlockMetaKeyAtom: (blockId: string, key: T) => { - const cacheKey = blockId + "#meta-" + key; - if (!blockMetaKeyAtomCache.has(cacheKey)) { + if (blockId == null) { + return NullAtom as Atom; + } + const oref = "block:" + blockId; + const cacheKey = oref + "#meta-" + key; + if (!orefMetaKeyAtomCache.has(cacheKey)) { const metaAtom = atom((get) => { - const blockORef = "block:" + blockId; - const blockAtom = env.wos.getWaveObjectAtom(blockORef); + const blockAtom = env.wos.getWaveObjectAtom(oref); const blockData = get(blockAtom); return blockData?.meta?.[key] as MetaType[T]; }); - blockMetaKeyAtomCache.set(cacheKey, metaAtom); + orefMetaKeyAtomCache.set(cacheKey, metaAtom); + } + return orefMetaKeyAtomCache.get(cacheKey) as Atom; + }, + getTabMetaKeyAtom: (tabId: string, key: T) => { + if (tabId == null) { + return NullAtom as Atom; + } + const oref = "tab:" + tabId; + const cacheKey = oref + "#meta-" + key; + if (!orefMetaKeyAtomCache.has(cacheKey)) { + const metaAtom = atom((get) => { + const tabAtom = env.wos.getWaveObjectAtom(oref); + const tabData = get(tabAtom); + return tabData?.meta?.[key] as MetaType[T]; + }); + orefMetaKeyAtomCache.set(cacheKey, metaAtom); } - return blockMetaKeyAtomCache.get(cacheKey) as Atom; + return orefMetaKeyAtomCache.get(cacheKey) as Atom; }, getConnConfigKeyAtom: (connName: string, key: T) => { const cacheKey = connName + "#conn-" + key; @@ -567,7 +587,8 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv { } return connConfigKeyAtomCache.get(cacheKey) as Atom; }, - getConfigBackgroundAtom: (bgKey: string) => { + getConfigBackgroundAtom: (bgKey: string | null) => { + if (bgKey == null) return NullAtom as Atom; if (!configBackgroundAtomCache.has(bgKey)) { configBackgroundAtomCache.set( bgKey, diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 4839cecea2..a029ca401f 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -839,6 +839,49 @@ func SetConnectionsConfigValue(connName string, toMerge waveobj.MetaMapType) err return WriteWaveHomeConfigFile(ConnectionsFile, m) } +func MigratePresetsBackgrounds() { + configDirAbsPath := wavebase.GetWaveConfigDir() + backgroundsFile := filepath.Join(configDirAbsPath, "backgrounds.json") + if _, err := os.Stat(backgroundsFile); err == nil { + return + } else if !os.IsNotExist(err) { + log.Printf("error checking backgrounds.json during migration: %v\n", err) + return + } + bgFile := filepath.Join(configDirAbsPath, "presets", "bg.json") + bgData, err := os.ReadFile(bgFile) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("error reading presets/bg.json for migration: %v\n", err) + } + return + } + var rawMap map[string]json.RawMessage + if err := json.Unmarshal(bgData, &rawMap); err != nil { + log.Printf("error parsing presets/bg.json for migration: %v\n", err) + return + } + filtered := make(map[string]json.RawMessage) + for k, v := range rawMap { + if strings.HasPrefix(k, "bg@") { + filtered[k] = v + } + } + if len(filtered) == 0 { + return + } + outBarr, err := json.MarshalIndent(filtered, "", " ") + if err != nil { + log.Printf("error marshaling backgrounds.json during migration: %v\n", err) + return + } + if err := fileutil.AtomicWriteFile(backgroundsFile, outBarr, 0644); err != nil { + log.Printf("error writing backgrounds.json during migration: %v\n", err) + return + } + log.Printf("migrated %d background presets from presets/bg.json to backgrounds.json\n", len(filtered)) +} + type WidgetConfigType struct { DisplayOrder float64 `json:"display:order,omitempty"` DisplayHidden bool `json:"display:hidden,omitempty"` From 55aef97fdfc24dc9a76f2fad1d0c301e56d17291 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 17:21:50 -0700 Subject: [PATCH 03/12] fix some typescript check issues --- frontend/app/view/term/fitaddon.ts | 17 ++++++++++++++++- tsconfig.json | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frontend/app/view/term/fitaddon.ts b/frontend/app/view/term/fitaddon.ts index d22b5577a7..3540a792f9 100644 --- a/frontend/app/view/term/fitaddon.ts +++ b/frontend/app/view/term/fitaddon.ts @@ -8,7 +8,22 @@ import type { FitAddon as IFitApi } from "@xterm/addon-fit"; import type { ITerminalAddon, Terminal } from "@xterm/xterm"; -import { IRenderDimensions } from "@xterm/xterm/src/browser/renderer/shared/Types"; + +interface IDimensions { + width: number; + height: number; +} + +interface IRenderDimensions { + css: { + canvas: IDimensions; + cell: IDimensions; + }; + device: { + canvas: IDimensions; + cell: IDimensions; + }; +} interface ITerminalDimensions { /** diff --git a/tsconfig.json b/tsconfig.json index 3ef02e0671..8fd50d2f96 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "include": ["frontend/**/*", "emain/**/*"], + "exclude": ["node_modules"], "compilerOptions": { "target": "es6", "module": "es2020", From 7ba4232e2091ee3b59d6ab260b345c5518590bd2 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 17:28:34 -0700 Subject: [PATCH 04/12] remove default, re-add as special case to context menu, fix setbg to clear the new tab:background key... --- cmd/wsh/cmd/wshcmd-setbg.go | 1 + frontend/app/tab/tabcontextmenu.ts | 12 ++++++++++++ pkg/wconfig/defaultconfig/backgrounds.json | 18 ------------------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go index fb5cf0fec0..19e3bdc2e5 100644 --- a/cmd/wsh/cmd/wshcmd-setbg.go +++ b/cmd/wsh/cmd/wshcmd-setbg.go @@ -101,6 +101,7 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { } else { // Handle background setting meta["bg:*"] = true + meta["tab:background"] = nil if setBgOpacity < 0 || setBgOpacity > 1 { return fmt.Errorf("opacity must be between 0.0 and 1.0") } diff --git a/frontend/app/tab/tabcontextmenu.ts b/frontend/app/tab/tabcontextmenu.ts index 2c901d0960..bc87302d4c 100644 --- a/frontend/app/tab/tabcontextmenu.ts +++ b/frontend/app/tab/tabcontextmenu.ts @@ -85,6 +85,18 @@ export function buildTabContextMenu( if (bgKeys.length > 0) { const submenu: ContextMenuItem[] = []; const oref = makeORef("tab", id); + submenu.push({ + label: "Default", + click: () => + fireAndForget(async () => { + await env.rpc.SetMetaCommand(TabRpcClient, { + oref, + meta: { "bg:*": true, "tab:background": null }, + }); + env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true }); + recordTEvent("action:settabtheme"); + }), + }); for (const bgKey of bgKeys) { const bg = backgrounds[bgKey]; submenu.push({ diff --git a/pkg/wconfig/defaultconfig/backgrounds.json b/pkg/wconfig/defaultconfig/backgrounds.json index 3d7cc1ad3b..ab044b9246 100644 --- a/pkg/wconfig/defaultconfig/backgrounds.json +++ b/pkg/wconfig/defaultconfig/backgrounds.json @@ -1,27 +1,19 @@ { - "bg@default": { - "display:name": "Default", - "display:order": -1, - "bg:*": true - }, "bg@rainbow": { "display:name": "Rainbow", "display:order": 2.1, - "bg:*": true, "bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )", "bg:opacity": 0.3 }, "bg@green": { "display:name": "Green", "display:order": 1.2, - "bg:*": true, "bg": "green", "bg:opacity": 0.3 }, "bg@blue": { "display:name": "Blue", "display:order": 1.1, - "bg:*": true, "bg": "blue", "bg:opacity": 0.3, "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" @@ -29,7 +21,6 @@ "bg@red": { "display:name": "Red", "display:order": 1.3, - "bg:*": true, "bg": "red", "bg:opacity": 0.3, "bg:activebordercolor": "rgba(255, 0, 0, 1.0)" @@ -37,14 +28,12 @@ "bg@ocean-depths": { "display:name": "Ocean Depths", "display:order": 2.2, - "bg:*": true, "bg": "linear-gradient(135deg, purple, blue, teal)", "bg:opacity": 0.7 }, "bg@aqua-horizon": { "display:name": "Aqua Horizon", "display:order": 2.3, - "bg:*": true, "bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)", "bg:opacity": 0.85, "bg:blendmode": "overlay" @@ -52,7 +41,6 @@ "bg@sunset": { "display:name": "Sunset", "display:order": 2.4, - "bg:*": true, "bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))", "bg:opacity": 0.8, "bg:blendmode": "normal" @@ -60,7 +48,6 @@ "bg@enchantedforest": { "display:name": "Enchanted Forest", "display:order": 2.7, - "bg:*": true, "bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)", "bg:opacity": 0.8, "bg:blendmode": "soft-light" @@ -68,7 +55,6 @@ "bg@twilight-mist": { "display:name": "Twilight Mist", "display:order": 2.9, - "bg:*": true, "bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)", "bg:opacity": 0.9, "bg:blendmode": "soft-light" @@ -76,7 +62,6 @@ "bg@duskhorizon": { "display:name": "Dusk Horizon", "display:order": 3.1, - "bg:*": true, "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", "bg:opacity": 0.9, "bg:blendmode": "overlay" @@ -84,7 +69,6 @@ "bg@tropical-radiance": { "display:name": "Tropical Radiance", "display:order": 3.3, - "bg:*": true, "bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", "bg:opacity": 0.9, "bg:blendmode": "overlay" @@ -92,7 +76,6 @@ "bg@twilight-ember": { "display:name": "Twilight Ember", "display:order": 3.5, - "bg:*": true, "bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)", "bg:blendmode": "overlay", "bg:text": "rgb(200, 200, 200)" @@ -101,7 +84,6 @@ "display:name": "Cosmic Tide", "display:order": 3.6, "bg:activebordercolor": "#ff55aa", - "bg:*": true, "bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)", "bg:opacity": 0.6 } From e69ebec0a937a071ac150e5609771ecce8bf851a Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 17:40:30 -0700 Subject: [PATCH 05/12] fix blockframe to use tab:background --- frontend/app/block/blockenv.ts | 2 ++ frontend/app/block/blockframe.tsx | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/app/block/blockenv.ts b/frontend/app/block/blockenv.ts index 0c115371b4..f4eebb192d 100644 --- a/frontend/app/block/blockenv.ts +++ b/frontend/app/block/blockenv.ts @@ -46,4 +46,6 @@ export type BlockEnv = WaveEnvSubset<{ | "frame:title" | "frame:icon" >; + getTabMetaKeyAtom: MetaKeyAtomFnType<"bg:activebordercolor" | "bg:bordercolor" | "tab:background">; + getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"]; }>; diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index fddc2b3a67..e6bfb72a48 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -36,8 +36,14 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => { waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:activebordercolor") ); const frameBorderColor = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:bordercolor")); - const tabActiveBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:activebordercolor")); - const tabBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:bordercolor")); + const tabActiveBorderColorDirect = jotai.useAtomValue( + waveEnv.getTabMetaKeyAtom(tabModel.tabId, "bg:activebordercolor") + ); + const tabBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabModel.tabId, "bg:bordercolor")); + const tabBg = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabModel.tabId, "tab:background")); + const configBg = jotai.useAtomValue(waveEnv.getConfigBackgroundAtom(tabBg)); + const tabActiveBorderColor = tabActiveBorderColorDirect ?? configBg?.["bg:activebordercolor"]; + const tabBorderColor = tabBorderColorDirect ?? configBg?.["bg:bordercolor"]; const style: React.CSSProperties = {}; let showBlockMask = false; From 74ed8cf6a0f3d9d4eca18e12db9f346e0ec85433 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 18:02:56 -0700 Subject: [PATCH 06/12] set border colors in setbg... fix customization docs, fix aipanel border colors --- cmd/wsh/cmd/wshcmd-setbg.go | 62 ++++++++++++++++++++++++++----- docs/docs/customization.mdx | 6 +-- docs/docs/wsh-reference.mdx | 8 +++- frontend/app/aipanel/aipanel.tsx | 10 ++++- frontend/app/block/blockframe.tsx | 11 +----- frontend/app/block/blockutil.tsx | 24 ++++++++++++ 6 files changed, 96 insertions(+), 25 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go index 19e3bdc2e5..24f373cb3a 100644 --- a/cmd/wsh/cmd/wshcmd-setbg.go +++ b/cmd/wsh/cmd/wshcmd-setbg.go @@ -19,7 +19,7 @@ import ( ) var setBgCmd = &cobra.Command{ - Use: "setbg [--opacity value] [--tile|--center] [--scale value] (image-path|\"#color\"|color-name)", + Use: "setbg [--opacity value] [--tile|--center] [--scale value] [--border-color color] [--active-border-color color] (image-path|\"#color\"|color-name)", Short: "set background image or color for a tab", Long: `Set a background image or color for a tab. Colors can be specified as: - A quoted hex value like "#ff0000" (quotes required to prevent # being interpreted as a shell comment) @@ -31,18 +31,22 @@ You can also: - Use --opacity without other arguments to change just the opacity - Use --center for centered images without scaling (good for logos) - Use --scale with --center to control image size + - Use --border-color to set the block frame border color + - Use --active-border-color to set the block frame focused border color - Use --print to see the metadata without applying it`, RunE: setBgRun, PreRunE: preRunSetupRpcClient, } var ( - setBgOpacity float64 - setBgTile bool - setBgCenter bool - setBgSize string - setBgClear bool - setBgPrint bool + setBgOpacity float64 + setBgTile bool + setBgCenter bool + setBgSize string + setBgClear bool + setBgPrint bool + setBgBorderColor string + setBgActiveBorderColor string ) func init() { @@ -53,8 +57,9 @@ func init() { setBgCmd.Flags().StringVar(&setBgSize, "size", "auto", "size for centered images (px, %, or auto)") setBgCmd.Flags().BoolVar(&setBgClear, "clear", false, "clear the background") setBgCmd.Flags().BoolVar(&setBgPrint, "print", false, "print the metadata without applying it") + setBgCmd.Flags().StringVar(&setBgBorderColor, "border-color", "", "block frame border color (#RRGGBB, #RRGGBBAA, or CSS color name)") + setBgCmd.Flags().StringVar(&setBgActiveBorderColor, "active-border-color", "", "block frame focused border color (#RRGGBB, #RRGGBBAA, or CSS color name)") - // Make tile and center mutually exclusive setBgCmd.MarkFlagsMutuallyExclusive("tile", "center") } @@ -73,17 +78,41 @@ func validateHexColor(color string) error { return nil } +func validateColor(color string) error { + if strings.HasPrefix(color, "#") { + return validateHexColor(color) + } + if !CssColorNames[strings.ToLower(color)] { + return fmt.Errorf("invalid color %q: must be a hex color (#RRGGBB or #RRGGBBAA) or a CSS color name", color) + } + return nil +} + func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { defer func() { sendActivity("setbg", rtnErr == nil) }() + borderColorChanged := cmd.Flags().Changed("border-color") + activeBorderColorChanged := cmd.Flags().Changed("active-border-color") + + if borderColorChanged { + if err := validateColor(setBgBorderColor); err != nil { + return fmt.Errorf("--border-color: %v", err) + } + } + if activeBorderColorChanged { + if err := validateColor(setBgActiveBorderColor); err != nil { + return fmt.Errorf("--active-border-color: %v", err) + } + } + // Create base metadata meta := map[string]interface{}{} // Handle opacity-only change or clear if len(args) == 0 { - if !cmd.Flags().Changed("opacity") && !setBgClear { + if !cmd.Flags().Changed("opacity") && !setBgClear && !borderColorChanged && !activeBorderColorChanged { OutputHelpMessage(cmd) return fmt.Errorf("setbg requires an image path or color value") } @@ -92,9 +121,15 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { } if setBgClear { meta["bg:*"] = true - } else { + } else if cmd.Flags().Changed("opacity") { meta["bg:opacity"] = setBgOpacity } + if borderColorChanged { + meta["bg:bordercolor"] = setBgBorderColor + } + if activeBorderColorChanged { + meta["bg:activebordercolor"] = setBgActiveBorderColor + } } else if len(args) > 1 { OutputHelpMessage(cmd) return fmt.Errorf("too many arguments") @@ -160,6 +195,13 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { meta["bg"] = bgStyle } + if borderColorChanged { + meta["bg:bordercolor"] = setBgBorderColor + } + if activeBorderColorChanged { + meta["bg:activebordercolor"] = setBgActiveBorderColor + } + if setBgPrint { jsonBytes, err := json.MarshalIndent(meta, "", " ") if err != nil { diff --git a/docs/docs/customization.mdx b/docs/docs/customization.mdx index 02fedca70a..900386680e 100644 --- a/docs/docs/customization.mdx +++ b/docs/docs/customization.mdx @@ -10,7 +10,9 @@ title: "Customization" Right click on any tab to bring up a menu which allows you to rename the tab and select different backgrounds. -It is also possible to create your own themes using custom colors, gradients, images and more by editing your presets.json config file. To see how Wave's built in tab themes are defined, you can check out our [default presets file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/presets.json). +It is also possible to create your own background themes using custom colors, gradients, images and more by editing your backgrounds.json config file. To see how Wave's built-in tab backgrounds are defined, you can check out the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json). + +To apply a tab background to all new tabs by default, set the key `tab:background` in your [Wave Config File](/config) to one of the background preset keys (e.g. `"bg@ocean-depths"`). The available built-in background keys can be found in the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json). ## Terminal Customization @@ -26,8 +28,6 @@ in the [default termthemes.json file](https://github.com/wavetermdev/waveterm/bl If you add your own termthemes.json file in the config directory, you can also add your own custom terminal themes (just follow the same format). -You can set the key `tab:preset` in your [Wave Config File](/config) to apply a theme to all new tabs. - #### Font Size From the same context menu you can also change the font-size of the terminal. To change the default font size across all of your (non-overridden) terminals, you can set the config key `term:fontsize` to the size you want. e.g. `{ "term:fontsize": 14}`. diff --git a/docs/docs/wsh-reference.mdx b/docs/docs/wsh-reference.mdx index 6ff8c2e8f5..fb6a565655 100644 --- a/docs/docs/wsh-reference.mdx +++ b/docs/docs/wsh-reference.mdx @@ -200,7 +200,7 @@ wsh editconfig presets/ai.json The `setbg` command allows you to set a background image or color for the current tab with various customization options. ```sh -wsh setbg [--opacity value] [--tile|--center] [--size value] (image-path|"#color"|color-name) +wsh setbg [--opacity value] [--tile|--center] [--size value] [--border-color color] [--active-border-color color] (image-path|"#color"|color-name) ``` You can set a background using: @@ -216,6 +216,8 @@ Flags: - `--center` - center the image without scaling (good for logos) - `--size` - size for centered images (px, %, or auto) - `--clear` - remove the background +- `--border-color color` - set the block frame border color (hex or CSS color name) +- `--active-border-color color` - set the block frame focused border color (hex or CSS color name) - `--print` - show the metadata without applying it Supported image formats: JPEG, PNG, GIF, WebP, and SVG. @@ -243,6 +245,10 @@ wsh setbg forestgreen # CSS color name # Change just the opacity of current background wsh setbg --opacity 0.7 +# Set border colors alongside a background +wsh setbg --border-color "#ff0000" --active-border-color "#00ff00" ~/pictures/background.jpg +wsh setbg --border-color steelblue forestgreen + # Remove background wsh setbg --clear diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index b903380544..4b354d0900 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -3,11 +3,13 @@ import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu"; import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils"; +import { useTabBackground } from "@/app/block/blockutil"; import { ErrorBoundary } from "@/app/element/errorboundary"; import { atoms, getSettingsKeyAtom } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import { useTabModelMaybe } from "@/app/store/tab-model"; import { isBuilderWindow } from "@/app/store/windowtype"; +import { useWaveEnv } from "@/app/waveenv/waveenv"; import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { isMacOS, isWindows } from "@/util/platformutil"; import { cn } from "@/util/util"; @@ -24,7 +26,7 @@ import { AIPanelInput } from "./aipanelinput"; import { AIPanelMessages } from "./aipanelmessages"; import { AIRateLimitStrip } from "./airatelimitstrip"; import { WaveUIMessage } from "./aitypes"; -import { BYOKAnnouncement } from "./byokannouncement"; +import { BYOKAnnouncement } from "./byokannouncement"; import { TelemetryRequiredMessage } from "./telemetryrequired"; import { WaveAIModel } from "./waveai-model"; @@ -255,6 +257,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps const [initialLoadDone, setInitialLoadDone] = useState(false); const model = WaveAIModel.getInstance(); const containerRef = useRef(null); + const waveEnv = useWaveEnv(); const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom); const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true; const isFocused = jotai.useAtomValue(model.isWaveAIFocusedAtom); @@ -262,6 +265,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false; const isPanelVisible = jotai.useAtomValue(model.getPanelVisibleAtom()); const tabModel = useTabModelMaybe(); + const [tabBorderColor, tabActiveBorderColor] = useTabBackground(waveEnv, tabModel?.tabId); const defaultMode = jotai.useAtomValue(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced"; const aiModeConfigs = jotai.useAtomValue(model.aiModeConfigs); @@ -546,6 +550,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps }; const showBlockMask = isLayoutMode && showOverlayBlockNums; + const borderColor = isFocused ? (tabActiveBorderColor ?? null) : (tabBorderColor ?? null); return (
{ waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:activebordercolor") ); const frameBorderColor = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:bordercolor")); - const tabActiveBorderColorDirect = jotai.useAtomValue( - waveEnv.getTabMetaKeyAtom(tabModel.tabId, "bg:activebordercolor") - ); - const tabBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabModel.tabId, "bg:bordercolor")); - const tabBg = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabModel.tabId, "tab:background")); - const configBg = jotai.useAtomValue(waveEnv.getConfigBackgroundAtom(tabBg)); - const tabActiveBorderColor = tabActiveBorderColorDirect ?? configBg?.["bg:activebordercolor"]; - const tabBorderColor = tabBorderColorDirect ?? configBg?.["bg:bordercolor"]; + const [tabBorderColor, tabActiveBorderColor] = useTabBackground(waveEnv, tabModel.tabId); const style: React.CSSProperties = {}; let showBlockMask = false; diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 01346183a0..92d976400f 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -2,13 +2,24 @@ // SPDX-License-Identifier: Apache-2.0 import { Button } from "@/app/element/button"; +import { + MetaKeyAtomFnType, + WaveEnv, + WaveEnvSubset, +} from "@/app/waveenv/waveenv"; import { IconButton, ToggleIconButton } from "@/element/iconbutton"; import { MagnifyIcon } from "@/element/magnify"; import { MenuButton } from "@/element/menubutton"; import * as util from "@/util/util"; import clsx from "clsx"; +import * as jotai from "jotai"; import * as React from "react"; +export type TabBackgroundEnv = WaveEnvSubset<{ + getTabMetaKeyAtom: MetaKeyAtomFnType<"bg:activebordercolor" | "bg:bordercolor" | "tab:background">; + getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"]; +}>; + export const colorRegex = /^((#[0-9a-f]{6,8})|([a-z]+))$/; export const NumActiveConnColors = 8; @@ -155,6 +166,19 @@ export function getViewIconElem( } } +export function useTabBackground( + waveEnv: TabBackgroundEnv, + tabId: string | null +): [string, string, BackgroundConfigType] { + const tabActiveBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "bg:activebordercolor")); + const tabBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "bg:bordercolor")); + const tabBg = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "tab:background")); + const configBg = jotai.useAtomValue(waveEnv.getConfigBackgroundAtom(tabBg)); + const tabActiveBorderColor = tabActiveBorderColorDirect ?? configBg?.["bg:activebordercolor"]; + const tabBorderColor = tabBorderColorDirect ?? configBg?.["bg:bordercolor"]; + return [tabBorderColor, tabActiveBorderColor, configBg]; +} + export const Input = React.memo( ({ decl, className, preview }: { decl: HeaderInput; className: string; preview: boolean }) => { const { value, ref, isDisabled, onChange, onKeyDown, onFocus, onBlur } = decl; From 25f4d49057466a0e64f7e3a9f5ffdc1521d462e9 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 18:12:41 -0700 Subject: [PATCH 07/12] fix tab backgrounds docs, remove presets --- docs/docs/customization.mdx | 2 +- .../docs/{presets.mdx => tab-backgrounds.mdx} | 82 +++++++------------ .../app/view/waveconfig/waveconfig-model.ts | 2 +- 3 files changed, 30 insertions(+), 56 deletions(-) rename docs/docs/{presets.mdx => tab-backgrounds.mdx} (54%) diff --git a/docs/docs/customization.mdx b/docs/docs/customization.mdx index 900386680e..e393c8fdb9 100644 --- a/docs/docs/customization.mdx +++ b/docs/docs/customization.mdx @@ -79,6 +79,6 @@ To preview the metadata for any background without applying it, use the `--print wsh setbg --print "#ff0000" ``` -For more advanced customization options including gradients, colors, and saving your own background presets, check out our [Background Configuration](/presets#background-configurations) documentation. +For more advanced customization options including gradients, colors, and saving your own custom backgrounds, check out our [Tab Backgrounds](/tab-backgrounds) documentation. diff --git a/docs/docs/presets.mdx b/docs/docs/tab-backgrounds.mdx similarity index 54% rename from docs/docs/presets.mdx rename to docs/docs/tab-backgrounds.mdx index 31fc5f57d7..77c02a2bb4 100644 --- a/docs/docs/presets.mdx +++ b/docs/docs/tab-backgrounds.mdx @@ -1,80 +1,57 @@ --- sidebar_position: 3.5 -id: "presets" -title: "Presets" +id: "tab-backgrounds" +title: "Tab Backgrounds" --- -# Presets +# Tab Backgrounds -Wave's preset system allows you to save and apply multiple configuration settings at once. Presets are used for: - -- Tab backgrounds: Apply visual styles to your tabs - -## Managing Presets - -You can store presets in two locations: +Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together. -- `~/.config/waveterm/presets.json`: Main presets file -- `~/.config/waveterm/presets/`: Directory for organizing presets into separate files +## Managing Backgrounds -All presets are aggregated regardless of which file they're in, so you can use the `presets` directory to organize them (e.g., `presets/bg.json`). +Custom backgrounds are stored in `~/.config/waveterm/backgrounds.json`. -:::info -You can easily edit your presets using the built-in editor: +**To edit using the UI:** +1. Click the settings (gear) icon in the widget bar +2. Select "Settings" from the menu +3. Choose "Tab Backgrounds" from the settings sidebar +**Or launch from the command line:** ```bash -wsh editconfig presets.json # Edit main presets file -wsh editconfig presets/bg.json # Edit background presets +wsh editconfig backgrounds.json ``` -::: - ## File Format -Presets follow this format: +Backgrounds follow this format: ```json { - "@": { - "display:name": "", - "display:order": "", // optional - "": "" - ... + "bg@": { + "display:name": "", + "display:order": , + "bg": "", + "bg:opacity": } } ``` -The `preset-type` determines where the preset appears in Wave's interface: - -- `bg`: Appears in the "Backgrounds" submenu when right-clicking a tab - -### Common Keys - -| Key Name | Type | Function | -| ------------- | ------ | ----------------------------------------- | -| display:name | string | Name shown in the UI menu (required) | -| display:order | float | Controls the order in the menu (optional) | - -:::info -When a preset is applied, it overrides the default configuration values for that tab or block. Using `bg:*` will clear any previously overridden values, setting them back to defaults. It's recommended to include this key in your presets to ensure a clean slate. -::: - -## Background Presets - -Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together. +To see how Wave's built-in backgrounds are defined, check out the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json). -### Configuration Keys +## Configuration Keys | Key Name | Type | Function | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------- | -| bg:\* | bool | Reset all existing bg keys (recommended to prevent any existing background settings from carrying over) | -| bg | string | CSS `background` attribute for the tab (supports colors, gradients images, etc.) | +| display:name | string | Name shown in the UI menu (required) | +| display:order | float | Controls the order in the menu (optional) | +| bg | string | CSS `background` attribute for the tab (supports colors, gradients, images, etc.) | | bg:opacity | float | The opacity of the background (defaults to 0.5) | | bg:blendmode | string | The [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background | | bg:bordercolor | string | The color of the border when a block is not active (rarely used) | | bg:activebordercolor | string | The color of the border when a block is active | -### Examples +## Examples #### Simple solid color: @@ -82,7 +59,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo { "bg@blue": { "display:name": "Blue", - "bg:*": true, "bg": "blue", "bg:opacity": 0.3, "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" @@ -96,7 +72,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo { "bg@duskhorizon": { "display:name": "Dusk Horizon", - "bg:*": true, "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", "bg:opacity": 0.9, "bg:blendmode": "overlay" @@ -110,7 +85,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo { "bg@ocean": { "display:name": "Ocean Scene", - "bg:*": true, "bg": "url('/path/to/ocean.jpg') center/cover no-repeat", "bg:opacity": 0.2 } @@ -122,10 +96,10 @@ Background images support both URLs and local file paths. For better reliability ::: :::tip -The `setbg` command can help generate background preset JSON: +The `setbg` command can help generate background JSON: ```bash -# Preview a solid color preset +# Preview a solid color background wsh setbg --print "#ff0000" { "bg:*": true, @@ -133,7 +107,7 @@ wsh setbg --print "#ff0000" "bg:opacity": 0.5 } -# Preview a centered image preset +# Preview a centered image background wsh setbg --print --center --opacity 0.3 ~/logo.png { "bg:*": true, @@ -142,5 +116,5 @@ wsh setbg --print --center --opacity 0.3 ~/logo.png } ``` -Just add the required `display:name` field to complete your preset! +Just add the required `display:name` field and a `bg@` wrapper to complete your background entry! ::: diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index ac3a0d6444..aa3688b684 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -93,7 +93,7 @@ function makeConfigFiles(isWindows: boolean): ConfigFile[] { name: "Tab Backgrounds", path: "backgrounds.json", language: "json", - docsUrl: "https://docs.waveterm.dev/presets#background-configurations", + docsUrl: "https://docs.waveterm.dev/tab-backgrounds", hasJsonView: true, }, { From f8d461d5a595152f7c6165eb0aaf99ac3136a8f6 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 18:14:56 -0700 Subject: [PATCH 08/12] fix links --- docs/docs/releasenotes.mdx | 2 +- docs/docs/wsh-reference.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx index 4d985607d0..adc2ce6fe7 100644 --- a/docs/docs/releasenotes.mdx +++ b/docs/docs/releasenotes.mdx @@ -479,7 +479,7 @@ New minor release that introduces Wave's connected computing extensions. We've i ### v0.9.2 — Nov 11, 2024 -New minor release with bug fixes and new features! Fixed the bug around making Wave fullscreen (also affecting certain window managers like Hyprland). We've also put a lot of work into the doc site (https://docs.waveterm.dev), including documenting how [Widgets](./widgets) and [Presets](./presets) work! +New minor release with bug fixes and new features! Fixed the bug around making Wave fullscreen (also affecting certain window managers like Hyprland). We've also put a lot of work into the doc site (https://docs.waveterm.dev), including documenting how [Widgets](./widgets) and Presets work! - Updated documentation - Wave AI now supports the Anthropic API! Checkout the [FAQ](./faq) for how to use the Claude models with Wave AI. diff --git a/docs/docs/wsh-reference.mdx b/docs/docs/wsh-reference.mdx index fb6a565655..6ed1bcaa3f 100644 --- a/docs/docs/wsh-reference.mdx +++ b/docs/docs/wsh-reference.mdx @@ -264,7 +264,7 @@ The command validates that: - The center and tile options are not used together :::tip -Use `--print` to preview the metadata for any background configuration without applying it. You can then copy this JSON representation to use as a [Background Preset](/presets#background-configurations) +Use `--print` to preview the metadata for any background configuration without applying it. You can then copy this JSON representation to use as a [Background entry](/tab-backgrounds) ::: --- From 6eb6db65645a243c098ccd65e58cfac51ee5284d Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 19:01:55 -0700 Subject: [PATCH 09/12] fix nits --- cmd/generateschema/main-generateschema.go | 6 +++--- cmd/wsh/cmd/wshcmd-setbg.go | 6 ------ frontend/app/aipanel/aipanel.tsx | 2 +- frontend/types/gotypes.d.ts | 2 +- pkg/wconfig/settingsconfig.go | 2 +- schema/backgrounds.json | 5 ++++- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go index 24a3d9b106..c09ef31305 100644 --- a/cmd/generateschema/main-generateschema.go +++ b/cmd/generateschema/main-generateschema.go @@ -185,10 +185,10 @@ func main() { log.Fatalf("widgets schema error: %v", err) } - bgPresetsTemplate := make(map[string]wconfig.BackgroundConfigType) - err = generateSchema(&bgPresetsTemplate, WaveSchemaBackgroundsFileName) + backgroundsTemplate := make(map[string]wconfig.BackgroundConfigType) + err = generateSchema(&backgroundsTemplate, WaveSchemaBackgroundsFileName) if err != nil { - log.Fatalf("bg presets schema error: %v", err) + log.Fatalf("backgrounds schema error: %v", err) } waveAITemplate := make(map[string]wconfig.AIModeConfigType) diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go index 24f373cb3a..4385409187 100644 --- a/cmd/wsh/cmd/wshcmd-setbg.go +++ b/cmd/wsh/cmd/wshcmd-setbg.go @@ -124,12 +124,6 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { } else if cmd.Flags().Changed("opacity") { meta["bg:opacity"] = setBgOpacity } - if borderColorChanged { - meta["bg:bordercolor"] = setBgBorderColor - } - if activeBorderColorChanged { - meta["bg:activebordercolor"] = setBgActiveBorderColor - } } else if len(args) > 1 { OutputHelpMessage(cmd) return fmt.Errorf("too many arguments") diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 4b354d0900..32b8582141 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -26,7 +26,7 @@ import { AIPanelInput } from "./aipanelinput"; import { AIPanelMessages } from "./aipanelmessages"; import { AIRateLimitStrip } from "./airatelimitstrip"; import { WaveUIMessage } from "./aitypes"; -import { BYOKAnnouncement } from "./byokannouncement"; +import { BYOKAnnouncement } from "./byokannouncement"; import { TelemetryRequiredMessage } from "./telemetryrequired"; import { WaveAIModel } from "./waveai-model"; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index baa737e007..9032d3c4fa 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -115,7 +115,7 @@ declare global { "bg:blendmode"?: string; "bg:bordercolor"?: string; "bg:activebordercolor"?: string; - "display:name"?: string; + "display:name": string; "display:order"?: number; }; diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index a029ca401f..0034aa0a13 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -900,7 +900,7 @@ type BackgroundConfigType struct { BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"` BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"` BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"` - DisplayName string `json:"display:name,omitempty" jsonschema_description:"The name shown in the context menu"` + DisplayName string `json:"display:name" jsonschema_description:"The name shown in the context menu"` DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"` } diff --git a/schema/backgrounds.json b/schema/backgrounds.json index 10d77ff5ba..e8685b1633 100644 --- a/schema/backgrounds.json +++ b/schema/backgrounds.json @@ -33,7 +33,10 @@ } }, "additionalProperties": false, - "type": "object" + "type": "object", + "required": [ + "display:name" + ] } }, "additionalProperties": { From 49f5f9e76b452ed73796c6ffa0d6fc2fe41f9061 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 19:13:06 -0700 Subject: [PATCH 10/12] fix type error, move types to top in go file --- frontend/app/app-bg.tsx | 2 +- frontend/util/waveutil.ts | 2 +- pkg/wconfig/settingsconfig.go | 108 +++++++++++++++++----------------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/frontend/app/app-bg.tsx b/frontend/app/app-bg.tsx index 04c3c3f25a..2956e36d58 100644 --- a/frontend/app/app-bg.tsx +++ b/frontend/app/app-bg.tsx @@ -23,7 +23,7 @@ export function AppBackground() { const env = useWaveEnv(); const tabBg = useAtomValue(env.getTabMetaKeyAtom(tabId, "tab:background")); const configBg = useAtomValue(env.getConfigBackgroundAtom(tabBg)); - const resolvedMeta: BackgroundConfigType = tabBg && configBg ? configBg : tabData?.meta; + const resolvedMeta: Omit = tabBg && configBg ? configBg : tabData?.meta; const style: CSSProperties = computeBgStyleFromMeta(resolvedMeta, 0.5) ?? {}; const getAvgColor = useCallback( debounce(30, () => { diff --git a/frontend/util/waveutil.ts b/frontend/util/waveutil.ts index d22f5a4896..4d0f5952fc 100644 --- a/frontend/util/waveutil.ts +++ b/frontend/util/waveutil.ts @@ -69,7 +69,7 @@ export function processBackgroundUrls(cssText: string): string { return rtnStyle.replace(/^background:\s*/, ""); } -export function computeBgStyleFromMeta(meta: MetaType, defaultOpacity: number = null): React.CSSProperties { +export function computeBgStyleFromMeta(meta: Omit, defaultOpacity: number = null): React.CSSProperties { const bgAttr = meta?.["bg"]; if (isBlank(bgAttr)) { return null; diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 0034aa0a13..7245cab2bf 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -309,6 +309,60 @@ type AIModeConfigUpdate struct { Configs map[string]AIModeConfigType `json:"configs"` } +type WidgetConfigType struct { + DisplayOrder float64 `json:"display:order,omitempty"` + DisplayHidden bool `json:"display:hidden,omitempty"` + Icon string `json:"icon,omitempty"` + Color string `json:"color,omitempty"` + Label string `json:"label,omitempty"` + Description string `json:"description,omitempty"` + Workspaces []string `json:"workspaces,omitempty"` + Magnified bool `json:"magnified,omitempty"` + BlockDef waveobj.BlockDef `json:"blockdef"` +} + +type BackgroundConfigType struct { + Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"` + BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"` + BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"` + BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"` + BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"` + DisplayName string `json:"display:name" jsonschema_description:"The name shown in the context menu"` + DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"` +} + +type MimeTypeConfigType struct { + Icon string `json:"icon"` + Color string `json:"color"` +} + +type TermThemeType struct { + DisplayName string `json:"display:name"` + DisplayOrder float64 `json:"display:order"` + Black string `json:"black"` + Red string `json:"red"` + Green string `json:"green"` + Yellow string `json:"yellow"` + Blue string `json:"blue"` + Magenta string `json:"magenta"` + Cyan string `json:"cyan"` + White string `json:"white"` + BrightBlack string `json:"brightBlack"` + BrightRed string `json:"brightRed"` + BrightGreen string `json:"brightGreen"` + BrightYellow string `json:"brightYellow"` + BrightBlue string `json:"brightBlue"` + BrightMagenta string `json:"brightMagenta"` + BrightCyan string `json:"brightCyan"` + BrightWhite string `json:"brightWhite"` + Gray string `json:"gray"` + CmdText string `json:"cmdtext"` + Foreground string `json:"foreground"` + SelectionBackground string `json:"selectionBackground"` + Background string `json:"background"` + Cursor string `json:"cursor"` +} + type FullConfigType struct { Settings SettingsType `json:"settings" merge:"meta"` MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` @@ -882,60 +936,6 @@ func MigratePresetsBackgrounds() { log.Printf("migrated %d background presets from presets/bg.json to backgrounds.json\n", len(filtered)) } -type WidgetConfigType struct { - DisplayOrder float64 `json:"display:order,omitempty"` - DisplayHidden bool `json:"display:hidden,omitempty"` - Icon string `json:"icon,omitempty"` - Color string `json:"color,omitempty"` - Label string `json:"label,omitempty"` - Description string `json:"description,omitempty"` - Workspaces []string `json:"workspaces,omitempty"` - Magnified bool `json:"magnified,omitempty"` - BlockDef waveobj.BlockDef `json:"blockdef"` -} - -type BackgroundConfigType struct { - Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"` - BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"` - BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"` - BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"` - BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"` - DisplayName string `json:"display:name" jsonschema_description:"The name shown in the context menu"` - DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"` -} - -type MimeTypeConfigType struct { - Icon string `json:"icon"` - Color string `json:"color"` -} - -type TermThemeType struct { - DisplayName string `json:"display:name"` - DisplayOrder float64 `json:"display:order"` - Black string `json:"black"` - Red string `json:"red"` - Green string `json:"green"` - Yellow string `json:"yellow"` - Blue string `json:"blue"` - Magenta string `json:"magenta"` - Cyan string `json:"cyan"` - White string `json:"white"` - BrightBlack string `json:"brightBlack"` - BrightRed string `json:"brightRed"` - BrightGreen string `json:"brightGreen"` - BrightYellow string `json:"brightYellow"` - BrightBlue string `json:"brightBlue"` - BrightMagenta string `json:"brightMagenta"` - BrightCyan string `json:"brightCyan"` - BrightWhite string `json:"brightWhite"` - Gray string `json:"gray"` - CmdText string `json:"cmdtext"` - Foreground string `json:"foreground"` - SelectionBackground string `json:"selectionBackground"` - Background string `json:"background"` - Cursor string `json:"cursor"` -} - // CountCustomWidgets returns the number of custom widgets the user has defined. // Custom widgets are identified as widgets whose ID doesn't start with "defwidget@". func (fc *FullConfigType) CountCustomWidgets() int { From 3150e0316d50f06c733358efa73cf37e4212f017 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 23 Mar 2026 19:29:25 -0700 Subject: [PATCH 11/12] allow overflow for monaco tooltips, allow nulls for backgrounds/wdigets json schema --- cmd/generateschema/main-generateschema.go | 31 +++++++++++++++++---- frontend/app/view/codeeditor/codeeditor.tsx | 2 +- frontend/app/view/waveconfig/waveconfig.tsx | 2 +- schema/backgrounds.json | 9 +++++- schema/widgets.json | 9 +++++- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go index c09ef31305..dd24a4df0d 100644 --- a/cmd/generateschema/main-generateschema.go +++ b/cmd/generateschema/main-generateschema.go @@ -105,8 +105,26 @@ type WidgetsMetaSchemaHints struct { TermDurable *bool `json:"term:durable,omitempty"` } -func generateSchema(template any, dir string) error { +// allowNullValues wraps the top-level additionalProperties of a map schema with +// anyOf: [originalSchema, {type: "null"}] so that setting a key to null is valid +// (e.g. "bg@foo": null to remove a default entry). +func allowNullValues(schema *jsonschema.Schema) { + if schema.AdditionalProperties != nil && schema.AdditionalProperties != jsonschema.TrueSchema && schema.AdditionalProperties != jsonschema.FalseSchema { + original := schema.AdditionalProperties + schema.AdditionalProperties = &jsonschema.Schema{ + AnyOf: []*jsonschema.Schema{ + original, + {Type: "null"}, + }, + } + } +} + +func generateSchema(template any, dir string, allowNull bool) error { settingsSchema := jsonschema.Reflect(template) + if allowNull { + allowNullValues(settingsSchema) + } jsonSettingsSchema, err := json.MarshalIndent(settingsSchema, "", " ") if err != nil { @@ -147,6 +165,7 @@ func generateWidgetsSchema(dir string) error { widgetsTemplate := make(map[string]wconfig.WidgetConfigType) widgetsSchema := r.Reflect(&widgetsTemplate) + allowNullValues(widgetsSchema) jsonWidgetsSchema, err := json.MarshalIndent(widgetsSchema, "", " ") if err != nil { @@ -163,19 +182,19 @@ func generateWidgetsSchema(dir string) error { } func main() { - err := generateSchema(&wconfig.SettingsType{}, WaveSchemaSettingsFileName) + err := generateSchema(&wconfig.SettingsType{}, WaveSchemaSettingsFileName, false) if err != nil { log.Fatalf("settings schema error: %v", err) } connectionTemplate := make(map[string]wconfig.ConnKeywords) - err = generateSchema(&connectionTemplate, WaveSchemaConnectionsFileName) + err = generateSchema(&connectionTemplate, WaveSchemaConnectionsFileName, false) if err != nil { log.Fatalf("connections schema error: %v", err) } aiPresetsTemplate := make(map[string]wconfig.AiSettingsType) - err = generateSchema(&aiPresetsTemplate, WaveSchemaAiPresetsFileName) + err = generateSchema(&aiPresetsTemplate, WaveSchemaAiPresetsFileName, false) if err != nil { log.Fatalf("ai presets schema error: %v", err) } @@ -186,13 +205,13 @@ func main() { } backgroundsTemplate := make(map[string]wconfig.BackgroundConfigType) - err = generateSchema(&backgroundsTemplate, WaveSchemaBackgroundsFileName) + err = generateSchema(&backgroundsTemplate, WaveSchemaBackgroundsFileName, true) if err != nil { log.Fatalf("backgrounds schema error: %v", err) } waveAITemplate := make(map[string]wconfig.AIModeConfigType) - err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName) + err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName, false) if err != nil { log.Fatalf("waveai schema error: %v", err) } diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index 48461887b5..ba1e28666e 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -93,7 +93,7 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan }, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, readonly]); return ( -
+
)} -
+
{isLoading ? (
Loading... diff --git a/schema/backgrounds.json b/schema/backgrounds.json index e8685b1633..298bdbf2a3 100644 --- a/schema/backgrounds.json +++ b/schema/backgrounds.json @@ -40,7 +40,14 @@ } }, "additionalProperties": { - "$ref": "#/$defs/BackgroundConfigType" + "anyOf": [ + { + "$ref": "#/$defs/BackgroundConfigType" + }, + { + "type": "null" + } + ] }, "type": "object" } \ No newline at end of file diff --git a/schema/widgets.json b/schema/widgets.json index a4e6adb16a..1c55fd8e09 100644 --- a/schema/widgets.json +++ b/schema/widgets.json @@ -224,7 +224,14 @@ } }, "additionalProperties": { - "$ref": "#/$defs/WidgetConfigType" + "anyOf": [ + { + "$ref": "#/$defs/WidgetConfigType" + }, + { + "type": "null" + } + ] }, "type": "object" } \ No newline at end of file From 7e822a26ea6bd143eabef503baa27caf99542bfc Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Mar 2026 09:00:16 -0700 Subject: [PATCH 12/12] two space indent --- pkg/wconfig/settingsconfig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 7245cab2bf..b55cab8cbf 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -924,7 +924,7 @@ func MigratePresetsBackgrounds() { if len(filtered) == 0 { return } - outBarr, err := json.MarshalIndent(filtered, "", " ") + outBarr, err := json.MarshalIndent(filtered, "", " ") if err != nil { log.Printf("error marshaling backgrounds.json during migration: %v\n", err) return