feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(web): replace xterm.js with ghostty-web - #3820

Closed
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal
Closed

feat(web): replace xterm.js with ghostty-web#3820
PixPMusic wants to merge 15 commits into
pingdotgg:mainfrom
PixPMusic:ghostty-web-terminal

Conversation

@PixPMusic

@PixPMusicPixPMusic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Working on #1310.

Swaps the thread terminal drawer from @xterm/xterm to 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:

  • WASM init: terminal creation waits on init(); a session buffer that arrives first is replayed once the terminal mounts.
  • Custom key handler semantics are inverted: ghostty-web treats a true return as "consumed, skip terminal input" — the opposite of xterm.js. Without this, all typing is swallowed.
  • Link providers: rows/ranges are converted between the app's 1-based link helpers and ghostty-web's 0-based coordinates. The built-in OSC8/URL providers are removed since their activate() calls window.open directly, bypassing the app's preview/editor flow.
  • Zero-length write crash: ghostty-web 0.4.0's alloc(0) returns an out-of-bounds pointer, so write("") throws a RangeError deep in WASM. Status-only session updates produced exactly that and took the app down; writes are now guarded. (Worth an upstream report.)
  • viewportY semantics are inverted (0 = bottom), so the at-bottom checks used for resize scroll restoration are adapted.
  • Scrollbar/selection: ghostty-web renders its own canvas scrollbar and selection; the xterm-specific scrollbar theme keys and CSS import are gone, and @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 — including light:/dark: variants, resolved from the user themes dir or the Ghostty.app bundle) into an optional ServerConfig.terminalStyle field. 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

  • Runtime theme switching only updates default fg/bg/cursor — cell colors are baked in WASM at write time (ghostty-web limitation).
  • Ghostty config changes are picked up on client reload, not watched live.

Note

Medium Risk
Large client renderer swap with WASM/CSP changes and new server file reads; terminal I/O protocol unchanged, but regressions in input, links, scrollback, or focus are plausible in a critical UI surface.

Overview
Replaces @xterm/xterm with ghostty-web in the thread terminal drawer. The renderer waits on WASM init and bundled Symbols Nerd Font Mono before mounting; session output that arrives early is replayed once the terminal exists.

Electron CSP adds wasm-unsafe-eval and data: in connect-src for ghostty-web’s WASM and embedded payload.

The drawer adapts ghostty-web vs xterm: inverted custom key-handler semantics, 0-based link row/range conversion, clearing built-in link providers so URLs go through preview/editor, guarding zero-length writes, inverted viewportY for scroll-at-bottom, and writePreservingScrollback so scrolled-back history stays anchored on writes.

Server best-effort reads local Ghostty config (ghosttyStyle.ts) and exposes optional ServerConfig.terminalStyle (fonts, size, light/dark colors). The client overlays that on the app theme and prepends configured font families. Link detection expands to more URI schemes and git+ transport URLs; non-http(s) links open externally.

Focus on style-driven remounts uses shouldFocusTerminalAfterSetup so split views and the composer don’t lose focus.

Reviewed by Cursor Bugbot for commit c405032. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Replace xterm.js with ghostty-web in the web terminal

  • Swaps the terminal renderer from xterm.js to ghostty-web, initializing a WASM module before terminal creation in ThreadTerminalDrawer.tsx.
  • Adds server-side Ghostty config parsing in ghosttyStyle.ts to read fonts, theme colors, and palette entries from the user's local Ghostty config; the result is forwarded to the client via a new terminalStyle field in ServerConfig.
  • Bundles Symbols Nerd Font Mono as a WOFF2 fallback font for icon/glyph rendering in the terminal.
  • Routes http(s) links through the in-app preview while delegating other schemes (e.g. git+ssh://) to the OS; strips leading git+ transport prefixes before activation.
  • Adds wasm-unsafe-eval to script-src and data: to connect-src in the desktop Content Security Policy to support the WASM-based renderer.
  • Risk: ghostty-web snaps the viewport to the bottom on every write; a writePreservingScrollback wrapper restores the previous scroll position, but this is new behavior that may surface edge cases.

Macroscope summarized c405032.

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 501ceddd-9005-49f5-9097-690d09e7a356

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web terminalReplace xterm.js with ghostty-web in the web/desktop terminalJul 9, 2026
@macroscopeapp

macroscopeappBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR replaces the terminal emulator library (xterm.js → ghostty-web) and adds new server-side Ghostty config parsing for user terminal theming. The scope includes CSP security policy changes, new contracts, and complex WebAssembly initialization — substantial new functionality warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@PixPMusicPixPMusic changed the title Replace xterm.js with ghostty-web in the web/desktop terminalfeat(web,desktop): replace xterm.js with ghostty-web in the terminalJul 9, 2026
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-web in the terminalfeat(web,desktop): replace xterm.js with ghostty-webJul 9, 2026
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from b4665fe to bb28ac9CompareJuly 9, 2026 06:18
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
@juliusmarminge
juliusmarminge self-requested a review July 20, 2026 08:54
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/server/src/terminal/ghosttyStyle.ts
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
juliusmarminge added a commit to PixPMusic/t3code that referenced this pull request Jul 20, 2026
Co-authored-by: codex <codex@users.noreply.github.com>
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web): replace xterm.js with ghostty-webfeat(web,desktop): replace xterm.js with ghostty-webJul 25, 2026
@PixPMusicPixPMusic changed the title feat(web,desktop): replace xterm.js with ghostty-webfeat(web): replace xterm.js with ghostty-webJul 25, 2026
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.
PixPMusicand others added 9 commits July 30, 2026 20:06
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>
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from 8eed30a to c923244CompareJuly 31, 2026 03:16
Comment threadapps/server/src/terminal/ghosttyStyle.ts Outdated
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
Comment threadapps/web/src/components/ThreadTerminalDrawer.tsx
@PixPMusic
PixPMusicforce-pushed the ghostty-web-terminal branch from c923244 to 40382ceCompareJuly 31, 2026 03:29
Comment threadapps/web/src/terminal-links.ts
Comment threadapps/web/src/terminal-links.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca084e1. Configure here.

Comment threadapps/web/src/terminal-links.ts Outdated
@juliusmarminge

Copy link
Copy Markdown
Member

#4860. ghosttty-web felt kinda unmaintained and we dont "gained" anything from the added xterm compatibility.

we'll keep the bundled fonts lean and aim to add configurable fonts in a follow up

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@PixPMusic@juliusmarminge