### Closes#7649
The bar lives alongside URL Hover in VStack at the bottom. The current
body of SurfaceView is becoming rather long and complicated, so this pr
also contains some refactors:
- Move URL Hover to a separate file
> The text is copied from previous input string to keep it consistent,
also I’m confused with text on GTK so this is my first choice, but it
can be changed as the same as GTK.
Separate prs will be opened for:
1. Set to Read-only after exits
2. Hide cursor when in Read-only
### Preview
https://github.com/user-attachments/assets/eb44e211-eac5-4f40-836c-4912b18dfb01
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>
Fix: #11989
Cause identified to: ab352b5af9
Original PR: #10003
Problem: I don't think it is OK to hard code the keybind like this at
all. Ghostty's config is flexible enough to achieve this.
Proposal: Revert the above commit via this PR.
@yasuf @bo2themax
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
https://github.com/user-attachments/assets/cb91cd85-4a08-4c16-9efb-1a9ab30fc2bc
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.
## AI Usage
I used GPT 5.4 to find the initial issue and fix it. I cleaned up and
narrowed down the commit afterwards.
-----
Additional Notes:
I use `cmd+o` to `close_tab:other`
https://github.com/jamylak/dotfiles/blob/main/ghostty/config#L106C1-L106C34
Try it for your self if you want to reproduce, just do a quick `cmd+t`
`cmd+o` and you will see
The previous version requested general notification permissions but
omitted the `.badge` option. Because the initial request was granted,
`settings.authorizationStatus` returns `.authorized`, leading the app to
believe it has full notification privileges when it actually lacks the
authority to update the dock icon badge.
Debug hint:
You can reset the notification settings by right-clicking on the app
name.
<img width="307" height="85" alt=""
src="https://github.com/user-attachments/assets/660cd332-eda6-45d6-8bfd-a6f9e28e21e8"
/>
The previous version requested general notification permissions but omitted the `.badge` option. Because the initial request was granted, `settings.authorizationStatus` returns `.authorized`, leading the app to believe it has full notification privileges when it actually lacks the authority to update the dock icon badge.
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.
- Fixes: https://github.com/ghostty-org/ghostty/issues/11964
Made a private enum type `VersionConfig` to reference whether the
release is a semver or tip, makes it easier for later in the view to
`switch` between cases.
I do think there could be a better place for this enum or we can get rid
of it, open to opinions. Right now version parsing is kind of duplicated
between `AboutView` and `UpdateModalView` so we can also extract to a
common helper if wanted.
Tested by manually setting `Marketing Version` in build settings to
`1.3.1`
<img width="412" height="532" alt="Screenshot 2026-03-30 at 18 31 15"
src="https://github.com/user-attachments/assets/285bb94d-138b-4169-bb66-684eb04b6ca3"
/>
`332b2aefc`
<img width="412" height="532" alt="Screenshot 2026-03-30 at 18 32 48"
src="https://github.com/user-attachments/assets/fea30d39-bea7-4885-8221-1696e148f45e"
/>
### AI Disclosure
I used Sonnet 4.6 to understand where the version strings came from and
in what format, it read release yml files to see what's going on. Then
it proposed really bad code so I manually went in and cleaned up the
view.
This adds features like:
1. Clicking outside of SearchBar works like typing `escape`
2. Typing `tab` while search bar is focused also works like typing `escape`
This is the first step (also another step forward for completing #7879)
to fix various responder issues regarding keyboard shortcuts. I tried my
best to separate changes chunk by chunk; there will follow up pr based
on this to fix them.
This pr doesn't change any existing behaviours/flaws, but following
changes will be easier to review after this.
## AI Disclosure
Claude wrote most of the test cases