From 87d91c29e23d136436342542f83a20bfc4a3d0bf Mon Sep 17 00:00:00 2001 From: Halil Tezcan KARABULUT Date: Wed, 21 Jan 2026 15:49:46 +0300 Subject: fix(app): terminal improvements - focus, rename, error state, CSP (#9700) --- packages/app/src/pages/session.tsx | 200 ++++++++++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 36 deletions(-) (limited to 'packages/app/src/pages') diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 562176c1b..a75a0f0c1 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,16 @@ -import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on, createSignal } from "solid-js" +import { + For, + Index, + onCleanup, + onMount, + Show, + Match, + Switch, + createMemo, + createEffect, + on, + createSignal, +} from "solid-js" import { createMediaQuery } from "@solid-primitives/media" import { createResizeObserver } from "@solid-primitives/resize-observer" import { Dynamic } from "solid-js/web" @@ -350,14 +362,7 @@ export default function Page() { const current = activeMessage() const currentIndex = current ? msgs.findIndex((m) => m.id === current.id) : -1 - - let targetIndex: number - if (currentIndex === -1) { - targetIndex = offset > 0 ? 0 : msgs.length - 1 - } else { - targetIndex = currentIndex + offset - } - + const targetIndex = currentIndex === -1 ? (offset > 0 ? 0 : msgs.length - 1) : currentIndex + offset if (targetIndex < 0 || targetIndex >= msgs.length) return scrollToMessage(msgs[targetIndex], "auto") @@ -381,11 +386,16 @@ export default function Page() { sync.session.sync(params.id) }) + const [autoCreated, setAutoCreated] = createSignal(false) + createEffect(() => { - if (!view().terminal.opened()) return - if (!terminal.ready()) return - if (terminal.all().length !== 0) return + if (!view().terminal.opened()) { + setAutoCreated(false) + return + } + if (!terminal.ready() || terminal.all().length !== 0 || autoCreated()) return terminal.new() + setAutoCreated(true) }) createEffect( @@ -401,6 +411,32 @@ export default function Page() { ), ) + createEffect( + on( + () => terminal.active(), + (activeId) => { + if (!activeId || !view().terminal.opened()) return + // Immediately remove focus + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur() + } + const wrapper = document.getElementById(`terminal-wrapper-${activeId}`) + const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement + if (!element) return + + // Find and focus the ghostty textarea (the actual input element) + const textarea = element.querySelector("textarea") as HTMLTextAreaElement + if (textarea) { + textarea.focus() + return + } + // Fallback: focus container and dispatch pointer event + element.focus() + element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true })) + }, + ), + ) + createEffect( on( () => visibleUserMessages().at(-1)?.id, @@ -753,6 +789,9 @@ export default function Page() { return } + // Don't autofocus chat if terminal panel is open + if (view().terminal.opened()) return + if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) { inputRef?.focus() } @@ -800,6 +839,23 @@ export default function Page() { const handleTerminalDragEnd = () => { setStore("activeTerminalDraggable", undefined) + const activeId = terminal.active() + if (!activeId) return + setTimeout(() => { + const wrapper = document.getElementById(`terminal-wrapper-${activeId}`) + const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement + if (!element) return + + // Find and focus the ghostty textarea (the actual input element) + const textarea = element.querySelector("textarea") as HTMLTextAreaElement + if (textarea) { + textarea.focus() + return + } + // Fallback: focus container and dispatch pointer event + element.focus() + element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true })) + }, 0) } const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context")) @@ -1855,7 +1911,7 @@ export default function Page() {
- - - t.id)}> - {(pty) => } - -
- - - -
-
- - {(pty) => ( - - terminal.clone(pty.id)} /> - - )} - -
+
+ { + // Only switch tabs if not in the middle of starting edit mode + terminal.open(id) + }} + class="!h-auto !flex-none" + > + + t.id)}> + + {(pty) => ( + { + view().terminal.close() + setAutoCreated(false) + }} + /> + )} + + +
+ + + +
+
+
+
+ + {(pty) => { + const [dismissed, setDismissed] = createSignal(false) + return ( +
+ terminal.update({ ...data, id: pty.id })} + onConnectError={() => { + terminal.update({ id: pty.id, error: true }) + }} + /> + +
+ +
+
Connection Lost
+
+ The terminal connection was interrupted. This can happen when the server restarts. +
+
+ +
+
+
+ ) + }} +
+
+
{(draggedId) => { -- cgit v1.2.3