summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-05 11:28:22 -0600
committerAdam <[email protected]>2026-02-05 14:21:13 -0600
commit287540551427198a02811e85d90547a17ac6164e (patch)
tree1c0cc702641780d9bdbe50d77a5eab666087116c
parent8c0300c021dd70e363ee0ffa966deb4b974803c1 (diff)
downloadopencode-287540551427198a02811e85d90547a17ac6164e.tar.gz
opencode-287540551427198a02811e85d90547a17ac6164e.zip
fix(app): more terminal stability fixes
-rw-r--r--bun.lock7
-rw-r--r--package.json4
-rw-r--r--packages/app/package.json2
-rw-r--r--packages/app/src/components/terminal.tsx46
-rw-r--r--packages/app/src/context/terminal.tsx1
5 files changed, 43 insertions, 17 deletions
diff --git a/bun.lock b/bun.lock
index 5b4616ae7..ea8c612d8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -44,7 +44,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
- "ghostty-web": "0.3.0",
+ "ghostty-web": "0.4.0",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
@@ -496,9 +496,6 @@
"web-tree-sitter",
"tree-sitter-bash",
],
- "patchedDependencies": {
- },
"overrides": {
"@types/bun": "catalog:",
"@types/node": "catalog:",
@@ -2605,7 +2602,7 @@
"get-tsconfig": ["[email protected]", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
- "ghostty-web": ["[email protected]", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="],
+ "ghostty-web": ["[email protected]", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
"gifwrap": ["[email protected]", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
diff --git a/package.json b/package.json
index b052e2bbf..65cd0dea8 100644
--- a/package.json
+++ b/package.json
@@ -100,7 +100,5 @@
"@types/bun": "catalog:",
"@types/node": "catalog:"
},
- "patchedDependencies": {
- }
+ "patchedDependencies": {}
}
diff --git a/packages/app/package.json b/packages/app/package.json
index 81e589e38..12688ac07 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -54,7 +54,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
- "ghostty-web": "0.3.0",
+ "ghostty-web": "0.4.0",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index e87670f59..4d44d5f7e 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -70,6 +70,7 @@ export const Terminal = (props: TerminalProps) => {
let handleTextareaBlur: () => void
let disposed = false
const cleanups: VoidFunction[] = []
+ let tail = local.pty.tail ?? ""
const cleanup = () => {
if (!cleanups.length) return
@@ -256,6 +257,7 @@ export const Terminal = (props: TerminalProps) => {
serializeAddon = serializer
t.open(container)
+
container.addEventListener("pointerdown", handlePointerDown)
cleanups.push(() => container.removeEventListener("pointerdown", handlePointerDown))
@@ -276,15 +278,11 @@ export const Terminal = (props: TerminalProps) => {
focusTerminal()
+ fit.fit()
+
if (local.pty.buffer) {
- if (local.pty.rows && local.pty.cols) {
- t.resize(local.pty.cols, local.pty.rows)
- }
t.write(local.pty.buffer, () => {
- if (local.pty.scrollY) {
- t.scrollToLine(local.pty.scrollY)
- }
- fitAddon.fit()
+ if (local.pty.scrollY) t.scrollToLine(local.pty.scrollY)
})
}
@@ -322,6 +320,19 @@ export const Terminal = (props: TerminalProps) => {
// console.log("Scroll position:", ydisp)
// })
+ const limit = 16_384
+ const seed = tail
+ let sync = !!seed
+
+ const overlap = (data: string) => {
+ if (!seed) return 0
+ const max = Math.min(seed.length, data.length)
+ for (let i = max; i > 0; i--) {
+ if (seed.slice(-i) === data.slice(0, i)) return i
+ }
+ return 0
+ }
+
const handleOpen = () => {
local.onConnect?.()
sdk.client.pty
@@ -338,7 +349,25 @@ export const Terminal = (props: TerminalProps) => {
cleanups.push(() => socket.removeEventListener("open", handleOpen))
const handleMessage = (event: MessageEvent) => {
- t.write(event.data)
+ const data = typeof event.data === "string" ? event.data : ""
+ if (!data) return
+
+ const next = (() => {
+ if (!sync) return data
+ const n = overlap(data)
+ if (!n) {
+ sync = false
+ return data
+ }
+ const trimmed = data.slice(n)
+ if (trimmed) sync = false
+ return trimmed
+ })()
+
+ if (!next) return
+
+ t.write(next)
+ tail = next.length >= limit ? next.slice(-limit) : (tail + next).slice(-limit)
}
socket.addEventListener("message", handleMessage)
cleanups.push(() => socket.removeEventListener("message", handleMessage))
@@ -392,6 +421,7 @@ export const Terminal = (props: TerminalProps) => {
props.onCleanup({
...local.pty,
buffer,
+ tail,
rows: t.rows,
cols: t.cols,
scrollY: t.getViewportY(),
diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx
index e01b8bc4d..0c383a78d 100644
--- a/packages/app/src/context/terminal.tsx
+++ b/packages/app/src/context/terminal.tsx
@@ -13,6 +13,7 @@ export type LocalPTY = {
cols?: number
buffer?: string
scrollY?: number
+ tail?: string
}
const WORKSPACE_KEY = "__workspace__"