Commit Graph
421 Commits
Author SHA1 Message Date
l0ng-ai ffee5cc576 Merge pull request #234 from l0ng-ai/fix/boxdraw-underline-and-fractional-dpi
fix(render): keep underlines on natively-drawn cells, and stroke weight uniform at fractional DPI
2026-07-28 11:14:49 +08:00
thomas 52b1a64e8a Merge remote-tracking branch 'origin/main' into pr-234-review 2026-07-28 11:03:39 +08:00
l0ng-ai 6722e35f06 Merge pull request #233 from l0ng-ai/fix/windows-italic-cjk-mojibake
fix(render): stop italic CJK rendering as unrelated CJK on Windows
2026-07-28 11:00:53 +08:00
thomas 39d21edf47 Merge origin/main into fix/windows-italic-cjk-mojibake 2026-07-28 10:50:51 +08:00
l0ng-ai 664ff5bea8 Merge pull request #219 from l0ng-ai/feat/term-program-env
feat(daemon): tell every pane which terminal it is running in
2026-07-28 10:49:33 +08:00
thomas c3ebc28393 Merge remote-tracking branch 'origin/main' into feat/term-program-env
# Conflicts:
#	CHANGELOG.md
2026-07-28 10:44:13 +08:00
l0ng-ai 36a7e67a80 Merge pull request #232 from l0ng-ai/fix/dim-inactive-panes-followup
chore(settings): finish off the dim-inactive-panes setting
2026-07-28 10:42:59 +08:00
thomasandClaude Fable 5 042bb784ed fix(daemon): match capability env keys case-insensitively on Windows
Windows environment blocks are case-insensitive: portable-pty's
CommandBuilder keeps one slot per lowercased key, so a configured
`Term`/`ColorTerm` in `env` would land in the same slot as
`TERM`/`COLORTERM` and, coming later, replace it -- sidestepping the
rule that user env may rename the terminal but not contradict what the
pane's decoder implements. Filter capability keys with the platform's
own notion of "the same variable": case-insensitive on Windows, exact
elsewhere (where a differently-cased key is a genuinely distinct
variable and stays the user's to set). Pinned by a Windows-only test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:40:58 +08:00
thomas 07b00c2a0b Merge branch 'main' into fix/dim-inactive-panes-followup 2026-07-28 10:37:51 +08:00
thomasandClaude Fable 5 7ffd6afe65 docs(render): reattach seg_clip_width's doc comment
native_cell_residue was inserted between seg_clip_width's doc block and
the function itself, so rustdoc attached the clip-width prose to the
residue helper and left seg_clip_width undocumented. Move the helper
(with its own doc) above the block instead. No code change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:36:46 +08:00
thomasandClaude Fable 5 4d09df3b71 chore: drop stale "focus rings" comment the show_focus rename missed
The render call site still said "show focus rings only when split" -- the
same misdescription the parameter rename in this PR removes: nothing ever
drew a ring, the flag only gated the fade, and the fade condition is now
spelled out two lines below.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:36:28 +08:00
l0ng-ai 38975ab97e Merge pull request #230 from mingrath/feat/thai-sara-am
fix(terminal): shape Thai SARA AM with the base it belongs to
2026-07-28 09:48:07 +08:00
l0ng-ai 0a01970f76 Merge branch 'main' into feat/thai-sara-am 2026-07-28 09:43:59 +08:00
thomasandClaude Opus 5 063e4a5064 fix(render): keep underlines on natively-drawn cells, and stroke weight uniform at fractional DPI
Two follow-ups to #229.

Underlines ride on the `TextRun` that `paint_glyphs` builds, and the Solo
arm returned early for every natively-drawn cell — so an `ESC[4m` span or a
hovered URL showed a one-column hole wherever it crossed a box-drawing
character. The mechanism predates #229 (the Powerline branch has always had
it), but #229 widened it from a dozen private-use separators to all 256
characters of U+2500–U+259F. Such a cell now shapes a space in its own
style instead of returning, so gpui draws the line from the same
`UnderlineStyle` — curly and double included — that every other cell uses.

Stroke weight varied between cells at fractional device scale. `rectb`
snaps a rect's two edges independently, which is what makes neighbouring
cells tile, but two edges `w` apart land `w × scale` device pixels apart:
when that is not a whole number the two roundings straddle it, so a
1-logical-pixel rule came out 1 device pixel wide in one column and 2 in
the next. At Windows' default 125%/150% scaling that alternated thin/thick
across every column of a TUI table, and down every row for horizontal
rules. Integer scales are blind to it by construction, which is why 1x and
2x looked right.

