diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 5c26d461e58c..0e346f2cfa73 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -78,9 +78,11 @@ type ColorValue = HexColor | RefName | Variant | RGBA export type ThemeJson = { $schema?: string defs?: Record - theme: Omit, "selectedListItemText" | "backgroundMenu"> & { + theme: Omit, "selectedListItemText" | "backgroundMenu" | "backgroundDialogOverlay" | "backgroundSidebarOverlay"> & { selectedListItemText?: ColorValue backgroundMenu?: ColorValue + backgroundDialogOverlay?: ColorValue + backgroundSidebarOverlay?: ColorValue thinkingOpacity?: number } } @@ -222,7 +224,7 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") { const resolved = Object.fromEntries( Object.entries(theme.theme) - .filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "thinkingOpacity") + .filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "backgroundDialogOverlay" && key !== "backgroundSidebarOverlay" && key !== "thinkingOpacity") .map(([key, value]) => { return [key, resolveColor(value as ColorValue)] }), @@ -245,6 +247,20 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") { resolved.backgroundMenu = resolved.backgroundElement } + // Handle backgroundDialogOverlay - optional with fallback to semi-transparent black + if (theme.theme.backgroundDialogOverlay !== undefined) { + resolved.backgroundDialogOverlay = resolveColor(theme.theme.backgroundDialogOverlay) + } else { + resolved.backgroundDialogOverlay = RGBA.fromInts(0, 0, 0, 150) + } + + // Handle backgroundSidebarOverlay - optional with fallback to semi-transparent black + if (theme.theme.backgroundSidebarOverlay !== undefined) { + resolved.backgroundSidebarOverlay = resolveColor(theme.theme.backgroundSidebarOverlay) + } else { + resolved.backgroundSidebarOverlay = RGBA.fromInts(0, 0, 0, 70) + } + // Handle thinkingOpacity - optional with default of 0.6 const thinkingOpacity = theme.theme.thinkingOpacity ?? 0.6 @@ -575,6 +591,8 @@ function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJs backgroundPanel: grays[2], backgroundElement: grays[3], backgroundMenu: grays[3], + backgroundDialogOverlay: transparent, + backgroundSidebarOverlay: transparent, // Border colors borderSubtle: grays[6], diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 8855338d1d4b..02ad87ffe388 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1221,7 +1221,7 @@ export function Session() { right={0} bottom={0} alignItems="flex-end" - backgroundColor={RGBA.fromInts(0, 0, 0, 70)} + backgroundColor={theme.backgroundSidebarOverlay} > diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx index a5da735f6556..798e3a48db40 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx @@ -1,7 +1,7 @@ import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import { batch, createContext, Show, useContext, type JSX, type ParentProps } from "solid-js" import { useTheme } from "@tui/context/theme" -import { MouseButton, Renderable, RGBA } from "@opentui/core" +import { MouseButton, Renderable } from "@opentui/core" import { createStore } from "solid-js/store" import { useToast } from "./toast" import { Flag } from "@opencode-ai/core/flag/flag" @@ -44,7 +44,7 @@ export function Dialog( paddingTop={dimensions().height / 4} left={0} top={0} - backgroundColor={RGBA.fromInts(0, 0, 0, 150)} + backgroundColor={theme.backgroundDialogOverlay} > { diff --git a/packages/opencode/test/cli/tui/theme-store.test.ts b/packages/opencode/test/cli/tui/theme-store.test.ts index 9ebfc4320ed5..fbf60e7e7bfc 100644 --- a/packages/opencode/test/cli/tui/theme-store.test.ts +++ b/packages/opencode/test/cli/tui/theme-store.test.ts @@ -49,3 +49,19 @@ test("resolveTheme rejects circular color refs", () => { expect(() => resolveTheme(item, "dark")).toThrow("Circular color reference") }) + +test("resolveTheme uses default for overlay backgrounds when not specified", () => { + const item = structuredClone(DEFAULT_THEMES.opencode) + const resolved = resolveTheme(item, "dark") + expect(resolved.backgroundDialogOverlay.a).toBeCloseTo(150 / 255, 2) + expect(resolved.backgroundSidebarOverlay.a).toBeCloseTo(70 / 255, 2) +}) + +test("resolveTheme uses custom overlay background values", () => { + const item = structuredClone(DEFAULT_THEMES.opencode) + item.theme.backgroundDialogOverlay = "#ff0000" + item.theme.backgroundSidebarOverlay = "#0000ff" + const resolved = resolveTheme(item, "dark") + expect(resolved.backgroundDialogOverlay.r).toEqual(1) + expect(resolved.backgroundSidebarOverlay.b).toEqual(1) +}) diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index 26913222e894..f3887039e002 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -38,6 +38,8 @@ function themeCurrent(): HostPluginApi["theme"]["current"] { backgroundPanel: h, backgroundElement: i, backgroundMenu: i, + backgroundDialogOverlay: RGBA.fromInts(0, 0, 0, 150), + backgroundSidebarOverlay: RGBA.fromInts(0, 0, 0, 70), border: j, borderActive: c, borderSubtle: i, diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index 1c57a71ab315..4824d42b6e8f 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -203,6 +203,8 @@ export type TuiThemeCurrent = { readonly backgroundPanel: RGBA readonly backgroundElement: RGBA readonly backgroundMenu: RGBA + readonly backgroundDialogOverlay: RGBA + readonly backgroundSidebarOverlay: RGBA readonly border: RGBA readonly borderActive: RGBA readonly borderSubtle: RGBA