"Why is my quick terminal not taking up the entire top of my docked Mac
screen after I reconnect?" Boy howdy are you in the right PR.
It turns out that the quick terminal caches its last-closed window frame
per display so it can restore the user's size when reopened. The cache
entry was considered valid whenever the current screen was the same size
*or larger* than when the frame was saved ("persist when screens grow").
This has led to a pattern that was simply maddening. To wit: that rule
breaks across display changes.
When an external display is disconnected and later reconnected at a
different resolution (common after traveling with a laptop, do not even
get me started on projectors) the same display can come back larger than
when the frame was cached. The stale frame is still treated as valid and
restored, so the quick terminal no longer fills the screen (it appears
at a partial width/height). Because the cache is persisted, restarting
Ghostty does not clear it, and the user is slowly driven mad. Welcome to
madness; we have snacks.
This PR addresses this by treating a cached frame as valid when the
screen geometry matches exactly (both backing scale factor and frame
size). On any mismatch we drop the entry and fall back to the configured
quick-terminal-size. Manual resizes are still remembered across toggles
within a stable display configuration.
Fixes the regression reported in #12348.
AI disclaimer: I used AI for this. Of course I used AI for this, my code
is terrible on a good day. Specifically, Claude Code, as well as a
custom harness that has the curious tendency to write commit messages
containing conspiracy theories about the code because I am history's
greatest monster.
Fight me!
The quick terminal caches its last-closed window frame per display so it
can restore the user's size when reopened. The cache entry was considered
valid whenever the current screen was the same size *or larger* than when
the frame was saved ("persist when screens grow"). This has led to a pattern
that was simply maddening. To wit:
That rule breaks across display changes. When an external display is
disconnected and later reconnected at a different resolution (common
after traveling with a laptop) the same display can come back larger
than when the frame was cached. The stale frame is still treated as valid
and restored, so the quick terminal no longer fills the screen (it appears
at a partial width/height). Because the cache is persisted, restarting
Ghostty does not clear it, and the user is slowly driven mad.
Only treat a cached frame as valid when the screen geometry matches
exactly (both backing scale factor and frame size). On any mismatch we
drop the entry and fall back to the configured quick-terminal-size. Manual
resizes are still remembered across toggles within a stable display
configuration.
Fixes the regression reported in #12348.
Close#12825
Skip the initial emissions from the focused surface appearance publishers after a tab focus change. The focused surface is already synced immediately, so the initial Combine values only repeat the same titlebar and background updates. Subsequent derived config and OSC background changes still resync the window appearance.
Fixes 2 bugs
1. After dragging a non-focused surface from window A to window B
**quickly without making B the key window**, the focused surface in
window A is not receiving `keyDown` events.
https://github.com/user-attachments/assets/a8861c0a-9300-470d-bf7e-0f32a9ab2cd1
2. #12343 After dragging a surface from tab A to tab B within the same
window, the dragged surface is not rendering input correctly.
> The reason the thread is stuck is because the surface's occlusion
state is set to invisible after target tab's activate while dragging,
since the dragged surface is still in previous tree before dropping, and
after dropping the occlusion state of this surface is not updated to
visible, which causing the surface is accepting input but not rendering.
https://github.com/user-attachments/assets/d67f5dba-8609-4f67-a956-921982faf796
The reason the thread is stuck is because the surface's occlusion state is set to invisible after target tab's activate while dragging, since the dragged surface is still in previous tree before dropping, and after dropping the occlusion state of this surface is not updated to visible, which causing the surface is accepting input but not rendering.
This fixes a bug: after dragging a non-focused surface from window A to window B **quickly without making B the key window**, the focused surface in window A is not receiving `keyDown` events.
This should be safe to delete now after #12461.
I tested saving 27 tabs, 4 with 2 splits,
`TerminalRestorable.encode(with:` finished successfully.
And I check the breakpoints when the Sparkle sends
`-[NSRunningApplication treminate]`. The call stack at `-[NSResponder
invalidateRestorableState]` is pretty much the same as quitting via
`cmd+q`.
Looks like `NSWorkspace.shared.setIcon` can only be called from the main
App, DockTilePlugin is sandboxed and doesn't have the permission to
`file-write-finderinfo`.
<img width="1186" height="144" alt="image"
src="https://github.com/user-attachments/assets/e5ea4f1c-718c-493a-bda2-32787881881e"
/>
It works fine in debug, but not in release. This fixes#11489
Expose the foreground process PID and TTY device path as read-only properties on the AppleScript terminal class and App Intents TerminalEntity. This enables reliable process-to-terminal mapping for automation tools when multiple terminals share the same CWD.
Closes#11592Closes#10756
Session: 019d341c-a165-7843-a2f7-2f426114cf17
Looks like `NSWorkspace.shared.setIcon` can only be called from the main App, DockTilePlugin is sandboxed and doesn't have the permission to `file-write-finderinfo`.
It works fine in debug, but not in release. This fixes#11489, #11290
Extend String.matchedIndices(for:) to fall back to initials
matching when no substring match is found. Typing the first letter
of each word now matches commands, e.g. "tbo" matches "Toggle
Background Opacity", with each matched initial highlighted.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add String.matchedIndices(for:) to find substring matches and use
it to bold and tint matched characters with the accent color in
both titles and subtitles. Title matches take priority — subtitles
are only highlighted when the title didn't match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regression of #12119, this memory leak affects new tabs, since the terminal controller is not deallocated correctly. Hitting `cmd+t` will create a new window with two tabs, but only one actually contains usable surface.
You can reproduce by:
1. Quit and Reopen Ghostty
2. Open a new window if no window is created (initial-window = false)
3. Close the window
4. Hit `cmd+t`
The 👻 Ghost Tab Issue
Previous failure scenario (User perspective):
1. Open a new tab
2. Instantly trigger close other tabs
(eg. through custom user keyboard shortcut)
3. Now you will see an empty Ghost Tab
(Only a window bar with empty content)
The previous failure mode is:
1. Create a tab or window now in `newTab(...)` / `newWindow(...)`.
2. Queue its initial show/focus work with `DispatchQueue.main.async`.
3. Close that tab or window with `closeTabImmediately()` /
`closeWindowImmediately()` before the queued callback runs.
4. The queued callback still runs anyway and calls `showWindow(...)` /
`makeKeyAndOrderFront(...)` on stale state.
5. The tab can be resurrected as a half-closed blank ghost tab.
The fix:
- Store deferred presentation work in a cancellable
DispatchWorkItem and cancel it from the close paths
before AppKit finishes tearing down the tab or window.
- This prevents the stale show/focus callback from
running after close.