This repo uses a fork of Ghostty for local patches that aren't upstream yet. When we change the fork, update this document and the parent submodule SHA.
- In
ghostty/, branch off the SHA the parent repo pins, not off the fork'smain. The fork'smainis far ahead of the pin. - Commit and push the branch to
origin, which is the Darkroom Engineering ghostty fork. - Update this file with the new change summary and conflict notes.
- In the parent repo:
git add ghosttyand commit the submodule SHA.
The pinned fork head is a6d95cc23 on the fork branch programa/kitty-osc99, which is
fork main's bccfc8333fecf707dd918d46e3849fc8ed72cca0 plus the OSC 7501 patch group, the
idle display link park and the OSC 99 dispatch. Never delete that branch: the pin and its xcframework release point at it. It is built on
ghostty-org/ghostty main at c8634f3fce12f8189ed058e018195eb693f8562b and Zig 0.16.0, and
carries the twelve patch groups below: display-link restart, resize stale-frame mitigation, OSC 99
notifications, theme picker hooks, color scheme mode 2031, keyboard copy mode selection API,
layer-background flag, occluded-surface throttle, offscreen renderer realization, session
introspection and revival APIs (which include the PTY tee), OSC 7501 program status, and idle
display link parking.
Prebuilt framework for the pinned head:
- Release:
xcframework-a6d95cc23ee76efd016fee4711007d49f5e73316 - Asset SHA-256:
992da6a332817fd2bc3fd7f89f5d4a2a3d03405ff1078cc878525e7b5d55774e
- Commit:
05cf31b38(macos: restart display link after display ID change) - Files:
src/renderer/generic.zig
- Summary:
- Restarts the CVDisplayLink when
setMacOSDisplayIDupdates the current CGDisplay. - Prevents a rare state where vsync is "running" but no callbacks arrive, which can look like a frozen surface until focus/occlusion changes.
- Restarts the CVDisplayLink when
The resize commits are grouped by feature because they touch the same stale-frame replay path and tend to conflict together during rebases.
- Commits:
a3588ac53(macos: reduce transient blank/scaled frames during resize)9ba54a68c(macos: keep top-left gravity for stale-frame replay)
- Files:
pkg/macos/animation.zigsrc/Surface.zigsrc/apprt/embedded.zigsrc/renderer/Metal.zigsrc/renderer/generic.zigsrc/renderer/metal/IOSurfaceLayer.zig
- Summary:
- Replays the last rendered frame during resize and keeps its geometry anchored correctly.
- Reduces transient blank or scaled frames while a macOS window is being resized.
- Commits:
2033ffebc(Add OSC 99 notification parser)a75615992(Fix OSC 99 parser for upstream API changes)a6d95cc23(terminal: dispatch OSC 99 kitty notifications to the parser)
- Files:
src/terminal/osc.zigsrc/terminal/osc/parsers.zigsrc/terminal/osc/parsers/kitty_notification.zig
- Summary:
- Parses kitty OSC 99 notifications (title and body, chunked with
d=0, optional base64) and emitsshow_desktop_notification, the same command OSC 9 and OSC 777 produce. - The dispatch is the
99parser state inParser.next/Parser.end, thekitty_notification_pendingfield onParser(it outlivesresetso chunks can span sequences), and theparsers.zigexport. The parser reads its payload through thecapture/captureTrailing()API.
- Parses kitty OSC 99 notifications (title and body, chunked with
- Commits:
1da7281fd(Add Programa theme picker helper hooks)ea482b73e(Fix Programa theme picker preview writes)c7ab66056(Improve Programa theme picker footer contrast)c49f69f7b(Respect system theme in Programa picker)599b0ff43(Skip theme detection in Programa picker)b75388d95(Match Ghostty theme picker startup)f985d2d04(Harden Programa theme override writes)
- Files:
build.zigsrc/cli/list_themes.zigsrc/main_ghostty.zig
- Summary:
- Adds a
zig build cli-helperstep so Programa can bundle Ghostty's CLI helper binary on macOS. - Lets
+list-themesswitch into a Programa-managed mode via env vars, writing the Programa theme override file and posting the existing Programa reload notification for live app-wide preview. - Keeps the preview UI readable in light mode, matches upstream picker startup behavior, and hardens writes to the Programa-managed theme override file.
- Programa does not build or bundle the helper:
programa themeslists and sets themes in Swift. These patches stay on the fork branch only so the pin does not move; drop the group at the next fork rebase.
- Adds a
- Commits:
2be58ee0e(Fix DECRPM mode 2031 reporting wrong color scheme)74709c29b(Send initial color scheme report when mode 2031 is enabled)
- Files:
src/Surface.zigsrc/termio/stream_handler.zig
- Summary:
- Keeps Ghostty's mode 2031 color-scheme response aligned with the surface's actual conditional state after config reloads.
- Sends the initial DSR 997 report as soon as mode 2031 is enabled, which Programa relies on for immediate color-scheme awareness.
- Commit:
0b231db94(Re-export Programa selection APIs removed from upstream) - Files:
include/ghostty.hsrc/Surface.zigsrc/apprt/embedded.zig
- Summary:
- Restores
ghostty_surface_select_cursor_cellandghostty_surface_clear_selection. - Keeps Programa keyboard copy mode working against the refreshed Ghostty base after upstream removed those exports.
- Restores
- Commit:
ae3cc5d29(Restore macOS layer background hook) - Files:
src/config/Config.zigsrc/renderer/generic.zig
- Summary:
- Adds a
macos-background-from-layerbool config (default false). - When true, sets
bg_color[3] = 0in the per-frame uniform update so the Metal renderer skips the full-screen background fill. - Allows the host app to provide the terminal background via
CALayer.backgroundColorfor instant coverage during view resizes, avoiding alpha double-stacking. - Replays the layer-background restore on top of the refreshed Ghostty base so Programa keeps the resize-coverage fix after the upstream sync.
- Adds a
- Commit:
08bac45e9(perf(renderer): throttle instead of skip frame generation for occluded surfaces) - Files:
src/renderer/Thread.zig
- Summary:
renderCallbackcallsupdateFrameon every wakeup (every PTY output burst), even for surfaces the app has told us are fully occluded. That call locks the terminal mutex, consumes dirty tracking, and rebuilds render state, which is the dominant idle-CPU cost for hidden-but-busy surfaces such as background agent panes.- A hard skip of
updateFramewhile occluded is wrong: part of the scrollbar state machine lives insideupdateFrame(scrollbar_dirtyis set there in generic.zig but cleared only insidedrawFrame), anddrawFrameis also gated off while invisible. With every surface permanently occluded (as on CI's virtual display),ghostty_surface_read_text(locksrenderer_state.mutex; used by the app's socket event-subscription polling) hangs indefinitely. - The implementation throttles instead of skipping: both upstream call sites,
renderCallbackandrenderNow, runupdateFrameon every wakeup while visible and at most once perOCCLUDED_UPDATE_INTERVAL_MS(250ms / 4Hz) while occluded. The monotonic timestamp resets on the visible-to-occluded transition so the first occluded update fires immediately. This keeps renderer and Kitty-animation state moving forward without restoring the hard-skip deadlock. drainMailbox's.visiblefalse→true transition still callsrenderer.markDirty()to force one full rebuild at un-occlude, unchanged from the original skip implementation. Terminal-side dirty tracking is level-triggered (bits accumulate until consumed; dimensions/viewport compared directly), so this remains correctness-optional but cheap insurance against renderer-side cache staleness.
- Commits:
858e257f0(addghostty_surface_set_renderer_realized)d39ba5d84(return the renderer-mailbox enqueue result)5697db813(make the enqueue non-blocking)
- Files:
include/ghostty.hsrc/apprt/embedded.zigsrc/renderer/Thread.zigsrc/renderer/message.zig
- Summary:
- Lets the embedder release an occluded surface's Metal swap chain and IOSurfaces while leaving its PTY, terminal state, and scrollback alive.
- Recreates the renderer before the surface is shown again.
- Uses a non-blocking mailbox push and reports whether it was enqueued, so Programa never stalls the main actor or advances its mirror state after a dropped message.
- Commit:
96316fc50(reconcile Programa session APIs on fork main) - Files:
include/ghostty.hsrc/Surface.zigsrc/apprt/embedded.zigsrc/termio/Exec.zig
- Summary:
- Restores read-only child PID, PTY path, and PTY master-fd accessors used by Programa's durable session machinery.
- Restores surface revival through an existing PTY master fd and running child PID without taking ownership of or signaling that process.
- Programa uses the fork's
ghostty_surface_set_pty_tee_cbcallback for its session WAL. That callback runs before VT parsing. There is no separate output-tap API.
- Commits:
79173c0da(backport of upstreambae2c3cdb, ghostty-org/ghostty#14560),9d8aeccac(apprt action) - Files:
include/ghostty.h,include/ghostty/vt/osc.hsrc/terminal/osc.zig,src/terminal/osc/parsers.zig,src/terminal/osc/parsers/program_status.zig,src/terminal/osc/kitty_metadata.zigsrc/terminal/stream.zig,src/terminal/stream_readonly.zig,src/terminal/stream_terminal.zigsrc/termio/stream_handler.zig,src/apprt/surface.zig,src/apprt/action.zig,src/Surface.zig,src/apprt/gtk/class/application.zig
- Summary:
- Parses OSC 7501 (Program Status Protocol) and forwards each validated report body to the
embedder as
GHOSTTY_ACTION_PROGRAM_STATUS. Programa keeps the records. - Sends
RESET_ALLon a full reset (RIS) andPROMPTon OSC 133;A, both only after the terminal has seen a report. - Answers the support query
OSC 7501 ; ?with the same bytes and terminator.
- Parses OSC 7501 (Program Status Protocol) and forwards each validated report body to the
embedder as
- Conflict notes:
- The backport takes only the parser and stream action. Upstream's libghostty-vt C API
(
c/terminal.zig,vt/terminal.h) andstream_terminal.zigeffect were left out because the pinned tree lacks the effects they build on.stream_terminal.zigandstream_readonly.zigignore.program_status. Parser.nextSliceis a byte loop overnext; upstream's version appends to the capture in bulk. Take upstream's on the next sync.GHOSTTY_OSC_COMMAND_PROGRAM_STATUSis 26 here and 28 upstream, because the pin lacks the Kitty desktop notification and unknown-OSC commands. Take upstream's value on the next sync.
- The backport takes only the parser and stream action. Upstream's libghostty-vt C API
(
- Commits:
2076a6134(renderer: park an idle display link until the next change),617aac442(reset the idle timer underdraw_mutex) - Files:
src/renderer/generic.zig,src/renderer/Thread.zig
- Summary:
- A focused, visible terminal ran its CVDisplayLink nonstop. Every vsync woke the render thread
for a
drawFramethat found nothing to draw, about 1.2% of a core per focused terminal on an Apple Silicon Mac. drawFramenotes each tick with nothing to draw and stops the link after 250ms of them.Thread.drawFramecallsresumeVsyncinstead ofhasVsync, which restarts a parked link when the surface is visible and focused, so the change still draws on the next vsync rather than without one.- Restarting a parked link added about 4ms to that one draw on a bench. 250ms outlasts the gap between keystrokes, so typing keeps the link running, and stays under the 600ms cursor blink, so a blinking prompt still parks between blinks.
- A focused, visible terminal ran its CVDisplayLink nonstop. Every vsync woke the render thread
for a
- Conflict notes:
- Upstream reworks
syncDisplayLinkand the vsync path inThread.drawFramefrom time to time. Keep theresumeVsynccall at the!nowdraw gate and the idle-timer reset wherever the link starts.
- Upstream reworks
- Was local in the fork as
10a585754. - Landed upstream as
bb646926f, so the fork does not carry it as a separate patch.
- Were local in the fork as
8ade43ce5,0cf559581,312c7b23a, and404a3f175. - Not carried: newer Ghostty prompt-marking changes on the current base supersede these fork-only zsh redraw patches (dropped in the March 30, 2026 rebase).
- The older Programa-only surface-config plumbing was local as
c19c82bfdand was dropped when Programa returned to post-create focus synchronization. - The reconciled tree preserves the current core behavior: surfaces start with Ghostty's default focused state, a later host focus callback reports real transitions, and enabling DECSET 1004 immediately reports the terminal's current focus state before subsequent transition reports.
When syncing with upstream, re-check these areas.
These files change frequently upstream; be careful when rebasing the fork:
-
src/terminal/osc.zig- OSC dispatch logic moves often. Re-check the integration points for the OSC 99 parser (the
99state,kitty_notification_pending, theendprong) and keep thecapture/captureTrailing()API usage intact. A sync that drops them leaveskitty_notification.zigcompiled but unreachable.
- OSC dispatch logic moves often. Re-check the integration points for the OSC 99 parser (the
-
src/terminal/osc/parsers.zig- Ensure
kitty_notificationstays imported after upstream parser reorganizations.
- Ensure
-
include/ghostty.h,src/Surface.zig,src/apprt/embedded.zig,src/termio/Exec.zig- Upstream removed Programa-used selection exports. Preserve the re-exported
ghostty_surface_select_cursor_cellandghostty_surface_clear_selectionfunctions. - Preserve the child/PTY accessors and revival configuration described in section 10. If upstream changes subprocess ownership or watcher semantics, revived processes must remain non-owned: Programa may observe their exit but Ghostty must never signal them.
- Prefer the current PTY tee callback over reintroducing the retired output-tap API.
- Upstream removed Programa-used selection exports. Preserve the re-exported
-
src/renderer/generic.zig- The
macos-background-from-layercheck sits next to the glass-style check inupdateFrame. If upstream refactors the bg_color uniform update or the glass conditional, re-check that both paths still zero outbg_color[3]correctly.
- The
-
src/Surface.zig,src/apprt/embedded.zig,macos/Sources/Ghostty/Surface View/SurfaceView.swift- Keep host focus callbacks aligned with the core surface state. If upstream refactors surface creation or focus synchronization, re-check the initial DECSET 1004 report and later transition reports together.
-
src/renderer/Thread.zig- The occluded-render throttle guards both current
updateFramecall sites,renderCallbackandrenderNow. On future syncs, apply the same throttle to every new caller. Do not go back to a hardflags.visibleskip; see section 8 for why that brokeghostty_surface_read_textpolling on CI's permanently occluded virtual display. Keep themarkDirty()force on the occluded-to-visible transition indrainMailbox.
- The occluded-render throttle guards both current
-
src/termio/stream_handler.zig- Keep DECSET 1004 aligned with the terminal's current focus flag. Enabling
the mode reports the current state immediately; later
CSI I/CSI Oreports follow actual focus transitions.
- Keep DECSET 1004 aligned with the terminal's current focus flag. Enabling
the mode reports the current state immediately; later
If you resolve a conflict, update this doc with what changed.
Notes for maintainers who need the chronology behind the current state.
- The fork's
mainonce lacked the accessor and revival changes that older Programa commits carried on a temporary feature branch, because those commits reachedmainonly through retain-ancestry merges.96316fc50reconciled those required APIs onto the actual forkmaintree. bccfc8333reconciled the fork withghostty-org/ghosttymain(August 21, 2026), moved it to Zig 0.16.0, and brought in upstream's complete Kitty graphics implementation (validation and deletion fixes, relative placements, margin clipping, animation parsing, storage and playback, and renderer-driven frame scheduling).- The occluded-render hard skip (
c25020f99) was pinned and reverted on August 4, 2026 because of theghostty_surface_read_texthang described in section 8. The throttle (08bac45e9) landed on August 5, 2026. Its merge gate was three consecutive green CI runs, because the failure mode was probabilistic and one green run is not enough evidence. A separate use-after-free through stale surface userdata in the.scrollbarmailbox path was found and fixed app-side during that investigation.
The August 21, 2026 upstream reconciliation had literal conflicts in:
src/Surface.zigsrc/cli/list_themes.zigsrc/cli/toggle_quick_terminal.zigsrc/config/url.zigsrc/crash/dir.zigsrc/font/shaper/coretext.zigsrc/os/TempDir.zigsrc/renderer/generic.zigsrc/termio/Termio.zig
Semantic reconciliation was also required in src/App.zig,
src/apprt/embedded.zig, src/config/CApi.zig, and
src/renderer/Thread.zig. The important resolutions were:
- Migrate fork APIs to Zig 0.16's explicit
std.Iomutex, event, environment, file, mailbox, resize, and allocator interfaces without removing exports. - Preserve manual IO, mobile render-grid and tmux hooks, PTY tee and revival, selection and process accessors, layer-background alpha behavior, renderer realization, display-link restart, and resize fixes.
- Export render-grid rows through
pagePreservingStateso compressed scrollback is decoded at most once per page without changing PageList storage. - Apply the 4 Hz occlusion throttle to every upstream
updateFramecaller and retainmarkDirty()when a surface becomes visible. - Keep the upstream Kitty graphics animation scheduler active through the throttle rather than restoring upstream's hard invisible skip.