`light_thickness` now quantises to whole device pixels, and `vstroke` /
`hstroke` lay that width off from the snapped near edge rather than
inferring it from a second snap — float ties at `.5` made the quantisation
alone insufficient. Stroke ends still snap, so #229's tiling guarantee is
untouched. Block elements stay on `rectb`: they are area fills, not
strokes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 09:14:14 +08:00
l0ng-ai 19c979b84c Merge pull request #229 from yetone/fix/native-box-drawing
fix(render): draw box-drawing and block characters natively
2026-07-28 09:04:49 +08:00
l0ng-ai e19f4acbc7 Merge branch 'main' into fix/native-box-drawing 2026-07-28 08:55:26 +08:00
thomasandClaude Opus 5 0be3b67640 fix(render): stop italic CJK rendering as unrelated CJK on Windows
Every character came out as a different character, one for one, consistently
— it read as a broken locale or a mangled encoding, and it was neither.

Hack, the bundled default, has no CJK, so those cells are shaped through the
font-fallback chain. gpui's Windows backend then threw away the face
DirectWrite shaped the run with and looked a fresh one up by family, weight and
style. That round trip mapped DirectWrite's italic to oblique — the enum is
numbered OBLIQUE = 1, ITALIC = 2, and the mapping had them the other way around
— so an italic fallback face resolved to a request for an oblique one, and a
family with no oblique face (Maple Mono NF CN, first in our Windows chain) came
back as its upright face instead. The glyph indices were right; the outlines
they indexed belonged to a different face, at a fixed glyph-id skew.

Fixed upstream in our gpui fork by registering the face DirectWrite actually
chose rather than re-deriving one, which also closes a latent use-after-free in
the same cache: it keyed fonts by a raw pointer to a face nothing held a
reference to, so a released face could be aliased by any later allocation.

Bumps the fork pin; no tty7 code changes. Covered there by two tests in
`gpui_windows::direct_write` — one asserting a shaped run's glyphs round-trip
through the font id the run reports, one asserting every font-face cache key is
owned by the font it maps to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 08:42:08 +08:00
thomasandClaude Opus 5 c21508df6f chore(settings): finish off the dim-inactive-panes setting
#214 added the switch itself; this is the wiring around it that a new
setting in this codebase is expected to carry.

- Index the row in `settings_search_entries`, which the settings search
  box matches against. Without an entry, searching "dim", "fade" or
  "unfocused" — the words someone actually looks for — finds nothing,
  and the switch is only reachable by scrolling to it. Pinned in
  `index_titles_match_rendered_row_labels` so the title cannot drift.

- Pin the default and the round trip, as every other `default_true`
  flag here does (see `confirm_window_close_defaults_on_and_round_trips`):
  a config written before the switch existed must still dim, and a
  `false` must survive save/load or the effect comes back next launch.

- Hand the flag to `Pane::render` instead of reading the `Config` global
  from inside it. `pane.rs` had no global state before, deliberately —
  the leaf type is generic so the tree logic can be tested with plain
  values. The caller already computes the split test the dimming was
  gated on, so it can compute this too: one lookup per frame rather than
  one per leaf, and the tree stays renderable without a Config global.
  While there, `show_focus` is now named for what it does — nothing
  drew a focus ring; it only ever gated the fade.

- Move the row below "Follow theme". That button clears the opacity and
  blur overrides only, and a third row directly above it read as
  something it would also reset.

- Changelog entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 08:04:36 +08:00
l0ng-ai 58a1cb53be Merge pull request #214 from qiudaomao/feat/inactive_pane_dimm_optional
feat: make inactive pane fade optional
2026-07-28 08:00:22 +08:00
l0ng-ai 30077b2871 Merge branch 'main' into fix/native-box-drawing 2026-07-28 07:57:06 +08:00
l0ng-ai d6ce35043e Merge branch 'main' into feat/inactive_pane_dimm_optional 2026-07-28 07:55:25 +08:00
l0ng-ai e86cfb356a Merge pull request #224 from yetone/feat/one-dark-pro-theme
feat(theme): add One Dark Pro built-in theme
2026-07-28 07:52:47 +08:00
Qiu Daomao 91532f7a0b feat: make inactive pane fade optional 2026-07-27 19:55:54 +08:00
yetone 0cb5679842 fix(render): analytically antialiased rounded corners
The polyline arc band couldn't match kitty's corners on two counts,
both dictated by how gpui rasterizes paths:

