diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index 539195553..2c661edf8 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -18,12 +18,22 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 }) const [blurEnabled, setBlurEnabled] = createSignal(false) + const isDefaultTitle = () => { + const number = props.terminal.titleNumber + if (!Number.isFinite(number) || number <= 0) return false + const match = props.terminal.title.match(/^Terminal (\d+)$/) + if (!match) return false + const parsed = Number(match[1]) + if (!Number.isFinite(parsed) || parsed <= 0) return false + return parsed === number + } + const label = () => { language.locale() + if (props.terminal.title && !isDefaultTitle()) return props.terminal.title + const number = props.terminal.titleNumber - if (Number.isFinite(number) && number > 0) { - return language.t("terminal.title.numbered", { number }) - } + if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number }) if (props.terminal.title) return props.terminal.title return language.t("terminal.title") } @@ -102,8 +112,15 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => } return ( - // @ts-ignore -
+
onClick={focus} onMouseDown={(e) => e.preventDefault()} onContextMenu={menu} + class="!shadow-none" + classes={{ button: "outline-none focus:outline-none focus-visible:outline-none !shadow-none !ring-0" }} closeButton={ { pty: LocalPTY onSubmit?: () => void onCleanup?: (pty: LocalPTY) => void + onConnect?: () => void onConnectError?: (error: unknown) => void } @@ -40,7 +41,7 @@ export const Terminal = (props: TerminalProps) => { const settings = useSettings() const theme = useTheme() let container!: HTMLDivElement - const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"]) + const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"]) let ws: WebSocket | undefined let term: Term | undefined let ghostty: Ghostty @@ -241,6 +242,7 @@ export const Terminal = (props: TerminalProps) => { // console.log("Scroll position:", ydisp) // }) socket.addEventListener("open", () => { + local.onConnect?.() sdk.client.pty .update({ ptyID: local.pty.id, @@ -255,10 +257,12 @@ export const Terminal = (props: TerminalProps) => { t.write(event.data) }) socket.addEventListener("error", (error) => { + if (disposed) return console.error("WebSocket error:", error) local.onConnectError?.(error) }) socket.addEventListener("close", (event) => { + if (disposed) return // Normal closure (code 1000) means PTY process exited - server event handles cleanup // For other codes (network issues, server restart), trigger error handler if (event.code !== 1000) { @@ -268,6 +272,7 @@ export const Terminal = (props: TerminalProps) => { }) onCleanup(() => { + disposed = true if (handleResize) { window.removeEventListener("resize", handleResize) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7dcf60b72..560c13330 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1231,11 +1231,15 @@ export default function Page() { language.locale() const label = (pty: LocalPTY) => { + const title = pty.title const number = pty.titleNumber - if (Number.isFinite(number) && number > 0) { - return language.t("terminal.title.numbered", { number }) - } - if (pty.title) return pty.title + const match = title.match(/^Terminal (\d+)$/) + const parsed = match ? Number(match[1]) : undefined + const isDefaultTitle = Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number + + if (title && !isDefaultTitle) return title + if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number }) + if (title) return title return language.t("terminal.title") } @@ -2002,7 +2006,12 @@ export default function Page() { terminal.update({ ...data, id: pty.id })} + onConnect={() => { + terminal.update({ id: pty.id, error: false }) + setDismissed(false) + }} onConnectError={() => { + setDismissed(false) terminal.update({ id: pty.id, error: true }) }} /> @@ -2056,11 +2065,17 @@ export default function Page() { {(t) => (
{(() => { + const title = t().title const number = t().titleNumber - if (Number.isFinite(number) && number > 0) { + const match = title.match(/^Terminal (\d+)$/) + const parsed = match ? Number(match[1]) : undefined + const isDefaultTitle = + Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number + + if (title && !isDefaultTitle) return title + if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number }) - } - if (t().title) return t().title + if (title) return title return language.t("terminal.title") })()}