-
Notifications
You must be signed in to change notification settings - Fork 3.6k
feat(web): render terminals with libghostty-vt #4860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
juliusmarminge
merged 54 commits into
pingdotgg:main
from
StiensWout:agent/ghostty-web-terminal
Jul 31, 2026
+5,194
−495
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
49acd5a
feat(web): render terminals with libghostty-vt
StiensWout c4cb761
fix(web): harden Ghostty terminal interactions
StiensWout 6c2a882
fix(web): preserve terminal-native shortcuts
StiensWout 50d7145
fix(web): keep terminal selections stable
StiensWout 36e3129
fix(web): polish Ghostty terminal rendering
StiensWout c8db7a7
fix(web): forward terminal input events
StiensWout 9f552d3
fix(web): report terminal copy failures
StiensWout d72e2ca
fix(web): commit terminal composition once
StiensWout 5de75ee
fix(web): retain terminal composition guard
StiensWout 126cd30
fix(web): distinguish terminal composition input
StiensWout 92bd4ec
feat(web): finish Ghostty terminal interactions
StiensWout 8f08f6a
fix(web): harden Ghostty terminal lifecycle
StiensWout 43cb0e0
fix(web): retain Ghostty key text through encoding
StiensWout 8fd64a1
fix(web): handle Ghostty terminal edge cases
StiensWout 4e35993
fix(web): encode shifted Kitty keys
StiensWout e383d44
fix(web): respect active keyboard layouts
StiensWout a5ce471
fix(web): guard keyboard fallback by shift
StiensWout 6d748b6
test(web): cover remapped Kitty control keys
StiensWout 85c30c9
fix(web): keep Ghostty terminal input readable
StiensWout fa94040
fix(web): align Ghostty cursor with text
StiensWout 69ec0a2
fix(web): preserve terminal mouse defaults
StiensWout 374e1b4
fix(web): avoid terminal flicker while resizing
StiensWout ed1dc84
feat(web): add Ghostty scrollback scrollbar
StiensWout c0cf2be
fix(web): keep terminal text inside its cells
StiensWout e5af532
fix(web): clear stale Ghostty cursors
StiensWout 33cfc29
fix(web): refresh Ghostty scroll state before input
StiensWout 7930110
Merge branch 'main' into agent/ghostty-web-terminal
StiensWout 3dacf68
fix(web): report hover mouse motion only in any-event tracking
StiensWout 73a5323
fix(web): fall back to Nerd Font faces in the terminal canvas
StiensWout 6e17ad9
fix(web): run exit handling when attaching to exited terminals
StiensWout 91de531
Merge branch 'main' into agent/ghostty-web-terminal
StiensWout da91992
fix(web): polish Ghostty terminal scrolling, selection, and input
StiensWout 94e34c7
fix(web): polish terminal drawer lifecycle around the async surface
StiensWout 748d3a9
fix(web): swallow key releases for presses the terminal never saw
StiensWout 6fa11a7
fix(web): notify the PTY on first fit and keep keyup suppression acro…
StiensWout da2dbba
fix(web): always install the canvas DPR transform on first fit
StiensWout 0054869
Merge branch 'main' into agent/ghostty-web-terminal
StiensWout dcc5b37
fix(web): make the paste shortcut deterministic and refuse truncated …
StiensWout 69b11b1
fix(web): converge cached Ghostty source on the pinned revision
StiensWout 2bf617a
fix(web): use Ghostty's soft-wrap flag for links and keep native past…
StiensWout 16b4553
fix(web): only claim the paste race after the native paste delivers text
StiensWout ad3ec79
fix(web): suppress the browser's default paste insertion unconditionally
StiensWout 3dc5604
refactor(web): single-source the Ghostty pin through embedded build info
StiensWout 6a31d5e
feat(web): groundwork for configurable terminal fonts
StiensWout b72cc87
fix(web): survive WebKit table quirks, selection run splits, late fonts
StiensWout c94e6bf
fix(web): anchor the terminal grid to the bottom and serialize setFont
StiensWout 3cf380c
fix(web): repaint resizes synchronously and settle PTY dims after drags
StiensWout 7997cd1
feat(web): bundle the symbols-only Nerd Font for default glyph coverage
StiensWout e610992
fix(web): move the grid origin only together with a full repaint
StiensWout 124718c
fix(web): flush the pending PTY resize when the surface disposes
StiensWout 5da6b1f
fix(web): keep the resize debounce intact; flush only on dispose
StiensWout 2d2e6c5
fix(web): stop drawer and panel terminals from allocating the same id
StiensWout bba6211
fix(web): recreate terminal allocators when any allocation source cha…
StiensWout 0b3c1fc
fix(web): anchor to the bottom only once scrollback exists
StiensWout File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| WEB_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" | ||
| REPO_DIR="$(cd "${WEB_DIR}/../.." && pwd)" | ||
| MOBILE_VENDOR_DIR="${REPO_DIR}/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt" | ||
| VENDOR_DIR="${WEB_DIR}/src/terminal/ghostty/vendor" | ||
|
|
||
| GHOSTTY_REVISION="$(tr -d '[:space:]' < "${MOBILE_VENDOR_DIR}/VERSION")" | ||
| GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-${HOME}/.cache/t3code/ghostty-${GHOSTTY_REVISION:0:8}}" | ||
| GHOSTTY_ZIG_VERSION="${GHOSTTY_ZIG_VERSION:-0.15.2}" | ||
| GHOSTTY_ZIG="${GHOSTTY_ZIG:-}" | ||
|
|
||
| log() { | ||
| printf '[libghostty-vt-wasm] %s\n' "$*" | ||
| } | ||
|
|
||
| die() { | ||
| printf '[libghostty-vt-wasm] error: %s\n' "$*" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| require_cmd() { | ||
| command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" | ||
| } | ||
|
|
||
| ensure_zig() { | ||
| if [[ -n "${GHOSTTY_ZIG}" ]]; then | ||
| [[ -x "${GHOSTTY_ZIG}" ]] || die "GHOSTTY_ZIG is not executable: ${GHOSTTY_ZIG}" | ||
| return | ||
| fi | ||
| if command -v zig >/dev/null 2>&1 && [[ "$(zig version)" == "${GHOSTTY_ZIG_VERSION}" ]]; then | ||
| GHOSTTY_ZIG="$(command -v zig)" | ||
| return | ||
| fi | ||
|
|
||
| local host_os host_arch cache_dir | ||
| host_os="$(uname -s | tr '[:upper:]' '[:lower:]')" | ||
| host_arch="$(uname -m)" | ||
| case "${host_os}" in | ||
| darwin) host_os="macos" ;; | ||
| linux) ;; | ||
| *) die "unsupported host OS for Zig download: ${host_os}" ;; | ||
| esac | ||
| case "${host_arch}" in | ||
| arm64) host_arch="aarch64" ;; | ||
| aarch64 | x86_64) ;; | ||
| *) die "unsupported host architecture: ${host_arch}" ;; | ||
| esac | ||
|
|
||
| cache_dir="${HOME}/.cache/t3code/zig-${GHOSTTY_ZIG_VERSION}" | ||
| GHOSTTY_ZIG="${cache_dir}/zig" | ||
| if [[ -x "${GHOSTTY_ZIG}" ]]; then | ||
| return | ||
| fi | ||
|
|
||
| require_cmd curl | ||
| require_cmd tar | ||
| mkdir -p "${cache_dir}" | ||
| log "downloading Zig ${GHOSTTY_ZIG_VERSION}" | ||
| curl -fsSL \ | ||
| "https://ziglang.org/download/${GHOSTTY_ZIG_VERSION}/zig-${host_arch}-${host_os}-${GHOSTTY_ZIG_VERSION}.tar.xz" \ | ||
| | tar -xJ --strip-components=1 -C "${cache_dir}" | ||
| } | ||
|
|
||
| ensure_ghostty_source() { | ||
| require_cmd git | ||
| if [[ ! -d "${GHOSTTY_SOURCE_DIR}/.git" ]]; then | ||
| mkdir -p "$(dirname "${GHOSTTY_SOURCE_DIR}")" | ||
| log "cloning Ghostty ${GHOSTTY_REVISION}" | ||
| git clone --filter=blob:none --no-checkout https://github.com/ghostty-org/ghostty.git \ | ||
| "${GHOSTTY_SOURCE_DIR}" | ||
| fi | ||
|
|
||
| # A cached checkout may still be on a previously pinned revision; converge on | ||
| # the pinned one instead of failing the rebuild. | ||
| local actual_revision | ||
| actual_revision="$(git -C "${GHOSTTY_SOURCE_DIR}" rev-parse HEAD 2>/dev/null || echo none)" | ||
| if [[ "${actual_revision}" != "${GHOSTTY_REVISION}" ]]; then | ||
| log "checking out Ghostty ${GHOSTTY_REVISION}" | ||
| git -C "${GHOSTTY_SOURCE_DIR}" fetch --depth=1 origin "${GHOSTTY_REVISION}" | ||
| git -C "${GHOSTTY_SOURCE_DIR}" checkout --detach "${GHOSTTY_REVISION}" | ||
| fi | ||
|
|
||
| actual_revision="$(git -C "${GHOSTTY_SOURCE_DIR}" rev-parse HEAD)" | ||
| [[ "${actual_revision}" == "${GHOSTTY_REVISION}" ]] || \ | ||
| die "expected Ghostty ${GHOSTTY_REVISION}, found ${actual_revision}" | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| ensure_zig | ||
| ensure_ghostty_source | ||
|
|
||
| build_root="$(mktemp -d)" | ||
| trap 'rm -rf "${build_root}"' EXIT | ||
|
|
||
| log "building ${GHOSTTY_REVISION} for wasm32-freestanding" | ||
| ( | ||
| cd "${GHOSTTY_SOURCE_DIR}" | ||
| # The pinned revision rides along as semver build metadata so the artifact | ||
| # identifies its own provenance through ghostty_build_info(); mobile's | ||
| # VERSION file stays the single source of truth for the pin. | ||
| "${GHOSTTY_ZIG}" build \ | ||
| -Demit-lib-vt \ | ||
| -Dtarget=wasm32-freestanding \ | ||
| -Doptimize=ReleaseSmall \ | ||
| -Dstrip=true \ | ||
| -Dlib-version-string="0.1.0-dev+${GHOSTTY_REVISION}" \ | ||
| -p "${build_root}" | ||
| ) | ||
|
|
||
| mkdir -p "${VENDOR_DIR}" | ||
| cp "${build_root}/bin/ghostty-vt.wasm" "${VENDOR_DIR}/ghostty-vt.wasm" | ||
| "${GHOSTTY_ZIG}" build-exe \ | ||
| "${SCRIPT_DIR}/ghostty-write-pty.zig" \ | ||
| -target wasm32-freestanding \ | ||
| -O ReleaseSmall \ | ||
| -fno-entry \ | ||
| -rdynamic \ | ||
| -femit-bin="${VENDOR_DIR}/ghostty-write-pty.wasm" | ||
| chmod 0644 "${VENDOR_DIR}/ghostty-write-pty.wasm" | ||
| log "wrote ${VENDOR_DIR}/ghostty-vt.wasm" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| extern "env" fn t3_write_pty(terminal: u32, userdata: u32, data: u32, len: u32) void; | ||
|
|
||
| export fn ghostty_write_pty(terminal: u32, userdata: u32, data: u32, len: u32) void { | ||
| t3_write_pty(terminal, userdata, data, len); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.