feat(web,desktop): replace xterm.js with ghostty-web#3820
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review Replaces the core terminal emulator library (xterm.js → ghostty-web), introduces security-relevant CSP changes for WebAssembly, and adds new functionality to load user's local Ghostty theme/font configuration. This major infrastructure change warrants human review. You can customize Macroscope's approvability policy. Learn more. |
b4665fe to
bb28ac9
Compare
9291c4c to
069f1dd
Compare
Swap the thread terminal drawer from @xterm/xterm to ghostty-web, the libghostty-vt based drop-in. The server terminal protocol is unchanged; the swap is contained to the client renderer: - Gate terminal creation on the ghostty-web WASM init and replay the session buffer that arrives before it resolves - Invert custom key handler returns (ghostty-web treats true as "consumed", the opposite of xterm.js) - Convert link provider rows/ranges between the 1-based terminal-links helpers and ghostty-web's 0-based coordinates, and drop the built-in providers whose activate() bypasses the app's preview flow - Guard zero-length writes: ghostty-web 0.4.0 alloc(0) returns an out-of-bounds pointer and crashes on status-only session updates - Adapt at-bottom checks to ghostty-web's inverted viewportY semantics Also style the terminal from the user's local Ghostty config when one exists: the server parses font-family/font-size/theme (resolving named themes from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field, and the drawer applies it over the app-derived defaults. Nerd Font fallbacks cover powerline and devicon glyphs, and the mount is painted with the theme background so partial-cell fringes match the canvas.
The desktop protocol's Content-Security-Policy blocked the ghostty-web terminal from initializing: script-src had no 'wasm-unsafe-eval', so WebAssembly compilation was rejected, and connect-src had no data:, so the library's embedded WASM payload fetch failed. Add both sources.
- Retry WASM init on a later mount instead of caching a rejected promise until page reload - Blur non-active terminals after open(): ghostty-web focuses unconditionally, letting the last-mounted split steal focus - Seed the session snapshot with version 0 and a closed status at mount so an already-exited session still surfaces its exit and closes the viewport - Keep CSS generic font keywords (monospace, system-ui, ...) unquoted when building the terminal font stack - Resolve named Ghostty themes from the Linux system dirs (/usr/share/ghostty/themes, /usr/local/share/ghostty/themes) too
- Attach a rejection handler to the terminal WASM init so a load failure logs, shows a message in the viewport, and clears on retry instead of leaving a blank pane and an unhandled rejection - Treat an empty XDG_CONFIG_HOME as unset per the XDG spec instead of resolving the Ghostty config relative to the server cwd
- Bump a terminal epoch when the deferred WASM setup finishes so the session sync effect re-runs against the live terminal even when no session field changed during the load, completing the exited-at-mount handling - Read config.ghostty as an alternate config file name and merge the XDG and macOS Application Support configs in Ghostty's load order instead of stopping at the first existing file
- Preserve a scrolled-back viewport when session output arrives: ghostty-web snaps to the bottom on every write, so re-anchor the viewport by the number of lines the write pushed into scrollback - Read both config and config.ghostty when they coexist in a Ghostty config directory instead of letting the first shadow the second - Linkify mailto/ftp/ssh/git/tel/magnet/gemini/gopher/news URLs in the terminal and open non-http schemes with the OS handler, keeping the in-app preview flow for http(s)
- Once scrollback is full its length stops growing while writes keep evicting lines, so the preserved viewport drifted toward the tail; fall back to counting written newlines at the cap - Parse config.ghostty before config so config wins conflicts, matching Ghostty's own load order
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
069f1dd to
34c0fb2
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 34c0fb2. Configure here.

Working on #1310.
Swaps the thread terminal drawer from
@xterm/xtermto ghostty-web, the libghostty-vt based drop-in mentioned in the issue. The server terminal protocol is a raw byte stream, so the change is contained to the client renderer — no server terminal changes.Renderer swap
ghostty-web is close to API-compatible with xterm.js, but a few behaviors differ and are adapted here:
init(); a session buffer that arrives first is replayed once the terminal mounts.truereturn as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.activate()callswindow.opendirectly, bypassing the app's preview/editor flow.alloc(0)returns an out-of-bounds pointer, sowrite("")throws aRangeErrordeep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)viewportYsemantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.@xterm/*dependencies are removed.Ghostty config passthrough
Since the terminal engine is now Ghostty's, the drawer can match the user's actual Ghostty setup: the server parses
~/.config/ghostty/config(font-family, font-size, theme — includinglight:/dark:variants, resolved from the user themes dir or the Ghostty.app bundle) into an optionalServerConfig.terminalStylefield. The drawer overlays those colors on the app-derived theme and prepends the configured fonts. Everything is best-effort: no Ghostty config means the previous defaults exactly.Nerd Font families are appended to the font fallback stack so powerline/devicon glyphs resolve from locally installed patched fonts (Ghostty embeds its own symbols fallback; browser canvas only walks the CSS font list), and the terminal container is painted with the theme background so partial-cell fringes match the canvas during drawer resizing.
I like this approach, but I'll remove it if asked--I could also split it out into a separate PR.
Known limitations
Note
Medium Risk
Large client terminal refactor with WASM/CSP requirements and behavioral differences from xterm; mitigated by tests and fallbacks when Ghostty config is missing.
Overview
Replaces the thread terminal drawer renderer from @xterm/xterm with ghostty-web (WASM), including async
init(), inverted custom-key-handler semantics, 0-based link coordinates, and guards against empty writes that crash 0.4.0. writePreservingScrollback keeps scroll position when output arrives while scrolled back; shouldFocusTerminalAfterSetup avoids focus theft on style-driven remounts and in split view.The server best-effort loads local Ghostty config (
ghosttyStyle.ts) and exposes optionalServerConfig.terminalStyle(fonts, sizes, light/dark palettes); the drawer merges that with app theme, Nerd Font fallbacks, and expanded link schemes (mailto,ssh, etc.). Desktop CSP addswasm-unsafe-evalanddata:for WASM; xterm CSS/deps are removed.Reviewed by Cursor Bugbot for commit 8eed30a. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Replace xterm.js with ghostty-web as the web terminal renderer
@xterm/xtermand@xterm/addon-fitdependencies forghostty-web(v0.4.0), which uses a WASM module initialized once on first terminal mount.ghosttyStyleutilities on the server to parse the user's local Ghostty config (fonts, colors, palette) and expose aterminalStylefield in theServerConfigpayload so the web terminal can adopt the user's Ghostty appearance.writePreservingScrollbackso terminal output no longer snaps the viewport to the bottom when the user has scrolled up.wasm-unsafe-evalanddata:must be added to the Electron Content Security Policy to allow WASM compilation and ghostty-web's embedded WASM payload fetch.Macroscope summarized 8eed30a.