diff --git a/packages/cli/test/config.test.ts b/packages/cli/test/config.test.ts index 3c3dd45721..a337682666 100644 --- a/packages/cli/test/config.test.ts +++ b/packages/cli/test/config.test.ts @@ -35,9 +35,23 @@ test("migrates tui and kv config into cli.json", async () => { path.join(directory, "kv.json"), JSON.stringify({ theme_mode_lock: "light", + attention_sound_pack: "custom.pack", + diff_wrap_mode: "none", + diff_viewer_show_file_tree: false, + diff_viewer_single_patch: true, + diff_viewer_view: "split", + terminal_title_enabled: false, + file_context_enabled: false, paste_summary_enabled: false, + sidebar: "hide", + scrollbar_visible: true, + thinking_mode: "show", exploration_grouping: false, tips_hidden: true, + dismissed_getting_started: true, + animations_enabled: false, + skipped_version: "9.9.9", + which_key_layout: "overlay", }), ) @@ -56,12 +70,17 @@ test("migrates tui and kv config into cli.json", async () => { plugins: [{ package: "example", options: { mode: "safe" } }, "-disabled"], leader: { timeout: 500 }, scroll: { speed: 2, acceleration: true }, - diffs: { view: "unified" }, - prompt: { paste: "full" }, - session: { grouping: "none" }, - hints: { tips: false }, + attention: { sound_pack: "custom.pack" }, + diffs: { wrap: "none", tree: false, single: true, view: "split" }, + terminal: { title: false }, + prompt: { editor: false, paste: "full" }, + session: { sidebar: "hide", scrollbar: true, thinking: "show", grouping: "none" }, + hints: { tips: false, onboarding: false }, + animations: false, mouse: false, }) + expect(config).not.toHaveProperty("skipped_version") + expect(config).not.toHaveProperty("which_key") expect((await Bun.file(path.join(directory, "cli.json")).json()).keybinds).toEqual({ leader: "ctrl+o" }) expect(await Bun.file(path.join(directory, "cli.json")).exists()).toBe(true) expect(await Bun.file(path.join(directory, "tui.json")).exists()).toBe(true) diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index 1f6ae64293..3436ebb77c 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -358,12 +358,6 @@ export type TuiTheme = { readonly ready: boolean } -export type TuiKV = { - get: (key: string, fallback?: Value) => Value - set: (key: string, value: unknown) => void - readonly ready: boolean -} - export type TuiState = { readonly ready: boolean readonly config: SdkConfig @@ -434,6 +428,7 @@ type TuiConfigView = { sidebar?: "auto" | "hide" scrollbar?: boolean thinking?: "show" | "hide" + markdown?: "source" | "rendered" grouping?: "auto" | "none" } hints?: { tips?: boolean; onboarding?: boolean } @@ -628,7 +623,6 @@ export type TuiPluginApi = { dialog: TuiDialogStack } readonly tuiConfig: Frozen - kv: TuiKV state: TuiState theme: TuiTheme client: OpencodeClient diff --git a/packages/tui/package.json b/packages/tui/package.json index 8b445f6bd0..ad7934b633 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -19,7 +19,6 @@ "./context/args": "./src/context/args.tsx", "./context/epilogue": "./src/context/epilogue.tsx", "./context/exit": "./src/context/exit.tsx", - "./context/kv": "./src/context/kv.tsx", "./context/log": "./src/context/log.tsx", "./context/project": "./src/context/project.tsx", "./context/runtime": "./src/context/runtime.tsx", diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 7507c9208e..0718090e10 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -71,7 +71,6 @@ import { DialogAlert } from "./ui/dialog-alert" import { DialogConfirm } from "./ui/dialog-confirm" import { ToastProvider, useToast } from "./ui/toast" import { isDefaultTitle } from "./util/session" -import { KVProvider, useKV } from "./context/kv" import * as Model from "./util/model" import { ArgsProvider, useArgs, type Args } from "./context/args" import open from "open" @@ -181,23 +180,6 @@ function errorMessage(error: unknown) { return error instanceof Error ? error.message : String(error) } -function isVersionGreater(left: string, right: string) { - const parse = (value: string) => { - const [core, prerelease] = value.replace(/^v/, "").split("-", 2) - return { core: core.split(".").map((part) => Number.parseInt(part, 10) || 0), prerelease } - } - const a = parse(left) - const b = parse(right) - for (let index = 0; index < Math.max(a.core.length, b.core.length); index++) { - const difference = (a.core[index] ?? 0) - (b.core[index] ?? 0) - if (difference) return difference > 0 - } - if (a.prerelease === b.prerelease) return false - if (!a.prerelease) return true - if (!b.prerelease) return false - return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true }) > 0 -} - export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { const log = input.log ?? (() => {}) const global = yield* Global.Service @@ -343,67 +325,65 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { service={input.config} options={{ terminalSuspend: process.platform !== "win32" }} > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -440,13 +420,13 @@ function App(props: { }) { const log = useLog({ component: "app" }) const startup = useTuiStartup() - const config = useConfig().data + const configState = useConfig() + const config = configState.data const route = useRoute() const dimensions = useTerminalDimensions() const renderer = useRenderer() const dialog = useDialog() const local = useLocal() - const kv = useKV() const keymap = useOpencodeKeymap() const event = useEvent() const sdk = useSDK() @@ -459,7 +439,7 @@ function App(props: { const exit = useExit() const promptRef = usePromptRef() const pluginRuntime = usePluginRuntime() - const attention = createTuiAttention({ renderer, config, kv }) + const attention = createTuiAttention({ renderer, config, update: configState.update }) const clipboard = useClipboard() // Toast once when an MCP server enters a failed or needs-auth state so the user knows to act, @@ -496,7 +476,6 @@ function App(props: { tuiConfig: config, dialog, keymap, - kv, route, routes: pluginRuntime.routes, event, @@ -549,8 +528,8 @@ function App(props: { renderer.clearSelection() } - const [terminalTitleEnabled, setTerminalTitleEnabled] = kv.signal("terminal_title_enabled", true) - const [pasteSummaryEnabled, setPasteSummaryEnabled] = kv.signal("paste_summary_enabled", true) + const terminalTitleEnabled = () => config.terminal?.title ?? true + const pasteSummaryEnabled = () => config.prompt?.paste !== "full" createEffect(() => { renderer.useMouse = !Flag.OPENCODE_DISABLE_MOUSE && config.mouse @@ -880,6 +859,7 @@ function App(props: { { name: "theme.switch_mode", title: mode() === "dark" ? "Switch to light mode" : "Switch to dark mode", + hidden: true, run: () => { setMode(mode() === "dark" ? "light" : "dark") dialog.clear() @@ -889,6 +869,7 @@ function App(props: { { name: "theme.mode.lock", title: locked() ? "Unlock theme mode" : "Lock theme mode", + hidden: true, run: () => { if (locked()) unlock() else lock() @@ -956,41 +937,57 @@ function App(props: { name: "terminal.title.toggle", title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title", category: "System", + hidden: true, run: () => { - setTerminalTitleEnabled((prev) => { - const next = !prev - kv.set("terminal_title_enabled", next) - if (!next) renderer.setTerminalTitle("") - return next - }) + const next = !terminalTitleEnabled() + if (!next) renderer.setTerminalTitle("") + void configState + .update((draft) => { + draft.terminal = { ...draft.terminal, title: next } + }) + .catch(toast.error) dialog.clear() }, }, { name: "app.toggle.animations", - title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations", + title: (config.animations ?? true) ? "Disable animations" : "Enable animations", category: "System", + hidden: true, run: () => { - kv.set("animations_enabled", !kv.get("animations_enabled", true)) + void configState + .update((draft) => { + draft.animations = !(config.animations ?? true) + }) + .catch(toast.error) dialog.clear() }, }, { name: "app.toggle.file_context", - title: kv.get("file_context_enabled", true) ? "Disable file context" : "Enable file context", + title: (config.prompt?.editor ?? true) ? "Disable file context" : "Enable file context", category: "System", + hidden: true, run: () => { - kv.set("file_context_enabled", !kv.get("file_context_enabled", true)) + void configState + .update((draft) => { + draft.prompt = { ...draft.prompt, editor: !(config.prompt?.editor ?? true) } + }) + .catch(toast.error) dialog.clear() }, }, { name: "app.toggle.diffwrap", - title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping", + title: (config.diffs?.wrap ?? "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping", category: "System", + hidden: true, run: () => { - const current = kv.get("diff_wrap_mode", "word") - kv.set("diff_wrap_mode", current === "word" ? "none" : "word") + void configState + .update((draft) => { + draft.diffs = { ...draft.diffs, wrap: (config.diffs?.wrap ?? "word") === "word" ? "none" : "word" } + }) + .catch(toast.error) dialog.clear() }, }, @@ -998,12 +995,13 @@ function App(props: { name: "app.toggle.paste_summary", title: pasteSummaryEnabled() ? "Disable paste summary" : "Enable paste summary", category: "System", + hidden: true, run: () => { - setPasteSummaryEnabled((prev) => { - const next = !prev - kv.set("paste_summary_enabled", next) - return next - }) + void configState + .update((draft) => { + draft.prompt = { ...draft.prompt, paste: pasteSummaryEnabled() ? "full" : "compact" } + }) + .catch(toast.error) dialog.clear() }, }, @@ -1101,21 +1099,13 @@ function App(props: { event.on("installation.update-available", async (evt) => { const version = evt.data.version - const skipped = kv.get("skipped_version") - if (skipped && !isVersionGreater(version, skipped)) return - const choice = await DialogConfirm.show( dialog, `Update Available`, `A new release v${version} is available. Would you like to update now?`, - "skip", + "later", ) - if (choice === false) { - kv.set("skipped_version", version) - return - } - if (choice !== true) return toast.show({ diff --git a/packages/tui/src/attention.ts b/packages/tui/src/attention.ts index 948872a26b..8fb119ca11 100644 --- a/packages/tui/src/attention.ts +++ b/packages/tui/src/attention.ts @@ -5,7 +5,6 @@ import type { TuiAttentionNotifyResult, TuiAttentionNotifySkipReason, TuiAttentionWhen, - TuiKV, TuiAttentionSoundName, TuiAttentionSoundPack, TuiAttentionSoundPackInfo, @@ -40,7 +39,6 @@ type TuiAttentionHost = TuiAttention & { const DEFAULT_TITLE = "opencode" const DEFAULT_PACK_ID = "opencode.default" -const KV_SOUND_PACK = "attention_sound_pack" const TITLE_LIMIT = 80 const MESSAGE_LIMIT = 240 const BUILTIN_PACK: RegisteredSoundPack = { @@ -114,7 +112,7 @@ function focusSkip(when: TuiAttentionWhen, focus: FocusState) { export function createTuiAttention(input: { renderer: AttentionRenderer config: Pick - kv?: TuiKV + update?: Config.Interface["update"] audio?: Pick }): TuiAttentionHost { let focus: FocusState = "unknown" @@ -134,8 +132,7 @@ export function createTuiAttention(input: { input.renderer.on("blur", onBlur) function configuredPackID() { - const stored = input.kv?.get(KV_SOUND_PACK, undefined) - return activePackID ?? stored ?? input.config.attention.sound_pack + return activePackID ?? input.config.attention.sound_pack } function currentPack() { @@ -234,7 +231,12 @@ export function createTuiAttention(input: { const pack = packs.get(id) if (!pack) return false activePackID = pack.id - if (options?.persist) input.kv?.set(KV_SOUND_PACK, pack.id) + if (options?.persist) + void input + .update?.((draft) => { + draft.attention = { ...draft.attention, sound_pack: pack.id } + }) + .catch(() => {}) return true }, current() { diff --git a/packages/tui/src/component/dialog-config.tsx b/packages/tui/src/component/dialog-config.tsx index f4318695e2..7e964d3437 100644 --- a/packages/tui/src/component/dialog-config.tsx +++ b/packages/tui/src/component/dialog-config.tsx @@ -77,6 +77,13 @@ const settings: Setting[] = [ default: "hide", values: ["hide", "show"], }, + { + title: "Markdown", + category: "Session", + path: ["session", "markdown"], + default: "rendered", + values: ["source", "rendered"], + }, { title: "Grouping", category: "Session", diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 02af67b8fb..e9a22dff0b 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -43,7 +43,6 @@ import { useDialog } from "../../ui/dialog" import { DialogIntegration } from "../dialog-integration" import { useConnected } from "../use-connected" import { useToast } from "../../ui/toast" -import { useKV } from "../../context/kv" import { createFadeIn } from "../../util/signal" import { DialogSkill } from "../dialog-skill" import { useArgs } from "../../context/args" @@ -177,11 +176,10 @@ export function Prompt(props: PromptProps) { const exit = useExit() const dimensions = useTerminalDimensions() const { theme, syntax } = useTheme() - const kv = useKV() - const animationsEnabled = createMemo(() => kv.get("animations_enabled", true)) + const animationsEnabled = createMemo(() => config.animations ?? true) const list = createMemo(() => props.placeholders?.normal ?? []) const shell = createMemo(() => props.placeholders?.shell ?? []) - const fileContextEnabled = createMemo(() => kv.get("file_context_enabled", true)) + const fileContextEnabled = createMemo(() => config.prompt?.editor ?? true) const [dismissedEditorSelectionKey, setDismissedEditorSelectionKey] = createSignal() const editorContext = createMemo(() => { const selection = fileContextEnabled() ? editor.selection() : undefined @@ -1191,7 +1189,7 @@ export function Prompt(props: PromptProps) { const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1 if ( (lineCount >= 3 || pastedContent.length > 150) && - kv.get("paste_summary_enabled", true) + config.prompt?.paste !== "full" ) { pasteText(pastedContent, `[Pasted ~${lineCount} lines]`) return @@ -1495,7 +1493,7 @@ export function Prompt(props: PromptProps) { - [⋯]}> + [⋯]}> diff --git a/packages/tui/src/component/spinner.tsx b/packages/tui/src/component/spinner.tsx index f648a605a6..5ba9bc3133 100644 --- a/packages/tui/src/component/spinner.tsx +++ b/packages/tui/src/component/spinner.tsx @@ -1,6 +1,6 @@ import { Show } from "solid-js" import { useTheme } from "../context/theme" -import { useKV } from "../context/kv" +import { useConfig } from "../config" import type { JSX } from "@opentui/solid" import type { RGBA } from "@opentui/core" import { registerOpencodeSpinner } from "./register-spinner" @@ -11,10 +11,10 @@ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", export function Spinner(props: { children?: JSX.Element; color?: RGBA }) { const { theme } = useTheme() - const kv = useKV() + const config = useConfig().data const color = () => props.color ?? theme.textMuted return ( - ⋯ {props.children}}> + ⋯ {props.children}}> diff --git a/packages/tui/src/config/index.tsx b/packages/tui/src/config/index.tsx index 8c6d9c0a85..ea75ef0699 100644 --- a/packages/tui/src/config/index.tsx +++ b/packages/tui/src/config/index.tsx @@ -118,6 +118,9 @@ export const Info = Schema.Struct({ grouping: Schema.optional(Schema.Literals(["auto", "none"])).annotate({ description: "Group related transcript items automatically or render each item separately", }), + markdown: Schema.optional(Schema.Literals(["source", "rendered"])).annotate({ + description: "Show Markdown syntax markers or conceal them in rendered transcript content", + }), }), ).annotate({ description: "Session transcript presentation settings" }), hints: Schema.optional( diff --git a/packages/tui/src/context/kv.tsx b/packages/tui/src/context/kv.tsx deleted file mode 100644 index 95c088f194..0000000000 --- a/packages/tui/src/context/kv.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { createEffect, createSignal, type Setter } from "solid-js" -import { createStore, unwrap } from "solid-js/store" -import { createSimpleContext } from "./helper" -import { Flock } from "@opencode-ai/core/util/flock" -import { Global } from "@opencode-ai/core/global" -import { readJson, writeJsonAtomic } from "../util/persistence" -import { useTuiPaths } from "./runtime" -import path from "path" -import { useConfigOptional, type Config } from "../config" - -export const { use: useKV, provider: KVProvider } = createSimpleContext({ - name: "KV", - init: (props: { config?: Config.Info }) => { - const config = props.config ?? useConfigOptional()?.data - const paths = useTuiPaths() - void Global.Path.state - const file = path.join(paths.state, "kv.json") - const lock = `tui-kv:${file}` - const [ready, setReady] = createSignal(false) - const [store, setStore] = createStore>() - // Queue same-process writes so rapid updates persist in order. - let write = Promise.resolve() - - Flock.withLock(lock, () => readJson>(file)) - .then((x) => { - const values: Record = { ...x } - Object.entries(configValues(config ?? {})).forEach(([key, value]) => { - if (value === undefined) delete values[key] - else values[key] = value - }) - setStore(values) - }) - .catch((error) => { - console.error("Failed to read KV state", { error }) - }) - .finally(() => { - setReady(true) - }) - - createEffect(() => { - if (!ready() || !config) return - Object.entries(configValues(config)).forEach(([key, value]) => { - if (value === undefined) setStore(key, undefined) - else setStore(key, value) - }) - }) - - const result = { - get ready() { - return ready() - }, - get store() { - return store - }, - signal(name: string, defaultValue: T) { - if (store[name] === undefined) setStore(name, defaultValue) - return [ - function () { - return result.get(name) - }, - function setter(next: Setter) { - result.set(name, next) - }, - ] as const - }, - get(key: string, defaultValue?: any) { - return store[key] ?? defaultValue - }, - set(key: string, value: any) { - setStore(key, value) - const snapshot = structuredClone(unwrap(store)) - write = write - .then(() => Flock.withLock(lock, () => writeJsonAtomic(file, snapshot))) - .catch((error) => { - console.error("Failed to write KV state", { error }) - }) - }, - } - return result - }, -}) - -function configValues(config: Config.Info) { - const values: Record = {} - if (config.theme?.name !== undefined) values.theme = config.theme.name - if (config.theme?.mode !== undefined) { - values.theme_mode_lock = config.theme.mode === "system" ? undefined : config.theme.mode - values.theme_mode = undefined - } - if (config.attention?.sound_pack !== undefined) values.attention_sound_pack = config.attention.sound_pack - if (config.diffs?.wrap !== undefined) values.diff_wrap_mode = config.diffs.wrap - if (config.diffs?.tree !== undefined) values.diff_viewer_show_file_tree = config.diffs.tree - if (config.diffs?.single !== undefined) values.diff_viewer_single_patch = config.diffs.single - if (config.diffs?.view !== undefined) - values.diff_viewer_view = config.diffs.view === "auto" ? undefined : config.diffs.view - if (config.terminal?.title !== undefined) values.terminal_title_enabled = config.terminal.title - if (config.prompt?.editor !== undefined) values.file_context_enabled = config.prompt.editor - if (config.prompt?.paste !== undefined) values.paste_summary_enabled = config.prompt.paste === "compact" - if (config.session?.sidebar !== undefined) values.sidebar = config.session.sidebar - if (config.session?.scrollbar !== undefined) values.scrollbar_visible = config.session.scrollbar - if (config.session?.thinking !== undefined) values.thinking_mode = config.session.thinking - if (config.session?.grouping !== undefined) values.exploration_grouping = config.session.grouping === "auto" - if (config.hints?.tips !== undefined) values.tips_hidden = !config.hints.tips - if (config.hints?.onboarding !== undefined) values.dismissed_getting_started = !config.hints.onboarding - if (config.animations !== undefined) values.animations_enabled = config.animations - return values -} diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx index a2c180feac..8c4ab4de9f 100644 --- a/packages/tui/src/context/theme.tsx +++ b/packages/tui/src/context/theme.tsx @@ -22,7 +22,6 @@ import { import { createEffect, createMemo, onCleanup, onMount } from "solid-js" import { createStore, produce } from "solid-js/store" import { createSimpleContext } from "./helper" -import { useKV } from "./kv" import { useConfig } from "../config" import { Global } from "@opencode-ai/core/global" import { Glob } from "@opencode-ai/core/util/glob" @@ -103,8 +102,8 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ name: "Theme", init: (props: { mode: "dark" | "light"; source?: ThemeSource }) => { const renderer = useRenderer() - const config = useConfig().data - const kv = useKV() + const configState = useConfig() + const config = configState.data const themes = props.source ?? themeSource const pick = (value: unknown) => { if (value === "dark" || value === "light") return value @@ -113,12 +112,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ setStore( produce((draft) => { - const lock = pick(kv.get("theme_mode_lock")) + const lock = pick(config.theme?.mode) const mode = lock ?? pick(renderer.themeMode) ?? props.mode - if (!lock && pick(kv.get("theme_mode")) !== undefined) kv.set("theme_mode", undefined) draft.mode = mode draft.lock = lock - const active = config.theme?.name ?? kv.get("theme", "opencode") + const active = config.theme?.name ?? "opencode" draft.active = typeof active === "string" ? active : "opencode" draft.ready = false }), @@ -132,10 +130,10 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ createEffect(() => { const mode = config.theme?.mode if (mode === "dark" || mode === "light") { - pin(mode) + pin(mode, false) return } - if (mode === "system" && store.lock !== undefined) free() + if (mode === "system" && store.lock !== undefined) free(false) }) function syncCustomThemes() { @@ -209,23 +207,31 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ } function apply(mode: "dark" | "light") { - if (store.lock !== undefined) kv.set("theme_mode", mode) if (store.mode === mode) return setStore("mode", mode) refreshSystemTheme(mode) } - function pin(mode: "dark" | "light" = store.mode) { + function pin(mode: "dark" | "light" = store.mode, persist = true) { setStore("lock", mode) - kv.set("theme_mode_lock", mode) apply(mode) + if (!persist) return + void configState + .update((draft) => { + draft.theme = { ...draft.theme, mode } + }) + .catch(() => {}) } - function free() { + function free(persist = true) { setStore("lock", undefined) - kv.set("theme_mode_lock", undefined) - kv.set("theme_mode", undefined) refreshSystemTheme(renderer.themeMode ?? store.mode) + if (!persist) return + void configState + .update((draft) => { + draft.theme = { ...draft.theme, mode: "system" } + }) + .catch(() => {}) } const handle = (mode: "dark" | "light") => { @@ -265,13 +271,6 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ const values = createMemo(() => { const active = store.themes[store.active] if (active) return resolveTheme(active, store.mode) - - const saved = kv.get("theme") - if (typeof saved === "string") { - const theme = store.themes[saved] - if (theme) return resolveTheme(theme, store.mode) - } - return resolveTheme(store.themes.opencode, store.mode) }) @@ -302,7 +301,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ set(theme: string) { if (!hasTheme(theme)) return false setStore("active", theme) - kv.set("theme", theme) + void configState + .update((draft) => { + draft.theme = { ...draft.theme, name: theme } + }) + .catch(() => {}) return true }, get ready() { diff --git a/packages/tui/src/context/thinking.ts b/packages/tui/src/context/thinking.ts index bb1c2a6929..3d630fb62a 100644 --- a/packages/tui/src/context/thinking.ts +++ b/packages/tui/src/context/thinking.ts @@ -1,6 +1,3 @@ -import { createMemo, type Setter } from "solid-js" -import { useKV } from "./kv" - export type ThinkingMode = "show" | "hide" const MODES: readonly ThinkingMode[] = ["show", "hide"] as const @@ -16,52 +13,8 @@ export function reasoningSummary(text: string) { return { title: match[1].trim(), body: content.slice(match[0].length).trimEnd() } } -export function isThinkingMode(value: unknown): value is ThinkingMode { - return typeof value === "string" && (MODES as readonly string[]).includes(value) -} - // Cycle order matches the slash command: show → hide → show. export function nextThinkingMode(current: ThinkingMode): ThinkingMode { const idx = MODES.indexOf(current) return MODES[(idx + 1) % MODES.length] ?? "show" } - -export function useThinkingMode() { - const kv = useKV() - // Capture pre-state before `kv.signal` seeds a default, so we can detect - // first-time users with a legacy `thinking_visibility` boolean and migrate. - // The KVProvider only renders children once kv.ready, so reads here are safe. - const hadStored = kv.get("thinking_mode") !== undefined - const legacy = kv.get("thinking_visibility") - const [stored, setStored] = kv.signal("thinking_mode", "hide") - - // The kv signal exposes its setter typed as `Setter` which carries Solid's - // overload set; passing an updater fn through a property access loses the - // bivariance trick the existing `setX((prev) => ...)` callsites rely on. - // Wrap it in a sane shape so consumers can just call `set(next)` or pass - // an updater. - const set = (next: ThinkingMode | ((prev: ThinkingMode) => ThinkingMode)) => { - if (typeof next === "function") setStored(next as Setter) - else setStored(() => next) - } - - // Preserve previous experience for users who had explicitly toggled the - // legacy `thinking_visibility` boolean. First-time users (no legacy key) - // get the new "hide" default (collapsed thinking). - if (!hadStored) { - if (legacy === true) set("show") - else if (legacy === false) set("hide") - } - - if ((stored() as string) === "minimal") set("hide") - - const mode = createMemo(() => { - const value = stored() - return isThinkingMode(value) ? value : "hide" - }) - - return { - mode, - set, - } -} diff --git a/packages/tui/src/feature-plugins/home/tips.tsx b/packages/tui/src/feature-plugins/home/tips.tsx index 7b67d4aac5..2c516b8f32 100644 --- a/packages/tui/src/feature-plugins/home/tips.tsx +++ b/packages/tui/src/feature-plugins/home/tips.tsx @@ -5,10 +5,12 @@ import { Tips } from "./tips-view" import { useBindings } from "../../keymap" import { useData } from "../../context/data" import { hasConnectedProvider } from "../../util/connected-provider" +import { useConfig } from "../../config" const id = "internal:home-tips" function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connected: boolean }) { + const config = useConfig() useBindings(() => ({ commands: [ { @@ -16,8 +18,13 @@ function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connec title: props.hidden ? "Show tips" : "Hide tips", category: "System", namespace: "palette", + hidden: true, run() { - props.api.kv.set("tips_hidden", !props.api.kv.get("tips_hidden", false)) + void config + .update((draft) => { + draft.hints = { ...draft.hints, tips: props.hidden } + }) + .catch(() => {}) props.api.ui.dialog.clear() }, }, @@ -40,7 +47,8 @@ const tui: TuiPlugin = async (api) => { slots: { home_bottom() { const data = useData() - const hidden = createMemo(() => api.kv.get("tips_hidden", false)) + const config = useConfig().data + const hidden = createMemo(() => !(config.hints?.tips ?? true)) const first = createMemo(() => api.state.session.count() === 0) const connected = createMemo(() => hasConnectedProvider(data.location.integration.list() ?? [])) const show = createMemo(() => (!first() || !connected()) && !hidden()) diff --git a/packages/tui/src/feature-plugins/sidebar/footer.tsx b/packages/tui/src/feature-plugins/sidebar/footer.tsx index b0fb681153..2cb81e4cc6 100644 --- a/packages/tui/src/feature-plugins/sidebar/footer.tsx +++ b/packages/tui/src/feature-plugins/sidebar/footer.tsx @@ -4,18 +4,20 @@ import { createMemo, Show } from "solid-js" import { abbreviateHome } from "../../runtime" import { useTuiPaths } from "../../context/runtime" import { FilePath } from "../../ui/file-path" +import { useConfig } from "../../config" const id = "internal:sidebar-footer" function View(props: { api: TuiPluginApi; directory: string }) { const paths = useTuiPaths() + const config = useConfig() const theme = () => props.api.theme.current const has = createMemo(() => props.api.state.provider.some( (item) => item.id !== "opencode" || Object.values(item.models).some((model) => model.cost?.input !== 0), ), ) - const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false)) + const done = createMemo(() => !(config.data.hints?.onboarding ?? true)) const show = createMemo(() => !has() && !done()) const location = createMemo(() => { const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined @@ -44,7 +46,16 @@ function View(props: { api: TuiPluginApi; directory: string }) { Getting started - props.api.kv.set("dismissed_getting_started", true)}> + + void config + .update((draft) => { + draft.hints = { ...draft.hints, onboarding: false } + }) + .catch(() => {}) + } + > ✕ diff --git a/packages/tui/src/feature-plugins/system/diff-viewer.tsx b/packages/tui/src/feature-plugins/system/diff-viewer.tsx index 68be440dcb..6444a86950 100644 --- a/packages/tui/src/feature-plugins/system/diff-viewer.tsx +++ b/packages/tui/src/feature-plugins/system/diff-viewer.tsx @@ -19,6 +19,7 @@ import { DiffViewerFileTree } from "./diff-viewer-file-tree" import { Panel, PanelGroup, Separator } from "./diff-viewer-ui" import { DialogSelect } from "../../ui/dialog-select" import { getScrollAcceleration } from "../../util/scroll" +import { useConfig } from "../../config" import { allExpandedFileTreeDirectories, buildFileTree, @@ -41,9 +42,6 @@ const MIN_SPLIT_WIDTH = 100 const FILE_TREE_WIDTH = 32 const PLAIN_TEXT_FILETYPE = "opencode-plain-text" const VCS_DIFF_CONTEXT_LINES = 12 -const KV_SHOW_FILE_TREE = "diff_viewer_show_file_tree" -const KV_SINGLE_PATCH = "diff_viewer_single_patch" -const KV_VIEW = "diff_viewer_view" type DiffMode = "working" | "branch" | "last-turn" type DiffViewerFocus = "patches" | "files" type DiffView = "split" | "unified" @@ -92,6 +90,7 @@ function diffSourceLabel(mode: DiffMode) { function DiffViewer(props: { api: TuiPluginApi }) { const dimensions = useTerminalDimensions() const sdk = useSDK() + const config = useConfig() const themeState = useTheme() const theme = () => props.api.theme.current const params = () => @@ -135,11 +134,9 @@ function DiffViewer(props: { api: TuiPluginApi }) { }) const files = createMemo(() => diff() ?? []) const [focus, setFocus] = createSignal("patches") - const [fileTreeEnabled, setFileTreeEnabled] = createSignal( - props.api.kv.get(KV_SHOW_FILE_TREE, true) !== false, - ) + const [fileTreeEnabled, setFileTreeEnabled] = createSignal(config.data.diffs?.tree ?? true) const showFileTree = createMemo(() => showDiffViewerFileTree(fileTreeEnabled(), files().length)) - const [singlePatch, setSinglePatch] = createSignal(props.api.kv.get(KV_SINGLE_PATCH, false) === true) + const [singlePatch, setSinglePatch] = createSignal(config.data.diffs?.single ?? false) const patchPaneWidth = createMemo(() => dimensions().width - (showFileTree() ? 33 : 0) - 4) const patchLeftBorder = createMemo(() => (showFileTree() ? ["left"] : [])) const splitAvailable = createMemo(() => patchPaneWidth() >= MIN_SPLIT_WIDTH) @@ -148,7 +145,7 @@ function DiffViewer(props: { api: TuiPluginApi }) { if (props.api.tuiConfig.diffs?.view === "split") return "split" return splitAvailable() ? "split" : "unified" }) - const [viewOverride, setViewOverride] = createSignal(storedView(props.api.kv.get(KV_VIEW))) + const [viewOverride, setViewOverride] = createSignal(storedView(config.data.diffs?.view)) const view = createMemo(() => (splitAvailable() ? (viewOverride() ?? defaultView()) : "unified")) const fileTree = createMemo(() => buildFileTree(files())) const [expandedFileNodes, setExpandedFileNodes] = createSignal>(new Set()) @@ -623,23 +620,33 @@ function DiffViewer(props: { api: TuiPluginApi }) { name: "diff.toggle_file_tree", title: "Toggle diff viewer file tree", category: "VCS", + hidden: true, run() { const next = !fileTreeEnabled() if (!next) setFocus("patches") setFileTreeEnabled(next) - props.api.kv.set(KV_SHOW_FILE_TREE, next) + void config + .update((draft) => { + draft.diffs = { ...draft.diffs, tree: next } + }) + .catch(() => {}) }, }, { name: "diff.single_patch", title: "Toggle single patch view", category: "VCS", + hidden: true, run() { setSelectedHunk(undefined) if (!singlePatch()) { ensureHighlightedPatchFile() setSinglePatch(true) - props.api.kv.set(KV_SINGLE_PATCH, true) + void config + .update((draft) => { + draft.diffs = { ...draft.diffs, single: true } + }) + .catch(() => {}) scrollSinglePatchToTop() return } @@ -653,7 +660,11 @@ function DiffViewer(props: { api: TuiPluginApi }) { ) if (fileIndex !== undefined) selectPatchFile(fileIndex) setSinglePatch(false) - props.api.kv.set(KV_SINGLE_PATCH, false) + void config + .update((draft) => { + draft.diffs = { ...draft.diffs, single: false } + }) + .catch(() => {}) if (fileIndex !== undefined) scrollToPatchFileIndexAfterRender(fileIndex) }, }, @@ -669,12 +680,17 @@ function DiffViewer(props: { api: TuiPluginApi }) { name: "diff.toggle_view", title: "Toggle diff viewer split or unified view", category: "VCS", + hidden: true, run() { if (!splitAvailable()) return setSelectedHunk(undefined) const next = view() === "split" ? "unified" : "split" setViewOverride(next) - props.api.kv.set(KV_VIEW, next) + void config + .update((draft) => { + draft.diffs = { ...draft.diffs, view: next } + }) + .catch(() => {}) }, }, { diff --git a/packages/tui/src/feature-plugins/system/which-key.tsx b/packages/tui/src/feature-plugins/system/which-key.tsx index d16dfc28d2..248f49ff29 100644 --- a/packages/tui/src/feature-plugins/system/which-key.tsx +++ b/packages/tui/src/feature-plugins/system/which-key.tsx @@ -22,8 +22,6 @@ const command = { } as const const LAYER_PRIORITY = 900 -const KV_LAYOUT = "which_key_layout" -const KV_PENDING_PREVIEW = "which_key_pending_preview" const toggleCommands = [command.toggle, command.toggleLayout, command.togglePending] as const const scrollCommands = [ command.scrollUp, @@ -531,8 +529,8 @@ function WhichKeyPanel(props: { const tui: TuiPlugin = async (api) => { const [pinned, setPinned] = createSignal(false) - const [mode, setMode] = createSignal(layout(api.kv.get(KV_LAYOUT, "dock"))) - const [pendingPreview, setPendingPreview] = createSignal(api.kv.get(KV_PENDING_PREVIEW, false)) + const [mode, setMode] = createSignal(layout("dock")) + const [pendingPreview, setPendingPreview] = createSignal(false) api.keymap.registerLayer({ priority: LAYER_PRIORITY, @@ -554,7 +552,6 @@ const tui: TuiPlugin = async (api) => { run() { setMode((value) => { const next = value === "dock" ? "overlay" : "dock" - api.kv.set(KV_LAYOUT, next) return next }) }, @@ -566,7 +563,6 @@ const tui: TuiPlugin = async (api) => { category: "System", run() { setPendingPreview((value) => { - api.kv.set(KV_PENDING_PREVIEW, !value) return !value }) }, diff --git a/packages/tui/src/plugin/adapters.tsx b/packages/tui/src/plugin/adapters.tsx index f7a440c029..c0826ff33c 100644 --- a/packages/tui/src/plugin/adapters.tsx +++ b/packages/tui/src/plugin/adapters.tsx @@ -8,7 +8,6 @@ import type { useData } from "../context/data" import type { useTheme } from "../context/theme" import { Dialog as DialogUI, type useDialog } from "../ui/dialog" import type { useOpencodeKeymap } from "../keymap" -import type { useKV } from "../context/kv" import { DialogAlert } from "../ui/dialog-alert" import { DialogConfirm } from "../ui/dialog-confirm" import { DialogPrompt } from "../ui/dialog-prompt" @@ -26,7 +25,6 @@ type Input = { tuiConfig: Config.Resolved dialog: ReturnType keymap: ReturnType - kv: ReturnType route: ReturnType routes: PluginRoutes event: ReturnType @@ -291,17 +289,6 @@ export function createTuiApiAdapters(input: Input): Omit ThinkingMode showThinking: () => boolean + markdownMode: () => "source" | "rendered" groupExploration: () => boolean diffWrapMode: () => "word" | "none" models: () => ModelInfo[] @@ -147,8 +147,8 @@ export function Session() { const data = useData() const project = useProject() const paths = useTuiPaths() - const config = useConfig().data - const kv = useKV() + const configState = useConfig() + const config = configState.data const { theme } = useTheme() const promptRef = usePromptRef() const session = createMemo(() => data.session.get(route.sessionID)) @@ -194,15 +194,14 @@ export function Session() { }) const dimensions = useTerminalDimensions() - const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto") + const sidebar = createMemo(() => config.session?.sidebar ?? "auto") const [sidebarOpen, setSidebarOpen] = createSignal(false) - const thinking = useThinkingMode() - const thinkingMode = thinking.mode + const thinkingMode = createMemo(() => config.session?.thinking ?? "hide") const showThinking = createMemo(() => true) - const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) - const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") - const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true) - const [groupExploration, setGroupExploration] = kv.signal("exploration_grouping", true) + const showScrollbar = createMemo(() => config.session?.scrollbar ?? false) + const markdownMode = createMemo(() => config.session?.markdown ?? "rendered") + const diffWrapMode = createMemo(() => config.diffs?.wrap ?? "word") + const groupExploration = createMemo(() => config.session?.grouping !== "none") const wide = createMemo(() => dimensions().width > 120) const sidebarVisible = createMemo(() => { @@ -462,7 +461,11 @@ export function Session() { run: () => { batch(() => { const isVisible = sidebarVisible() - setSidebar(() => (isVisible ? "hide" : "auto")) + void configState + .update((draft) => { + draft.session = { ...draft.session, sidebar: isVisible ? "hide" : "auto" } + }) + .catch(toast.error) setSidebarOpen(!isVisible) }) dialog.clear() @@ -476,12 +479,17 @@ export function Session() { })(), value: "session.toggle.thinking", category: "Session", + hidden: true, slash: { name: "thinking", aliases: ["toggle-thinking"], }, run: () => { - thinking.set(nextThinkingMode(thinkingMode())) + void configState + .update((draft) => { + draft.session = { ...draft.session, thinking: nextThinkingMode(thinkingMode()) } + }) + .catch(toast.error) dialog.clear() }, }, @@ -489,8 +497,13 @@ export function Session() { title: "Toggle session scrollbar", value: "session.toggle.scrollbar", category: "Session", + hidden: true, run: () => { - setShowScrollbar((prev) => !prev) + void configState + .update((draft) => { + draft.session = { ...draft.session, scrollbar: !showScrollbar() } + }) + .catch(toast.error) dialog.clear() }, }, @@ -498,8 +511,13 @@ export function Session() { title: groupExploration() ? "Show tool calls individually" : "Group related tool calls", value: "session.toggle.exploration_grouping", category: "Session", + hidden: true, run: () => { - setGroupExploration((prev) => !prev) + void configState + .update((draft) => { + draft.session = { ...draft.session, grouping: groupExploration() ? "none" : "auto" } + }) + .catch(toast.error) dialog.clear() }, }, @@ -855,6 +873,7 @@ export function Session() { sessionID: route.sessionID, thinkingMode, showThinking, + markdownMode, groupExploration, diffWrapMode, models, @@ -1287,7 +1306,6 @@ function SessionSkillMessage(props: { message: Extract }) { const ctx = use() - const kv = useKV() const { theme, syntax } = useTheme() const status = () => props.message.status const text = () => (props.message.status === "failed" ? props.message.error.message : props.message.summary) @@ -1300,7 +1318,7 @@ function CompactionMessage(props: { message: Extract - ⋯}> + ⋯}> @@ -1320,7 +1338,7 @@ function CompactionMessage(props: { message: Extract @@ -1745,7 +1763,7 @@ function ReasoningPart(props: { streaming={true} syntaxStyle={syntax()} content={summary().body} - conceal={false} + conceal={ctx.markdownMode() === "rendered"} fg={theme.textMuted} /> @@ -1811,7 +1829,7 @@ function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) { internalBlockMode="top-level" content={props.part.text.trim()} tableOptions={{ style: "grid" }} - conceal={false} + conceal={ctx.markdownMode() === "rendered"} fg={theme.markdownText} bg={theme.background} /> diff --git a/packages/tui/test/cli/cmd/tui/sync-fixture.tsx b/packages/tui/test/cli/cmd/tui/sync-fixture.tsx index 5749113856..54cc029ecf 100644 --- a/packages/tui/test/cli/cmd/tui/sync-fixture.tsx +++ b/packages/tui/test/cli/cmd/tui/sync-fixture.tsx @@ -2,7 +2,6 @@ import { testRender } from "@opentui/solid" import { onMount } from "solid-js" import { ArgsProvider } from "../../../../src/context/args" -import { KVProvider, useKV } from "../../../../src/context/kv" import { ProjectProvider, useProject } from "../../../../src/context/project" import { SDKProvider } from "../../../../src/context/sdk" import { SyncProvider, useSync } from "../../../../src/context/sync" @@ -20,25 +19,23 @@ export async function wait(fn: () => boolean, timeout = 2000) { } } -type Ctx = { kv: ReturnType; project: ReturnType; sync: ReturnType } +type Ctx = { project: ReturnType; sync: ReturnType } export async function mount(override?: FetchHandler, state?: string) { const events = createEventStream() const calls = createFetch(override, events) let sync!: ReturnType let project!: ReturnType - let kv!: ReturnType let done!: () => void const ready = new Promise((resolve) => { done = resolve }) function Probe() { - const ctx: Ctx = { kv: useKV(), project: useProject(), sync: useSync() } + const ctx: Ctx = { project: useProject(), sync: useSync() } onMount(() => { sync = ctx.sync project = ctx.project - kv = ctx.kv done() }) return @@ -47,24 +44,22 @@ export async function mount(override?: FetchHandler, state?: string) { const app = await testRender(() => ( - - - - - {}}> - - - - - - - - + + + + {}}> + + + + + + + )) await ready await wait(() => sync.status === "complete") - return { app, emit: events.emit, kv, project, sync, session: calls.session } + return { app, emit: events.emit, project, sync, session: calls.session } } diff --git a/packages/tui/test/cli/tui/dialog-prompt.test.tsx b/packages/tui/test/cli/tui/dialog-prompt.test.tsx index b4b567b9e2..d648258746 100644 --- a/packages/tui/test/cli/tui/dialog-prompt.test.tsx +++ b/packages/tui/test/cli/tui/dialog-prompt.test.tsx @@ -26,12 +26,10 @@ async function mountPrompt(input: { }) { const state = path.join(input.root, "state") await mkdir(state, { recursive: true }) - await Bun.write(path.join(state, "kv.json"), "{}") const [ { DialogProvider }, { DialogPrompt }, - { KVProvider }, { ThemeProvider }, { ConfigProvider }, { ToastProvider }, @@ -39,7 +37,6 @@ async function mountPrompt(input: { ] = await Promise.all([ import("../../../src/ui/dialog"), import("../../../src/ui/dialog-prompt"), - import("../../../src/context/kv"), import("../../../src/context/theme"), import("../../../src/config"), import("../../../src/ui/toast"), @@ -67,15 +64,13 @@ async function mountPrompt(input: { > - - - - - - - - - + + + + + + + diff --git a/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx b/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx index 30ec95565c..6a7cb174a9 100644 --- a/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx +++ b/packages/tui/test/cli/tui/diff-viewer-file-tree.test.tsx @@ -4,7 +4,6 @@ import { RGBA } from "@opentui/core" import { testRender } from "@opentui/solid" import type { JSX } from "solid-js" import { createTuiResolvedConfig } from "../../fixture/tui-runtime" -import { KVProvider } from "../../../src/context/kv" import { ThemeProvider } from "../../../src/context/theme" import { ConfigProvider } from "../../../src/config" import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree" @@ -182,9 +181,7 @@ function withTheme(component: () => JSX.Element) { return ( - - {component()} - + {component()} ) diff --git a/packages/tui/test/cli/tui/diff-viewer.test.tsx b/packages/tui/test/cli/tui/diff-viewer.test.tsx index 36fff6f135..494837d9a0 100644 --- a/packages/tui/test/cli/tui/diff-viewer.test.tsx +++ b/packages/tui/test/cli/tui/diff-viewer.test.tsx @@ -5,7 +5,6 @@ import { DiffRenderable, type Renderable, ScrollBoxRenderable } from "@opentui/c import { testRender, useRenderer } from "@opentui/solid" import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui" import type { Session } from "@opencode-ai/sdk/v2" -import { KVProvider } from "../../../src/context/kv" import { ThemeProvider } from "../../../src/context/theme" import { ConfigProvider } from "../../../src/config" import { SDKProvider } from "../../../src/context/sdk" @@ -174,11 +173,9 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?: - - - {renderDiff?.({ params: "params" in current ? current.params : undefined })} - - + + {renderDiff?.({ params: "params" in current ? current.params : undefined })} + diff --git a/packages/tui/test/cli/tui/form.test.tsx b/packages/tui/test/cli/tui/form.test.tsx index 0a28ba8de3..b4ab1f57f3 100644 --- a/packages/tui/test/cli/tui/form.test.tsx +++ b/packages/tui/test/cli/tui/form.test.tsx @@ -7,7 +7,6 @@ import path from "node:path" import { onCleanup } from "solid-js" import { ClipboardProvider } from "../../../src/context/clipboard" import type { FormWithLocation } from "../../../src/context/data" -import { KVProvider } from "../../../src/context/kv" import { SDKProvider } from "../../../src/context/sdk" import { ThemeProvider } from "../../../src/context/theme" import { ConfigProvider } from "../../../src/config" @@ -21,7 +20,6 @@ import { createApi, createClient, createEventStream, createFetch } from "../../f async function mountForm(root: string, width = 80) { const state = path.join(root, "state") await mkdir(state, { recursive: true }) - await Bun.write(path.join(state, "kv.json"), "{}") const replies: unknown[] = [] const copied: string[] = [] @@ -78,13 +76,11 @@ async function mountForm(root: string, width = 80) { - - Promise.resolve({}) }}> - - - - - + Promise.resolve({}) }}> + + + + diff --git a/packages/tui/test/fixture/tui-plugin.ts b/packages/tui/test/fixture/tui-plugin.ts index e9fce425be..23dbf526ae 100644 --- a/packages/tui/test/fixture/tui-plugin.ts +++ b/packages/tui/test/fixture/tui-plugin.ts @@ -11,7 +11,6 @@ type Opts = { } export function createTuiPluginApi(opts: Opts = {}) { - const values = new Map() const color = RGBA.fromInts(200, 200, 200) const dialog = { clear() {}, replace() {}, setSize() {}, size: "medium" as const, depth: 0, open: false } return { @@ -19,15 +18,6 @@ export function createTuiPluginApi(opts: Opts = {}) { client: opts.client, event: opts.event, keymap: opts.keymap, - kv: { - get(name: string, fallback?: unknown) { - return values.has(name) ? values.get(name) : fallback - }, - set(name: string, value: unknown) { - values.set(name, value) - }, - ready: true, - }, state: { session: { get: () => undefined, ...opts.state?.session } }, theme: { current: new Proxy({}, { get: () => color }) }, tuiConfig: createTuiResolvedConfig(),