Commit Graph

16954 Commits

Author SHA1 Message Date
Mitchell Hashimoto
57c1baf43a macos: defer overlapping clipboard completion
Fixes #/13074

Overlapping clipboard confirmations now defer denial until the next
main queue turn rather than completing inside the confirmation callback.

This prevents the native request state from being invalidated while its
callback is still active, avoiding the OSC 52 crash reported in #13074.

The deferred closure retains the originating surface view and completes
the ignored request with empty data, preserving the existing deny
behavior.
2026-08-05 14:24:37 -07:00
Mitchell Hashimoto
880eded158 macos: avoid IOSurface leak on automated surface creation (#13640)
Fixes #13444

A close while AppKit temporarily cleared/changed a surface's window
would leak the surface in the controller's pslit tree. This retained
surface kept a bunch of resources around, particularly large IOSurfaces.

This seems to only be reproducible under scripted load: rapid terminal
creation/destruction so that destruction happens just while there is a
nil window on a surface view.

Track surface ownership in a weak controller map updated alongside the
split tree, with validated fallbacks for existing attachment state.
Resolve scripted and App Intent operations through that ownership, and
route non-confirming root closes directly through the immediate tab or
window close path so teardown always reaches the renderer.
2026-08-05 14:18:22 -07:00
Jeffrey C. Ollie
15ac61db16 gtk: implement drag-to-move for splits (#10423)
One major todo is moving splits across different split trees (i.e.
moving across tabs and windows), but that would involve a lot more
logic. This MVP version works for now.

Video demo (somehow the encode quality is terrible - I'll fix this
later):


https://github.com/user-attachments/assets/a5029451-9641-4680-bff6-38f52ebded4b


GTK version of #10090

Closes #10224
2026-08-05 15:54:52 -05:00
Mitchell Hashimoto
7a047553c7 macos: avoid IOSurface leak on automated surface creation
Fixes #13444

A close while AppKit temporarily cleared/changed a surface's window would
leak the surface in the controller's pslit tree. This retained surface kept
a bunch of resources around, particularly large IOSurfaces.

This seems to only be reproducible under scripted load: rapid terminal
creation/destruction so that destruction happens just while there is a nil
window on a surface view.

Track surface ownership in a weak controller map updated alongside the split
tree, with validated fallbacks for existing attachment state. Resolve
scripted and App Intent operations through that ownership, and route
non-confirming root closes directly through the immediate tab or window close
path so teardown always reaches the renderer.
2026-08-05 13:39:48 -07:00
Mitchell Hashimoto
168c7b9467 core: fix encoded key request cleanup (#13635)
Encoded key requests are owned by the caller until they are added to a
key sequence or queued for IO. The child_exited path and failed queue
append returned without freeing the allocated request.

The existing errdefer was also too broad: after queueIo took ownership,
a later setSelection or queueRender error could free the queued request.

We now free requests in the return paths that still own them, and the
errdefer has been removed.

Also, activate a sequence only after encoding and queue append succeed
so failure preserves the previous sequence state.
2026-08-05 12:32:48 -07:00
Mitchell Hashimoto
d0659ba521 macos: guard fullscreen tab presentation with objc catcher (#13636)
#13611

Route new-tab window presentation through an Objective-C exception
catcher.

AppKit can raise an NSInternalInconsistencyException while selecting a
new tab in native fullscreen.

Catch the presentation exception, report it through the existing error
logging path, and leave Ghostty running when AppKit’s fullscreen window
stack is inconsistent.

This was pretty hard to reproduce but I was able to reproduce it about
1/3rd of the time via AppleScript automation...
2026-08-05 11:46:02 -07:00
Mitchell Hashimoto
b1887bd716 terminal: reset wrap state for CSI 2 K (#13637)
#13616

Reset the soft-wrap state when CSI 2 K erases the complete cursor row.
Previously, erase-to-end reset the flag while complete-line erase left
it set.

WezTerm, kitty, Alacritty, VTE, and xterm.js clear the wrap state for
complete-line erase. xterm preserves it, but xterm copies physical rows
during resize instead of reflowing them. Diverge from xterm so reflow in
Ghostty does not treat erased rows as one logical line, and cover the
behavior with a resize regression test.
2026-08-05 11:41:56 -07:00
Mitchell Hashimoto
713ad0eb18 config: refill after ignored line boundaries (#13638)
Fixes https://github.com/ghostty-org/ghostty/discussions/13441

Refill the line iterator when an ignored comment or blank line consumes
the remaining buffered data.

Configuration parsing previously stopped silently at these boundaries
and left every subsequent setting unapplied.

Request more data before continuing the loop and cover both comment and
blank line boundaries with buffered-reader regression tests.
2026-08-05 11:40:37 -07:00
Mitchell Hashimoto
c247e455c2 config: refill after ignored line boundaries
Refill the line iterator when an ignored comment or blank line
consumes the remaining buffered data.

Configuration parsing previously stopped silently at these boundaries
and left every subsequent setting unapplied.

Request more data before continuing the loop and cover both comment
and blank line boundaries with buffered-reader regression tests.
2026-08-05 11:30:45 -07:00
Mitchell Hashimoto
bfd40c84bd terminal: reset wrap state for CSI 2 K
#13616

Reset the soft-wrap state when CSI 2 K erases the complete cursor
row. Previously, erase-to-end reset the flag while complete-line erase
left it set.

WezTerm, kitty, Alacritty, VTE, and xterm.js clear the wrap state for
complete-line erase. xterm preserves it, but xterm copies physical rows
during resize instead of reflowing them. Diverge from xterm so reflow in
Ghostty does not treat erased rows as one logical line, and cover the
behavior with a resize regression test.
2026-08-05 11:26:48 -07:00
Mitchell Hashimoto
8696bef644 macos: guard fullscreen tab presentation
#13611

Route new-tab window presentation through an Objective-C exception catcher.

AppKit can raise an NSInternalInconsistencyException while selecting a
new tab in native fullscreen.

Catch the presentation exception, report it through the existing error
logging path, and leave Ghostty running when AppKit’s fullscreen window
stack is inconsistent.

This was pretty hard to reproduce but I was able to reproduce it about
1/3rd of the time via AppleScript automation...
2026-08-05 11:25:18 -07:00
Jon Parise
0060d89b5b core: fix encoded key request cleanup
Encoded key requests are owned by the caller until they are added to a
key sequence or queued for IO. The child_exited path and failed queue
append returned without freeing the allocated request.

The existing errdefer was also too broad: after queueIo took ownership, a
later setSelection or queueRender error could free the queued request.

We now free requests in the return paths that still own them, and the
errdefer has been removed.

Also, activate a sequence only after encoding and queue append succeed
so failure preserves the previous sequence state.
2026-08-05 14:12:52 -04:00
Mitchell Hashimoto
54fe8e1885 macos: handled untrusted OSC8 hyperlinks more carefully (#13634)
OSC8 hyperlinks previously executed directly via the NSWorkspace opener
so a malicious application can just do whatever it wanted and trick the
user into opening something through Launch Services.

This PR notifies apprt of OSC8 hyperlinks so they can be handled
specially. In this PR, I added macOS-specific handling of OSC8 through a
variety of improvements:

  - Preview text is sanitized, so invisible Unicode characters now show.
- Questionable-looking URLs require confirmation to open, but a user can
confirm to open.
- Very questionable or definitely unsafe URLs are blocked with an alert
that only allows the user to copy the link. The alert also notifies the
user why.

This PR also adds an explicit `link-osc8` config (default true) that
users can use to disable osc8 completely.

## Demos

### Custom URL Schemes (Confirm)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 25
57@2x"
src="https://github.com/user-attachments/assets/f7773ca2-3389-4749-a5c9-393ae097c044"
/>

### Invisible Characters (Block)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 26
44@2x"
src="https://github.com/user-attachments/assets/bd2d0f33-f128-46e8-9bdb-227afecbb942"
/>

### Executable Target (Block)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 27
31@2x"
src="https://github.com/user-attachments/assets/080c0524-2c8e-4931-892f-d2643a5d0d4e"
/>
2026-08-05 10:44:56 -07:00
Mitchell Hashimoto
77537c8065 macos: handled untrusted OSC8 hyperlinks more carefully
OSC8 hyperlinks previously executed directly via the NSWorkspace opener
so a malicious application can just do whatever it wanted and trick the
user into opening something through Launch Services.

This PR notifies apprt of OSC8 hyperlinks so they can be handled
specially. In this PR, I added macOS-specific handling of OSC8 through a
variety of improvements:

  - Preview text is sanitized, so invisible Unicode characters now show.
  - Questionable-looking URLs require confirmation to open, but a user
    can confirm to open.
  - Very questionable or definitely unsafe URLs are blocked with an 
    alert that only allows the user to copy the link. The alert also
    notifies the user why.
2026-08-05 10:22:28 -07:00
Mitchell Hashimoto
46767b5213 terminal: bound OSC and grapheme allocations (#13633)
Cap allocating OSC payloads at 8 MiB and retain at most 64 grapheme
suffix codepoints per cell. Our limits are generous compared to other
terminals and this prevents an easy DoS.

When the grapheme codepoint max is hit we just ignore any remainders.
This can result in real broken graphemes because Unicode spec is really
unbounded on them but for all practical use cases its reasonable.

Compared to other terminals:

| Terminal | OSC capture limit | Cell codepoints | 
| --- | ---: | ---: |
| Ghostty | 8 MiB | 65 |
| kitty | ~256 KiB ordinary | 24 |
| VTE | 4,096 scalars | 11 |
| xterm | 20 or 600 KB | 3 default, 6 max |
| Alacritty | unbounded | unbounded |
| WezTerm | unbounded | no explicit limit |
2026-08-05 09:52:11 -07:00
Mitchell Hashimoto
ad27c989a4 libghostty-vt: require opt-in for title reports (#13632)
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.

Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction. Ghostty fixed
this a long time ago by making CSI 21 t an opt-in in the config. Do the
same but with our C/Zig API.
2026-08-05 09:32:45 -07:00
Mitchell Hashimoto
727b8a02f8 terminal: bound OSC and grapheme allocations
Cap allocating OSC payloads at 8 MiB and retain at most 64 grapheme
suffix codepoints per cell. Our limits are generous compared to other
terminals and this prevents an easy DoS.

When the grapheme codepoint max is hit we just ignore any remainders.
This can result in real broken graphemes because Unicode spec is really
unbounded on them but for all practical use cases its reasonable.

Compared to other terminals:

| Terminal | OSC capture limit | Cell codepoints |
| --- | ---: | ---: |
| Ghostty | 8 MiB | 65 |
| kitty | ~256 KiB ordinary | 24 |
| VTE | 4,096 scalars | 11 |
| xterm | 20 or 600 KB | 3 default, 6 max |
| Alacritty | unbounded | unbounded |
| WezTerm | unbounded | no explicit limit |
2026-08-05 09:30:24 -07:00
Mitchell Hashimoto
38e891e6c0 terminal: require opt-in for title reports
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.

Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction.

Gate the response in the shared terminal stream, append the C API
option without renumbering existing values, and cover the default,
opt-in, and reset behavior in Zig and C API tests.
2026-08-05 09:17:03 -07:00
Mitchell Hashimoto
bd21ff153e terminal: avoid VS15 cursor underflow (#13631)
Handle VS15 width changes when the wide grapheme base is directly under
the cursor.

A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.
2026-08-05 09:10:46 -07:00
Mitchell Hashimoto
fe98aef21c terminal: report DECECM as permanently reset (#12660)
Closes #12505 

This PR allows Ghostty to respond to DECRQM queries for DECECM with the
"permanently reset".

AI disclosure: I used Codex to help inspect the relevant code path and
explain the issue, but I reviewed and made the code changes myself.
2026-08-05 09:03:15 -07:00
Mitchell Hashimoto
33d34cf5ce terminal: avoid VS15 cursor underflow
Handle VS15 width changes when the wide grapheme base is directly under
the cursor. Cover both disabled wraparound and restored pending-wrap
cursor states.

A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.

Find the spacer from the wide base instead of subtracting from the
cursor distance. Reposition the cursor from the base column and clamp it
to the active right margin.
2026-08-05 08:55:21 -07:00
Mitchell Hashimoto
dd035284c2 Kitty graphics protocol bugs (#13630)
Specifics in each commit message. This will be part of a security
advisory in 1.4.0 since these patches issues related to overflows, DoS,
unbounded memory allocation, etc.
2026-08-05 08:53:44 -07:00
Jeffrey C. Ollie
e2065583a4 core: transfer long key encoding buffer (#13628)
The long-preedit fallback introduced in e95b1707c intentionally
allocated twice. The encoder wrote into an oversized caller-owned buffer
and returned only the written subslice, so transferring it required
manually shrinking the allocation or tracking its original capacity. The
copy kept that rare path simple.

The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping
when the allocator supports it and falling back to an allocation and
copy when it does not. Use it directly for WriteReq.alloc to remove the
guaranteed second allocation while preserving cleanup on failure.
2026-08-05 10:44:16 -05:00
Mitchell Hashimoto
402b9227de terminal/kitty: reclaim pruned placements
Reclaim pin-backed Kitty graphics placements after their tracked screen
content is pruned. Treat garbage pins as non-renderable until the next
placement command sweeps them.

Placements that scrolled beyond retained history previously remained in
the placement map and tracked-pin set. Long-running graphics output could
accumulate stale state, and remapped garbage pins could appear at an
unrelated fallback location.

Sweep garbage placements before growing the placement map, releasing each
tracked pin while preserving virtual placements. Return no geometry or
visible render position for garbage pins and cover both storage and C API
behavior with regression tests.
2026-08-05 08:37:45 -07:00
Mitchell Hashimoto
d0c516f8f3 terminal/kitty: release replaced placement pins
Release a Kitty graphics placement's tracked pin before replacement.

Repeated updates to an external placement previously leaked tracked pins.

Pass the owning screen to storage and deinitialize the old placement.
2026-08-05 08:28:13 -07:00
Mitchell Hashimoto
590d669c4a terminal/kitty: limit png decoder allocations
Limit individual allocator requests made by PNG decoders to the Kitty
graphics protocol's 400 MiB image ceiling. Add a reusable allocator
wrapper for callers that need per-request bounds.

PNG decoding previously used Wuffs' 4 GiB package limit and checked
the result only after allocation. A tiny PNG with oversized dimensions
could cause a multi-gigabyte RSS spike before being rejected.

Wrap decoder allocators with LimitedAllocator and translate limit
rejections to invalid image data while preserving genuine out-of-memory
errors. Add allocator boundary tests and regression coverage for a
crafted PNG below Wuffs' limit.
2026-08-05 08:24:42 -07:00
Mitchell Hashimoto
f766f303a7 terminal/kitty: validate shared memory ranges
Validate Kitty shared memory byte ranges before mapping and copying
image data. Interpret S as a byte count from O and preserve default
raw-image sizing.

Shared memory transmissions previously multiplied untrusted u32
dimensions before the limit check and sliced mappings with an unchecked
offset. Malformed commands could panic in safe builds or request a
wrapped allocation in fast builds.

Reject oversized dimensions before widening size arithmetic, derive
bounded ranges from the stat size, and enforce max_size before
constructing a slice. Add regression tests for explicit and implicit
offsets, out-of-bounds offsets, and maximum dimensions.
2026-08-05 08:20:41 -07:00
Mitchell Hashimoto
ec04900ab9 terminal/kitty: validate opened image file paths
Validate Kitty file transmissions against a canonical path derived from
the open file handle. Keep temporary file policy and cleanup keyed to
that handle path.

Path validation previously ran before opening, so a local cooperating
process could replace a symlink or directory entry and make Ghostty
read a blocklisted file.

Open the submitted path once, derive its canonical path from the handle,
and use the same handle for stat and reads. Add a regression test that
replaces a blocked symlink after open and verifies the pinned target is
still rejected.
2026-08-05 08:14:47 -07:00
Jon Parise
fb4c56159f core: transfer long key encoding buffer
The long-preedit fallback introduced in e95b1707c intentionally allocated
twice. The encoder wrote into an oversized caller-owned buffer and returned
only the written subslice, so transferring it required manually shrinking
the allocation or tracking its original capacity. The copy kept that rare
path simple.

The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping when
the allocator supports it and falling back to an allocation and copy when it
does not. Use it directly for WriteReq.alloc to remove the guaranteed second
allocation while preserving cleanup on failure.
2026-08-05 11:11:30 -04:00
Mitchell Hashimoto
e5840bb9ba terminal/kitty: harden placement geometry
Treat Kitty placement dimensions and offsets as untrusted values when
calculating pixel, grid, and rectangle geometry. Saturate results that
do not fit and return no rectangle when missing pixel metrics produces
an empty grid.

Unchecked u32 arithmetic previously panicked in safe builds and wrapped
in fast builds. A zero row count could underflow into a maximum-size
page traversal, while maximum dimensions could spin cursor movement or
overflow render visibility calculations.

Use checked integer scaling instead of floating-point casts, saturating
arithmetic for extents and cursor columns, and bound off-screen cursor
work to the terminal row count. Compute C API visibility in i64 and
cover maximum protocol values in storage, execution, and render-info
tests.
2026-08-05 08:07:25 -07:00
Mitchell Hashimoto
af2faa311a terminal/kitty: restrict temporary image file paths
Require temporary image file paths to match complete directory
components when checking /tmp, /dev/shm, the configured temporary
directory, and its resolved path.

The previous byte-prefix checks accepted similarly named sibling
directories such as /tmpX. A temporary-file transmission could read
and unlink a file outside the permitted temporary directories.

Add a component-boundary helper and regression coverage for built-in
and configured directory prefixes. An integration test also verifies
that a rejected file remains on disk.
2026-08-05 08:07:25 -07:00
Mitchell Hashimoto
d866fa4553 terminal/kitty: fix graphics range deletion
Use inclusive image ID bounds for the Kitty graphics protocol range 
delete operation.

Range deletion previously joined the lower and upper bound checks with or, 
which matched every placement for any valid range. A targeted delete could 
therefore remove every graphics placement.

Join the bounds with and and update the lowercase and uppercase range tests 
to keep placements below and above the selected interval.
2026-08-05 08:05:27 -07:00
Mitchell Hashimoto
5944ab286d macOS: suppress restart tips for auto update (#13623)
With this, users will not be prompted to restart the app when an
automatic update is ready. Relaunching with this state will have no
visible difference to users.

If the user checks for updates manually, either by menu, command
palette, or keybind, a restart alert will be prompted.

Closes #13478

Auto updates:


https://github.com/user-attachments/assets/f77f65a1-6e2f-4002-961c-fee4b73d447e

Manual Updates: 


https://github.com/user-attachments/assets/8a52a5a0-2022-413f-97c5-6f9a2eb26e7a
2026-08-05 06:57:18 -07:00
Mitchell Hashimoto
3f8b99bb68 terminal: print repeated characters through printSlice (#13625)
`printRepeat` (CSI `b`, repeat the previous character N times) calls
`print()` once per repeat, so something like `\x1b[2000b` ran grapheme
checks, width lookups, wrap handling, and the integrity assert 2000
times for what is usually the same character on the same row.

`Terminal.print` was 24% of samples on a REP-heavy micro benchmark.

This PR just aims to add a fast path by introducing a chunking
mechanism. anything that needs care (insert mode, grapheme clustering,
hyperlinks) still falls back to per-codepoint print() inside printSlice,
so behavior *should* stay unchanged.

Some profiling data:

Generated with some plain stupid logic:

```py
D = "benchdata"
parts, total = [], 0
while total < 40_000_000:
    line = "x" + "\x1b[80b" + "y" + "\x1b[35b" + "\r\n"
    parts.append(line); total += len(line)
open(f"{D}/rep.bin", "wb").write("".join(parts).encode())
```
**macOS (hyperfine, 15 runs, warmup 3):**

| | mean |
|---|---|
| before | 2.360 s |
| after | 1.166 s |


And now the really interesting and promising stuff

**Linux, 24-core NixOS x86_64 (poop, 6s sampling):**

| | wall_time | instructions | branch_misses | peak_rss |
|---|---|---|---|---|
| before | 1.51 s | 50.9 G | 9.41 M | 6.82 MB |
| after | 562 ms | 9.07 G | 114 K | 6.74 MB |
2026-08-05 06:56:01 -07:00
Mitchell Hashimoto
19e9f49089 surface: use id instead of intFromPtr (#13620)
intFromPtr was always a hack that we had to use before we had stable
surface IDs, and it was always slightly unsafe. Let's do it properly
this time.
2026-08-05 06:55:18 -07:00
Uzair Aftab
5b70f208bc terminal: print repeated characters through printSlice
While doing some work on my tmux fork I noticed multiple parts of
libghostty-vt was slower than tmux equivalents(isolated). Turns out they
do some smart stuff there.

printRepeat called print() once per repeat, so something like \x1b[2000b
ran grapheme checks, width lookups, wrap handling, etc etc 2000 times.

printSlice is already documented as semantically identical to
calling print per codepoint, so this just feeds the repeated
codepoint through it in 4096-entry stack chunks. Simple runs take
the batched fast path, and anything that needs care falls back to the
previous behaviour.
2026-08-05 15:43:25 +02:00
Lukas
c93752a008 macOS: suppress restart tips for auto update 2026-08-05 15:15:40 +02:00
Leah Amelia Chen
261848c2fb gtk: reformat blueprints 2026-08-05 16:22:28 +08:00
Leah Amelia Chen
98fae16a05 gtk: support cross-tree surface drag and drop 2026-08-05 16:18:49 +08:00
Leah Amelia Chen
1d053bd6ea gtk: implement drag-to-move for splits
One major todo is moving splits across different split trees (i.e.
moving across tabs and windows), but that would involve a lot more logic.
This MVP version works for now.

GTK version of #10090

Closes #10224
2026-08-05 16:18:49 +08:00
Leah Amelia Chen
f17b425aac surface: use id instead of intFromPtr
intFromPtr was always a hack that we had to use before we had stable
surface IDs, and it was always slightly unsafe. Let's do it properly
this time.
2026-08-05 14:40:45 +08:00
Leah Amelia Chen
9e30f70f23 gtk: do not set bell ringing if already focused (#13597) 2026-08-05 14:18:45 +08:00
ghostty-vouch[bot]
2346c4fe47 Update VOUCHED list (#13617)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/12984#issuecomment-5187316604)
from @mitchellh.

Denounce: @jamesarch

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 03:50:37 +00:00
Mitchell Hashimoto
d7f7a4e736 macOS: rename UpdateState.isIdle to isHidden (#13613) 2026-08-04 19:28:05 -07:00
Mitchell Hashimoto
d2f08f1589 config: limit command translations to GTK (#13615)
Fixes #13614

Only translate the shared default commands when building the GTK
runtime. macOS now use the source strings until we do broader
localization.
2026-08-04 19:27:52 -07:00
Mitchell Hashimoto
a00d155e9c config: limit command translations to GTK
Fixes #13614

Only translate the shared default commands when building the GTK runtime. 
macOS now use the source strings until we do broader localization.
2026-08-04 15:23:09 -07:00
Mitchell Hashimoto
ccb08f35f6 macOS: show cancel update option when its actually cancellable (#13612)
`extracting` and `installing` state aren't cancellable by us.

> Recommend reviewing with whitespace hidden
2026-08-04 14:07:38 -07:00
Lukas
a86c49d7af macOS: rename UpdateState.isIdle to isHidden 2026-08-04 22:38:35 +02:00
Lukas
63d08c0342 macOS: show cancel update option when its actually cancellable
`extracting` and `installing` state aren't cancellable by us
2026-08-04 22:18:35 +02:00
Mitchell Hashimoto
b8ab2ff168 macOS: show/search description when subtitle missing in CommandPalette (#13610)
<img width="1125" height="552" alt="image"
src="https://github.com/user-attachments/assets/09866c9b-d5c4-422f-860b-de4de4cca055"
/>
2026-08-04 11:20:01 -07:00