From 90c38d02546062228a7dab421384280f5ba33a68 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 00:59:10 +0000 Subject: [PATCH 1/5] feat: pin Ghostty v1.4.0 so DECSTR soft resets apply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ghostty v1.4.0 needs Zig 0.17.0 and replaces two C calls. Terminal creation now takes cols and rows and sets scrollback through ghostty_terminal_set. The byte limit keeps the old scrollbackLimit behavior, including 0 for no history. Mode reads go through ghostty_terminal_get. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: I04864bff70e3982b1b76e833037e9afed8dfd574 Signed-off-by: Thomas Kosiewski --- .../skills/verify-libghostty-vt-node/SKILL.md | 2 +- .../scripts/launch.sh | 2 +- README.md | 6 ++-- mise.lock | 30 +++++++++---------- mise.toml | 2 +- native/README.md | 6 ++-- native/terminal.cc | 24 ++++++++------- scripts/README.md | 4 +-- scripts/build-libghostty-vt.sh | 4 +-- test/terminal.test.ts | 30 +++++++++++++++---- 10 files changed, 65 insertions(+), 45 deletions(-) diff --git a/.agents/skills/verify-libghostty-vt-node/SKILL.md b/.agents/skills/verify-libghostty-vt-node/SKILL.md index c05a4cf..520a569 100644 --- a/.agents/skills/verify-libghostty-vt-node/SKILL.md +++ b/.agents/skills/verify-libghostty-vt-node/SKILL.md @@ -17,7 +17,7 @@ S=.agents/skills/verify-libghostty-vt-node/scripts ## Requirements -- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.15.2` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.15.2`. +- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.17.0` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.15.2`. - A C++17 compiler and Python for `node-gyp`. - A checkout that mise trusts. In an untrusted copy, a mise shim that a build step calls fails with `Config files in .../mise.toml are not trusted`, and `launch.sh` exits `1`. - The network on the first launch: `npm ci`, the Ghostty clone in `npm run build:libghostty`, and the dependency install in the consumer project. diff --git a/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh b/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh index 90d3d2c..5951eb6 100755 --- a/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh +++ b/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh @@ -102,7 +102,7 @@ lib_dir="${LIBGHOSTTY_VT_PREFIX:-$ROOT/vendor/libghostty-vt}" lib_stamp="$lib_dir/.verify-libghostty-vt-node.sha256" lib_inputs="$( { cat scripts/build-libghostty-vt.sh; env | grep -E '^(GHOSTTY_|LIBGHOSTTY_VT_)' | sort || true; } | sha_stdin)" if [[ ! -f "$lib_dir/lib/libghostty-vt.a" || "$(cat "$lib_stamp" 2>/dev/null)" != "$lib_inputs" ]]; then - step "npm run build:libghostty (missing, or built from other inputs: needs zig 0.15.2 and the network)" + step "npm run build:libghostty (missing, or built from other inputs: needs zig 0.17.0 and the network)" run npm run build:libghostty echo "$lib_inputs" > "$lib_stamp" fi diff --git a/README.md b/README.md index 46b4118..9952b76 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ This bootstrap was written against: - libghostty Doxygen: - Node-API docs: +The build now pins Ghostty `v1.4.0` (`83fb95624b0439ee660c2679b43b38c8b63769cd`), which needs Zig 0.17.0. + The binding talks to `libghostty-vt` directly through the C API. It has no runtime dependency on `go-libghostty`. ## API @@ -63,8 +65,6 @@ All dimensions are validated as positive integers. Using a terminal after `dispo `snapshot()` reports the cursor position (`cursorRow`, `cursorCol`) and its DECTCEM visibility (`cursorVisible`, toggled by `ESC[?25h` and `ESC[?25l`). With `includeCells: true`, each cell reports its text, width, optional `foreground` and `background` colors, and these SGR attributes: `bold`, `faint`, `italic`, `underline`, `inverse`, `invisible`, and `strikethrough`. An attribute is present only when it is set. `inverse` is the raw SGR 7 flag: `foreground` and `background` are not swapped, so consumers apply reverse video with their own default colors. -The pinned Ghostty ignores the DECSTR soft reset (`ESC[!p`). After it, a hidden cursor stays hidden and SGR attributes stay set. Programs that reset with `ESC c` (RIS) are unaffected. - ### Mouse encoding `encodeMouse` returns a `Buffer` containing the terminal input bytes for one @@ -191,7 +191,7 @@ Manual dispatch and direct `v*.*.*` tag pushes are available for recovery, but m The native layer currently uses these verified `libghostty-vt` C APIs: -- terminal lifecycle and stream processing: `ghostty_terminal_new`, `ghostty_terminal_vt_write`, `ghostty_terminal_resize`, `ghostty_terminal_free` +- terminal lifecycle and stream processing: `ghostty_terminal_new`, `ghostty_terminal_set`, `ghostty_terminal_vt_write`, `ghostty_terminal_resize`, `ghostty_terminal_free` - metadata and state: `ghostty_terminal_get`, `ghostty_build_info` - mode-aware mouse input: `ghostty_mouse_encoder_*`, `ghostty_mouse_event_*` - plain/HTML debug formatting: `ghostty_formatter_terminal_new`, `ghostty_formatter_format_alloc` diff --git a/mise.lock b/mise.lock index 97efd01..a27e3a2 100644 --- a/mise.lock +++ b/mise.lock @@ -67,33 +67,33 @@ checksum = "sha256:b3efd5c70cba3308ed46e7e699a9b8aac075bbc6b027fcb39c84c31acc25e url = "https://nodejs.org/dist/v26.12.1/node-v26.12.1-win-x64.zip" [[tools.zig]] -version = "0.15.2" +version = "0.17.0" backend = "core:zig" [tools.zig."platforms.linux-arm64"] -checksum = "sha256:958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f" -url = "https://ziglang.org/download/0.15.2/zig-aarch64-linux-0.15.2.tar.xz" +checksum = "sha256:9e8d11661d4ae3bd57702a3832781e23ad151dde5798e16a5ccd503f65234ff8" +url = "https://ziglang.org/download/0.17.0/zig-aarch64-linux-0.17.0.tar.xz" [tools.zig."platforms.linux-arm64-musl"] -checksum = "sha256:958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f" -url = "https://ziglang.org/download/0.15.2/zig-aarch64-linux-0.15.2.tar.xz" +checksum = "sha256:9e8d11661d4ae3bd57702a3832781e23ad151dde5798e16a5ccd503f65234ff8" +url = "https://ziglang.org/download/0.17.0/zig-aarch64-linux-0.17.0.tar.xz" [tools.zig."platforms.linux-x64"] -checksum = "sha256:02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239" -url = "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz" +checksum = "sha256:1cbe9df9f27e6b78d14ccbca43b6703a404ef79ef1c463de901d7f088d4e2026" +url = "https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz" [tools.zig."platforms.linux-x64-musl"] -checksum = "sha256:02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239" -url = "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz" +checksum = "sha256:1cbe9df9f27e6b78d14ccbca43b6703a404ef79ef1c463de901d7f088d4e2026" +url = "https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz" [tools.zig."platforms.macos-arm64"] -checksum = "sha256:3cc2bab367e185cdfb27501c4b30b1b0653c28d9f73df8dc91488e66ece5fa6b" -url = "https://ziglang.org/download/0.15.2/zig-aarch64-macos-0.15.2.tar.xz" +checksum = "sha256:b607e9b9234790a008116ae5bdb71c6243b84b9fb42a53a9e70fde41c06c536a" +url = "https://ziglang.org/download/0.17.0/zig-aarch64-macos-0.17.0.tar.xz" [tools.zig."platforms.macos-x64"] -checksum = "sha256:375b6909fc1495d16fc2c7db9538f707456bfc3373b14ee83fdd3e22b3d43f7f" -url = "https://ziglang.org/download/0.15.2/zig-x86_64-macos-0.15.2.tar.xz" +checksum = "sha256:4f9a1c5269aa17ebda5e6d3c2b89d6cbf36f7d2b22a0306e9ab98f25f95529c6" +url = "https://ziglang.org/download/0.17.0/zig-x86_64-macos-0.17.0.tar.xz" [tools.zig."platforms.windows-x64"] -checksum = "sha256:3a0ed1e8799a2f8ce2a6e6290a9ff22e6906f8227865911fb7ddedc3cc14cb0c" -url = "https://ziglang.org/download/0.15.2/zig-x86_64-windows-0.15.2.zip" +checksum = "sha256:b5663f69581dcf391293fbf16c06cb80d81d806545ce618b4d0bab7f0eb8c428" +url = "https://ziglang.org/download/0.17.0/zig-x86_64-windows-0.17.0.zip" diff --git a/mise.toml b/mise.toml index 7eba29e..509444a 100644 --- a/mise.toml +++ b/mise.toml @@ -1,7 +1,7 @@ [tools] communique = "1.0.3" node = "26.12.1" -zig = "0.15.2" +zig = "0.17.0" [tasks.ci] description = "Install dependencies, build libghostty-vt/native/TypeScript, run tests, and run smoke." diff --git a/native/README.md b/native/README.md index f83b0ed..95f9a3d 100644 --- a/native/README.md +++ b/native/README.md @@ -39,7 +39,7 @@ C++ declarations: - `createTerminal` and the constructor throw `TypeError("createTerminal options must be an object")` unless they get exactly one object argument [native/terminal.cc:CreateTerminal]. - `cols` and `rows`: a non-number throws `TypeError(" must be a positive integer")`. `NaN`, an infinity, a value below 1 or above 65535, or a non-integer throws `RangeError(" must be a positive 16-bit integer")`. The range check runs before the value is cast to `uint16_t` [native/terminal.cc:PositiveUint16]. -- `scrollbackLimit` is skipped when it is absent or `undefined`, and then defaults to 0. A non-number throws `TypeError`. `NaN`, an infinity, a negative value, a non-integer, or a value too large for `size_t` (2^64 or more on 64-bit targets) throws `RangeError`. Both use `"scrollbackLimit must be a non-negative integer"`, and the range check runs before the cast [native/terminal.cc:NonNegativeSize]. +- `scrollbackLimit` is skipped when it is absent or `undefined`, and then defaults to 0. A non-number throws `TypeError`. `NaN`, an infinity, a negative value, a non-integer, or a value too large for `size_t` (2^64 or more on 64-bit targets) throws `RangeError`. Both use `"scrollbackLimit must be a non-negative integer"`, and the range check runs before the cast [native/terminal.cc:NonNegativeSize]. The value becomes Ghostty's scrollback byte limit (`GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES`), so 0 keeps no scrollback. Ghostty prunes by whole pages, so a small nonzero limit still keeps some history [native/terminal.cc:TerminalWrap::TerminalWrap]. - Most failed libghostty-vt calls throw an `Error` with `ResultMessage` text, for example `"ghostty_terminal_new failed: GHOSTTY_OUT_OF_MEMORY"` [native/terminal.cc:ThrowResult]. These do not throw: a failed palette read leaves the color out, a failed `ghostty_build_info` leaves the field out, a first `ghostty_grid_ref_graphemes` call that does not return `GHOSTTY_OUT_OF_SPACE` with `required > 0` falls back to the single codepoint, and the code checks no result from `ghostty_terminal_vt_write` [native/terminal.cc:TerminalWrap::ResolveStyleColor] [native/terminal.cc:TerminalWrap::ReadCellText] [native/terminal.cc:TerminalWrap::Feed]. - Every method except `dispose` and `encodeMouse` calls `RequireTerminal` first. After dispose it throws `Error("GhosttyVtTerminal has been disposed")` [native/terminal.cc:TerminalWrap::RequireTerminal]. With `NAPI_CPP_EXCEPTIONS`, `RequireTerminal` reports a disposed terminal by throwing. - `encodeMouse` reads and checks every argument first, and only then calls `RequireTerminal` and `RequireMouseEncoder` (`"GhosttyVtTerminal has been disposed"`). `Get()` runs getters, also getters on `Object.prototype` for keys that the caller leaves out, and such JS can dispose the terminal or call `encodeMouse` again. After the handles are taken, no JS runs until the call returns [native/terminal.cc:TerminalWrap::EncodeMouse]. @@ -64,7 +64,7 @@ C++ declarations: ## Side Effects -- The constructor allocates a native terminal with `ghostty_terminal_new(nullptr, &created, options)` (default allocator) and stores it in `terminal_`. Then it allocates a mouse encoder with `ghostty_mouse_encoder_new(nullptr, ...)` and stores it in `mouse_encoder_`. When that fails, it frees the terminal, sets `terminal_` to `nullptr`, and throws the `ResultMessage` error [native/terminal.cc:TerminalWrap::TerminalWrap]. +- The constructor allocates a native terminal with `ghostty_terminal_new(nullptr, &created, cols, rows)` (default allocator) and sets its scrollback byte limit with `ghostty_terminal_set`. When that fails, it frees the terminal and throws the `ResultMessage` error. Otherwise it stores the terminal in `terminal_`. Then it allocates a mouse encoder with `ghostty_mouse_encoder_new(nullptr, ...)` and stores it in `mouse_encoder_`. When that fails, it frees the terminal, sets `terminal_` to `nullptr`, and throws the `ResultMessage` error [native/terminal.cc:TerminalWrap::TerminalWrap]. - `DisposeNative` frees them with `ghostty_mouse_encoder_free` and `ghostty_terminal_free` [native/terminal.cc:TerminalWrap::DisposeNative]. The destructor calls `DisposeNative`, so garbage collection of an undisposed object also frees both [native/terminal.cc:TerminalWrap::~TerminalWrap]. - `feed` changes terminal state through `ghostty_terminal_vt_write` and sets `mouse_modes_dirty_` [native/terminal.cc:TerminalWrap::Feed]. `encodeMouse` changes the encoder options and its last-cell state, and allocates one `GhosttyMouseEvent` per call that passes its checks, which it frees on every path after that [native/terminal.cc:TerminalWrap::EncodeMouse]. `resize` changes the size through `ghostty_terminal_resize` [native/terminal.cc:TerminalWrap::Resize]. - `Format` creates a formatter, calls `ghostty_formatter_format_alloc`, frees the formatter, then frees the output buffer with `ghostty_free(nullptr, ptr, len)` [native/terminal.cc:TerminalWrap::Format]. @@ -76,5 +76,5 @@ C++ declarations: - Run (after `npm ci`): `npm run build:libghostty && npm run build:native && npm test` - Every native test skips when the addon does not load (`test/native-support.ts:nativeSupport`), so check the test output for skips. - `mise run ci-ubsan` builds the addon with UBSan (Linux, GCC), then runs the tests and smoke. Undefined behavior aborts the run. The task also fails when the addon's object files contain no UBSan checks, which means the flags did not reach g++ (`mise.toml:tasks.ci-ubsan`). -- `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, the `RangeError` cases of the `createTerminal` checks in the addon, the `encodeMouse` bytes of each mouse format, `cursorVisible`, and the cell style fields. `test/mouse-safety.test.ts` runs the `encodeMouse` limits and the getter re-entry cases against both the package API and the raw addon object. It skips when the addon is missing, like the other native suites. Its child-process cases load `src/` through `tsx`, so they need no `dist/` build. No vitest test covers `formatPlain`, `formatHtml`, `includeScrollback`, or the typed-array errors. +- `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, the `RangeError` cases of the `createTerminal` checks in the addon, the `encodeMouse` bytes of each mouse format, `cursorVisible`, the cell style fields, DECSTR, and whether `scrollbackLimit` keeps history. `test/mouse-safety.test.ts` runs the `encodeMouse` limits and the getter re-entry cases against both the package API and the raw addon object. It skips when the addon is missing, like the other native suites. Its child-process cases load `src/` through `tsx`, so they need no `dist/` build. No vitest test covers `formatPlain`, `formatHtml`, the content of `scrollbackLines`, or the typed-array errors. - For changes to `TerminalWrap` or `InitAll`, also run the TLA+ lifecycle model: [`.agents/skills/verify-native-lifecycle/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-native-lifecycle/SKILL.md). diff --git a/native/terminal.cc b/native/terminal.cc index e095007..3105fae 100644 --- a/native/terminal.cc +++ b/native/terminal.cc @@ -225,17 +225,19 @@ TerminalWrap::TerminalWrap(const Napi::CallbackInfo& info) scrollback = NonNegativeSize(env, options.Get("scrollbackLimit"), "scrollbackLimit"); } - GhosttyTerminalOptions native_options = {}; - native_options.cols = cols; - native_options.rows = rows; - native_options.max_scrollback = scrollback; - GhosttyTerminal created = nullptr; - const GhosttyResult result = ghostty_terminal_new(nullptr, &created, native_options); + GhosttyResult result = ghostty_terminal_new(nullptr, &created, cols, rows); if (result != GHOSTTY_SUCCESS) { ThrowResult(env, "ghostty_terminal_new", result); } assert(created != nullptr); + // The byte limit, not the line limit, matches the pre-1.4 max_scrollback, + // which Ghostty counted in bytes and which disables scrollback at 0. + result = ghostty_terminal_set(created, GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES, &scrollback); + if (result != GHOSTTY_SUCCESS) { + ghostty_terminal_free(created); + ThrowResult(env, "ghostty_terminal_set(scrollback max bytes)", result); + } terminal_ = created; GhosttyMouseEncoder mouse_encoder = nullptr; @@ -383,11 +385,11 @@ Napi::Value TerminalWrap::EncodeMouse(const Napi::CallbackInfo& info) { GhosttyTerminal terminal = RequireTerminal(env); GhosttyMouseEncoder encoder = RequireMouseEncoder(env); - bool utf8_mouse = false; - const GhosttyResult mode_result = ghostty_terminal_mode_get( - terminal, GHOSTTY_MODE_UTF8_MOUSE, &utf8_mouse); - if (mode_result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_mode_get", mode_result); - if (utf8_mouse) { + GhosttyTerminalModeConfig utf8_mouse = {GHOSTTY_MODE_UTF8_MOUSE, false}; + const GhosttyResult mode_result = + ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_MODE, &utf8_mouse); + if (mode_result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(mode)", mode_result); + if (utf8_mouse.value) { // Mode bits cannot identify the last selected format. Conservatively guard // these exceptional cells whenever 1005 is set, even if another format was // selected later. Ordinary geometry and SGR-only large grids are unchanged. diff --git a/scripts/README.md b/scripts/README.md index 2d88d48..ac71044 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -19,8 +19,8 @@ In the `.mjs` scripts, a failed `assert` or a thrown `Error` ends the process wi ## Invariants -- The default Ghostty commit is `48ccec182a932c2ec04c344d45a5fc553861cb13`. `GHOSTTY_COMMIT` overrides it [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT]. -- `build-libghostty-vt.sh` exits 1 with `"error: missing required command ''"` and `"Install it first, then rerun npm run build:libghostty."` when `git` or `zig` is not on `PATH`. For `zig` it also prints `"This Ghostty commit requires Zig 0.15.2. With mise: mise install"` [scripts/build-libghostty-vt.sh:need]. +- The default Ghostty commit is `83fb95624b0439ee660c2679b43b38c8b63769cd`. `GHOSTTY_COMMIT` overrides it [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT]. +- `build-libghostty-vt.sh` exits 1 with `"error: missing required command ''"` and `"Install it first, then rerun npm run build:libghostty."` when `git` or `zig` is not on `PATH`. For `zig` it also prints `"This Ghostty commit requires Zig 0.17.0. With mise: mise install"` [scripts/build-libghostty-vt.sh:need]. - `build-libghostty-vt.sh` exits 1 when `zig version` differs from `minimum_zig_version` in Ghostty's `build.zig.zon`. It skips this check when that file names no version [scripts/build-libghostty-vt.sh:required_zig]. - `build-libghostty-vt.sh` runs `zig build -Demit-lib-vt=true -Dsimd=$SIMD -Doptimize=$OPTIMIZE --prefix $PREFIX install`, with `--sysroot ` on Darwin when `xcrun` returns a path. A failed build exits 1 [scripts/build-libghostty-vt.sh:zig_args]. - After the build, `$PREFIX/include/ghostty/vt.h` and `$PREFIX/lib/libghostty-vt.a` must exist. Otherwise the script exits 1 [scripts/build-libghostty-vt.sh:header]. diff --git a/scripts/build-libghostty-vt.sh b/scripts/build-libghostty-vt.sh index a49b5cc..08da6c6 100755 --- a/scripts/build-libghostty-vt.sh +++ b/scripts/build-libghostty-vt.sh @@ -3,7 +3,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" GHOSTTY_REPO="${GHOSTTY_REPO:-https://github.com/ghostty-org/ghostty.git}" -GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-48ccec182a932c2ec04c344d45a5fc553861cb13}" +GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-83fb95624b0439ee660c2679b43b38c8b63769cd}" GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-$ROOT/vendor/ghostty}" PREFIX="${LIBGHOSTTY_VT_PREFIX:-$ROOT/vendor/libghostty-vt}" SIMD="${LIBGHOSTTY_VT_SIMD:-false}" @@ -14,7 +14,7 @@ need() { echo "error: missing required command '$1'" >&2 echo "Install it first, then rerun npm run build:libghostty." >&2 if [[ "$1" == "zig" ]]; then - echo "This Ghostty commit requires Zig 0.15.2. With mise: mise install" >&2 + echo "This Ghostty commit requires Zig 0.17.0. With mise: mise install" >&2 fi exit 1 fi diff --git a/test/terminal.test.ts b/test/terminal.test.ts index b4d599f..717e270 100644 --- a/test/terminal.test.ts +++ b/test/terminal.test.ts @@ -133,15 +133,33 @@ describeIfNative("GhosttyVtTerminal", () => { } }); - // Ghostty added DECSTR in f473f31. Moving the pin past it makes this fail: - // then drop the README's soft-reset limit and assert the reset instead. - it("ignores the DECSTR soft reset with the pinned Ghostty", () => { + it("shows the cursor and resets the pen on DECSTR, keeping the screen", () => { const term = createTerminal({ cols: 80, rows: 24 }); try { - term.feed("\x1b[?25l\x1b[7m\x1b[!pX"); + term.feed("\x1b[?25l\x1b[7mA\x1b[!pX"); const snapshot = term.snapshot({ includeCells: true }); - expect(snapshot.cursorVisible).toBe(false); - expect(cellsByText(snapshot).get("X")?.inverse).toBe(true); + const cells = cellsByText(snapshot); + expect(snapshot.cursorVisible).toBe(true); + expect(cells.get("A")?.inverse).toBe(true); + expect(cells.get("X")).not.toHaveProperty("inverse"); + expect(snapshot.visibleLines[0]?.text).toBe("AX"); + } finally { + term.dispose(); + } + }); + + it.each([ + { scrollbackLimit: undefined, keepsHistory: false }, + { scrollbackLimit: 0, keepsHistory: false }, + { scrollbackLimit: 1, keepsHistory: true }, + ])("keeps scrollback only for a nonzero scrollbackLimit ($scrollbackLimit)", ({ scrollbackLimit, keepsHistory }) => { + const options: CreateTerminalOptions = { cols: 80, rows: 24 }; + if (scrollbackLimit !== undefined) options.scrollbackLimit = scrollbackLimit; + const term = createTerminal(options); + try { + term.feed(Array.from({ length: 200 }, (_, i) => `line ${i}\r\n`).join("")); + const scrollback = term.snapshot({ includeScrollback: true }).scrollbackLines ?? []; + expect(scrollback.length > 0).toBe(keepsHistory); } finally { term.dispose(); } From aa49c04447111e03456c6e022adf0bae5a6634f5 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 01:10:20 +0000 Subject: [PATCH 2/5] docs: rerun the dogfood bundle on Ghostty v1.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verify skill's fallback PATH named Zig 0.15.2, which the new pin rejects. dogfood/regenerate.sh reruns commands.sh and rewrites the bundle, which now records a passing build on the new pin. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: Ib17d40e43d656e1d2e6185959c2af87b31501e8c Signed-off-by: Thomas Kosiewski --- .../skills/verify-libghostty-vt-node/SKILL.md | 2 +- dogfood/README.md | 4 +- dogfood/bootstrap/build-libghostty-output.txt | 66 +---- dogfood/bootstrap/build-native-output.txt | 65 +++-- dogfood/bootstrap/commands.sh | 2 +- dogfood/bootstrap/environment.txt | 17 +- dogfood/bootstrap/native-info.json | 8 +- dogfood/bootstrap/npm-pack-output.txt | 48 ++-- dogfood/bootstrap/session.log | 243 +++++++++++++++--- dogfood/bootstrap/smoke-output.txt | 68 +++-- dogfood/bootstrap/test-output.txt | 19 +- dogfood/regenerate.sh | 23 ++ 12 files changed, 365 insertions(+), 200 deletions(-) create mode 100755 dogfood/regenerate.sh diff --git a/.agents/skills/verify-libghostty-vt-node/SKILL.md b/.agents/skills/verify-libghostty-vt-node/SKILL.md index 520a569..caee26b 100644 --- a/.agents/skills/verify-libghostty-vt-node/SKILL.md +++ b/.agents/skills/verify-libghostty-vt-node/SKILL.md @@ -17,7 +17,7 @@ S=.agents/skills/verify-libghostty-vt-node/scripts ## Requirements -- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.17.0` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.15.2`. +- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.17.0` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.17.0`. - A C++17 compiler and Python for `node-gyp`. - A checkout that mise trusts. In an untrusted copy, a mise shim that a build step calls fails with `Config files in .../mise.toml are not trusted`, and `launch.sh` exits `1`. - The network on the first launch: `npm ci`, the Ghostty clone in `npm run build:libghostty`, and the dependency install in the consumer project. diff --git a/dogfood/README.md b/dogfood/README.md index 7658283..91ce66d 100644 --- a/dogfood/README.md +++ b/dogfood/README.md @@ -1,6 +1,8 @@ # Dogfood Proof Bundle -`dogfood/bootstrap/` records the commands and local outputs from the repository bootstrap. The bundle is intentionally plain text so a reviewer can inspect what was attempted without reproducing the environment first. +`dogfood/bootstrap/` records the commands and local outputs from the repository bootstrap, rerun after each Ghostty pin change. The bundle is intentionally plain text so a reviewer can inspect what was attempted without reproducing the environment first. + +To rerun it, run `dogfood/regenerate.sh` from a checkout with the `mise.toml` toolchain. It runs `commands.sh` step by step and rewrites the output files and `environment.txt`. Expected files: diff --git a/dogfood/bootstrap/build-libghostty-output.txt b/dogfood/bootstrap/build-libghostty-output.txt index 86c1a6d..deffa46 100644 --- a/dogfood/bootstrap/build-libghostty-output.txt +++ b/dogfood/bootstrap/build-libghostty-output.txt @@ -1,63 +1,11 @@ +$ npm run build:libghostty -> @coder/libghostty-vt-node@0.1.0 build:libghostty +> @coder/libghostty-vt-node@0.1.0-beta.1 build:libghostty > scripts/build-libghostty-vt.sh From https://github.com/ghostty-org/ghostty - * branch 48ccec182a932c2ec04c344d45a5fc553861cb13 -> FETCH_HEAD -HEAD is now at 48ccec182 ci: fix vouch-check-issue to checkout the template file (#12412) -Building libghostty-vt at Ghostty commit 48ccec182a932c2ec04c344d45a5fc553861cb13 -error: undefined symbol: __availability_version_check - note: referenced by /Users/thomask33/.cache/zig/o/8cfa15f510d85730135433e2f42ddfd8/libcompiler_rt.a(libcompiler_rt_zcu.o):___isPlatformVersionAtLeast -error: undefined symbol: _abort - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.abort -error: undefined symbol: _arc4random_buf - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_crypto.tlcsprng.tlsCsprngFill -error: undefined symbol: _bzero - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged(usize,*debug.SelfInfo.Module__struct_3895,hash_map.AutoContext(usize),80).initMetadatas - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged([]const u8,u64,hash_map.StringContext,80).initMetadatas - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged([]const u8,debug.SelfInfo.Module__struct_3895.OFileInfo,hash_map.StringContext,80).initMetadatas - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait - note: referenced 14 more times -error: undefined symbol: _clock_gettime - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.clock_gettime -error: undefined symbol: _dispatch_queue_create - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.init -error: undefined symbol: _dispatch_semaphore_create - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.init -error: undefined symbol: _dispatch_semaphore_signal - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.eventCallback -error: undefined symbol: _dispatch_semaphore_wait - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait -error: undefined symbol: _dispatch_time - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait -error: undefined symbol: _exit - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.exit -error: undefined symbol: _fcopyfile - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_fs.File.Writer.sendFile -error: undefined symbol: _fork - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.fork -error: undefined symbol: _free - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedFree -error: undefined symbol: _getcwd - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.getcwd -error: undefined symbol: _getenv - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.getenvZ -error: undefined symbol: _isatty - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.isatty -error: undefined symbol: _malloc_size - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedAllocSize -error: undefined symbol: _posix_memalign - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedAlloc -error: undefined symbol: _realpath$DARWIN_EXTSN - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.realpathZ -error: undefined symbol: _sigaction - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sigaction -error: undefined symbol: _sigemptyset - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sigemptyset -error: undefined symbol: _sysctlbyname - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sysctlbynameZ -error: undefined symbol: _waitpid - note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.waitpid -error: failed to build libghostty-vt from Ghostty 48ccec182a932c2ec04c344d45a5fc553861cb13 -Verify that Zig 0.15.2 can build a simple project on this machine and that the platform SDK is discoverable. -On macOS, check `xcrun --show-sdk-path` and the Xcode command line tools selected by `xcode-select -p`. + * branch 83fb95624b0439ee660c2679b43b38c8b63769cd -> FETCH_HEAD +HEAD is now at 83fb95624 Bump version to 1.4.0 +Building libghostty-vt at Ghostty commit 83fb95624b0439ee660c2679b43b38c8b63769cd +Installed libghostty-vt into /home/coder/.mux/src/libghostty-vt-node/tough-seals-think/vendor/libghostty-vt +exit=0 diff --git a/dogfood/bootstrap/build-native-output.txt b/dogfood/bootstrap/build-native-output.txt index a5fa5f6..b4bca52 100644 --- a/dogfood/bootstrap/build-native-output.txt +++ b/dogfood/bootstrap/build-native-output.txt @@ -1,30 +1,31 @@ +$ npm run build:native -> @coder/libghostty-vt-node@0.1.0 build:native +> @coder/libghostty-vt-node@0.1.0-beta.1 build:native > node-gyp rebuild gyp info it worked if it ends with ok -gyp info using node-gyp@12.3.0 -gyp info using node@25.9.0 | darwin | arm64 -gyp info find Python using Python version 3.14.4 found at "/opt/homebrew/opt/python@3.14/bin/python3.14" +gyp info using node-gyp@12.4.0 +gyp info using node@22.23.4 | linux | x64 +gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3" -gyp info spawn /opt/homebrew/opt/python@3.14/bin/python3.14 +gyp info spawn /usr/bin/python3 gyp info spawn args [ -gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/gyp/gyp_main.py', +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/gyp/gyp_main.py', gyp info spawn args 'binding.gyp', gyp info spawn args '-f', gyp info spawn args 'make', gyp info spawn args '-I', -gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/build/config.gypi', +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build/config.gypi', gyp info spawn args '-I', -gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/addon.gypi', +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi', gyp info spawn args '-I', -gyp info spawn args '/Users/thomask33/Library/Caches/node-gyp/25.9.0/include/node/common.gypi', +gyp info spawn args '/home/coder/.cache/node-gyp/22.23.4/include/node/common.gypi', gyp info spawn args '-Dlibrary=shared_library', gyp info spawn args '-Dvisibility=default', -gyp info spawn args '-Dnode_root_dir=/Users/thomask33/Library/Caches/node-gyp/25.9.0', -gyp info spawn args '-Dnode_gyp_dir=/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp', -gyp info spawn args '-Dnode_lib_file=/Users/thomask33/Library/Caches/node-gyp/25.9.0/<(target_arch)/node.lib', -gyp info spawn args '-Dmodule_root_dir=/Users/thomask33/Projects/libghostty-vt-node', +gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/22.23.4', +gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp', +gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/22.23.4/<(target_arch)/node.lib', +gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think', gyp info spawn args '-Dnode_engine=v8', gyp info spawn args '--depth=.', gyp info spawn args '--no-parallel', @@ -32,26 +33,18 @@ gyp info spawn args '--generator-output', gyp info spawn args 'build', gyp info spawn args '-Goutput_dir=.' gyp info spawn args ] -@coder/libghostty-vt-node: missing header ghostty/vt.h - -Expected a local libghostty-vt installation. Either: - 1. run `npm run build:libghostty`, or - 2. set LIBGHOSTTY_VT_PREFIX=/path/to/prefix, or - 3. set LIBGHOSTTY_VT_INCLUDE_DIR and LIBGHOSTTY_VT_STATIC_LIB. - -Checked include dir: /Users/thomask33/Projects/libghostty-vt-node/vendor/libghostty-vt/include -Checked static lib: /Users/thomask33/Projects/libghostty-vt-node/vendor/libghostty-vt/lib/libghostty-vt.a -gyp: Call to 'node scripts/resolve-libghostty-vt.mjs include' returned exit status 1 while in binding.gyp. while trying to load binding.gyp -gyp ERR! configure error -gyp ERR! stack Error: `gyp` failed with exit code: 1 -gyp ERR! stack at ChildProcess. (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/lib/configure.js:317:18) -gyp ERR! stack at ChildProcess.emit (node:events:509:20) -gyp ERR! stack at ChildProcess._handle.onexit (node:internal/child_process:294:12) -gyp ERR! System Darwin 25.4.0 -gyp ERR! command "/Users/thomask33/.local/share/mise/installs/node/25.9.0/bin/node" "/Users/thomask33/Projects/libghostty-vt-node/node_modules/.bin/node-gyp" "rebuild" -gyp ERR! cwd /Users/thomask33/Projects/libghostty-vt-node -gyp ERR! node -v v25.9.0 -gyp ERR! node-gyp -v v12.3.0 -gyp ERR! $npm_package_name @coder/libghostty-vt-node -gyp ERR! $npm_package_version 0.1.0 -gyp ERR! not ok +gyp info spawn make +gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ] +make: Entering directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build' + CC(target) Release/obj.target/nothing/node_modules/node-addon-api/nothing.o +rm -f Release/obj.target/node_modules/node-addon-api/nothing.a Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list; mkdir -p `dirname Release/obj.target/node_modules/node-addon-api/nothing.a` +ar crs Release/obj.target/node_modules/node-addon-api/nothing.a @Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list + COPY Release/nothing.a + CXX(target) Release/obj.target/libghostty_vt_node/native/addon.o + CXX(target) Release/obj.target/libghostty_vt_node/native/libghostty_vt_shim.o + CXX(target) Release/obj.target/libghostty_vt_node/native/terminal.o + SOLINK_MODULE(target) Release/obj.target/ghostty_vt_node.node + COPY Release/ghostty_vt_node.node +make: Leaving directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build' +gyp info ok +exit=0 diff --git a/dogfood/bootstrap/commands.sh b/dogfood/bootstrap/commands.sh index 094d29a..0496955 100755 --- a/dogfood/bootstrap/commands.sh +++ b/dogfood/bootstrap/commands.sh @@ -3,9 +3,9 @@ set -u npm install npm run build -npm test npm run build:libghostty npm run build:native +npm test npm run smoke npm pack --dry-run node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })" diff --git a/dogfood/bootstrap/environment.txt b/dogfood/bootstrap/environment.txt index 8bfca00..c5e1d07 100644 --- a/dogfood/bootstrap/environment.txt +++ b/dogfood/bootstrap/environment.txt @@ -1,10 +1,7 @@ -date=2026-04-24T14:06:15Z -cwd=/Users/thomask33/Projects/libghostty-vt-node -node=v25.9.0 -npm=11.12.1 -zig=0.15.2 -platform=Darwin Mac.fritz.box 25.4.0 Darwin Kernel Version 25.4.0: Thu Mar 19 19:31:56 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T6030 arm64 -xcode_path=/Applications/Xcode.app/Contents/Developer -sdkroot=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -ghostty_commit=48ccec182a932c2ec04c344d45a5fc553861cb13 -go_libghostty_commit=542c76ff595ad533348e23aac74489ad095e0b36 +date=2026-10-30T01:10:05Z +node=v26.12.1 +npm=11.20.0 +zig=0.17.0 +platform=Linux 6.8.0-139-generic x86_64 +ghostty_commit=83fb95624b0439ee660c2679b43b38c8b63769cd +git_head=90c38d02546062228a7dab421384280f5ba33a68 diff --git a/dogfood/bootstrap/native-info.json b/dogfood/bootstrap/native-info.json index 32036e0..934790b 100644 --- a/dogfood/bootstrap/native-info.json +++ b/dogfood/bootstrap/native-info.json @@ -1,5 +1,7 @@ { - "ok": false, - "error": "Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX.", - "cause": "No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0\n loaded from: /Users/thomask33/Projects/libghostty-vt-node\n" + "napiVersion": 10, + "ghosttyVersion": "0.1.0-dev", + "packageVersion": "0.1.0-beta.1", + "platform": "linux", + "arch": "x64" } diff --git a/dogfood/bootstrap/npm-pack-output.txt b/dogfood/bootstrap/npm-pack-output.txt index 04200a3..64b6751 100644 --- a/dogfood/bootstrap/npm-pack-output.txt +++ b/dogfood/bootstrap/npm-pack-output.txt @@ -1,38 +1,46 @@ +$ npm pack --dry-run npm notice -npm notice 📦 @coder/libghostty-vt-node@0.1.0 +npm notice package: @coder/libghostty-vt-node@0.1.0-beta.1 npm notice Tarball Contents +npm notice 968B CHANGELOG.md npm notice 1.1kB LICENSE -npm notice 5.6kB README.md +npm notice 16.9kB README.md npm notice 1.2kB binding.gyp -npm notice 418B dist/index.d.ts -npm notice 376B dist/index.d.ts.map -npm notice 3.2kB dist/index.js -npm notice 3.1kB dist/index.js.map +npm notice 648B dist/index.d.ts +npm notice 485B dist/index.d.ts.map +npm notice 8.6kB dist/index.js +npm notice 7.7kB dist/index.js.map npm notice 325B dist/loader.d.ts npm notice 334B dist/loader.d.ts.map npm notice 2.0kB dist/loader.js npm notice 1.7kB dist/loader.js.map -npm notice 1.7kB dist/types.d.ts -npm notice 1.9kB dist/types.d.ts.map +npm notice 3.3kB dist/types.d.ts +npm notice 3.1kB dist/types.d.ts.map npm notice 44B dist/types.js npm notice 102B dist/types.js.map npm notice 346B native/addon.cc npm notice 2.3kB native/libghostty_vt_shim.cc npm notice 549B native/libghostty_vt_shim.hh -npm notice 19.6kB native/terminal.cc -npm notice 1.8kB native/terminal.hh -npm notice 1.5kB package.json +npm notice 18.1kB native/README.md +npm notice 34.7kB native/terminal.cc +npm notice 2.1kB native/terminal.hh +npm notice 2.0kB package.json +npm notice 3.8kB scripts/assemble-prebuilds.mjs npm notice 2.9kB scripts/build-libghostty-vt.sh +npm notice 2.7kB scripts/create-release-tag.mjs +npm notice 11.2kB scripts/README.md npm notice 1.8kB scripts/resolve-libghostty-vt.mjs -npm notice 755B scripts/verify-prebuilds.mjs +npm notice 1.9kB scripts/resolve-npm-publish-tag.mjs +npm notice 1.5kB scripts/verify-prebuilds.mjs npm notice Tarball Details npm notice name: @coder/libghostty-vt-node -npm notice version: 0.1.0 -npm notice filename: coder-libghostty-vt-node-0.1.0.tgz -npm notice package size: 14.9 kB -npm notice unpacked size: 54.8 kB -npm notice shasum: 3f0dcbb66cc2931a58ee4954b68d00717b2e4d9e -npm notice integrity: sha512-GA2ucjo7iJW24[...]bQWFxo7mLm8KA== -npm notice total files: 24 +npm notice version: 0.1.0-beta.1 +npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz +npm notice package size: 36.6 kB +npm notice unpacked size: 134.2 kB +npm notice shasum: 7454ce26d89bc4bf57c09fc8b95e4f3af5379699 +npm notice integrity: sha512-HN9NJw4gfkESq[...]FI+TqQBKfzbsg== +npm notice total files: 30 npm notice -coder-libghostty-vt-node-0.1.0.tgz +coder-libghostty-vt-node-0.1.0-beta.1.tgz +exit=0 diff --git a/dogfood/bootstrap/session.log b/dogfood/bootstrap/session.log index 0635915..7954310 100644 --- a/dogfood/bootstrap/session.log +++ b/dogfood/bootstrap/session.log @@ -1,41 +1,202 @@ -^D -> @coder/libghostty-vt-node@0.1.0 test -> vitest run - -[?25l - RUN v4.1.5 /Users/thomask33/Projects/libghostty-vt-node - - - Test Files 2 passed (2) - Tests 2 passed | 4 skipped (6) - Start at 16:06:30 - Duration 118ms (transform 49ms, setup 0ms, import 71ms, tests 4ms, environment 0ms) - -[?25h⠙ -> @coder/libghostty-vt-node@0.1.0 smoke -> tsx examples/smoke.ts - -/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39 - throw new Error( - ^ - -Error: Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX. - at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39:11) - at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23) - ... 3 lines matching cause stack trace ... - at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) { - [cause]: Error: No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0 - loaded from: /Users/thomask33/Projects/libghostty-vt-node - - at load.resolve.load.path (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:60:9) - at load (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:22:30) - at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:34:19) - at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23) - at (/Users/thomask33/Projects/libghostty-vt-node/examples/smoke.ts:3:14) - at ModuleJob.run (node:internal/modules/esm/module_job:437:25) - at async node:internal/modules/esm/loader:639:26 - at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) -} - -Node.js v25.9.0 -⠙ \ No newline at end of file +$ npm install + +> @coder/libghostty-vt-node@0.1.0-beta.1 install +> node -e "console.log('native build is explicit: run npm run build:native')" + +native build is explicit: run npm run build:native + +up to date, audited 93 packages in 481ms + +22 packages are looking for funding + run `npm fund` for details + +found 0 vulnerabilities +npm warn install-scripts 1 package has install scripts not yet covered by allowScripts: +npm warn install-scripts esbuild@0.28.2 (postinstall: node install.js) +npm warn install-scripts +npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve ` to allow. +exit=0 +$ npm run build + +> @coder/libghostty-vt-node@0.1.0-beta.1 build +> tsc -p tsconfig.json + +exit=0 +$ npm run build:libghostty + +> @coder/libghostty-vt-node@0.1.0-beta.1 build:libghostty +> scripts/build-libghostty-vt.sh + +From https://github.com/ghostty-org/ghostty + * branch 83fb95624b0439ee660c2679b43b38c8b63769cd -> FETCH_HEAD +HEAD is now at 83fb95624 Bump version to 1.4.0 +Building libghostty-vt at Ghostty commit 83fb95624b0439ee660c2679b43b38c8b63769cd +Installed libghostty-vt into /home/coder/.mux/src/libghostty-vt-node/tough-seals-think/vendor/libghostty-vt +exit=0 +$ npm run build:native + +> @coder/libghostty-vt-node@0.1.0-beta.1 build:native +> node-gyp rebuild + +gyp info it worked if it ends with ok +gyp info using node-gyp@12.4.0 +gyp info using node@22.23.4 | linux | x64 +gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3" + +gyp info spawn /usr/bin/python3 +gyp info spawn args [ +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/gyp/gyp_main.py', +gyp info spawn args 'binding.gyp', +gyp info spawn args '-f', +gyp info spawn args 'make', +gyp info spawn args '-I', +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build/config.gypi', +gyp info spawn args '-I', +gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi', +gyp info spawn args '-I', +gyp info spawn args '/home/coder/.cache/node-gyp/22.23.4/include/node/common.gypi', +gyp info spawn args '-Dlibrary=shared_library', +gyp info spawn args '-Dvisibility=default', +gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/22.23.4', +gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp', +gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/22.23.4/<(target_arch)/node.lib', +gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think', +gyp info spawn args '-Dnode_engine=v8', +gyp info spawn args '--depth=.', +gyp info spawn args '--no-parallel', +gyp info spawn args '--generator-output', +gyp info spawn args 'build', +gyp info spawn args '-Goutput_dir=.' +gyp info spawn args ] +gyp info spawn make +gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ] +make: Entering directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build' + CC(target) Release/obj.target/nothing/node_modules/node-addon-api/nothing.o +rm -f Release/obj.target/node_modules/node-addon-api/nothing.a Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list; mkdir -p `dirname Release/obj.target/node_modules/node-addon-api/nothing.a` +ar crs Release/obj.target/node_modules/node-addon-api/nothing.a @Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list + COPY Release/nothing.a + CXX(target) Release/obj.target/libghostty_vt_node/native/addon.o + CXX(target) Release/obj.target/libghostty_vt_node/native/libghostty_vt_shim.o + CXX(target) Release/obj.target/libghostty_vt_node/native/terminal.o + SOLINK_MODULE(target) Release/obj.target/ghostty_vt_node.node + COPY Release/ghostty_vt_node.node +make: Leaving directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build' +gyp info ok +exit=0 +$ npm test + +> @coder/libghostty-vt-node@0.1.0-beta.1 test +> vitest run + + + RUN  v4.1.12 /home/coder/.mux/src/libghostty-vt-node/tough-seals-think + + ✓ test/load.test.ts (1 test) 5ms + ✓ test/terminal.test.ts (32 tests) 21ms + ✓ test/workers.test.ts (3 tests) 304ms + ✓ test/mouse-safety.test.ts (12 tests) 436ms + ✓ test/scripts.test.ts (24 tests) 1261ms + + Test Files  5 passed (5) + Tests  72 passed (72) + Start at  01:10:03 + Duration  1.45s (transform 208ms, setup 0ms, import 352ms, tests 2.03s, environment 0ms) + +exit=0 +$ npm run smoke + +> @coder/libghostty-vt-node@0.1.0-beta.1 smoke +> tsx examples/smoke.ts + +native info +{ + "napiVersion": 10, + "ghosttyVersion": "0.1.0-dev", + "packageVersion": "0.1.0-beta.1", + "platform": "linux", + "arch": "x64" +} +visible text +hello + red text + cursor + + + + + + + + + + + + + + + + + + + + + +mouse bytes +[27,91,60,48,59,53,59,54,77] +snapshot summary +{ + "cols": 80, + "rows": 24, + "cursorRow": 2, + "cursorCol": 10, + "isAltScreen": false, + "visibleLineCount": 24, + "cellCount": 19 +} +exit=0 +$ npm pack --dry-run +npm notice +npm notice package: @coder/libghostty-vt-node@0.1.0-beta.1 +npm notice Tarball Contents +npm notice 968B CHANGELOG.md +npm notice 1.1kB LICENSE +npm notice 16.9kB README.md +npm notice 1.2kB binding.gyp +npm notice 648B dist/index.d.ts +npm notice 485B dist/index.d.ts.map +npm notice 8.6kB dist/index.js +npm notice 7.7kB dist/index.js.map +npm notice 325B dist/loader.d.ts +npm notice 334B dist/loader.d.ts.map +npm notice 2.0kB dist/loader.js +npm notice 1.7kB dist/loader.js.map +npm notice 3.3kB dist/types.d.ts +npm notice 3.1kB dist/types.d.ts.map +npm notice 44B dist/types.js +npm notice 102B dist/types.js.map +npm notice 346B native/addon.cc +npm notice 2.3kB native/libghostty_vt_shim.cc +npm notice 549B native/libghostty_vt_shim.hh +npm notice 18.1kB native/README.md +npm notice 34.7kB native/terminal.cc +npm notice 2.1kB native/terminal.hh +npm notice 2.0kB package.json +npm notice 3.8kB scripts/assemble-prebuilds.mjs +npm notice 2.9kB scripts/build-libghostty-vt.sh +npm notice 2.7kB scripts/create-release-tag.mjs +npm notice 11.2kB scripts/README.md +npm notice 1.8kB scripts/resolve-libghostty-vt.mjs +npm notice 1.9kB scripts/resolve-npm-publish-tag.mjs +npm notice 1.5kB scripts/verify-prebuilds.mjs +npm notice Tarball Details +npm notice name: @coder/libghostty-vt-node +npm notice version: 0.1.0-beta.1 +npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz +npm notice package size: 36.6 kB +npm notice unpacked size: 134.2 kB +npm notice shasum: 7454ce26d89bc4bf57c09fc8b95e4f3af5379699 +npm notice integrity: sha512-HN9NJw4gfkESq[...]FI+TqQBKfzbsg== +npm notice total files: 30 +npm notice +coder-libghostty-vt-node-0.1.0-beta.1.tgz +exit=0 diff --git a/dogfood/bootstrap/smoke-output.txt b/dogfood/bootstrap/smoke-output.txt index 2632c64..abf84f6 100644 --- a/dogfood/bootstrap/smoke-output.txt +++ b/dogfood/bootstrap/smoke-output.txt @@ -1,27 +1,51 @@ +$ npm run smoke -> @coder/libghostty-vt-node@0.1.0 smoke +> @coder/libghostty-vt-node@0.1.0-beta.1 smoke > tsx examples/smoke.ts -/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39 - throw new Error( - ^ - -Error: Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX. - at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39:11) - at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23) - ... 3 lines matching cause stack trace ... - at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) { - [cause]: Error: No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0 - loaded from: /Users/thomask33/Projects/libghostty-vt-node - - at load.resolve.load.path (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:60:9) - at load (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:22:30) - at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:34:19) - at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23) - at (/Users/thomask33/Projects/libghostty-vt-node/examples/smoke.ts:3:14) - at ModuleJob.run (node:internal/modules/esm/module_job:437:25) - at async node:internal/modules/esm/loader:639:26 - at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) +native info +{ + "napiVersion": 10, + "ghosttyVersion": "0.1.0-dev", + "packageVersion": "0.1.0-beta.1", + "platform": "linux", + "arch": "x64" } +visible text +hello + red text + cursor -Node.js v25.9.0 + + + + + + + + + + + + + + + + + + + + +mouse bytes +[27,91,60,48,59,53,59,54,77] +snapshot summary +{ + "cols": 80, + "rows": 24, + "cursorRow": 2, + "cursorCol": 10, + "isAltScreen": false, + "visibleLineCount": 24, + "cellCount": 19 +} +exit=0 diff --git a/dogfood/bootstrap/test-output.txt b/dogfood/bootstrap/test-output.txt index 9e41cfa..8ea5291 100644 --- a/dogfood/bootstrap/test-output.txt +++ b/dogfood/bootstrap/test-output.txt @@ -1,13 +1,20 @@ +$ npm test -> @coder/libghostty-vt-node@0.1.0 test +> @coder/libghostty-vt-node@0.1.0-beta.1 test > vitest run - RUN v4.1.5 /Users/thomask33/Projects/libghostty-vt-node + RUN  v4.1.12 /home/coder/.mux/src/libghostty-vt-node/tough-seals-think + ✓ test/load.test.ts (1 test) 5ms + ✓ test/terminal.test.ts (32 tests) 21ms + ✓ test/workers.test.ts (3 tests) 304ms + ✓ test/mouse-safety.test.ts (12 tests) 436ms + ✓ test/scripts.test.ts (24 tests) 1261ms - Test Files 2 passed (2) - Tests 2 passed | 4 skipped (6) - Start at 16:06:17 - Duration 137ms (transform 62ms, setup 0ms, import 89ms, tests 4ms, environment 0ms) + Test Files  5 passed (5) + Tests  72 passed (72) + Start at  01:10:03 + Duration  1.45s (transform 208ms, setup 0ms, import 352ms, tests 2.03s, environment 0ms) +exit=0 diff --git a/dogfood/regenerate.sh b/dogfood/regenerate.sh new file mode 100755 index 0000000..d742abb --- /dev/null +++ b/dogfood/regenerate.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(git rev-parse --show-toplevel)" +D=dogfood/bootstrap +run() { local out="$1"; shift; { echo "\$ $*"; "$@"; echo "exit=$?"; } > "$D/.step" 2>&1; cat "$D/.step" >> "$D/session.log"; [ -n "$out" ] && cp "$D/.step" "$D/$out"; rm -f "$D/.step"; } +: > "$D/session.log" +run "" npm install +run "" npm run build +run build-libghostty-output.txt npm run build:libghostty +run build-native-output.txt npm run build:native +run test-output.txt npm test +run smoke-output.txt npm run smoke +run npm-pack-output.txt npm pack --dry-run +node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })" > "$D/native-info.json" +{ + echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "node=$(node --version)" + echo "npm=$(npm --version)" + echo "zig=$(npm exec -c 'zig version')" + echo "platform=$(uname -srm)" + echo "ghostty_commit=$(sed -n 's/^GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-\(.*\)}"$/\1/p' scripts/build-libghostty-vt.sh)" + echo "git_head=$(git rev-parse HEAD)" +} > "$D/environment.txt" From 834fc2d9d467a5af04f196a8d9491e2b77544b33 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 01:39:31 +0000 Subject: [PATCH 3/5] fix(native): pull scrollback back on a narrower and taller resize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ghostty v1.4.0 skips the scrollback pull when one resize both narrows the grid and adds rows, so the prompt stayed up with blank rows below it. Resizing the rows first, at the old width, restores the earlier result. regenerate.sh now fails when a step fails or when npm scripts run a Node other than the mise.toml pin. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: I6b009462abc8865ad9052dd655bc3c8d3107e642 Signed-off-by: Thomas Kosiewski --- dogfood/regenerate.sh | 21 +++++++++++++++++++-- native/README.md | 2 +- native/terminal.cc | 14 +++++++++++++- test/terminal.test.ts | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/dogfood/regenerate.sh b/dogfood/regenerate.sh index d742abb..c2ae7ef 100755 --- a/dogfood/regenerate.sh +++ b/dogfood/regenerate.sh @@ -2,7 +2,23 @@ set -uo pipefail cd "$(git rev-parse --show-toplevel)" D=dogfood/bootstrap -run() { local out="$1"; shift; { echo "\$ $*"; "$@"; echo "exit=$?"; } > "$D/.step" 2>&1; cat "$D/.step" >> "$D/session.log"; [ -n "$out" ] && cp "$D/.step" "$D/$out"; rm -f "$D/.step"; } +want_node="v$(sed -n 's/^node = "\(.*\)"$/\1/p' mise.toml)" +got_node="$(npm exec -c 'node --version')" +if [ "$got_node" != "$want_node" ]; then + echo "error: npm scripts run node $got_node, but mise.toml pins $want_node" >&2 + exit 1 +fi +failed=0 +run() { + local out="$1"; shift + local status=0 + { echo "\$ $*"; "$@"; } > "$D/.step" 2>&1 || status=$? + echo "exit=$status" >> "$D/.step" + cat "$D/.step" >> "$D/session.log" + if [ -n "$out" ]; then cp "$D/.step" "$D/$out"; fi + rm -f "$D/.step" + if [ "$status" -ne 0 ]; then echo "error: '$*' exited $status" >&2; failed=1; fi +} : > "$D/session.log" run "" npm install run "" npm run build @@ -14,10 +30,11 @@ run npm-pack-output.txt npm pack --dry-run node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })" > "$D/native-info.json" { echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" - echo "node=$(node --version)" + echo "node=$got_node" echo "npm=$(npm --version)" echo "zig=$(npm exec -c 'zig version')" echo "platform=$(uname -srm)" echo "ghostty_commit=$(sed -n 's/^GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-\(.*\)}"$/\1/p' scripts/build-libghostty-vt.sh)" echo "git_head=$(git rev-parse HEAD)" } > "$D/environment.txt" +exit "$failed" diff --git a/native/README.md b/native/README.md index 95f9a3d..d58ad8b 100644 --- a/native/README.md +++ b/native/README.md @@ -20,7 +20,7 @@ JavaScript exports: - `createTerminal(options: { cols, rows, scrollbackLimit? })`: returns a new `GhosttyVtTerminal` [native/terminal.cc:CreateTerminal] - `getNativeInfo()`: returns `{ napiVersion, ghosttyVersion?, ghosttyCommit? }`. `napiVersion` comes from `napi_get_version` at run time, not from the compiled `NAPI_VERSION`. `ghosttyVersion` comes from `GHOSTTY_BUILD_INFO_VERSION_STRING` and `ghosttyCommit` from `GHOSTTY_BUILD_INFO_VERSION_BUILD` [native/terminal.cc:GetNativeInfo] - `feed(data: string | Buffer | Uint8Array)`: writes the bytes (a string as UTF-8) [native/terminal.cc:TerminalWrap::Feed] -- `resize(cols, rows)`: calls `ghostty_terminal_resize(terminal, cols, rows, 0, 0)` [native/terminal.cc:TerminalWrap::Resize] +- `resize(cols, rows)`: calls `ghostty_terminal_resize(terminal, cols, rows, 0, 0)`. When the call makes the grid narrower and taller at once, it first resizes to the new rows at the old width, because Ghostty v1.4.0 pulls scrollback back into view only for that order [native/terminal.cc:TerminalWrap::Resize] - `encodeMouse(event: { action, button?, x, y, modifiers? }, options: { geometry, anyButtonPressed?, trackLastCell? })`: returns a `Buffer` with the mouse input bytes for the tracking mode and format that the terminal output selected. The buffer is empty when tracking is off or the mode drops the event. `geometry` has `screenWidth`, `screenHeight`, `cellWidth`, `cellHeight`, `paddingTop`, `paddingBottom`, `paddingRight`, and `paddingLeft`, all required here [native/terminal.cc:TerminalWrap::EncodeMouse] - `snapshot(options?: { includeScrollback?, includeCells? })`: returns `{ cols, rows, cursorRow, cursorCol, cursorVisible, isAltScreen, visibleLines, scrollbackLines?, cells? }`. Each entry of `visibleLines` and `scrollbackLines` is `{ row, text }`. `cursorVisible` is always set, from `GHOSTTY_TERMINAL_DATA_CURSOR_VISIBLE` (DECTCEM). `isAltScreen` is true when the active screen is `GHOSTTY_TERMINAL_SCREEN_ALTERNATE` [native/terminal.cc:TerminalWrap::BuildSnapshot] - A snapshot cell is `{ row, col, text, width }`. A styled cell can also carry `bold`, `italic`, `underline`, `faint`, `inverse`, `invisible`, and `strikethrough` (each set only when true) and `foreground` / `background` as `#rrggbb`. `inverse` is the raw SGR 7 flag: `foreground` and `background` are not swapped [native/terminal.cc:TerminalWrap::BuildCellObject] diff --git a/native/terminal.cc b/native/terminal.cc index 3105fae..e0507cc 100644 --- a/native/terminal.cc +++ b/native/terminal.cc @@ -326,7 +326,19 @@ Napi::Value TerminalWrap::Resize(const Napi::CallbackInfo& info) { const uint16_t cols = PositiveUint16(env, info[0], "cols"); const uint16_t rows = PositiveUint16(env, info[1], "rows"); - const GhosttyResult result = ghostty_terminal_resize(terminal, cols, rows, 0, 0); + uint16_t old_cols = 0; + uint16_t old_rows = 0; + GhosttyResult result = ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLS, &old_cols); + if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(cols)", result); + result = ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_ROWS, &old_rows); + if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(rows)", result); + // Ghostty v1.4.0 does not pull scrollback back into view when one resize + // both narrows and grows the grid, but it does when the rows grow first. + if (cols < old_cols && rows > old_rows) { + result = ghostty_terminal_resize(terminal, old_cols, rows, 0, 0); + if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_resize", result); + } + result = ghostty_terminal_resize(terminal, cols, rows, 0, 0); if (result != GHOSTTY_SUCCESS) { ThrowResult(env, "ghostty_terminal_resize", result); } diff --git a/test/terminal.test.ts b/test/terminal.test.ts index 717e270..57c8ad7 100644 --- a/test/terminal.test.ts +++ b/test/terminal.test.ts @@ -165,6 +165,23 @@ describeIfNative("GhosttyVtTerminal", () => { } }); + it("pulls history back when one resize makes the terminal narrower and taller", () => { + const resized = (steps: [number, number][]) => { + const term = createTerminal({ cols: 80, rows: 24, scrollbackLimit: 10_000 }); + try { + term.feed(Array.from({ length: 40 }, (_, i) => `out ${i}\r\n`).join("") + "$ "); + for (const [cols, rows] of steps) term.resize(cols, rows); + return term.snapshot({ includeScrollback: true }); + } finally { + term.dispose(); + } + }; + const oneStep = resized([[80, 10], [60, 30]]); + expect(oneStep.cursorRow).toBe(29); + expect(oneStep.visibleLines[29]?.text).toBe("$"); + expect(oneStep).toEqual(resized([[80, 10], [80, 30], [60, 30]])); + }); + it("updates snapshot dimensions on resize", () => { const term = createTerminal({ cols: 80, rows: 24 }); try { From 7ff47a56ba2566a213e6ca2e95cf9965ddbe0820 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 01:39:43 +0000 Subject: [PATCH 4/5] docs: rerun the dogfood bundle on the resize fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: I6b79e14cd14acdb142433ed6f7588cd17e8a629f Signed-off-by: Thomas Kosiewski --- dogfood/bootstrap/build-native-output.txt | 8 ++--- dogfood/bootstrap/environment.txt | 4 +-- dogfood/bootstrap/npm-pack-output.txt | 15 +++++---- dogfood/bootstrap/session.log | 41 ++++++++++++----------- dogfood/bootstrap/test-output.txt | 16 ++++----- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/dogfood/bootstrap/build-native-output.txt b/dogfood/bootstrap/build-native-output.txt index b4bca52..7d3763b 100644 --- a/dogfood/bootstrap/build-native-output.txt +++ b/dogfood/bootstrap/build-native-output.txt @@ -5,7 +5,7 @@ $ npm run build:native gyp info it worked if it ends with ok gyp info using node-gyp@12.4.0 -gyp info using node@22.23.4 | linux | x64 +gyp info using node@26.12.1 | linux | x64 gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3" gyp info spawn /usr/bin/python3 @@ -19,12 +19,12 @@ gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/b gyp info spawn args '-I', gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi', gyp info spawn args '-I', -gyp info spawn args '/home/coder/.cache/node-gyp/22.23.4/include/node/common.gypi', +gyp info spawn args '/home/coder/.cache/node-gyp/26.12.1/include/node/common.gypi', gyp info spawn args '-Dlibrary=shared_library', gyp info spawn args '-Dvisibility=default', -gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/22.23.4', +gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/26.12.1', gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp', -gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/22.23.4/<(target_arch)/node.lib', +gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/26.12.1/<(target_arch)/node.lib', gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think', gyp info spawn args '-Dnode_engine=v8', gyp info spawn args '--depth=.', diff --git a/dogfood/bootstrap/environment.txt b/dogfood/bootstrap/environment.txt index c5e1d07..f46257d 100644 --- a/dogfood/bootstrap/environment.txt +++ b/dogfood/bootstrap/environment.txt @@ -1,7 +1,7 @@ -date=2026-10-30T01:10:05Z +date=2026-10-30T01:39:42Z node=v26.12.1 npm=11.20.0 zig=0.17.0 platform=Linux 6.8.0-139-generic x86_64 ghostty_commit=83fb95624b0439ee660c2679b43b38c8b63769cd -git_head=90c38d02546062228a7dab421384280f5ba33a68 +git_head=834fc2d9d467a5af04f196a8d9491e2b77544b33 diff --git a/dogfood/bootstrap/npm-pack-output.txt b/dogfood/bootstrap/npm-pack-output.txt index 64b6751..3f29e35 100644 --- a/dogfood/bootstrap/npm-pack-output.txt +++ b/dogfood/bootstrap/npm-pack-output.txt @@ -21,10 +21,11 @@ npm notice 102B dist/types.js.map npm notice 346B native/addon.cc npm notice 2.3kB native/libghostty_vt_shim.cc npm notice 549B native/libghostty_vt_shim.hh -npm notice 18.1kB native/README.md -npm notice 34.7kB native/terminal.cc +npm notice 18.3kB native/README.md +npm notice 35.5kB native/terminal.cc npm notice 2.1kB native/terminal.hh npm notice 2.0kB package.json +npm notice 12.7MB prebuilds/linux-x64/@coder+libghostty-vt-node.node npm notice 3.8kB scripts/assemble-prebuilds.mjs npm notice 2.9kB scripts/build-libghostty-vt.sh npm notice 2.7kB scripts/create-release-tag.mjs @@ -36,11 +37,11 @@ npm notice Tarball Details npm notice name: @coder/libghostty-vt-node npm notice version: 0.1.0-beta.1 npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz -npm notice package size: 36.6 kB -npm notice unpacked size: 134.2 kB -npm notice shasum: 7454ce26d89bc4bf57c09fc8b95e4f3af5379699 -npm notice integrity: sha512-HN9NJw4gfkESq[...]FI+TqQBKfzbsg== -npm notice total files: 30 +npm notice package size: 3.7 MB +npm notice unpacked size: 12.9 MB +npm notice shasum: 745acaff5d026b0ffa6dd5fca645c2134867e848 +npm notice integrity: sha512-zphUs2JiLK6LS[...]zSI7BzBU3xt6Q== +npm notice total files: 31 npm notice coder-libghostty-vt-node-0.1.0-beta.1.tgz exit=0 diff --git a/dogfood/bootstrap/session.log b/dogfood/bootstrap/session.log index 7954310..61121e7 100644 --- a/dogfood/bootstrap/session.log +++ b/dogfood/bootstrap/session.log @@ -5,7 +5,7 @@ $ npm install native build is explicit: run npm run build:native -up to date, audited 93 packages in 481ms +up to date, audited 93 packages in 491ms 22 packages are looking for funding run `npm fund` for details @@ -40,7 +40,7 @@ $ npm run build:native gyp info it worked if it ends with ok gyp info using node-gyp@12.4.0 -gyp info using node@22.23.4 | linux | x64 +gyp info using node@26.12.1 | linux | x64 gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3" gyp info spawn /usr/bin/python3 @@ -54,12 +54,12 @@ gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/b gyp info spawn args '-I', gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi', gyp info spawn args '-I', -gyp info spawn args '/home/coder/.cache/node-gyp/22.23.4/include/node/common.gypi', +gyp info spawn args '/home/coder/.cache/node-gyp/26.12.1/include/node/common.gypi', gyp info spawn args '-Dlibrary=shared_library', gyp info spawn args '-Dvisibility=default', -gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/22.23.4', +gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/26.12.1', gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp', -gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/22.23.4/<(target_arch)/node.lib', +gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/26.12.1/<(target_arch)/node.lib', gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think', gyp info spawn args '-Dnode_engine=v8', gyp info spawn args '--depth=.', @@ -91,16 +91,16 @@ $ npm test  RUN  v4.1.12 /home/coder/.mux/src/libghostty-vt-node/tough-seals-think - ✓ test/load.test.ts (1 test) 5ms - ✓ test/terminal.test.ts (32 tests) 21ms - ✓ test/workers.test.ts (3 tests) 304ms - ✓ test/mouse-safety.test.ts (12 tests) 436ms - ✓ test/scripts.test.ts (24 tests) 1261ms + ✓ test/load.test.ts (1 test) 4ms + ✓ test/terminal.test.ts (33 tests) 19ms + ✓ test/workers.test.ts (3 tests) 170ms + ✓ test/mouse-safety.test.ts (12 tests) 309ms + ✓ test/scripts.test.ts (24 tests) 952ms  Test Files  5 passed (5) - Tests  72 passed (72) - Start at  01:10:03 - Duration  1.45s (transform 208ms, setup 0ms, import 352ms, tests 2.03s, environment 0ms) + Tests  73 passed (73) + Start at  01:39:39 + Duration  1.08s (transform 95ms, setup 0ms, import 205ms, tests 1.45s, environment 0ms) exit=0 $ npm run smoke @@ -177,10 +177,11 @@ npm notice 102B dist/types.js.map npm notice 346B native/addon.cc npm notice 2.3kB native/libghostty_vt_shim.cc npm notice 549B native/libghostty_vt_shim.hh -npm notice 18.1kB native/README.md -npm notice 34.7kB native/terminal.cc +npm notice 18.3kB native/README.md +npm notice 35.5kB native/terminal.cc npm notice 2.1kB native/terminal.hh npm notice 2.0kB package.json +npm notice 12.7MB prebuilds/linux-x64/@coder+libghostty-vt-node.node npm notice 3.8kB scripts/assemble-prebuilds.mjs npm notice 2.9kB scripts/build-libghostty-vt.sh npm notice 2.7kB scripts/create-release-tag.mjs @@ -192,11 +193,11 @@ npm notice Tarball Details npm notice name: @coder/libghostty-vt-node npm notice version: 0.1.0-beta.1 npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz -npm notice package size: 36.6 kB -npm notice unpacked size: 134.2 kB -npm notice shasum: 7454ce26d89bc4bf57c09fc8b95e4f3af5379699 -npm notice integrity: sha512-HN9NJw4gfkESq[...]FI+TqQBKfzbsg== -npm notice total files: 30 +npm notice package size: 3.7 MB +npm notice unpacked size: 12.9 MB +npm notice shasum: 745acaff5d026b0ffa6dd5fca645c2134867e848 +npm notice integrity: sha512-zphUs2JiLK6LS[...]zSI7BzBU3xt6Q== +npm notice total files: 31 npm notice coder-libghostty-vt-node-0.1.0-beta.1.tgz exit=0 diff --git a/dogfood/bootstrap/test-output.txt b/dogfood/bootstrap/test-output.txt index 8ea5291..9c0d831 100644 --- a/dogfood/bootstrap/test-output.txt +++ b/dogfood/bootstrap/test-output.txt @@ -6,15 +6,15 @@ $ npm test  RUN  v4.1.12 /home/coder/.mux/src/libghostty-vt-node/tough-seals-think - ✓ test/load.test.ts (1 test) 5ms - ✓ test/terminal.test.ts (32 tests) 21ms - ✓ test/workers.test.ts (3 tests) 304ms - ✓ test/mouse-safety.test.ts (12 tests) 436ms - ✓ test/scripts.test.ts (24 tests) 1261ms + ✓ test/load.test.ts (1 test) 4ms + ✓ test/terminal.test.ts (33 tests) 19ms + ✓ test/workers.test.ts (3 tests) 170ms + ✓ test/mouse-safety.test.ts (12 tests) 309ms + ✓ test/scripts.test.ts (24 tests) 952ms  Test Files  5 passed (5) - Tests  72 passed (72) - Start at  01:10:03 - Duration  1.45s (transform 208ms, setup 0ms, import 352ms, tests 2.03s, environment 0ms) + Tests  73 passed (73) + Start at  01:39:39 + Duration  1.08s (transform 95ms, setup 0ms, import 205ms, tests 1.45s, environment 0ms) exit=0 From f267d866a479631d83bc740aef1d8570be686368 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 01:57:06 +0000 Subject: [PATCH 5/5] fix(dogfood): fail regeneration when dist/index.js cannot load the addon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ Change-Id: I05348d97a66fea2f4619268072c74aee96e29342 Signed-off-by: Thomas Kosiewski --- dogfood/regenerate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dogfood/regenerate.sh b/dogfood/regenerate.sh index c2ae7ef..cf2e872 100755 --- a/dogfood/regenerate.sh +++ b/dogfood/regenerate.sh @@ -28,6 +28,7 @@ run test-output.txt npm test run smoke-output.txt npm run smoke run npm-pack-output.txt npm pack --dry-run node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })" > "$D/native-info.json" +if grep -q '"ok": false' "$D/native-info.json"; then echo "error: dist/index.js failed to load the addon" >&2; failed=1; fi { echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "node=$got_node"