* Separately painted segments composite as premultiplied sprites, so
  two abutting antialiased edges meet at 75% opacity — a lighter seam
  at every joint. All contours now ride in ONE Path (via move_to),
  where the 4x-MSAA samples partition cleanly across shared edges.
* Straight path edges only get 4-level MSAA antialiasing, while
  `curve_to` quadratics are antialiased analytically (Loop–Blinn
  signed distance) — a continuous ramp, the same quality kitty gets
  from supersampling. The outer arc edge is now a real quadratic per
  30° slice (control point at the tangents' intersection).

The inner edge stays a fine polyline: gpui accumulates coverage with
no winding cancellation, so a concave-side curve bulge can only
over-cover (the origin of the original quarter-disc blob). Its chord
error at 7.5° steps is under 0.1px and hides inside the MSAA. Each 30°
contour is star-shaped from its start vertex, which is what the fan
fill actually requires.
2026-07-27 18:15:58 +08:00
yetone fac3e4492e fix(render): snap box-drawing strokes to the device pixel grid
Straight strokes with edges at fractional device pixels rasterize an
antialiasing ramp at each end, and two abutting 50%-coverage ramps
composite to 75% opacity — so a multi-row │ was perforated by a lighter
band at every cell boundary and read as broken next to kitty's solid
lines (kitty's cell-aligned box bitmaps never sit off-grid).

Snap every rectangle edge to whole device pixels via the window scale
factor. Edges are snapped individually (not origin + size), so the two
cells sharing a boundary snap the same coordinate to the same pixel
line: zero gap, zero overlap, whatever the window position. Arcs and
diagonals keep their antialiasing on purpose; the rounded corners'
straight stubs now reach one device pixel into the arc band to cover
the snapped-meets-unsnapped handoff.
2026-07-27 18:03:35 +08:00
yetone ee921c4879 fix(render): draw box-drawing and block characters natively
Box characters rendered as font glyphs only cover the font's own line
height, but the cell is font_size × line_height (1.4 by default) — so
every vertical run of │/╭/╰ broke into dashes with a gap at each row
boundary: a two-line prompt's corners never connected, a TUI frame was
perforated down both sides.

New `terminal::boxdraw` module draws U+2500–U+257F and U+2580–U+259F as
geometry pinned to the cell's real edges, the same special case every
terminal with a line-height setting ships (kitty, alacritty, WezTerm)
and the same approach the existing Powerline separators use:

* light/heavy lines, corners, tees, crosses: per-arm rectangles with a
  centre overshoot so any weight combination joins solid
* the double-line set: explicit per-character stroke lists, keeping the
  open junctions (╬ is four corners around a hole) intact
* rounded corners ╭╮╯╰: straight stubs plus a quarter-circle band of
  overlapping convex quads — a single band outline is concave, which
  gpui's fan fill renders as a solid blob, and butted segments seam at
  75% opacity where two antialiased edges meet
* dashed lines, diagonals ╱╲╳, block eighths/quadrants, and the ░▒▓
  shades as foreground-alpha washes

Anything outside the range still renders through the font.
2026-07-27 17:54:47 +08:00
mingrathandClaude Opus 5 a7835a0b8c fix(terminal): shape Thai SARA AM with the base it belongs to
SARA AM (ำ U+0E33) is `Lo` and width 1, so the grid gives it its own
column — but it is not atomic to the shaper. The Thai shaper decomposes
it into NIKHAHIT + SARA AA and moves the nikhahit backwards over any
above-base marks onto the base consonant. Shaped in a run of its own it
has no base to reorder onto, so `น้ำ` came out as `น้` plus a dotted
circle, losing the vowel entirely.

Absorb a following SARA AM into the preceding cell's cluster, so base,
tone mark and SARA AM reach `shape_line` in one string. Lao SARA AM
(U+0EB3) takes the same shaper path and is handled with it.

That makes `cells == 2` ambiguous, so `Cluster` now records why: a wide
base is one glyph spanning two columns and pins at `2 × cell_width`,
while an absorbed SARA AM is two base glyphs of one column each and pins
like a `Run`. `apply_force_width_to_layout` classifies glyphs by advance
rather than by count, so the marks ride their base under either pinning.

Two deliberate limits, both pinned by tests:

A SARA AM is not a base for another one. Absorbing there would pin the
second one's glyphs past the cluster's two-cell clip and swallow it, so
`ำำ` stays two `Solo`s and both remain visible. A SARA AM with nothing
before it likewise paints alone — a dotted circle is the shaper's honest
answer for an orphaned mark, and inventing a base would be worse.

An absorbed SARA AM takes its base's style rather than its own, so a
colour change mid-syllable (`grep --color` landing between a consonant
and its vowel) recolours the vowel. Unlike `Run` and `Wide`, the cluster
cannot break on a style change: split off, the vowel renders as a dotted
circle. A recoloured vowel beats a broken one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:50:57 +07:00
yetone c69db5fa83 feat(theme): add One Dark Pro built-in theme
Add One Dark Pro as a ninth built-in, slotted alphabetically among the
dark themes: background #282c34, foreground #abb2bf, the classic One
Dark syntax palette for the normal ANSI slots and One Dark Pro's bright
variants for the bright ones.

Two seeds deliberately diverge from the VS Code theme's terminal set:

* The accent is the editor cursor/focus blue #528bff, not the syntax
  blue #61afef — the accent doubles as the switch's checked track, and
  #61afef sits at the same luminance as the #abb2bf knob (1.11:1).
* The normal red is the classic #e06c75, not the Pro terminal #e05561 —
  conditioned for AA the latter lands within 37 channel-distance of the
  orange-yellow #d18f52, under the 40 separability floor danger/warning
  must clear.

Also bumps the theme count in README and docs (eight → nine).
2026-07-27 17:18:37 +08:00
l0ng-ai 4aedb1faf7 feat(daemon): tell every pane which terminal it is running in
`TERM` names terminfo capabilities; it cannot answer "which program is
this". The de-facto standard pair that does — `TERM_PROGRAM` and
`TERM_PROGRAM_VERSION`, introduced by Apple Terminal and set by iTerm2,
WezTerm, Ghostty, VS Code and tmux — went unset, so anything asking was
told nothing.

Plenty asks. Capability probes (`supports-color`, `supports-hyperlinks`,
and the CLI ecosystem built on them) read the program name to decide on
truecolor and OSC 8; editors branch on it for terminal-specific
workarounds; shell prompts adapt their glyphs to it. Absent, they all
fall back to their most conservative behaviour. The `TTY7` marker we do
export is no substitute: it exists so globally-installed agent hooks
stay silent in other terminals, and nothing third-party looks for it.

Both new variables stay overridable through `env` in `config.json`,
unlike `TERM` and `COLORTERM`. Those two state what the pane's decoder
implements, which isn't the user's to contradict; the program name is an
identity, and posing as another terminal is a legitimate way to get a
tool that only recognises a fixed list to light up.

Building the pane's environment is now one function returning the pairs
in application order, so that precedence is testable without a
`CommandBuilder` or a real `config.json`.

Local panes only. ssh forwards environment variables solely by agreement
between client and server (`SendEnv`/`AcceptEnv`, `LANG` and `LC_*` by
default), so a native-SSH pane still sees whatever the remote host sets
for itself — as is already true of `COLORTERM` and `TTY7`.

Closes #212
2026-07-27 15:11:58 +08:00
l0ng-ai 0030b98faa chore(release): v26.7.5 v26.7.5 2026-07-27 14:00:22 +08:00
l0ng-ai fcdfbfbfec Merge pull request #216 from mingrath/test/thai-multi-mark-cluster
test(terminal): pin that a base keeps every stacked combining mark
2026-07-27 13:50:20 +08:00
l0ng-ai 2da33d2e0d Merge branch 'main' into test/thai-multi-mark-cluster 2026-07-27 13:50:00 +08:00
l0ng-ai de5758d917 Merge pull request #217 from l0ng-ai/feat/remote-path-completion
Complete remote paths over the pane's SSH connection
2026-07-27 13:49:16 +08:00
l0ng-ai ab8ac37fd7 Merge branch 'main' into feat/remote-path-completion 2026-07-27 13:48:56 +08:00
l0ng-ai 7574acd728 Merge pull request #215 from l0ng-ai/fix/config-bom
fix(config): don't let a UTF-8 BOM silently reset every setting
2026-07-27 13:48:30 +08:00
l0ng-ai 4bcc4066d7 feat(completion): complete remote paths over the pane's SSH connection
Tab at a path position in a native-SSH pane had nothing to offer: path
candidates come off the local filesystem, and a remote pane deliberately
passes no local cwd, so `complete` returned `None`. A no-match hands the
line to the shell (#136), which costs the user the inline editor until
the next prompt — so in practice every path Tab in an SSH session dropped
them back to the raw shell for the rest of that line.

Ask the far side instead. The pane already holds an authenticated russh
connection, and `SftpList` — the same request the SFTP panel browses with
— lists a remote directory over it. No shell involvement: nothing is
echoed into the scrollback, no prompt hooks need suppressing, and there
is no background process to cancel when the user submits.

The listing is a network round-trip, so it can't answer the keystroke
synchronously. Results land on the main thread and only then behave as a
local Tab would; a line that moved on in the meantime drops the answer,
and an empty one falls back to the shell handoff, so a directory we
cannot read is no worse off than before.

completion.rs stays pure: it splits the word into a request and turns the
answer into candidates, both unit-tested. Candidate rules mirror the
local path engine exactly — hidden entries need a dot prefix, `cd` takes
directories only, symlinks follow to their target's kind, same ordering
and cap. Positions a listing cannot help (command position, `~` without
the remote's $HOME, no absolute remote cwd) decline and let the shell
have the Tab.

Only native-SSH panes qualify: a foreground `ssh` typed into a local
shell and a WSL pane have no tty7-owned connection to ask.
2026-07-27 13:38:59 +08:00
l0ng-ai b139568ca3 fix(daemon): report every prompt-boundary crossing in one chunk
The OSC sniffer folded a whole chunk's 133 marks into a single shell
state and `apply_signals` sent one `Prompt` frame for it. That loses
information the client depends on: it counts `at_prompt` false -> true
edges as prompt *cycles*, which is how it tells a fresh prompt from a
same-prompt redraw (a prompt framework re-emitting the PS1-embedded
`133;B` on reset-prompt bumps the report seq but must not count).

A chunk carrying a whole command cycle (`C` ... `D`) therefore reported
only the final at-prompt state, and the edge vanished. Over SSH that is
routine rather than exotic: a fast command's `C`, its output and its `D`
leave the remote together and arrive in one read.

Report one state per crossing instead. Marks on the same side of the
boundary still fold into their latest state, so the ordinary `D`/`A`/`B`
prompt draw costs exactly one frame as before.
2026-07-27 13:38:47 +08:00
thomasandClaude Opus 5 aad0a49ef7 fix(config): don't let a UTF-8 BOM silently reset every setting
Every config-dir file is read by a loader that treats any parse error as
"there is no file" and falls back to defaults. serde_json rejects the
U+FEFF a BOM puts before the opening brace, so a BOM never surfaced as a
broken config — it surfaced as an absent one, and the app booted on
defaults with nothing to explain it.

Windows makes that easy to hit by accident: PowerShell's `>`, `Out-File`
and `Set-Content -Encoding utf8` all write a BOM, so editing config.json
from a shell was enough to lose every setting.

Strip a leading BOM in the three loaders whose files people hand-edit:
config.json, session.json (which dropped every workspace the same way),
and themes/*.yaml. read_to_string decodes the marker to one U+FEFF char,
so this strips the char, not the three raw bytes — and only the first
one, since a second is content the parser should still reject.

window.json and update.json are left alone: they are machine-written
state a relaunch rebuilds, never hand-edited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
2026-07-27 13:12:41 +08:00
mingrathandClaude Opus 5 28aa52de20 test(terminal): pin that a base keeps every stacked combining mark
The combining-mark cases carry exactly one mark — `e` + U+0301, and
❤ + U+FE0F. Thai routinely stacks two on one consonant: `ที่` is U+0E17
with an above-base vowel and a tone mark both sitting on it, and
Devanagari, Arabic and Hebrew stack the same way.

Nothing pinned that. Truncating the mark iterator to its first element
leaves all thirteen `segment_row` tests green — the cluster still forms,
still spans one column, and still leaves its neighbour intact. Only the
trailing codepoint goes missing, which in Thai is the one carrying the
tone: ไม่ (not) and ไม้ (wood) both collapse to ไม.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 11:42:15 +07:00
l0ng-ai d3064e40f5 Merge pull request #207 from l0ng-ai/fix/pane-cwd-from-process-table
fix(daemon): track a pane's cwd from the process table when OSC 7 is absent
2026-07-27 11:00:10 +08:00
l0ng-ai 29a51b7460 Merge pull request #210 from l0ng-ai/fix/emoji-presentation-width
fix(terminal): render emoji presentation sequences at their real width
2026-07-27 10:56:54 +08:00
thomas 60ecadd5b7 Merge remote-tracking branch 'origin/main' into fix/pane-cwd-from-process-table 2026-07-27 10:55:13 +08:00
l0ng-ai 5bfea2c095 Merge pull request #206 from l0ng-ai/feat/confirm-window-close
feat(settings): let the last-window close confirmation be turned off
2026-07-27 10:54:25 +08:00
thomasandClaude Opus 5 0cfbe90455 fix(terminal): only treat real combining marks as marks
`Cell::zerowidth` answers out of alacritty's one lazily boxed `extra`,
which also holds SGR 58 and OSC 8 — so a cell carrying nothing but an
underline color or a hyperlink reports `Some(&[])`, not `None`.

`snapshot_cell` mapped that straight into `RenderCell::marks`, and an
empty list is still `Some`: every SGR-58 or OSC-8 cell then failed
`is_blank`, broke the run it sat in, and took a `RowSeg::Cluster` of its
own — one `shape_line` per character for a whole `ls --hyperlink`
listing, with the powerline fast path skipped on the way past.

Filter the empties at the source, where the `Option` is built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
2026-07-27 10:52:23 +08:00
thomasandClaude Opus 5 64744fce61 test(config): pin that an unknown key can't silently re-enable the prompt
`Config::load` turns any parse error into *defaults*, so a config that
fails to deserialize doesn't fall back field-by-field — it comes back with
`confirm_window_close: true` and nothing said. The struct has no
`deny_unknown_fields` today; this pins that, since the opt-out is exactly
the setting whose silent reversal nobody would notice.

Also index the Windows/Linux spelling of the chord: the prompt is reached
by Ctrl-W off macOS, and search only knew "cmd-w".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
2026-07-27 10:50:16 +08:00
thomasandClaude Opus 5 6e98f55981 fix(daemon): don't hand a new tab a Linux cwd the kernel marked deleted
`/proc/<pid>/cwd` still resolves after the directory is unlinked, and the
kernel spells that `<path> (deleted)` — a label, not a path. That was
harmless while the reading only backfilled `List` for a pane with no cwd
at all; now that the poll broadcasts it, one `rm -rf` from another pane
(or a `git clean`) leaves every new tab, split, git probe and path
completion pointed at a directory that cannot be opened, and the pane's
last good directory has already been overwritten.

Treat an unstat-able reading as no reading, so the foreground group's
answer is discarded and the shell pid gets its turn — the same shape the
rest of the chain already uses for a pid it cannot inspect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
2026-07-27 10:50:02 +08:00
thomasandClaude Opus 5 714bf23f9e test(daemon): stop the symlink cwd test from failing on an ordinary Windows box
`probed_cwd_keeps_the_shells_spelling_for_a_symlinked_path` built its
fixture with `std::os::windows::fs::symlink_dir`, which needs
`SeCreateSymbolicLinkPrivilege` — a privilege an unelevated shell without
Developer Mode does not hold. The test therefore failed on any normal
Windows checkout with `ERROR_PRIVILEGE_NOT_HELD` (os error 1314) before
reaching a single assertion, observed here on Windows 11.

Nothing is lost by making it `cfg(unix)`: `foreground_cwd` answers `None`
off macOS/Linux, so no Windows pane ever reaches that comparison. What
every platform *does* run — a reading identical to the one already
reported, resolved with no filesystem access at all — gets its own test
instead, because a re-send there would push a `Cwd` frame and the git
probe behind it twice a second for the life of an idle pane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD
2026-07-27 10:49:46 +08:00
thomas e07fc3703e Merge remote-tracking branch 'origin/main' into fix/emoji-presentation-width 2026-07-27 10:41:39 +08:00
thomas 88d21d19ed Merge remote-tracking branch 'origin/main' into feat/confirm-window-close
# Conflicts:
#	src/ui/settings.rs
2026-07-27 10:41:06 +08:00
thomas dc8faec5d9 Merge remote-tracking branch 'origin/main' into fix/pane-cwd-from-process-table
# Conflicts:
#	CHANGELOG.md
2026-07-27 10:40:53 +08:00