From b93f33eaa4ea48f5096abe01af8bc5d9a6095fb9 Mon Sep 17 00:00:00 2001 From: Vinicius da Motta Date: Wed, 21 Jan 2026 01:22:38 -0300 Subject: [PATCH] fix(tui): responsive layout for narrow screens (#9703) --- .../src/cli/cmd/tui/routes/session/header.tsx | 78 ++++++++++--------- .../cli/cmd/tui/routes/session/permission.tsx | 20 +++-- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx index afcb2c611..5e814c3d2 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx @@ -8,6 +8,7 @@ import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2" import { useCommandDialog } from "@tui/component/dialog-command" import { useKeybind } from "../../context/keybind" import { Installation } from "@/installation" +import { useTerminalDimensions } from "@opentui/solid" const Title = (props: { session: Accessor }) => { const { theme } = useTheme() @@ -63,6 +64,8 @@ export function Header() { const keybind = useKeybind() const command = useCommandDialog() const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null) + const dimensions = useTerminalDimensions() + const narrow = createMemo(() => dimensions().width < 80) return ( @@ -79,49 +82,52 @@ export function Header() { > - - - Subagent session - - setHover("parent")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.parent")} - backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel} - > + + - Parent {keybind.print("session_parent")} + Subagent session + + + v{Installation.VERSION} + - setHover("prev")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.child.previous")} - backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} - > - - Prev {keybind.print("session_child_cycle_reverse")} - - - setHover("next")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.child.next")} - backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} - > - - Next {keybind.print("session_child_cycle")} - - - - - - v{Installation.VERSION} + + setHover("parent")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.parent")} + backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel} + > + + Parent {keybind.print("session_parent")} + + + setHover("prev")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.child.previous")} + backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} + > + + Prev {keybind.print("session_child_cycle_reverse")} + + + setHover("next")} + onMouseOut={() => setHover(null)} + onMouseUp={() => command.trigger("session.child.next")} + backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} + > + + Next {keybind.print("session_child_cycle")} + + - + <box flexDirection="row" gap={1} flexShrink={0}> <ContextInfo context={context} cost={cost} /> diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index c4ff4c04b..0414ec39b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -302,6 +302,8 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( const { theme } = useTheme() const keybind = useKeybind() const textareaKeybindings = useTextareaKeybindings() + const dimensions = useTerminalDimensions() + const narrow = createMemo(() => dimensions().width < 80) useKeyboard((evt) => { if (evt.name === "escape" || keybind.match("app_exit", evt)) { @@ -332,14 +334,16 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( </box> </box> <box - flexDirection="row" + flexDirection={narrow() ? "column" : "row"} flexShrink={0} paddingTop={1} paddingLeft={2} paddingRight={3} paddingBottom={1} backgroundColor={theme.backgroundElement} - justifyContent="space-between" + justifyContent={narrow() ? "flex-start" : "space-between"} + alignItems={narrow() ? "flex-start" : "center"} + gap={1} > <textarea ref={(val: TextareaRenderable) => (input = val)} @@ -349,7 +353,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( cursorColor={theme.primary} keyBindings={textareaKeybindings()} /> - <box flexDirection="row" gap={2} flexShrink={0} marginLeft={1}> + <box flexDirection="row" gap={2} flexShrink={0}> <text fg={theme.text}> enter <span style={{ fg: theme.textMuted }}>confirm</span> </text> @@ -379,6 +383,7 @@ function Prompt<const T extends Record<string, string>>(props: { expanded: false, }) const diffKey = Keybind.parse("ctrl+f")[0] + const narrow = createMemo(() => dimensions().width < 80) useKeyboard((evt) => { if (evt.name === "left" || evt.name == "h") { @@ -440,7 +445,7 @@ function Prompt<const T extends Record<string, string>>(props: { {props.body} </box> <box - flexDirection="row" + flexDirection={narrow() ? "column" : "row"} flexShrink={0} gap={1} paddingTop={1} @@ -448,9 +453,10 @@ function Prompt<const T extends Record<string, string>>(props: { paddingRight={3} paddingBottom={1} backgroundColor={theme.backgroundElement} - justifyContent="space-between" + justifyContent={narrow() ? "flex-start" : "space-between"} + alignItems={narrow() ? "flex-start" : "center"} > - <box flexDirection="row" gap={1}> + <box flexDirection="row" gap={1} flexShrink={0}> <For each={keys}> {(option) => ( <box @@ -470,7 +476,7 @@ function Prompt<const T extends Record<string, string>>(props: { )} </For> </box> - <box flexDirection="row" gap={2}> + <box flexDirection="row" gap={2} flexShrink={0}> <Show when={props.fullscreen}> <text fg={theme.text}> {"ctrl+f"} <span style={{ fg: theme.textMuted }}>{hint()}</span>