diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 6cb331f7371f..9afc18eacc95 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -494,14 +494,22 @@ function App(props: { pair?: DialogPairCredentials }) { const clipboard = useClipboard() const terminalEnvironment = useTuiTerminalEnvironment() let systemThemeTimeout: ReturnType | undefined + let terminalTitleTimeout: ReturnType | undefined + const [terminalTitleReady, setTerminalTitleReady] = createSignal(false) const prepareSystemTheme = () => { // The native writer can still be flushing the frame when FRAME fires. Keep OSC probes behind visible app output. systemThemeTimeout = setTimeout(themes.prepareSystem, 50) } - onMount(() => renderer.once(CliRenderEvents.FRAME, prepareSystemTheme)) + const finishFirstFrame = () => { + // Native terminal updates serialize behind frame output, so keep them from forcing an empty frame ahead of Home. + prepareSystemTheme() + terminalTitleTimeout = setTimeout(() => setTerminalTitleReady(true), 50) + } + onMount(() => renderer.once(CliRenderEvents.FRAME, finishFirstFrame)) onCleanup(() => { - renderer.off(CliRenderEvents.FRAME, prepareSystemTheme) + renderer.off(CliRenderEvents.FRAME, finishFirstFrame) if (systemThemeTimeout) clearTimeout(systemThemeTimeout) + if (terminalTitleTimeout) clearTimeout(terminalTitleTimeout) }) createEffect(() => { if (client.connection.status() !== "connected") return @@ -616,6 +624,7 @@ function App(props: { pair?: DialogPairCredentials }) { const session = route.data.type === "session" ? data.session.get(route.data.sessionID) : undefined if (session) active = { id: session.id, title: session.title } if (!terminalTitleEnabled()) return + if (!terminalTitleReady()) return if (route.data.type === "home") { renderer.setTerminalTitle("OpenCode") @@ -639,6 +648,7 @@ function App(props: { pair?: DialogPairCredentials }) { }) const args = useArgs() + const promptFirstHome = () => route.data.type === "home" && !args.sessionID && !args.continue const startupPrompt = args.prompt ? { text: args.prompt, files: [], agents: [], pasted: [] } : undefined onMount(() => { batch(() => { @@ -1323,14 +1333,14 @@ function App(props: { pair?: DialogPairCredentials }) { - + - + @@ -1351,7 +1361,9 @@ function App(props: { pair?: DialogPairCredentials }) { - + + + @@ -1361,7 +1373,7 @@ function App(props: { pair?: DialogPairCredentials }) { - + diff --git a/packages/tui/src/component/logo.tsx b/packages/tui/src/component/logo.tsx index 32b5e96b1f1a..d1163d07a90d 100644 --- a/packages/tui/src/component/logo.tsx +++ b/packages/tui/src/component/logo.tsx @@ -4,6 +4,26 @@ import { useTerminalDimensions } from "@opentui/solid" import { useTheme } from "../context/theme" import { tint } from "../theme/color" import { go, logo } from "../logo" +import { stringWidth } from "../util/string-width" + +export function logoSize(width: number, height: number) { + if (height < 12) return { width: 0, height: 0 } + if (width < 22) + return { + width: Math.max(...go.right.slice(1).map((line) => stringWidth(line))), + height: go.right.length - 1, + } + if (width < 44) { + const lines = [...logo.left.slice(1), ...logo.right] + return { width: Math.max(...lines.map((line) => stringWidth(line))), height: lines.length } + } + return { + width: Math.max( + ...logo.left.map((line, index) => stringWidth(line) + 1 + stringWidth(logo.right[index] ?? "")), + ), + height: logo.left.length, + } +} export function Logo() { const theme = useTheme() @@ -50,7 +70,7 @@ export function Logo() { } return ( - +