feat(terminal): replace ghostty-web with an in-repo libghostty-vt adapter

The terminal ran on the ghostty-web npm package plus a hand-written patch,
and every rendering bug (recycled rows, duplicated reflow fragments, prompt
artifacts) had to be worked around from outside. The emulator now is the
official libghostty-vt C ABI compiled to WebAssembly, driven by a browser
adapter ported from T3 Code (MIT, notice in LICENSE-T3CODE) and owned in
packages/ui/src/lib/ghostty. The artifact is reproducible with
scripts/build-libghostty-wasm.sh, including a workaround for Zig 0.15.2 on
macOS 27 SDKs.

On top of the port: one WASM instance per page with every tab kept mounted
and hidden tabs paused; history replayed at the PTY size it was drawn for;
shells spawned only after the first fitted grid so zsh never prints the
PROMPT_SP marker; box drawing, block elements and Powerline arrows drawn
procedurally to the exact cell so TUI borders and block logos have no gaps
between rows; a software-rasterized canvas so Gecko renders every tab's text
with the same smoothing; the symbols-only Nerd Font bundled instead of a CDN
fetch; touch selection and scrolling driven through the surface API; a copy
button in the tab strip for touch hosts; localized aria labels.

Testing: bun tests run the real WASM (reflow, palette, replay isolation,
recycled rows, box glyph geometry); viewport and view tests use a surface
double; verified in Chromium and Zen (windowed and headless) for crisp text,
new tabs, panel reopen, resize and box glyph rendering; package type-check,
oxlint/eslint on new files, web build.
This commit is contained in:
Bohdan Triapitsyn
2026-09-07 12:18:22 +03:00
parent 3132d1361a
commit 39fa8c1917
61 changed files with 6678 additions and 990 deletions
-38
View File
@@ -593,44 +593,6 @@
}, 10000);
</script>
<!-- CSS Font Loading API for reliable Nerd Font loading. The ~2 MB of
Nerd Fonts are only used by the terminal viewport, so they are no
longer downloaded on every cold start: the terminal requests them on
first mount through this idempotent hook. -->
<script>
(function() {
let nerdFontsPromise = null;
window.__openchamberEnsureNerdFonts = function() {
if (nerdFontsPromise) {
return nerdFontsPromise;
}
const fonts = [
{
name: 'JetBrainsMono Nerd Font',
url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/JetBrainsMonoNerdFont-Regular.woff2'
},
{
name: 'FiraCode Nerd Font',
url: 'https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/FiraCodeNerdFont-Regular.woff2'
}
];
const fontPromises = fonts.map(font => {
const fontFace = new FontFace(font.name, `url(${font.url}) format('woff2')`);
document.fonts.add(fontFace);
return fontFace.load().catch(err => {
console.warn(`Failed to load font: ${font.name}`, err);
});
});
nerdFontsPromise = Promise.allSettled(fontPromises).then(() => {
document.documentElement.classList.add('fonts-loaded');
});
return nerdFontsPromise;
};
})();
</script>
<!-- Polyfill for process before loading React -->
<script>
if (typeof process === 'undefined') {
-1
View File
@@ -77,7 +77,6 @@
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.0",
"ghostty-web": "0.4.0-next.20.g1858a59",
"globals": "^16.3.0",
"next-themes": "^0.4.6",
"nodemon": "^3.1.7",
+1 -1
View File
@@ -73,7 +73,7 @@ export default defineConfig({
injectRegister: false,
manifest: false,
injectManifest: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,otf,eot}'],
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,otf,eot,wasm}'],
// iOS Safari/PWA is much more reliable with a classic (non-module) SW bundle.
rollupFormat: 'iife',
// We already keep a custom manifest in index.html