From 835a27cf517fae5d9952c30989de8be8f760d7a5 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:29:57 -0500 Subject: fix(app): terminal jank --- packages/app/src/pages/session.tsx | 11 +- packages/app/src/pages/session/terminal-panel.tsx | 292 ++++++++++++---------- 2 files changed, 161 insertions(+), 142 deletions(-) (limited to 'packages/app/src/pages') diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index c1552ad02..79c8d42f5 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -32,8 +32,9 @@ import { useLayout } from "@/context/layout" import { usePrompt } from "@/context/prompt" import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" +import { useTerminal } from "@/context/terminal" import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer" -import { createOpenReviewFile, createSizing } from "@/pages/session/helpers" +import { createOpenReviewFile, createSizing, focusTerminalById } from "@/pages/session/helpers" import { MessageTimeline } from "@/pages/session/message-timeline" import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab" import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers" @@ -267,6 +268,7 @@ export default function Page() { const sdk = useSDK() const prompt = usePrompt() const comments = useComments() + const terminal = useTerminal() const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>() createEffect(() => { @@ -759,8 +761,11 @@ export default function Page() { return } - // Don't autofocus chat if desktop terminal panel is open - if (isDesktop() && view().terminal.opened()) return + // Prefer the open terminal over the composer when it can take focus + if (view().terminal.opened()) { + const id = terminal.active() + if (id && focusTerminalById(id)) return + } // Only treat explicit scroll keys as potential "user scroll" gestures. if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") { diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx index 19a656b53..a6c3929c1 100644 --- a/packages/app/src/pages/session/terminal-panel.tsx +++ b/packages/app/src/pages/session/terminal-panel.tsx @@ -1,6 +1,5 @@ import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js" import { createStore } from "solid-js/store" -import { createMediaQuery } from "@solid-primitives/media" import { useParams } from "@solidjs/router" import { Tabs } from "@opencode-ai/ui/tabs" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" @@ -27,12 +26,10 @@ export function TerminalPanel() { const language = useLanguage() const command = useCommand() - const isDesktop = createMediaQuery("(min-width: 768px)") const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const view = createMemo(() => layout.view(sessionKey)) const opened = createMemo(() => view().terminal.opened()) - const open = createMemo(() => isDesktop() && opened()) const size = createSizing() const height = createMemo(() => layout.terminal.height()) const close = () => view().terminal.close() @@ -41,6 +38,25 @@ export function TerminalPanel() { const [store, setStore] = createStore({ autoCreated: false, activeDraggable: undefined as string | undefined, + view: typeof window === "undefined" ? 1000 : (window.visualViewport?.height ?? window.innerHeight), + }) + + const max = () => store.view * 0.6 + const pane = () => Math.min(height(), max()) + + createEffect(() => { + if (typeof window === "undefined") return + + const sync = () => setStore("view", window.visualViewport?.height ?? window.innerHeight) + const port = window.visualViewport + + sync() + window.addEventListener("resize", sync) + port?.addEventListener("resize", sync) + onCleanup(() => { + window.removeEventListener("resize", sync) + port?.removeEventListener("resize", sync) + }) }) createEffect(() => { @@ -69,14 +85,14 @@ export function TerminalPanel() { focusTerminalById(id) const frame = requestAnimationFrame(() => { - if (!open()) return + if (!opened()) return if (terminal.active() !== id) return focusTerminalById(id) }) const timers = [120, 240].map((ms) => window.setTimeout(() => { - if (!open()) return + if (!opened()) return if (terminal.active() !== id) return focusTerminalById(id) }, ms), @@ -90,7 +106,7 @@ export function TerminalPanel() { createEffect( on( - () => [open(), terminal.active()] as const, + () => [opened(), terminal.active()] as const, ([next, id]) => { if (!next || !id) return const stop = focus(id) @@ -100,7 +116,7 @@ export function TerminalPanel() { ) createEffect(() => { - if (open()) return + if (opened()) return const active = document.activeElement if (!(active instanceof HTMLElement)) return if (!root?.contains(active)) return @@ -165,151 +181,149 @@ export function TerminalPanel() { } return ( - +
-
-
size.start()}> - { - size.touch() - layout.terminal.resize(next) - }} - onCollapse={close} - /> -
- -
- - {(title) => ( -
- {title} -
- )} -
-
-
- {language.t("common.loading")} - {language.t("common.loading.ellipsis")} -
-
-
- {language.t("terminal.loading")} -
-
- } - > - - - -
- terminal.open(id)} - class="!h-auto !flex-none" - > - - - - {(id) => ( - - {(pty) => } - - )} - - -
- - - + + +
+ + {(title) => ( +
+ {title}
- - -
- - {(id) => ( - - {(pty) => ( -
- terminal.trim(id)} - onCleanup={terminal.update} - onConnectError={() => terminal.clone(id)} - /> -
- )} -
- )} -
+ )} + +
+
+ {language.t("common.loading")} + {language.t("common.loading.ellipsis")}
- - - {(draggedId) => ( - - {(t) => ( -
- {terminalTabLabel({ - title: t().title, - titleNumber: t().titleNumber, - t: language.t as (key: string, vars?: Record) => string, - })} +
{language.t("terminal.loading")}
+
+ } + > + + + +
+ terminal.open(id)} + class="!h-auto !flex-none" + > + + + + {(id) => ( + + {(pty) => } + + )} + + +
+ + + +
+
+
+
+ + {(id) => ( + + {(pty) => ( +
+ terminal.trim(id)} + onCleanup={terminal.update} + onConnectError={() => terminal.clone(id)} + />
)}
)}
- - - -
+
+
+ + + {(draggedId) => ( + + {(t) => ( +
+ {terminalTabLabel({ + title: t().title, + titleNumber: t().titleNumber, + t: language.t as (key: string, vars?: Record) => string, + })} +
+ )} +
+ )} +
+
+ +
-
+
) } -- cgit v1.2.3