From 0481dba88e6b7bbf58b03f073814c7610f2b55b9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 31 Jul 2026 14:44:13 -0400 Subject: [PATCH] fix(tui): improve narrow layouts (#39918) --- packages/tui/src/component/logo.tsx | 35 ++++++++--- packages/tui/src/component/prompt/index.tsx | 48 ++++++++++----- .../tui/src/feature-plugins/home/footer.tsx | 59 ++++++------------- .../tui/src/feature-plugins/prompt/footer.tsx | 17 ++++-- packages/tui/src/routes/home.tsx | 9 ++- packages/tui/src/routes/session/index.tsx | 16 ++++- 6 files changed, 108 insertions(+), 76 deletions(-) diff --git a/packages/tui/src/component/logo.tsx b/packages/tui/src/component/logo.tsx index adf6eb7d6d..32b5e96b1f 100644 --- a/packages/tui/src/component/logo.tsx +++ b/packages/tui/src/component/logo.tsx @@ -1,11 +1,13 @@ import { RGBA, TextAttributes } from "@opentui/core" import { For, type JSX } from "solid-js" +import { useTerminalDimensions } from "@opentui/solid" import { useTheme } from "../context/theme" import { tint } from "../theme/color" -import { logo } from "../logo" +import { go, logo } from "../logo" export function Logo() { const theme = useTheme() + const dimensions = useTerminalDimensions() const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => { const shadow = tint(theme.background.default, fg, 0.25) @@ -49,14 +51,29 @@ export function Logo() { return ( - - {(line, index) => ( - - {renderLine(line, theme.text.subdued, false)} - {renderLine(logo.right[index()], theme.text.default, true)} - - )} - + {dimensions().height < 12 ? null : dimensions().width < 22 ? ( + + {(line) => {renderLine(line, theme.text.default, true)}} + + ) : dimensions().width < 44 ? ( + <> + + {(line) => {renderLine(line, theme.text.subdued, false)}} + + + {(line) => {renderLine(line, theme.text.default, true)}} + + + ) : ( + + {(line, index) => ( + + {renderLine(line, theme.text.subdued, false)} + {renderLine(logo.right[index()], theme.text.default, true)} + + )} + + )} ) } diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index d24d4cca37..92f7fab061 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1314,13 +1314,20 @@ export function Prompt(props: PromptProps) { const placeholderText = createMemo(() => { if (props.showPlaceholder === false) return undefined - if (store.mode === "shell") { - if (!shell().length) return undefined - const example = shell()[store.placeholder % shell().length] - return `Run a command... "${example}"` - } - if (!list().length) return undefined - return `Ask anything... "${list()[store.placeholder % list().length]}"` + const value = (() => { + if (store.mode === "shell") { + if (!shell().length) return undefined + return `Run a command... "${shell()[store.placeholder % shell().length]}"` + } + if (!list().length) return undefined + return `Ask anything... "${list()[store.placeholder % list().length]}"` + })() + if (!value) return undefined + const width = + dimensions().width < 44 + ? dimensions().width - 5 + : Math.min(75, dimensions().width - 4) - 5 + return Locale.takeWidth(value, Math.max(1, width)).trimEnd() }) const locationLabel = createMemo(() => { if (!props.sessionID) { @@ -1370,8 +1377,8 @@ export function Prompt(props: PromptProps) { }} > - + }> {(label) => ( <> {label()} - + = 44} + > auto - - + = 28}> + · {local.model.parsed().model} - {currentProviderLabel()} - + = 50}> + + {currentProviderLabel()} + + + = 70}> · - props.context.location ? props.context.ui.format.path(props.context.location.directory) : undefined, - ) - - return ( - - ) -} function Mcp(props: { context: Plugin.Context }) { const list = createMemo(() => props.context.data.location.mcp.server.list(props.context.location) ?? []) @@ -53,34 +36,26 @@ function Mcp(props: { context: Plugin.Context }) { function View(props: { context: Plugin.Context }) { const dimensions = useTerminalDimensions() - const mcpWidth = createMemo(() => { - const list = props.context.data.location.mcp.server.list(props.context.location) ?? [] - if (list.length === 0) return 0 - const count = list.filter((item) => item.status.status === "connected").length - return stringWidth(`⊙ ${count} MCP /status`) + 2 - }) return ( - - - - - - {props.context.app.version} + = 12 && dimensions().width >= 44}> + + + + + {props.context.app.version} + - + ) } diff --git a/packages/tui/src/feature-plugins/prompt/footer.tsx b/packages/tui/src/feature-plugins/prompt/footer.tsx index 341e8494ea..1169da7510 100644 --- a/packages/tui/src/feature-plugins/prompt/footer.tsx +++ b/packages/tui/src/feature-plugins/prompt/footer.tsx @@ -1,6 +1,7 @@ import { Plugin } from "@opencode-ai/plugin/tui" import { createMemo, Match, Show, Switch } from "solid-js" import { contextUsage, formatContextUsage } from "../../util/session" +import { useTerminalDimensions } from "@opentui/solid" const money = new Intl.NumberFormat("en-US", { style: "currency", @@ -8,6 +9,7 @@ const money = new Intl.NumberFormat("en-US", { }) export function PromptFooter(props: { context: Plugin.Context; sessionID?: string; mode: "normal" | "shell" }) { + const dimensions = useTerminalDimensions() const activeSubagents = createMemo(() => { if (!props.sessionID) return 0 return props.context.data.session @@ -60,19 +62,24 @@ export function PromptFooter(props: { context: Plugin.Context; sessionID?: strin 0}>{status().join(" · ")} - + = 44}> {shortcut("agent.cycle")} agents - - {shortcut("command.palette.show")} commands - + = 44}> + + {shortcut("command.palette.show")} commands + + - esc exit shell mode + esc{" "} + + {dimensions().width < 44 ? "shell" : "exit shell mode"} + diff --git a/packages/tui/src/routes/home.tsx b/packages/tui/src/routes/home.tsx index 1a27dd94bf..7b4644fedb 100644 --- a/packages/tui/src/routes/home.tsx +++ b/packages/tui/src/routes/home.tsx @@ -10,6 +10,7 @@ import { useData } from "../context/data" import { useLocation } from "../context/location" import { FormPrompt } from "./session/form" import { PluginSlot } from "../plugin/context" +import { useTerminalDimensions } from "@opentui/solid" let once = false const placeholder = { @@ -26,6 +27,7 @@ export function Home() { const editor = useEditorContext() const data = useData() const location = useLocation() + const dimensions = useTerminalDimensions() // Global MCP elicitations can arrive without a session route, so keep them reachable from Home. const currentLocation = () => route.location ?? data.location.default() const forms = createMemo(() => data.session.form.list("global", currentLocation()) ?? []) @@ -71,7 +73,12 @@ export function Home() { return ( <> - + diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 98d174b54d..4e6aebbc8b 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -958,7 +958,14 @@ export function Session() { }} > - + (scroll = r)} @@ -1540,6 +1547,7 @@ function SessionGroupView(props: { function AssistantFooter(props: { message: SessionMessageAssistant }) { const ctx = use() const local = useLocal() + const dimensions = useTerminalDimensions() const theme = useTheme("elevated") const model = createMemo( () => @@ -1573,8 +1581,10 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) { {Locale.titlecase(props.message.agent)} - · {model()} - + = 28}> + · {model()} + + = 36)}> · {Locale.duration(duration())}