From 70dbcc5486b1e64e679649b3278f37eb2c75b4a1 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Sun, 26 Jul 2026 23:47:10 +0700 Subject: [PATCH 1/2] feat(tui): pong spinner in the busy footer The prompt footer's busy indicator (and the codev run footer's) changes from the procedural Knight-Rider scanner bar to cli-spinners' pong preset, rendered via opentui-spinner's name prop (frames + 80ms interval come from the preset; no new dependency). The agent accent color in the TUI and the theme highlight in codev run are kept, as is the reduced-motion fallback. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 9 ++++++++ DIVERGENCES.md | 1 + .../opencode/src/cli/cmd/run/footer.view.tsx | 19 +-------------- packages/tui/src/component/prompt/index.tsx | 23 +++---------------- 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca445455ec..4de04ccdeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ file is the human-readable summary of what changed and why. ## Unreleased +### TUI + +- **Pong footer spinner** — the busy indicator in the prompt footer (and the + `codev run` footer) changed from the dotted scanner bar to + [cli-spinners](https://github.com/sindresorhus/cli-spinners)' `pong` + bouncing-ball animation. It keeps the agent's accent color in the TUI and + the theme highlight in `codev run`; the reduced-motion `[⋯]` fallback is + unchanged. + ## 1.18.3-1 ### Sessions diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 562fa54a12..6b3ccd6422 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -159,6 +159,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/context/language.tsx`, `packages/app/src/entry.tsx`, `packages/desktop/src/renderer/i18n/index.ts`, `packages/console/app/src/i18n/index.ts`, `packages/console/app/src/lib/language.ts`, `packages/console/app/src/component/footer.tsx`, `packages/stats/app/src/i18n.ts`, `packages/enterprise/src/app.tsx`, `packages/app/e2e/regression/session-timeline-locale-projection.spec.ts` | Locale registries trimmed to `en` + `es`: `Locale` types, LOCALES/LABEL/TAG/DOCS maps, dict loaders, accept-language matchers; enterprise/entry `zh` special cases now use `es`; console footer always links Discord (Feishu was zh-only); e2e locale-projection spec tests `es` ("Explorado") instead of de/ar | Fork ships English + Spanish only. | Keep our trimmed lists; take upstream's structural changes and re-trim any new locale entries. | | `packages/tui/src/component/prompt/autocomplete.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/opencode/src/cli/cmd/run/footer.prompt.tsx`, `packages/core/src/filesystem/search.ts`, `packages/tui/test/fixture/tui-sdk.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx`, `packages/core/test/filesystem/search.test.ts` | `@` mention autocomplete lists file/directory results before references/agents/MCP resources; TUI and run-CLI menus cap the leading files so up to 3 non-file matches stay in the first visible page; web app group rank reordered to recent → file → reference → agent → resource (extracted into fork-only `prompt-input/at-options.ts`). TUI and run-CLI file resources track menu visibility so opening on an empty query fetches (previously nothing refetched until the query changed); core's ripgrep-fallback `find` returns its first entries for an empty query to mirror fff. Tests: tui-sdk fixture's `FetchHandler` gains an optional `method` arg, `renderFooter` accepts `findFiles`/`agents` overrides + a mention-menu ordering test, core search tests cover the fallback's empty query (fork-only test files: `packages/tui/test/prompt-autocomplete.test.tsx`, `packages/app/test-browser/prompt-at-options.test.ts`, `packages/app/src/components/prompt-input/at-options{,.test}.ts`) | Files are the dominant `@` use case; they should rank first and appear on a bare `@`. | Keep our ordering/reserve logic, resource source tracking, empty-query branch, fixture/test additions; take upstream's surrounding changes. | | `packages/slack/.gitignore`, `sdks/vscode/.gitignore` | Added the missing trailing newline (content unchanged) | Upstream's copies end without a newline; POSIX text files should end with one, and tools that concatenate or append to these files misbehave without it. | Whitespace-only: keep the trailing newline; take upstream's entries. | +| `packages/tui/src/component/prompt/index.tsx`, `packages/opencode/src/cli/cmd/run/footer.view.tsx` | Busy footer spinner: the procedural Knight-Rider scanner bar (`createFrames`/`createColors` from `ui/spinner.ts`, `style: "blocks"`, `interval={40}`) is replaced by cli-spinners' `pong` preset via opentui-spinner's `name` prop (``; the preset supplies frames + 80ms interval). prompt/index.tsx keeps the agent-color logic in a `spinnerColor` memo (was inside `spinnerDef`); footer.view.tsx passes `theme().highlight` directly. Both `createFrames`/`createColors` imports are dropped; `ui/spinner.ts` itself is untouched (now unconsumed in the TUI/run CLI). | The footer busy indicator is the pong bouncing-ball animation instead of the dotted scanner. | Keep the `name="pong"` renders, the `spinnerColor` memo, and the dropped imports; take upstream's surrounding changes. If upstream reworks the footer spinner, re-apply pong at the new render site. | ## Known rebrand gaps diff --git a/packages/opencode/src/cli/cmd/run/footer.view.tsx b/packages/opencode/src/cli/cmd/run/footer.view.tsx index d334ebd251..3396fb2a0c 100644 --- a/packages/opencode/src/cli/cmd/run/footer.view.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.view.tsx @@ -11,7 +11,6 @@ import { useTerminalDimensions } from "@opentui/solid" import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js" import { registerOpencodeSpinner } from "@opencode-ai/tui/component/register-spinner" -import { createColors, createFrames } from "@opencode-ai/tui/ui/spinner" import { RUN_SUBAGENT_PANEL_ROWS, RunCommandMenuBody, @@ -254,22 +253,6 @@ export function RunFooterView(props: RunFooterViewProps) { const runTheme = createMemo(() => props.theme()) const theme = createMemo(() => runTheme().footer) const block = createMemo(() => runTheme().block) - const spin = createMemo(() => { - return { - frames: createFrames({ - color: theme().highlight, - style: "blocks", - inactiveFactor: 0.6, - minAlpha: 0.3, - }), - color: createColors({ - color: theme().highlight, - style: "blocks", - inactiveFactor: 0.6, - minAlpha: 0.3, - }), - } - }) const permission = createMemo | undefined>(() => { const view = active() return view.type === "permission" ? view : undefined @@ -841,7 +824,7 @@ export function RunFooterView(props: RunFooterViewProps) { > - + diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index ba0a3cfd38..7ce953f3d2 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -42,7 +42,6 @@ import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v import { Locale } from "../../util/locale" import { errorMessage } from "../../util/error" import { formatDuration } from "../../util/format" -import { createColors, createFrames } from "../../ui/spinner" import { useDialog } from "../../ui/dialog" import { DialogProvider as DialogProviderConnect } from "../dialog-provider" import { useConnected } from "../use-connected" @@ -1313,28 +1312,12 @@ export function Prompt(props: PromptProps) { return `Ask anything... "${list()[store.placeholder % list().length]}"` }) - const spinnerDef = createMemo(() => { + const spinnerColor = createMemo(() => { const agent = status().type !== "idle" ? (local.agent.list().find((a) => a.name === lastUserMessage()?.agent) ?? local.agent.current()) : local.agent.current() - const color = agent ? local.agent.color(agent.name) : theme.border - return { - frames: createFrames({ - color, - style: "blocks", - inactiveFactor: 0.6, - // enableFading: false, - minAlpha: 0.3, - }), - color: createColors({ - color, - style: "blocks", - inactiveFactor: 0.6, - // enableFading: false, - minAlpha: 0.3, - }), - } + return agent ? local.agent.color(agent.name) : theme.border }) const maxHeight = createMemo(() => tuiConfig.prompt?.max_height ?? Math.max(6, Math.floor(dimensions().height / 3))) const moveLabelWidth = createMemo(() => Math.max(12, Math.min(44, dimensions().width - 48))) @@ -1498,7 +1481,7 @@ export function Prompt(props: PromptProps) { [⋯]}> - + From 315b15ac296aa85166ea6f86b7bb258afe52b114 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Sun, 26 Jul 2026 23:51:04 +0700 Subject: [PATCH 2/2] feat(tui): use bouncingBar instead of pong in the busy footer Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 8 ++++---- DIVERGENCES.md | 2 +- packages/opencode/src/cli/cmd/run/footer.view.tsx | 2 +- packages/tui/src/component/prompt/index.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de04ccdeb..32612b7f6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ file is the human-readable summary of what changed and why. ### TUI -- **Pong footer spinner** — the busy indicator in the prompt footer (and the - `codev run` footer) changed from the dotted scanner bar to - [cli-spinners](https://github.com/sindresorhus/cli-spinners)' `pong` - bouncing-ball animation. It keeps the agent's accent color in the TUI and +- **Bouncing-bar footer spinner** — the busy indicator in the prompt footer + (and the `codev run` footer) changed from the dotted scanner bar to + [cli-spinners](https://github.com/sindresorhus/cli-spinners)' `bouncingBar` + animation (`[== ]`). It keeps the agent's accent color in the TUI and the theme highlight in `codev run`; the reduced-motion `[⋯]` fallback is unchanged. diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 6b3ccd6422..3c2315fb17 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -159,7 +159,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/context/language.tsx`, `packages/app/src/entry.tsx`, `packages/desktop/src/renderer/i18n/index.ts`, `packages/console/app/src/i18n/index.ts`, `packages/console/app/src/lib/language.ts`, `packages/console/app/src/component/footer.tsx`, `packages/stats/app/src/i18n.ts`, `packages/enterprise/src/app.tsx`, `packages/app/e2e/regression/session-timeline-locale-projection.spec.ts` | Locale registries trimmed to `en` + `es`: `Locale` types, LOCALES/LABEL/TAG/DOCS maps, dict loaders, accept-language matchers; enterprise/entry `zh` special cases now use `es`; console footer always links Discord (Feishu was zh-only); e2e locale-projection spec tests `es` ("Explorado") instead of de/ar | Fork ships English + Spanish only. | Keep our trimmed lists; take upstream's structural changes and re-trim any new locale entries. | | `packages/tui/src/component/prompt/autocomplete.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/opencode/src/cli/cmd/run/footer.prompt.tsx`, `packages/core/src/filesystem/search.ts`, `packages/tui/test/fixture/tui-sdk.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx`, `packages/core/test/filesystem/search.test.ts` | `@` mention autocomplete lists file/directory results before references/agents/MCP resources; TUI and run-CLI menus cap the leading files so up to 3 non-file matches stay in the first visible page; web app group rank reordered to recent → file → reference → agent → resource (extracted into fork-only `prompt-input/at-options.ts`). TUI and run-CLI file resources track menu visibility so opening on an empty query fetches (previously nothing refetched until the query changed); core's ripgrep-fallback `find` returns its first entries for an empty query to mirror fff. Tests: tui-sdk fixture's `FetchHandler` gains an optional `method` arg, `renderFooter` accepts `findFiles`/`agents` overrides + a mention-menu ordering test, core search tests cover the fallback's empty query (fork-only test files: `packages/tui/test/prompt-autocomplete.test.tsx`, `packages/app/test-browser/prompt-at-options.test.ts`, `packages/app/src/components/prompt-input/at-options{,.test}.ts`) | Files are the dominant `@` use case; they should rank first and appear on a bare `@`. | Keep our ordering/reserve logic, resource source tracking, empty-query branch, fixture/test additions; take upstream's surrounding changes. | | `packages/slack/.gitignore`, `sdks/vscode/.gitignore` | Added the missing trailing newline (content unchanged) | Upstream's copies end without a newline; POSIX text files should end with one, and tools that concatenate or append to these files misbehave without it. | Whitespace-only: keep the trailing newline; take upstream's entries. | -| `packages/tui/src/component/prompt/index.tsx`, `packages/opencode/src/cli/cmd/run/footer.view.tsx` | Busy footer spinner: the procedural Knight-Rider scanner bar (`createFrames`/`createColors` from `ui/spinner.ts`, `style: "blocks"`, `interval={40}`) is replaced by cli-spinners' `pong` preset via opentui-spinner's `name` prop (``; the preset supplies frames + 80ms interval). prompt/index.tsx keeps the agent-color logic in a `spinnerColor` memo (was inside `spinnerDef`); footer.view.tsx passes `theme().highlight` directly. Both `createFrames`/`createColors` imports are dropped; `ui/spinner.ts` itself is untouched (now unconsumed in the TUI/run CLI). | The footer busy indicator is the pong bouncing-ball animation instead of the dotted scanner. | Keep the `name="pong"` renders, the `spinnerColor` memo, and the dropped imports; take upstream's surrounding changes. If upstream reworks the footer spinner, re-apply pong at the new render site. | +| `packages/tui/src/component/prompt/index.tsx`, `packages/opencode/src/cli/cmd/run/footer.view.tsx` | Busy footer spinner: the procedural Knight-Rider scanner bar (`createFrames`/`createColors` from `ui/spinner.ts`, `style: "blocks"`, `interval={40}`) is replaced by cli-spinners' `bouncingBar` preset via opentui-spinner's `name` prop (``; the preset supplies frames + 80ms interval). prompt/index.tsx keeps the agent-color logic in a `spinnerColor` memo (was inside `spinnerDef`); footer.view.tsx passes `theme().highlight` directly. Both `createFrames`/`createColors` imports are dropped; `ui/spinner.ts` itself is untouched (now unconsumed in the TUI/run CLI). | The footer busy indicator is the bouncingBar animation (`[== ]`) instead of the dotted scanner. | Keep the `name="bouncingBar"` renders, the `spinnerColor` memo, and the dropped imports; take upstream's surrounding changes. If upstream reworks the footer spinner, re-apply bouncingBar at the new render site. | ## Known rebrand gaps diff --git a/packages/opencode/src/cli/cmd/run/footer.view.tsx b/packages/opencode/src/cli/cmd/run/footer.view.tsx index 3396fb2a0c..58cee66089 100644 --- a/packages/opencode/src/cli/cmd/run/footer.view.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.view.tsx @@ -824,7 +824,7 @@ export function RunFooterView(props: RunFooterViewProps) { > - + diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 7ce953f3d2..b47eeb6e68 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1481,7 +1481,7 @@ export function Prompt(props: PromptProps) { [⋯]}> - +