fix(terminal): update ghostty-web to a build that clears recycled rows

ghostty-web 0.4.0 hands rows that scroll into view out of recycled WASM
page memory without clearing them, so after a tab or project switch the
new emulator showed the previous terminal's text (upstream #138). The fix
landed only in prereleases, so pin 0.4.0-next.20 and carry the local
block-glyph rendering patch over to the new dist file.

Verified in a production build: creating an emulator after disposing a
full one no longer exposes its rows, and switching between two projects
with live output in each keeps every terminal's content to itself.
This commit is contained in:
Bohdan Triapitsyn
2026-09-06 22:57:43 +03:00
parent c2f36fb5e7
commit eb6f7b0904
5 changed files with 66 additions and 11 deletions
+8 -8
View File
@@ -47,7 +47,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"express": "^5.1.0",
"ghostty-web": "0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"http-proxy-middleware": "^3.0.5",
"next-themes": "^0.4.6",
"node-pty": "1.2.0-beta.12",
@@ -97,7 +97,7 @@
},
"packages/electron": {
"name": "@openchamber/electron",
"version": "1.22.1",
"version": "1.22.2",
"dependencies": {
"@openchamber/web": "workspace:*",
"electron-context-menu": "^4.1.2",
@@ -134,7 +134,7 @@
},
"packages/ui": {
"name": "@openchamber/ui",
"version": "1.22.1",
"version": "1.22.2",
"dependencies": {
"@aparajita/capacitor-secure-storage": "^8.0.0",
"@base-ui/react": "^1.4.0",
@@ -185,7 +185,7 @@
"express": "^5.1.0",
"fflate": "^0.8.3",
"fuse.js": "^7.1.0",
"ghostty-web": "^0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"heic2any": "^0.0.4",
"html-to-image": "^1.11.13",
"http-proxy-middleware": "^3.0.5",
@@ -241,7 +241,7 @@
},
"packages/vscode": {
"name": "openchamber",
"version": "1.22.1",
"version": "1.22.2",
"dependencies": {
"@openchamber/ui": "workspace:*",
"@opencode-ai/sdk": "1.18.29",
@@ -264,7 +264,7 @@
},
"packages/web": {
"name": "@openchamber/web",
"version": "1.22.1",
"version": "1.22.2",
"bin": {
"openchamber": "./bin/cli.js",
},
@@ -323,7 +323,7 @@
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.0",
"ghostty-web": "0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"globals": "^16.3.0",
"next-themes": "^0.4.6",
"nodemon": "^3.1.7",
@@ -2122,7 +2122,7 @@
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
"ghostty-web": ["ghostty-web@0.4.0-next.20.g1858a59", "", {}, "sha512-NXA9H3IJlx+DGJukXbOPQWFkigYdAatTqkoIvM8tvhfbaYoDf3gGKxXLLyXtLYOBt5qzGdEZa294TU36gULjVg=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
+1 -1
View File
@@ -135,7 +135,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"express": "^5.1.0",
"ghostty-web": "0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"http-proxy-middleware": "^3.0.5",
"next-themes": "^0.4.6",
"node-pty": "1.2.0-beta.12",
+1 -1
View File
@@ -61,7 +61,7 @@
"express": "^5.1.0",
"fflate": "^0.8.3",
"fuse.js": "^7.1.0",
"ghostty-web": "^0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"heic2any": "^0.0.4",
"html-to-image": "^1.11.13",
"http-proxy-middleware": "^3.0.5",
+1 -1
View File
@@ -77,7 +77,7 @@
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.0",
"ghostty-web": "0.4.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"globals": "^16.3.0",
"next-themes": "^0.4.6",
"nodemon": "^3.1.7",
@@ -0,0 +1,55 @@
diff --git a/node_modules/ghostty-web/dist/ghostty-web.js b/node_modules/ghostty-web/dist/ghostty-web.js
index 0000000000000000000000000000000000000000..1111111111111111111111111111111111111111 100644
--- a/node_modules/ghostty-web/dist/ghostty-web.js
+++ b/node_modules/ghostty-web/dist/ghostty-web.js
@@ -1844,7 +1844,7 @@
A.flags & G.FAINT && (this.ctx.globalAlpha = 0.5);
const s = C, h = I + this.metrics.baseline;
let N;
- if (A.grapheme_len > 0 && ((k = this.currentBuffer) != null && k.getGraphemeString) ? N = this.currentBuffer.getGraphemeString(g, Q) : N = String.fromCodePoint(A.codepoint || 32), this.ctx.fillText(N, s, h), A.flags & G.FAINT && (this.ctx.globalAlpha = 1), A.flags & G.UNDERLINE) {
+ if (A.grapheme_len > 0 && ((k = this.currentBuffer) != null && k.getGraphemeString) ? N = this.currentBuffer.getGraphemeString(g, Q) : N = A.codepoint == null || A.codepoint <= 0 || A.codepoint > 1114111 || A.codepoint >= 55296 && A.codepoint <= 57343 ? " " : String.fromCodePoint(A.codepoint), this.renderBlockChar(A.codepoint || 32, C, I, D) || this.ctx.fillText(N, s, h), A.flags & G.FAINT && (this.ctx.globalAlpha = 1), A.flags & G.UNDERLINE) {
const t = I + this.metrics.baseline + 2;
this.ctx.strokeStyle = this.ctx.fillStyle, this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(C, t), this.ctx.lineTo(C + D, t), this.ctx.stroke();
}
@@ -1864,6 +1864,41 @@
}
}
}
+ renderBlockChar(A, B, g, E) {
+ const C = this.metrics.height, I = E / 2, D = C / 2;
+ switch (A) {
+ case 9600: this.ctx.fillRect(B, g, E, D); return !0;
+ case 9601: this.ctx.fillRect(B, g + C * 7 / 8, E, C / 8); return !0;
+ case 9602: this.ctx.fillRect(B, g + C * 3 / 4, E, C / 4); return !0;
+ case 9603: this.ctx.fillRect(B, g + C * 5 / 8, E, C * 3 / 8); return !0;
+ case 9604: this.ctx.fillRect(B, g + D, E, D); return !0;
+ case 9605: this.ctx.fillRect(B, g + C * 3 / 8, E, C * 5 / 8); return !0;
+ case 9606: this.ctx.fillRect(B, g + C / 4, E, C * 3 / 4); return !0;
+ case 9607: this.ctx.fillRect(B, g + C / 8, E, C * 7 / 8); return !0;
+ case 9608: this.ctx.fillRect(B, g, E, C); return !0;
+ case 9609: this.ctx.fillRect(B, g, E * 7 / 8, C); return !0;
+ case 9610: this.ctx.fillRect(B, g, E * 3 / 4, C); return !0;
+ case 9611: this.ctx.fillRect(B, g, E * 5 / 8, C); return !0;
+ case 9612: this.ctx.fillRect(B, g, I, C); return !0;
+ case 9613: this.ctx.fillRect(B, g, E * 3 / 8, C); return !0;
+ case 9614: this.ctx.fillRect(B, g, E / 4, C); return !0;
+ case 9615: this.ctx.fillRect(B, g, E / 8, C); return !0;
+ case 9616: this.ctx.fillRect(B + I, g, I, C); return !0;
+ case 9620: this.ctx.fillRect(B, g, E, C / 8); return !0;
+ case 9621: this.ctx.fillRect(B + E * 7 / 8, g, E / 8, C); return !0;
+ case 9622: this.ctx.fillRect(B, g + D, I, D); return !0;
+ case 9623: this.ctx.fillRect(B + I, g + D, I, D); return !0;
+ case 9624: this.ctx.fillRect(B, g, I, D); return !0;
+ case 9625: this.ctx.fillRect(B, g, I, C); this.ctx.fillRect(B + I, g + D, I, D); return !0;
+ case 9626: this.ctx.fillRect(B, g, I, D); this.ctx.fillRect(B + I, g + D, I, D); return !0;
+ case 9627: this.ctx.fillRect(B, g, E, D); this.ctx.fillRect(B, g + D, I, D); return !0;
+ case 9628: this.ctx.fillRect(B, g, E, D); this.ctx.fillRect(B + I, g + D, I, D); return !0;
+ case 9629: this.ctx.fillRect(B + I, g, I, D); return !0;
+ case 9630: this.ctx.fillRect(B + I, g, I, D); this.ctx.fillRect(B, g + D, I, D); return !0;
+ case 9631: this.ctx.fillRect(B + I, g, I, C); this.ctx.fillRect(B, g + D, I, D); return !0;
+ default: return !1;
+ }
+ }
/**
* Render cursor
*/