From 3ba1111ed047ca4cc42bc964ae23c32959c0e8fd Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Wed, 21 Jan 2026 13:23:45 -0600
Subject: [PATCH] fix(app): terminal issues/regression
---
.../session/session-sortable-terminal-tab.tsx | 29 +++++++++++++++----
packages/app/src/components/terminal.tsx | 7 ++++-
packages/app/src/pages/session.tsx | 29 ++++++++++++++-----
3 files changed, 52 insertions(+), 13 deletions(-)
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")
})()}