From 3c8bc137b2eb657f8dee7f3b8b3ebdb8b8e5cb34 Mon Sep 17 00:00:00 2001 From: anntnzrb Date: Tue, 23 Dec 2025 23:17:28 -0500 Subject: [PATCH 1/3] feat: add tui.animations config to disable spinner animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new config option `tui.animations` (default: true) that allows users to disable spinner animations in the TUI. When disabled, a static `[⋯]` indicator is shown instead. This helps users who find animations distracting or experience performance issues on slower machines. Closes #6008, #3990 --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 8 +++++++- .../src/cli/cmd/tui/component/prompt/index.tsx | 10 ++++++++-- packages/opencode/src/config/config.ts | 1 + packages/sdk/js/src/v2/gen/types.gen.ts | 4 ++++ packages/sdk/openapi.json | 4 ++++ packages/web/src/content/docs/config.mdx | 2 ++ 6 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 1217bb54ae0..83580cb6c28 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -45,7 +45,13 @@ export function DialogSessionList() { value: x.id, category, footer: Locale.time(x.time.updated), - gutter: isWorking ? : undefined, + gutter: isWorking ? ( + sync.data.config.tui?.animations !== false ? ( + + ) : ( + [⋯] + ) + ) : undefined, } }) .slice(0, 150) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 8972ba36e84..40620a3fb17 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1002,8 +1002,14 @@ export function Prompt(props: PromptProps) { justifyContent={status().type === "retry" ? "space-between" : "flex-start"} > - {/* @ts-ignore // SpinnerOptions doesn't support marginLeft */} - + + [⋯]} + > + + + {(() => { const retry = createMemo(() => { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ba9d1973025..ca8f87201e7 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -585,6 +585,7 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + animations: z.boolean().optional().describe("Enable or disable spinner animations (default: true)"), }) export const Layout = z.enum(["auto", "stretch"]).meta({ diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0a31394ed9c..b3d526e03f0 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1409,6 +1409,10 @@ export type Config = { * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column */ diff_style?: "auto" | "stacked" + /** + * Enable or disable spinner animations (default: true) + */ + animations?: boolean } /** * Command configuration, see https://opencode.ai/docs/commands diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 96ba0720c73..71de0362508 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8167,6 +8167,10 @@ "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", "enum": ["auto", "stacked"] + }, + "animations": { + "description": "Enable or disable spinner animations (default: true)", + "type": "boolean" } } }, diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 5ba22ff2d78..d0a77e1cd8d 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -99,6 +99,7 @@ You can configure TUI-specific settings through the `tui` option. { "$schema": "https://opencode.ai/config.json", "tui": { + "animations": false, "scroll_speed": 3, "scroll_acceleration": { "enabled": true @@ -109,6 +110,7 @@ You can configure TUI-specific settings through the `tui` option. Available options: +- `animations` - Enable or disable spinner animations (default: `true`). Disable to reduce distractions or improve performance. - `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.** - `scroll_speed` - Custom scroll speed multiplier (default: `1`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`. From a2c6f724156ef7064d675d8088fb28339024cb8e Mon Sep 17 00:00:00 2001 From: anntnzrb Date: Wed, 24 Dec 2025 00:16:47 -0500 Subject: [PATCH 2/3] refactor: use command palette toggle instead of config file Animations toggle is now accessible via command palette instead of config file. Setting persists to KV. --- .../cmd/tui/component/dialog-session-list.tsx | 4 +++- .../src/cli/cmd/tui/component/prompt/index.tsx | 7 +++---- .../src/cli/cmd/tui/routes/session/index.tsx | 16 ++++++++++++++++ packages/opencode/src/config/config.ts | 1 - packages/sdk/js/src/v2/gen/types.gen.ts | 4 ---- packages/sdk/openapi.json | 4 ---- packages/web/src/content/docs/config.mdx | 2 -- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 83580cb6c28..c0e363aadcc 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -8,6 +8,7 @@ import { Keybind } from "@/util/keybind" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" import { DialogSessionRename } from "./dialog-session-rename" +import { useKV } from "../context/kv" import "opentui-spinner/solid" export function DialogSessionList() { @@ -16,6 +17,7 @@ export function DialogSessionList() { const { theme } = useTheme() const route = useRoute() const sdk = useSDK() + const kv = useKV() const [toDelete, setToDelete] = createSignal() @@ -46,7 +48,7 @@ export function DialogSessionList() { category, footer: Locale.time(x.time.updated), gutter: isWorking ? ( - sync.data.config.tui?.animations !== false ? ( + kv.get("animations_enabled", true) ? ( ) : ( [⋯] diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 40620a3fb17..1a06c671ace 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -29,6 +29,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogProvider as DialogProviderConnect } from "../dialog-provider" import { DialogAlert } from "../../ui/dialog-alert" import { useToast } from "../../ui/toast" +import { useKV } from "../../context/kv" export type PromptProps = { sessionID?: string @@ -127,6 +128,7 @@ export function Prompt(props: PromptProps) { const tall = createMemo(() => dimensions().height > 40) const wide = createMemo(() => dimensions().width > 120) const { theme, syntax } = useTheme() + const kv = useKV() function promptModelWarning() { toast.show({ @@ -1003,10 +1005,7 @@ export function Prompt(props: PromptProps) { > - [⋯]} - > + [⋯]}> 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 c685d8c66cc..edd2ca162e7 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -91,6 +91,7 @@ const context = createContext<{ showDetails: () => boolean userMessageMarkdown: () => boolean diffWrapMode: () => "word" | "none" + animationsEnabled: () => boolean sync: ReturnType }>() @@ -129,6 +130,7 @@ export function Session() { const [showScrollbar, setShowScrollbar] = createSignal(kv.get("scrollbar_visible", false)) const [userMessageMarkdown, setUserMessageMarkdown] = createSignal(kv.get("user_message_markdown", true)) const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word") + const [animationsEnabled, setAnimationsEnabled] = createSignal(kv.get("animations_enabled", true)) const wide = createMemo(() => dimensions().width > 120) const tall = createMemo(() => dimensions().height > 40) @@ -587,6 +589,19 @@ export function Session() { dialog.clear() }, }, + { + title: animationsEnabled() ? "Disable animations" : "Enable animations", + value: "session.toggle.animations", + category: "Session", + onSelect: (dialog) => { + setAnimationsEnabled((prev) => { + const next = !prev + kv.set("animations_enabled", next) + return next + }) + dialog.clear() + }, + }, { title: "Page up", value: "session.page.up", @@ -959,6 +974,7 @@ export function Session() { showDetails, userMessageMarkdown, diffWrapMode, + animationsEnabled, sync, }} > diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ca8f87201e7..ba9d1973025 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -585,7 +585,6 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), - animations: z.boolean().optional().describe("Enable or disable spinner animations (default: true)"), }) export const Layout = z.enum(["auto", "stretch"]).meta({ diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index b3d526e03f0..0a31394ed9c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1409,10 +1409,6 @@ export type Config = { * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column */ diff_style?: "auto" | "stacked" - /** - * Enable or disable spinner animations (default: true) - */ - animations?: boolean } /** * Command configuration, see https://opencode.ai/docs/commands diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 71de0362508..96ba0720c73 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8167,10 +8167,6 @@ "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", "enum": ["auto", "stacked"] - }, - "animations": { - "description": "Enable or disable spinner animations (default: true)", - "type": "boolean" } } }, diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index d0a77e1cd8d..5ba22ff2d78 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -99,7 +99,6 @@ You can configure TUI-specific settings through the `tui` option. { "$schema": "https://opencode.ai/config.json", "tui": { - "animations": false, "scroll_speed": 3, "scroll_acceleration": { "enabled": true @@ -110,7 +109,6 @@ You can configure TUI-specific settings through the `tui` option. Available options: -- `animations` - Enable or disable spinner animations (default: `true`). Disable to reduce distractions or improve performance. - `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.** - `scroll_speed` - Custom scroll speed multiplier (default: `1`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`. From 5529ca8e5755fc3f4d4543b84edf0c2b5ee81168 Mon Sep 17 00:00:00 2001 From: anntnzrb Date: Wed, 24 Dec 2025 18:57:09 -0500 Subject: [PATCH 3/3] fix: use Show component for proper reactivity in dialog-session-list The ternary expression with kv.get() wasn't tracked as a reactive dependency. Using ensures the UI updates when animations setting changes while the dialog is open. --- .../src/cli/cmd/tui/component/dialog-session-list.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index c0e363aadcc..cb7b5d282ee 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,7 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { createEffect, createMemo, createSignal, onMount } from "solid-js" +import { createEffect, createMemo, createSignal, onMount, Show } from "solid-js" import { Locale } from "@/util/locale" import { Keybind } from "@/util/keybind" import { useTheme } from "../context/theme" @@ -48,11 +48,9 @@ export function DialogSessionList() { category, footer: Locale.time(x.time.updated), gutter: isWorking ? ( - kv.get("animations_enabled", true) ? ( + [⋯]}> - ) : ( - [⋯] - ) + ) : undefined, } })