Keep the standalone style and hyperlink codecs strict while allowing PAGE and SCREEN decoders to discard invalid optional data at boundaries they own. Normalize invalid styles to defaults, ignore unrepresentable hyperlinks, reuse duplicate entries, and validate hyperlink values before encoding.
Keep SCREEN encoding strict while allowing decoding to recover from unknown or noncanonical semantic state. Cursor positions now clamp to the restored active area, and invalid enum values, reserved bits, and optional state degrade to native defaults.
Keep snapshot grid encoding strict by rejecting malformed wide-cell relationships before they can produce invalid wire data.
Decode untrusted grids liberally while preserving record alignment. Unknown semantic values and content kinds degrade to safe defaults, optional graphemes and hyperlinks are dropped when invalid or over capacity, and malformed wide-cell markers normalize to narrow cells.
Treat Kitty virtual placeholder codepoints as ordinary valid grid content during snapshot restore and derive the native row lookup hint from decoded cells. Image and placement registries remain intentionally omitted.
Cover the behavior with a complete snapshot round trip containing a real virtual placement and its grapheme diacritics.
Run the snapshot Kaitai verifier in its own required xsm job. This gives schema, fixture, checksum, and cross-record validation a distinct CI result without coupling it to the libghostty-vt test suite.
Describe the complete version 1 snapshot format with a Kaitai schema and make every golden fixture self-describing for automatic discovery. Add a verifier that compiles the schema, parses all fixtures, and checks record checksums, checkpoint digests, and cross-record invariants.
Preserve Kaitai metadata when generating fixture candidates and provide the compiler and Python runtime dependencies through the development shell. Keep the mode registry portable to Kaitai JavaScript targets so the complete fixture also works in the web IDE.
Extracted out the raw `src/terminal` changes needed for the future
snapshot work, 4 separate changes. These are uncontroversial and
relatively simple, summarized below. Tests AI assisted but the rest
including commit messages, this PR message, etc. all organic.
* **Add iterator to ref counted set.** Iterate over live entries and
their IDs. Const, doesn't mutate the set.
* **lib.Enum produces stable enums for Zig.** Basically the same as C
except it uses the smallest fitting integer including the holes.
* **PageList: a couple helpers for manually creating pages.** There is
`PageList.Builder` for creating a new pagelist and
`PageList.allocatePage` for modifying an existing one. This allows
PageList construction from raw pages.
Rename Builder.addPage and PageAllocation.cancel to their consistent allocatePage and deinit forms. Track successful ownership transfers so both builder APIs can use unconditional deferred cleanup without releasing pages transferred to a PageList.
Fixes#13522
Fixes unreachable when reflow dupes a hyperlink into a destination page
whose string allocator is nearly full.
The capacity precondition in ReflowCursor.writeCell performed a single
test allocation of `uri.len + id.len` bytes before duping a hyperlink
into the destination page. But PageEntry.dupe allocates the URI and the
explicit ID as two separate allocations, and the string allocator rounds
every allocation up to its 32-byte chunk size independently, so the two
separate allocations can require one more chunk than the single combined
test allocation.
Write a new helper to make sure we get the right amount of space using
the same allocation pattern of dupe.
**AI note:** Verified upstream via Fable. I told it to ignore any
conclusions and do its own validation and fix suggestion. It did
validate it with a failing test which I studied. It implement a fix, I
rewrote it to be more idiomatic.
Fixes#13522
Fixes unreachable when reflow dupes a hyperlink into a destination page
whose string allocator is nearly full.
The capacity precondition in ReflowCursor.writeCell performed a single
test allocation of `uri.len + id.len` bytes before duping a hyperlink
into the destination page. But PageEntry.dupe allocates the URI and
the explicit ID as two separate allocations, and the string allocator
rounds every allocation up to its 32-byte chunk size independently, so
the two separate allocations can require one more chunk than the
single combined test allocation.
Write a new helper to make sure we get the right amount of space
using the same allocation pattern of dupe.
This change improves the user experience for Pi TUI users on macOS.
As a user of Ghostty 1.3.1, Pi 0.80.7, and macOS 26.5, I noticed that
Command-click was not working.
With Pi's help (GPT-5.6 Terra High), I narrowed the cause down to Pi's
redraw
behavior and `src/config/url.zig`'s regular expression. More details are
in
[Vouch Request
#13491](https://github.com/ghostty-org/ghostty/discussions/13491).
The `trailing_spaces_at_eol` behavior in `src/config/url.zig` was
introduced in
[PR #9921](https://github.com/ghostty-org/ghostty/pull/9921) while
improving
Command-click handling for relative and local paths. The concern about
matching
trailing whitespace was also noted in [a review
comment](https://github.com/ghostty-org/ghostty/pull/9921#issuecomment-3661107609).
However, supporting file paths with trailing spaces does not seem like a
good
trade-off because it blocks Command-click for file paths displayed by Pi
TUI.
This PR removes that behavior.
I tested this on my Mac with a patched Ghostty build, and Command-click
worked
correctly for file paths in Pi TUI.
AI disclosure: I used Pi with GPT-5.6 Terra High to investigate and
implement this change.
I reviewed the code and tested the result myself.
I've come up with a way to avoid manually allocating each entry which
honestly makes the code flow much more smoothly. Basically you collect
all the applicable keybinds first, then try to bind them with their
stable memory addresses.
I've come up with a way to avoid manually allocating each entry which
honestly makes the code flow much more smoothly. Basically you collect
all the applicable keybinds first, then try to bind them with their
stable memory addresses.
reword: The doc said "macOS doesn't need any dependencies" and then
immediately listed things you needed to install for macOS 😁. This is
just rewording the doc to be more consistent.
#13491#9921
Path matching previously included end-of-line spaces. Pi redraws can
leave blank cells after a path, causing cmd-click to open a pathname
that includes those cells. Do not include trailing whitespace in path
matches.
AI disclosure: Pi using GPT-5.6 Terra High was used to investigate
and write this change. I reviewed it personally.
reword: The doc said "macOS doesn't need any dependencies" and then immediately listed things you needed to install for macOS 😁. This is just rewording the doc to be more consistent.
## Summary
Applications cannot reliably determine whether an unfocused terminal is
still
visible, so focus reports alone are insufficient for avoiding
unnecessary
rendering.
This adds terminal visibility reporting by:
- implementing private mode 2033
- supporting `CSI ? 998 n` visibility queries and `CSI ? 999 ; Ps n`
responses
- reporting effective visibility changes while mode 2033 is enabled
- preserving host-owned visibility state across terminal resets
- treating unknown visibility conservatively as potentially visible
On GTK 4.12 and newer, surface visibility now combines widget mapping
with the
toplevel `suspended` state. This allows Ghostty to recognize windows
hidden on
another workspace or otherwise known by the compositor to be
non-visible.
Older GTK versions retain the existing conservative behavior.
## Testing
Added coverage for:
- mode 2033 support and enable/disable behavior
- explicit visibility queries
- immediate reports when enabling the mode
- visible and non-visible responses
- visibility persistence across terminal resets
- suppression of visibility queries in read-only mode
## AI disclosure
Amp assisted with the implementation, tests, commit messages, and this
pull
request description. I reviewed the resulting changes and understand how
they
interact with the terminal, termio, surface, and GTK visibility paths.
Implements: #13451
Reference: https://rockorager.dev/misc/visibility-reports/