diff options
| author | Dax <[email protected]> | 2026-04-17 02:12:41 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-17 06:12:41 +0000 |
| commit | 65b2a10e97d34e6b8a832a69b5a75f90f21e076c (patch) | |
| tree | d78d07f60e3131e7590a7fa3c52b291d9c27917e | |
| parent | 7605acff650db0d41d80429b662b5c0725d89675 (diff) | |
| download | opencode-65b2a10e97d34e6b8a832a69b5a75f90f21e076c.tar.gz opencode-65b2a10e97d34e6b8a832a69b5a75f90f21e076c.zip | |
fade in prompt metadata transitions (#23037)
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/app.tsx | 18 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx | 44 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/context/route.tsx | 13 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/context/sync.tsx | 1 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/util/signal.ts | 33 |
5 files changed, 87 insertions, 22 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 7e883ec0e..74eca9a0f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -148,7 +148,16 @@ export function tui(input: { <ExitProvider onBeforeExit={onBeforeExit} onExit={onExit}> <KVProvider> <ToastProvider> - <RouteProvider> + <RouteProvider + initialRoute={ + (input.args.sessionID || input.args.continue) && !input.args.fork + ? { + type: "session", + sessionID: "dummy", + } + : undefined + } + > <TuiConfigProvider config={input.config}> <SDKProvider url={input.url} @@ -333,13 +342,6 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) { }) local.model.set({ providerID, modelID }, { recent: true }) } - // Handle --session without --fork immediately (fork is handled in createEffect below) - if (args.sessionID && !args.fork) { - route.navigate({ - type: "session", - sessionID: args.sessionID, - }) - } }) }) 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 82cdefebc..08540e62e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1,5 +1,15 @@ -import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, decodePasteBytes } from "@opentui/core" -import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js" +import { BoxRenderable, RGBA, TextareaRenderable, MouseEvent, PasteEvent, decodePasteBytes } from "@opentui/core" +import { + createEffect, + createMemo, + onMount, + createSignal, + onCleanup, + on, + Show, + Switch, + Match, +} from "solid-js" import "opentui-spinner/solid" import path from "path" import { fileURLToPath } from "url" @@ -35,6 +45,7 @@ import { DialogProvider as DialogProviderConnect } from "../dialog-provider" import { DialogAlert } from "../../ui/dialog-alert" import { useToast } from "../../ui/toast" import { useKV } from "../../context/kv" +import { createFadeIn } from "../../util/signal" import { useTextareaKeybindings } from "../textarea-keybindings" import { DialogSkill } from "../dialog-skill" import { useArgs } from "@tui/context/args" @@ -75,6 +86,10 @@ function randomIndex(count: number) { return Math.floor(Math.random() * count) } +function fadeColor(color: RGBA, alpha: number) { + return RGBA.fromValues(color.r, color.g, color.b, color.a * alpha) +} + let stashed: { prompt: PromptInfo; cursor: number } | undefined export function Prompt(props: PromptProps) { @@ -97,6 +112,7 @@ export function Prompt(props: PromptProps) { const renderer = useRenderer() const { theme, syntax } = useTheme() const kv = useKV() + const animationsEnabled = createMemo(() => kv.get("animations_enabled", true)) const list = createMemo(() => props.placeholders?.normal ?? []) const shell = createMemo(() => props.placeholders?.shell ?? []) const [auto, setAuto] = createSignal<AutocompleteRef>() @@ -858,6 +874,13 @@ export function Prompt(props: PromptProps) { return !!current }) + const agentMetaAlpha = createFadeIn(() => !!local.agent.current(), animationsEnabled) + const modelMetaAlpha = createFadeIn(() => !!local.agent.current() && store.mode === "normal", animationsEnabled) + const variantMetaAlpha = createFadeIn( + () => !!local.agent.current() && store.mode === "normal" && showVariant(), + animationsEnabled, + ) + const placeholderText = createMemo(() => { if (props.showPlaceholder === false) return undefined if (store.mode === "shell") { @@ -1133,17 +1156,24 @@ export function Prompt(props: PromptProps) { <Show when={local.agent.current()} fallback={<box height={1} />}> {(agent) => ( <> - <text fg={highlight()}>{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)} </text> + <text fg={fadeColor(highlight(), agentMetaAlpha())}> + {store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}{" "} + </text> <Show when={store.mode === "normal"}> <box flexDirection="row" gap={1}> - <text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}> + <text + flexShrink={0} + fg={fadeColor(keybind.leader ? theme.textMuted : theme.text, modelMetaAlpha())} + > {local.model.parsed().model} </text> - <text fg={theme.textMuted}>{currentProviderLabel()}</text> + <text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>{currentProviderLabel()}</text> <Show when={showVariant()}> - <text fg={theme.textMuted}>·</text> + <text fg={fadeColor(theme.textMuted, variantMetaAlpha())}>·</text> <text> - <span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span> + <span style={{ fg: fadeColor(theme.warning, variantMetaAlpha()), bold: true }}> + {local.model.variant.current()} + </span> </text> </Show> </box> diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 6db824759..35be17801 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -23,13 +23,14 @@ export type Route = HomeRoute | SessionRoute | PluginRoute export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ name: "Route", - init: () => { + init: (props: { initialRoute?: Route }) => { const [store, setStore] = createStore<Route>( - process.env["OPENCODE_ROUTE"] - ? JSON.parse(process.env["OPENCODE_ROUTE"]) - : { - type: "home", - }, + props.initialRoute ?? + (process.env["OPENCODE_ROUTE"] + ? JSON.parse(process.env["OPENCODE_ROUTE"]) + : { + type: "home", + }), ) return { diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 57326e3a1..d2a7e5c4d 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -467,6 +467,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ return store.status }, get ready() { + return true if (process.env.OPENCODE_FAST_BOOT) return true return store.status !== "loading" }, diff --git a/packages/opencode/src/cli/cmd/tui/util/signal.ts b/packages/opencode/src/cli/cmd/tui/util/signal.ts index 15b57886d..1c7cc0008 100644 --- a/packages/opencode/src/cli/cmd/tui/util/signal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/signal.ts @@ -1,7 +1,38 @@ -import { createSignal, type Accessor } from "solid-js" +import { createEffect, createSignal, on, onCleanup, type Accessor } from "solid-js" import { debounce, type Scheduled } from "@solid-primitives/scheduled" export function createDebouncedSignal<T>(value: T, ms: number): [Accessor<T>, Scheduled<[value: T]>] { const [get, set] = createSignal(value) return [get, debounce((v: T) => set(() => v), ms)] } + +export function createFadeIn(show: Accessor<boolean>, enabled: Accessor<boolean>) { + const [alpha, setAlpha] = createSignal(show() ? 1 : 0) + + createEffect( + on([show, enabled], ([visible, animate], previous) => { + if (!visible) { + setAlpha(0) + return + } + + if (!animate || !previous) { + setAlpha(1) + return + } + + const start = performance.now() + setAlpha(0) + + const timer = setInterval(() => { + const progress = Math.min((performance.now() - start) / 160, 1) + setAlpha(progress * progress * (3 - 2 * progress)) + if (progress >= 1) clearInterval(timer) + }, 16) + + onCleanup(() => clearInterval(timer)) + }), + ) + + return alpha +} |
