Skip to content

Commit 835a27c

Browse files
committed
fix(app): terminal jank
1 parent 85afaaa commit 835a27c

File tree

4 files changed

+208
-175
lines changed

4 files changed

+208
-175
lines changed

packages/app/src/components/session/session-header.tsx

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { useLayout } from "@/context/layout"
2121
import { usePlatform } from "@/context/platform"
2222
import { useServer } from "@/context/server"
2323
import { useSync } from "@/context/sync"
24+
import { useTerminal } from "@/context/terminal"
25+
import { focusTerminalById } from "@/pages/session/helpers"
2426
import { decode64 } from "@/utils/base64"
2527
import { Persist, persisted } from "@/utils/persist"
2628
import { StatusPopover } from "../status-popover"
@@ -229,6 +231,7 @@ export function SessionHeader() {
229231
const sync = useSync()
230232
const platform = usePlatform()
231233
const language = useLanguage()
234+
const terminal = useTerminal()
232235

233236
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
234237
const project = createMemo(() => {
@@ -296,6 +299,16 @@ export function SessionHeader() {
296299
] as const
297300
})
298301

302+
const toggleTerminal = () => {
303+
const next = !view().terminal.opened()
304+
view().terminal.toggle()
305+
if (!next) return
306+
307+
const id = terminal.active()
308+
if (!id) return
309+
focusTerminalById(id)
310+
}
311+
299312
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
300313
const [menu, setMenu] = createStore({ open: false })
301314
const [openRequest, setOpenRequest] = createStore({
@@ -617,39 +630,39 @@ export function SessionHeader() {
617630
</div>
618631
</Show>
619632
<div class="flex items-center gap-1">
620-
<div class="hidden md:flex items-center gap-1 shrink-0">
621-
<TooltipKeybind
622-
title={language.t("command.terminal.toggle")}
623-
keybind={command.keybind("terminal.toggle")}
633+
<TooltipKeybind
634+
title={language.t("command.terminal.toggle")}
635+
keybind={command.keybind("terminal.toggle")}
636+
>
637+
<Button
638+
variant="ghost"
639+
class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border shrink-0"
640+
onClick={toggleTerminal}
641+
aria-label={language.t("command.terminal.toggle")}
642+
aria-expanded={view().terminal.opened()}
643+
aria-controls="terminal-panel"
624644
>
625-
<Button
626-
variant="ghost"
627-
class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border"
628-
onClick={() => view().terminal.toggle()}
629-
aria-label={language.t("command.terminal.toggle")}
630-
aria-expanded={view().terminal.opened()}
631-
aria-controls="terminal-panel"
632-
>
633-
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
634-
<Icon
635-
size="small"
636-
name={view().terminal.opened() ? "layout-bottom-partial" : "layout-bottom"}
637-
class="group-hover/terminal-toggle:hidden"
638-
/>
639-
<Icon
640-
size="small"
641-
name="layout-bottom-partial"
642-
class="hidden group-hover/terminal-toggle:inline-block"
643-
/>
644-
<Icon
645-
size="small"
646-
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-partial"}
647-
class="hidden group-active/terminal-toggle:inline-block"
648-
/>
649-
</div>
650-
</Button>
651-
</TooltipKeybind>
645+
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
646+
<Icon
647+
size="small"
648+
name={view().terminal.opened() ? "layout-bottom-partial" : "layout-bottom"}
649+
class="group-hover/terminal-toggle:hidden"
650+
/>
651+
<Icon
652+
size="small"
653+
name="layout-bottom-partial"
654+
class="hidden group-hover/terminal-toggle:inline-block"
655+
/>
656+
<Icon
657+
size="small"
658+
name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-partial"}
659+
class="hidden group-active/terminal-toggle:inline-block"
660+
/>
661+
</div>
662+
</Button>
663+
</TooltipKeybind>
652664

665+
<div class="hidden md:flex items-center gap-1 shrink-0">
653666
<TooltipKeybind
654667
title={language.t("command.review.toggle")}
655668
keybind={command.keybind("review.toggle")}

packages/app/src/components/terminal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const TOGGLE_TERMINAL_ID = "terminal.toggle"
1717
const DEFAULT_TOGGLE_TERMINAL_KEYBIND = "ctrl+`"
1818
export interface TerminalProps extends ComponentProps<"div"> {
1919
pty: LocalPTY
20+
autoFocus?: boolean
2021
onSubmit?: () => void
2122
onCleanup?: (pty: Partial<LocalPTY> & { id: string }) => void
2223
onConnect?: () => void
@@ -157,7 +158,7 @@ export const Terminal = (props: TerminalProps) => {
157158
const language = useLanguage()
158159
const server = useServer()
159160
let container!: HTMLDivElement
160-
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"])
161+
const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"])
161162
const id = local.pty.id
162163
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
163164
const restoreSize =
@@ -386,7 +387,7 @@ export const Terminal = (props: TerminalProps) => {
386387
handleLinkClick,
387388
})
388389

389-
focusTerminal()
390+
if (local.autoFocus !== false) focusTerminal()
390391

391392
if (typeof document !== "undefined" && document.fonts) {
392393
document.fonts.ready.then(scheduleFit)

packages/app/src/pages/session.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ import { useLayout } from "@/context/layout"
3232
import { usePrompt } from "@/context/prompt"
3333
import { useSDK } from "@/context/sdk"
3434
import { useSync } from "@/context/sync"
35+
import { useTerminal } from "@/context/terminal"
3536
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
36-
import { createOpenReviewFile, createSizing } from "@/pages/session/helpers"
37+
import { createOpenReviewFile, createSizing, focusTerminalById } from "@/pages/session/helpers"
3738
import { MessageTimeline } from "@/pages/session/message-timeline"
3839
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
3940
import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers"
@@ -267,6 +268,7 @@ export default function Page() {
267268
const sdk = useSDK()
268269
const prompt = usePrompt()
269270
const comments = useComments()
271+
const terminal = useTerminal()
270272
const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>()
271273

272274
createEffect(() => {
@@ -759,8 +761,11 @@ export default function Page() {
759761
return
760762
}
761763

762-
// Don't autofocus chat if desktop terminal panel is open
763-
if (isDesktop() && view().terminal.opened()) return
764+
// Prefer the open terminal over the composer when it can take focus
765+
if (view().terminal.opened()) {
766+
const id = terminal.active()
767+
if (id && focusTerminalById(id)) return
768+
}
764769

765770
// Only treat explicit scroll keys as potential "user scroll" gestures.
766771
if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") {

0 commit comments

Comments
 (0)