From 4ffc3c9db41707c9dfa2fe988e71a77d9da464b9 Mon Sep 17 00:00:00 2001 From: Lexlian Date: Mon, 25 May 2026 23:09:17 +0800 Subject: [PATCH] fix(tui): stop writing kv.json on every theme preview keystroke Add theme.preview() that changes the active theme without persisting to kv.json. Use preview() for cursor movement and filter changes in the theme dialog, set() only on confirm. Cancel restores via preview() instead of set(), avoiding unnecessary writes on every keystroke (#28893). --- .../src/cli/cmd/tui/component/dialog-theme-list.tsx | 8 ++++---- packages/opencode/src/cli/cmd/tui/context/theme.tsx | 5 +++++ packages/opencode/test/fixture/tui-plugin.ts | 11 +++++++++++ packages/plugin/src/tui.ts | 1 + 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx index 6cf3539ad947..89db62373a47 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx @@ -17,7 +17,7 @@ export function DialogThemeList() { const initial = theme.selected onCleanup(() => { - if (!confirmed) theme.set(initial) + if (!confirmed) theme.preview(initial) }) return ( @@ -26,7 +26,7 @@ export function DialogThemeList() { options={options} current={initial} onMove={(opt) => { - theme.set(opt.value) + theme.preview(opt.value) }} onSelect={(opt) => { theme.set(opt.value) @@ -38,12 +38,12 @@ export function DialogThemeList() { }} onFilter={(query) => { if (query.length === 0) { - theme.set(initial) + theme.preview(initial) return } const first = ref.filtered[0] - if (first) theme.set(first.value) + if (first) theme.preview(first.value) }} /> ) diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 33dfd3056db6..63546e1a1fb9 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -478,6 +478,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ kv.set("theme", theme) return true }, + preview(theme: string) { + if (!hasTheme(theme)) return false + setStore("active", theme) + return true + }, get ready() { return store.ready }, diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index ef18801571f0..b4d46c7b7bc6 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -109,6 +109,7 @@ type Opts = { selected?: string has?: HostPluginApi["theme"]["has"] set?: HostPluginApi["theme"]["set"] + preview?: HostPluginApi["theme"]["preview"] install?: HostPluginApi["theme"]["install"] mode?: HostPluginApi["theme"]["mode"] ready?: boolean @@ -149,6 +150,13 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { selected = name return true }) + const preview = + opts.theme?.preview ?? + ((name: string) => { + if (!has(name)) return false + selected = name + return true + }) const renderer: CliRenderer = opts.renderer ?? { ...Object.create(null), once(this: CliRenderer) { @@ -339,6 +347,9 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { set(name) { return set(name) }, + preview(name) { + return preview(name) + }, async install(file) { if (opts.theme?.install) return opts.theme.install(file) throw new Error("base theme.install should not run") diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index e36d91381d5d..5b83bbb791cc 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -361,6 +361,7 @@ export type TuiTheme = { readonly selected: string has: (name: string) => boolean set: (name: string) => boolean + preview: (name: string) => boolean install: (jsonPath: string) => Promise mode: () => "dark" | "light" readonly ready: boolean