Phase 9, MA1: crates/ghostty-macos (AppKit via objc2; empty stub on non-Apple targets) ports the foundation of Ghostty 1.3.1's Swift macOS app (ghostty/macos): main, AppDelegate (+Ghostty) with lifecycle, dock menu, open-file, notifications, shortcut sync and quit/reopen paths; MainMenu.xib as generated menu tables built into NSMenus; the Ghostty.App/Config/Surface wrappers driving libghostty in-process via ghostty-lib; the generic helpers; scripts/macos-bundle.sh to assemble Ghostty.app (dry-run on Linux). Feature areas are stubbed for MA2-MA5. Design and type mapping: docs/rewrite/notes/macos-app-design.md (App Intents is a Swift-only framework and cannot be ported). Compile-checked only (no macOS here): just check-macos passes for aarch64/x86_64-apple-darwin with -D warnings; 125 platform-independent tests run on Linux. Test Plan: - just check-macos - cargo test -p ghostty-macos
25 KiB
Deviant fixes: where the Rust port intentionally differs from Ghostty 1.3.1
The rule
The port is faithful to Ghostty 1.3.1 (including its quirks), with one
exception: genuine upstream bugs are fixed, not replicated. That means
memory-safety problems and undefined behaviour (use-after-free, stale
pointers, out-of-bounds reads, leaks), infinite loops, data loss or
corruption (lost or overwritten output), and crashes/asserts reachable from
input. See rule 2 in AGENT_GUIDE.md.
From now on every such fix is committed on its own as
fix-deviant(<scope>): ... and gets a row in the first table below. This
register also covers the deviations made before that convention; their
commits bundle other work, so the "Commit" column names the commit that
introduced the Rust behaviour (found with git log -S / git blame), not a
dedicated fix commit.
Conventions: Zig paths are relative to ghostty/src/. Line numbers refer to
the Zig tree used for the port. "unverified" means I could not confirm the
detail from the notes or code; "none" means no test exercises the deviation.
"uncommitted" means the change was in the working tree (agent Z17, ghostty-cli)
when this file was written.
Upstream bugs fixed (behaviour deliberately differs)
| # | Area | Zig location | Ghostty behaviour | Rust behaviour | Commit | Test |
|---|---|---|---|---|---|---|
| 1 | Kitty graphics: placement pins | terminal/kitty/graphics_storage.zig:148-176 (addPlacement), :517- (evictImage) |
The tracked pin of a placement is leaked when its image is evicted and when an external placement id is reused (getOrPut then overwrite of the value). |
add_image/add_placement/set_limit take the PageList and release the replaced/evicted placement's tracked pin. |
d47408f |
storage_add_placement_replaces_and_releases_pin, storage_eviction_prefers_unused_then_oldest (ghostty-terminal/src/kitty/graphics_storage.rs) |
| 2 | Kitty graphics: Placement::rect |
terminal/kitty/graphics_storage.zig:~789-798 (grid_size.rows - 1, grid_size.cols - 1) |
Unsigned underflow for a 0-sized grid. | saturating_sub(1) on rows/cols. |
d47408f |
none |
| 3 | Kitty graphics: file medium S= |
terminal/kitty/graphics_image.zig:268-269 (appendRemaining(.limited(size))) |
StreamTooLong whenever exactly size bytes are read, so any satisfiable S>0 fails with InvalidData (Zig tests only use S=0). |
Data is truncated to min(S, 400MB), as the Kitty spec says. |
d47408f (note clarified in 687752c) |
image_load_file_offset_and_size (ghostty-terminal/src/kitty/graphics_image.rs) |
| 4 | Kitty graphics: zlib size boundary | terminal/kitty/graphics_image.zig:412 (.limited(max_size)) |
Decompression errors at exactly 400MB of output. | Errors only above 400MB (take(MAX_SIZE + 1) then len > MAX_SIZE). |
d47408f |
none |
| 5 | Renderer: link matching | renderer/link.zig:99-123 (defer offset = end) |
A zero-length regex match leaves offset unchanged, so the loop never terminates. |
A zero-length match advances by one character. | 871c301 |
none dedicated (render_cell_map* in ghostty-renderer/src/link.rs do not cover it) |
| 6 | Input: keybind formatter | input/Binding.zig:2112-2150 (Set.Value.formatEntries) |
The shared buffer is rewound to a fixed position after each sub-entry, which corrupts the prefix of sibling entries for chained leaves in sequences. | Each entry is formatted from its own prefix. | e034322 |
set_format_entries_sequence_shares_prefix (ghostty-input) |
| 7 | Core: selection replacement | Surface.zig:2310-2322 (setSelection) |
select() untracks the previous selection's pins, then sel.eql(prev) compares through those released pins (stale pointers). |
The comparison happens before the old selection is replaced. (Rust handles are checked, so the naive port panicked; the e2e tests found it.) | 2f58f02 |
select_all_twice_with_copy_on_select_does_not_panic (ghostty-core/tests/e2e_bindings.rs), drag_selects_and_copy_on_select_fills_selection_clipboard (e2e_mouse.rs). Dedicated eql-before-replace test: unverified |
| 8 | Datastruct: lru eviction report |
datastruct/lru.zig:163-166 |
entry.data.key is overwritten with the new key before entry.data is copied out as evicted, so the evicted key is wrong. |
GetOrPutResult.evicted carries the old key and old value. |
5b8c92b |
get_or_put checks only evicted.value; the key is not asserted (ghostty-datastruct/src/lru.rs) |
| 9 | Terminal: stale cell pointer in grapheme attach | terminal/Terminal.zig:482 and :539 |
prev.cell survives a page reallocation (increaseCapacity in printCell of the spacer tail), so appendGrapheme writes into freed memory and the grapheme (for example U+FE0F) is lost. |
Cells are addressed by index; the grapheme is kept. | 4eb03f6 |
difftest_upstream_uaf_grapheme_survives_page_growth (ghostty-terminal/src/stream_readonly.rs); the difftest harness stores such mismatches as suspect* |
| 10 | Terminal: asserts and segfaults reachable from input | Terminal.zig:375 (assert(!unicode.graphemeBreak(cp1, cp2, &state)) after mode 2027 is toggled with U+2028/U+2060/BOM), terminal/page.zig:1012 (assert(y < size.rows); Devanagari conjunct at the right margin, including the bottom-row case where the margin wrap scrolls and moveGrapheme(prev.cell, ...) runs on a blank cell), Terminal.zig:834 (assert(!cell.hyperlink); REP with a huge count while a hyperlinked page grows) |
Zig aborts (ReleaseSafe) or faults (ReleaseFast) in the margin case; the first and third abort only with assertions on (roughly 0.1-0.4 % of generated difftest cases). | No panic and no error. The grapheme replay ignores the break result like ReleaseFast (the assertion is dropped, because this repository's release profile enables debug assertions, so input could crash a release build). The margin case moves the grapheme from the cell that actually holds it (prev.cell, else the cell above) and never flags a cell without data. Verified with a Rust-only panic hunt (difftest_fuzz panics, debug build, about 60k cases, 0 panics) and the Zig differential run (0 divergences). Zig-aborting cases are excluded from the differential comparison. |
4eb03f6 (REP, first margin case); Z21 (uncommitted): assertion and bottom-row margin case |
difftest_grapheme_wrap_with_margins_moves_prev_cell, difftest_grapheme_wrap_scrolling_margins_keeps_grapheme, difftest_mode_2027_toggle_with_bom_does_not_panic, tools/difftest (ZIG CRASH handling, panics mode) |
| 11 | VT: tmux control-mode ids | terminal/tmux/control.zig:199,230,270,308 (parseInt(usize) catch unreachable) |
An id that overflows usize hits unreachable (crash/UB). |
The line is treated as a failed match. | b2b248e |
none |
| 12 | VT: SGR separator bitset | terminal/sgr.zig:228,514 (params_sep.isSet(idx) past MAX_PARAMS) |
Asserts / out-of-range in StaticBitSet for long parameter lists. |
Bounds-tolerant sep_is_set. |
b2b248e |
sgr_underline_colon_with_trailing_separator_and_short_slice (ghostty-vt/src/sgr.rs); whether Zig's path is reachable: unverified |
| 13 | OS: NOTIFY_SOCKET |
os/systemd.zig:100-116 |
A leading @ (abstract socket) is accepted by the check, then copied literally into the address, which cannot work. |
A leading @ becomes an abstract-namespace address. |
f8c5ce0 |
none (the notify tests use a path socket) |
| 14 | Renderer: GL Buffer::init_fill |
renderer/opengl/buffer.zig:62 |
len = data.len * @sizeOf(T), so len is in bytes while the rest of the type counts elements. |
len is the element count. |
568ce78 |
none |
| 15 | Renderer: Pipeline teardown |
renderer/opengl/Pipeline.zig:72-74 |
deinit destroys only the program; the FBO and VAO leak. |
Drop also deletes the FBO and VAO. | 568ce78 |
none |
| 16 | Renderer: custom-shader uniforms | renderer/generic.zig:2061-2064 (cursor_text.color, and the selection colours) |
The union payload is read unconditionally; invalid for cell-foreground / cell-background. |
Uniforms are set only for explicit colours. | 871c301 |
none |
| 17 | Renderer (Metal): texture upload | renderer/metal/Texture.zig (replaceRegion) |
The data length is not checked against the region. | A too-short slice is rejected (safe API). Zig line: unverified. | bab49ee |
none |
| 18 | Input: binding order with catch-all | input/Binding.zig:287-294 (sort key of trigger.key) |
Reads the key of a catch-all trigger from the C union, which is undefined. | A catch-all sorts as 0. | e034322 |
none |
| 19 | Font: unsupported FreeType pixel mode | font/face/freetype.zig:707 |
@panic("unsupported pixel mode"). |
Error::UnsupportedPixelMode. |
2580176 |
none |
| 20 | Font: OpenType SVG table | font/opentype/svg.zig:45-46 |
Reads records[0] and the last record unconditionally, even for an empty document list. |
An empty list returns an error. | 2580176 |
none |
| 21 | Termio (Windows): process thread handle | Command.zig:347 (CreateProcessW) |
The thread handle returned by CreateProcessW is never closed (leak). |
It is closed. | 76771be |
none (compile-checked only on Windows) |
| 22 | Termio (Windows): focus_gained |
termio/Exec.zig (Windows branch; exact line unverified) |
@panic on Windows. |
No-op. | 76771be |
none |
| 23 | GTK: close confirmation dialog (fallback build) | apprt/gtk/class/close_confirmation_dialog.zig:45-46 |
The fallback registers a signal named close-request, which already exists on GtkWindow; GObject aborts (dead code upstream). |
The fallback names it dialog-close-request; the default build is unchanged. |
f2252bb |
runtime check recorded in notes/compat-GC.md; no unit test |
| 24 | libghostty: ghostty_surface_inherited_config |
apprt/embedded.zig:1573, :941 (dupeZ of the working directory) |
Allocates a new copy per call and never frees it. | The strings are kept in the surface and freed with it. | 75bf604 |
none |
| 25 | libghostty: invalid enum values from C | apprt/embedded.zig (mouse state/button, split direction, colour scheme, key action, pressure stage) |
Undefined behaviour or safety panic. | Ignored / returns false; null handles are tolerated. | 75bf604 |
unverified |
| 26 | Crash reporting: envelope attachments | crash/sentry_envelope.zig:399-419 (Attachment.encode) |
Never writes the type header, so the output cannot be parsed back. |
Adds "type":"attachment" when absent. |
b3145ad |
envelope_parse_multiple (ghostty-crash/src/envelope.rs) is the nearest test; a dedicated test: unverified |
| 27 | Build data: webgen output | build/webgen/main_actions.zig:5-6, main_commands.zig:6, main_config.zig:6 |
A 2048-byte stdout buffer that is never flushed, so the generated website files are truncated. | The complete files are emitted. | e4cdd8e |
none (checked byte-identical to Zig output of a patched copy, per final-Z4.md) |
| 28 | CLI: +list-colors plain output |
cli/list_colors.zig:59-72 |
4096-byte buffer, never flushed; piped output stops midway through PeachPuff3 (12,297 bytes). |
Flushed (15,434 bytes). | 6286e02 |
none; found by RV11 (review-rv11-codex.md) |
| 29 | CLI: +list-themes diagnostics |
cli/list_themes.zig:124-131,171 |
The messages ("Could not find the Ghostty resources directory", "No themes found...") go to a 4096-byte stderr buffer that is never flushed, so they are lost. | Flushed when the action ends. | 410d8fc fix-deviant(cli) |
zig_writer::tests::unflushed_buffer_is_flushed_on_drop |
| 30 | CLI: stdout as a regular file | std.fs.File.writer (Zig 0.15.2) as used in cli/show_face.zig:68-73, cli/help.zig:34, cli/validate_config.zig:43-46, cli/show_config.zig:84 |
The writer pwrites at its own offset starting at 0: with stdout redirected to a file, output overwrites itself and earlier file content, and Writer.end truncates. |
Writes sequentially at the inherited descriptor's current offset (no pwrite from offset 0, so earlier content and the writers' own output are not overwritten; this appends only when the descriptor is opened for append, e.g. >>); no truncation. |
410d8fc fix-deviant(cli) (append behaviour first in f76179f) |
zig_writer::tests::regular_file_output_is_complete_and_appended |
| 31 | GTK: +new-window D-Bus arguments |
apprt/gtk/ipc/new_window.zig:49-53 (Bytes.new(ptr, len + 1) + Variant.newFromBytes(..., trusted = true)) |
Arguments are passed as trusted GVariant strings without validation; invalid UTF-8 or an interior NUL makes GIO abort the process while serializing (input-triggered crash). | arguments_variant validates first and returns Error::IpcFailed (message on stderr, no D-Bus call); valid arguments are byte-identical to Zig. |
65dd9d5 fix-deviant(gtk) |
ipc::new_window::tests::invalid_dbus_strings_are_rejected_without_abort (ghostty-apprt-gtk) |
| 32 | OS: URL opener child reaping | os/open.zig (try collectOutput(...) before wait()) |
When collecting the opener's output fails (e.g. more than 50 KiB on a stream), the error returns before wait(), leaving the child as a zombie. |
The child is always reaped; the collection error is still logged. | 4a55abf fix-deviant(os) |
ghostty-os open tests |
| 33 | OS: maximum-length NOTIFY_SOCKET |
os/systemd.zig:100-116 (socket_address.path.len < socket_path.len, then path[socket_path.len] = 0) |
A 108-byte path (the size of sockaddr_un.path) passes the length check, and the terminating NUL is written at path[108], one byte past the array (out-of-bounds write). Separate from row 13 (the abstract-socket @ handling). |
The address is built with rustix's SocketAddrUnix::new, which takes the 108 bytes without a terminator (valid for Linux AF_UNIX) and rejects longer paths; no out-of-bounds write. Verified in code. |
f8c5ce0 (git log -S on ghostty-os/src/systemd.rs: the only commit touching it, besides formatting/macOS changes) |
notify_maximum_length_socket_path_is_rejected_safely (ghostty-os/src/systemd.rs) |
| 34 | Renderer (Metal): buffer reallocation size | renderer/metal/buffer.zig:89 (Buffer.sync) and :146 (syncFromArrayLists) |
size = req_bytes * 2 is already in bytes, then size * @sizeOf(T) is passed to newBufferWithLength:options:, so the new buffer is sizeof(T) times larger than intended (GPU-memory over-allocation). |
The new buffer is exactly 2 * req_bytes (metal_layout::grown_buffer_bytes); the element count is 2 * total_len. |
bab49ee; registered + test in 2040677 fix-deviant(renderer) |
metal_layout::tests::grown_buffer_bytes_is_double_the_required_bytes_not_scaled_by_size_of_t (runs on any host; the Metal buffer itself is compile-checked only) |
| 35 | Termio: PTY hangup drops unread output | termio/Exec.zig ReadThread.threadMainPosix (exits as soon as POLL.HUP is seen) |
POLLIN | POLLHUP can arrive together with unread final output (a Linux PTY check confirmed this); exiting immediately loses the tail of the child's output. |
After HUP the read thread makes one more pass over the fd, so the remaining output is delivered before exit. | 6629fb0 |
none (needs a live PTY; verified by RV18 on Linux) |
| 36 | Font (wasm/browser Canvas): atlas pixel format | font/face/web_canvas.zig (atlas write branch on .rgba) |
The branch tests a stale .rgba format against an enum that contains .bgra, so BGRA atlases are never converted. |
The BGRA atlas conversion is implemented. No claim of equivalence with a real browser (not testable here). | d89691b |
ghostty-font web_canvas tests (compile/Node level) |
| 37 | Termio: subprocess cleanup | termio/Exec.zig:1188-1224 (getpgid) |
Loops forever while getpgid(child) == our pgid, i.e. also when the child died before setsid and is an unreaped zombie (its pgid stays ours), hanging the surface close. |
Still waits without a cap (no 5 s limit) while the child is alive, but stops (no SIGHUP, nothing to kill) once waitid(WNOWAIT) shows the child exited. |
b9ad595 fix-deviant(termio) |
getpgid_gives_up_on_exited_child_without_setsid, getpgid_waits_for_late_setsid (ghostty-termio/src/exec.rs) |
| 38 | Config (Windows): absolute theme paths | config/Config.zig:9788-9799 (Theme.parseCLI) |
Any value containing ,, = or : is parsed as a light:..,dark:.. pair, and a Windows absolute path (C:\\themes\\x, \\\\?\\C:\\...) always contains a colon, so theme = <absolute path> is rejected on Windows (found by running the config tests under Wine). |
On Windows a value that starts like an absolute Windows path (drive letter + : + separator, or \\\\) is a single theme; the pair syntax is unchanged (quote backslashes). Other platforms are unchanged. |
97a262d fix-deviant(config) |
windows_absolute_path_detection, theme_windows_absolute_path_and_pair_with_paths (ghostty-config/src/types_b.rs), theme_loading under Wine |
| 39 | Termio (Windows): bare program names | Command.zig:261,347-358 (startWindows, CreateProcessW(application_w, ...)) with termio/Exec.zig:1004 (.path = self.args[0]) |
lpApplicationName is never searched on PATH (a partial name only completes against the current directory), so a direct: command / -e argv[0] such as cmd.exe fails with FileNotFound; only the default shell works (it is wrapped as %WINDIR%\\System32\\cmd.exe /C ...). |
A name without drive/separator is looked up on PATH (.exe implied) before CreateProcessW; failures keep the original name (same error as Zig). |
4d7ad7c fix-deviant(termio) |
resolve_application_searches_path_for_bare_names, bare_program_name_starts_via_path_search (ghostty-termio/src/command_windows.rs), echo_output_reaches_terminal_and_exit_is_reported (tests/exec_windows.rs, Wine) |
Upstream quirks deliberately kept (not fixed)
These look odd but are not memory-safety, loop, corruption or crash bugs, so the port matches Ghostty.
shadertoy::Uniforms: the selection foreground/background fields are swapped relative to the GLSL block. Both arevec3, so offsets agree (ghostty-renderer/src/shadertoy.rs:878; R1 note).- Kitty storage:
delete rangeuses||(matches everything); eviction stops only whenevicted > req;set_limit(0)resets storage to defaults; screen reset also resetstotal_limit. lru::getdoes not change LRU order.Tabstops::unsetXORs.Screen/keybind docs:format_entry_docsemits a blank line only (the@tagNamelookup never matches).Search: an empty needle underflows exactly as in Zig, so the needle must be non-empty.- i18n: the
strlen == 7guard skips the tables for 8-byte locale names. - CSI:
CSI Kaccepts 0..2 only; ANSI DECRQM is unimplemented; unknownmany keycodemedia keys stay a TODO. - libghostty C header:
chostty_ipc_target_stypo kept;ghostty_inspector_metal_shutdownisboolin the header butvoidin Zig (we follow Zig). - Braille sprites:
debug_assert!where Zig asserts, so odd cell sizes (for example 14x23) trip it in builds with debug assertions, as in Zig. - Windows: no file locking in the ssh-cache
DiskCache(Zig does not lock on Windows either). - Config theme-name basename and numeric parsing follow Zig 0.15.2, not 0.16 (
fix(config)commits3068e3a,a57ff41).
Other deliberate deviations (platform/Rust-imposed or out of scope)
Only entries with an observable behavioural difference are listed.
| Area | Zig/Ghostty behaviour | Rust behaviour | Where | Commit |
|---|---|---|---|---|
| Swift macOS app | Ships macos/ (Swift/SwiftUI/xib, Xcode, Sparkle, App Intents) |
Ported to Rust/AppKit in crates/ghostty-macos (programmatic AppKit instead of xib/SwiftUI; menu tables generated from MainMenu.xib; bundle assembled by scripts/macos-bundle.sh). Only compile-checked (just check-macos), never run (no macOS here). Deviations (menu quirks kept, no nib loading, config background fallback, byte-length URL in open_config, non-compatible CodableBridge archives, ...) are listed in notes/macos-app-MA1.md; App Intents is unportable from Rust (notes/macos-app-design.md). The Rust side also still provides the libghostty C API for the unchanged Swift app. |
notes/macos-app-design.md, notes/macos-app-MA1.md, ARCHITECTURE.md, notes/macos-M1*.md |
(uncommitted) |
| Windows (and wasm natively) | Native targets | Windows is compile-checked only (just check-windows), never run. wasm is built and its Node smoke/ABI tests run (just check-wasm, scripts/wasm-check.sh), but not in a real browser. |
notes/windows-WIN.md, notes/wasm-W1.md |
76771be, d89691b |
| Crash reporting | Sentry with Breakpad on macOS, SIGSEGV handlers, local DB/cache, sessions, init thread |
In-process reporter: .ghosttycrash envelopes in $XDG_STATE_HOME/ghostty/crash, panic hook and signal handler, minidumps on macOS/Windows. No Sentry DB/cache dir, no sessions, no init thread; reports are written on any panic; the crash dir is resolved at init; the signal path writes no attachment. |
notes/crash-CR.md, ARCHITECTURE.md (Scope changes) |
b3145ad, 72bfac1 |
| Surface copy-on-select on release | setSelection returns early when the new selection equals the old one |
Left-release copies directly via copy_on_select, because the early return would never copy |
notes/ghostty-core.md |
ad76ae2 |
| Locking / threading | Renderer mutex held while sending mailbox messages and running mouse reports (.locked sends); process_links called unlocked |
Reports, IO messages and runtime actions are collected and run after the lock is released; process_links takes the lock itself; termio overflow goes to an ordered outbox |
notes/ghostty-core.md, notes/ghostty-termio-IO*.md |
6629fb0, ad76ae2 |
| Rendering of overlay / images | z2d overlay, wuffs image decoding, glslang+SPIR-V+spirv-cross | Direct RGBA overlay with approximate hairline placement; png/jpeg-decoder; naga GLSL front end (narrower than glslang, with a sampler2D helper rewrite) |
notes/ghostty-renderer-R1.md, R2.md, gaps-X2.md; just visual-parity reports 0 differing pixels for the tested scenes |
568ce78, 871c301, f68b11d |
| Oniguruma | Native regex library for links and tmux | Hand-written matchers for the tmux parser; link regexes use the minimal ghostty-onig FFI crate |
notes/ghostty-vt-W2.md |
b2b248e, 2aa2a12 |
| zlib / PNG / base64 / shm | zig std, wuffs, simdutf, mmap | flate2, png, base64 crate (ASCII whitespace ignored), rustix shm reads without mmap |
notes/ghostty-terminal-kitty.md, notes/ghostty-vt-W2.md |
d47408f, b2b248e |
| VT parser buffer | Fixed 2048-byte buffer with a reserved NUL | Growing Vec<u8> with a cap that models the reserved-NUL capacity; no observable difference is known (internal representation only) |
notes/ghostty-vt-W1.md |
b2b248e |
| CSI W logging | Logs a spurious "invalid cursor tabulation control" after CSI 2 W / CSI 5 W |
No log line (log-only) | notes/ghostty-vt-stream.md |
47b479d |
| Searching | CircBuf + overlap buffer, libxev thread |
VecDeque, crossbeam-channel, 24 ms refresh timer; identical results, assertions about buffer layout dropped |
notes/ghostty-terminal-search-tmux.md |
8e9be42 |
| Config strings | UTF-8 not required in many places | Escaped-bytes representation (EncodedOs) so non-UTF-8 survives; behaviour and error names matched to Zig (for example theme=/etc/passwd/x gives error.NotDir, checked against the oracle) |
notes/review-rv11-codex.md (item 3) |
672e8a4, 4bbe0d7 |
+version / build info |
Real build metadata | Placeholder build_info values in places; gtk-opengl-debug follows cfg!(debug_assertions) (the release profile has them on, production off) |
notes/ghostty-cli.md, notes/ghostty-config.md |
6286e02 |
| OSC 52 base64 acceptance | Surface.zig:2149,2163 rules | Was different in both directions (Codex core audit); fixed to match Zig | notes/review-core-codex.md |
2f58f02 |
| Benchmark / synthetic tooling extensions | ghostty-bench has no --time option and only the simplified terminal handler; ghostty-synthetic has the actions ascii, osc, utf8 |
ghostty-bench --time prints the measured step time to stderr; ghostty-bench --handler=full runs the full stream_readonly handler instead of the print-only one; ghostty-synthetic +sgr generates SGR-heavy output. Dev/benchmark tooling only; otherwise upstream behaviour is unchanged |
crates/ghostty-bench/src/main.rs, src/benches.rs, src/bin/ghostty-synthetic.rs; notes/review-rv16-codex.md |
0c4f0ee |
| Inspector font rasterizer (Windows, Apple, wasm) | Dear ImGui is built with IMGUI_ENABLE_FREETYPE (pkg/dcimgui), so the 32 px inspector font is rasterized by FreeType |
Linux and other Unix targets: FreeType (dear-imgui-rs feature freetype, system FreeType through pkg-config), 32 px. Windows, Apple and wasm32 keep Dear ImGui's stb_truetype loader at 32 px, because dear-imgui-sys can only locate FreeType through pkg-config/vcpkg and those targets cannot be built or run here. Glyph anti-aliasing differs slightly there. |
crates/ghostty-inspector/Cargo.toml, src/inspector.rs (embedded_font_source) |
Z20 (uncommitted) |
| Development resources fallback | resourcesDir returns nothing when no terminfo bundle is found next to the binary |
In the Cargo dev profile, if neither installed resources nor GHOSTTY_RESOURCES_DIR apply, the in-repo <workspace>/resources directory is used when the binary lives inside this workspace |
crates/ghostty-os/src/resourcesdir.rs (dev_resources_dir) |
967e3a1 |
Maintenance
When you fix a genuine upstream bug, commit it alone as
fix-deviant(<scope>): ... and add a row to the first table with the hash,
the Zig file:line and the test. Replace "uncommitted" entries with hashes once
the Z17 work is committed.