summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2026-04-17 02:12:41 -0400
committerGitHub <[email protected]>2026-04-17 06:12:41 +0000
commit65b2a10e97d34e6b8a832a69b5a75f90f21e076c (patch)
treed78d07f60e3131e7590a7fa3c52b291d9c27917e
parent7605acff650db0d41d80429b662b5c0725d89675 (diff)
downloadopencode-65b2a10e97d34e6b8a832a69b5a75f90f21e076c.tar.gz
opencode-65b2a10e97d34e6b8a832a69b5a75f90f21e076c.zip
fade in prompt metadata transitions (#23037)
-rw-r--r--packages/opencode/src/cli/cmd/tui/app.tsx18
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx44
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/route.tsx13
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/sync.tsx1
-rw-r--r--packages/opencode/src/cli/cmd/tui/util/signal.ts33
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
+}