Commit Graph
225 Commits
Author SHA1 Message Date
thomasandClaude Opus 4.8 f91707e2a7 fix(wsl): don't block the spawn path probing the distro's shell
`setup_wsl` resolved the distro's login shell with a synchronous `wsl.exe`
call. The client waits for the daemon's `Spawn` reply
(`terminal::remote::spawn`), so on a cold WSL start — seconds, while the
distro boots — the entire window froze. Reported from a real session; the
`--cd`/`-d` unit tests never saw it because they never reach the probe,
and the live-PTY test only ever ran against an already-warm distro.

Caching per distro was not a fix: the first WSL pane after launch is
exactly when the distro is cold, so the freeze hit precisely the case the
cache could not cover.

Fold the decision into the one `wsl.exe` invocation we were always going
to make. The command is now `sh -c` over a `case` on `$SHELL` that execs
bash with our rcfile, or falls back to a plain login shell for a distro we
don't integrate. It cannot block, because there is no second invocation.

`$SHELL` rather than `getent passwd`: WSL populates it from the user's
passwd entry, so inside the distro it already is the login shell of
record — the same source `shell_kind` trusts on Unix. Written without a
variable assignment so the whole thing stays one `case`, robust to the
layers of quoting between the daemon and `sh`.

The rcfile is now written before the shell is known. That is a local write
into a throwaway dir the terminal already cleans up on drop, and paying it
unconditionally is what buys the decision being free.

Regression test names a distro that cannot exist and asserts setup still
succeeds — if anything asked the distro a question, it could not. A timing
bound would only have caught this on a cold machine, which is the same
blind spot that let it ship.

Removes `wsl_login_shell` and `inner_shell_kind`, both now unreachable.

736 tests pass, clippy warning count unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:10:13 +08:00
thomasandClaude Opus 4.8 5f7a771938 feat(shell-integration): support WSL
`wsl.exe` is a launcher, not a shell, so the integration has to reach
through it into the distro. Probe the distro's login shell, write the
matching rcfile on the Windows side, and pass its path in via `WSLENV`,
whose `/p` flag rewrites it to the distro's own view of the filesystem
(`C:\…` -> `/mnt/c/…`) — so the `/mnt` automount root, which is
configurable in `/etc/wsl.conf`, is never hardcoded.

The argv becomes `[<launch flags>] -- sh -c 'exec <shell> --rcfile "$RC" -i'`
rather than `-- <shell> --rcfile <path>`: the path only exists as an env
var *inside* the distro after translation, and `wsl.exe` execs its command
directly with no shell to expand it. The one-shot `sh` execs away at once.

No new shell code — the distro runs bash, so the existing snippet applies
verbatim. Only bash is wired up; zsh and fish inside a distro are
reachable the same way but each needs its own verification pass, and
declining leaves those panes launching bare, as every WSL pane did before.

Tag WSL panes with a new `RemoteKind::Wsl` so `TerminalView::local_cwd`
declines their cwd. This is the load-bearing half: the distro reports
`/home/me/proj`, which Windows reads not as invalid but as *drive-relative*,
resolving to `C:\home\me\proj`. Without the tag, the local git probe, path
completion, link resolution and cwd inheritance would all consume it — and
on a machine that happens to have such a directory, silently consume the
wrong one. The gate itself landed in #133; this adds the third kind to it.

Two consequences of that tag needed explicit handling, since nothing
matches exhaustively on `RemoteKind` and every miss would have been a
silent fall-through:

  - the foreground-`ssh` poll cleared any context the probe didn't produce,
    which would have blanked the WSL tag (and the pane's cwd with it) twice
    a second. It now only replaces the kind it authors.
  - the tab status dot and `active_ssh_pane` treated "has a RemoteContext"
    as "is an SSH pane". Both now test the kind.

`Injection::force_non_login` is renamed `replaces_argv`: bash needed it
because `--rcfile` is ignored for login shells, WSL needs it because the
launch flags and command must be reordered around `--`. The mechanism was
always "these args replace rather than extend"; only the name was bash's.

Verified end-to-end on a real ConPTY into a real distro — the new test
asserts the full A/B/C/D cycle comes back through `wsl.exe`, which is the
only way to show `WSLENV` translation, `wsl.exe`'s argv passing and the
distro's own startup chain all survive together. It shares its harness
with the Git Bash test, including the two ConPTY behaviors that harness
encodes.

736 tests pass, clippy warning count unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:58:27 +08:00
l0ng-ai c52c0d85ac Merge pull request #133 from l0ng-ai/fix/remote-cwd-local-ops
fix(cwd): never run local filesystem operations on a remote pane's cwd
2026-07-19 19:31:05 +08:00
thomas 6442d33c8c Merge remote-tracking branch 'origin/main' into fix/remote-cwd-local-ops 2026-07-19 19:26:56 +08:00
l0ng-ai 87575f28ba Merge pull request #130 from l0ng-ai/feat/gitbash-shell-integration
feat(shell-integration): support Git Bash on Windows
2026-07-19 19:26:18 +08:00
thomas 9023e1ada8 Merge remote-tracking branch 'origin/main' into feat/gitbash-shell-integration 2026-07-19 19:15:46 +08:00
l0ng-ai 6fdd2c73d4 Merge pull request #132 from l0ng-ai/fix/macos-ime-text-input
fix(input): route macOS text through the IME so synthesized keys keep their text
2026-07-19 19:15:23 +08:00
thomasandClaude Opus 4.8 08ca3a3cf7 fix(cwd): close the agent-cwd bypass and validate inherited dirs
Follow-ups to the `local_cwd` gate.

The agent-reported cwd sat ahead of `local_cwd` in the git-status chain
and bypassed the gate entirely. A native-SSH pane keeps sentinel-sourced
agent state on purpose, so an agent running *on the remote host* reported
a remote path that won unconditionally and reached the local `git` — the
exact collision the gate exists to prevent, on its most likely trigger
(running `claude` in an SSH pane). Route the agent's report through the
same remote check.

Completion's fallback to `std::env::current_dir()` meant a remote pane
now offers *this* machine's filenames for insertion into a remote command
line, where before the remote path simply failed `read_dir` and produced
nothing. `complete` takes `Option<&Path>` so "no local filesystem" is an
explicit contract: command completion still runs, path and signature
sources are skipped.

`apply_remote_context` left `st.cwd` pointing into the namespace it just
left, so after `exit` from `ssh` a local shell without shell integration
kept serving the remote's last path to the local `git` probe. Clear it on
both sides of the boundary; `DaemonMsg::Cwd` has no cleared form, so the
client mirrors it off `RemoteContext`.

Finally, validate in `initial_working_directory`: whatever wins must be a
directory *here*. This bounds the whole class rather than one shell's
spelling — an unresolvable path now falls through to the next candidate
instead of failing the spawn with "The directory name is invalid".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:04:59 +08:00
l0ng-ai f2340f4bd0 Merge pull request #128 from l0ng-ai/feat/smart-select
feat(terminal): smart double-click selection with CJK segmentation
2026-07-19 19:03:12 +08:00
thomasandClaude Opus 4.8 e723bc7fd1 fix(shell-integration): decline WSL's bash.exe and untranslatable cwds
Two holes in the Git Bash support.

`shell_kind` matched any `bash.exe`, including
`C:\Windows\System32\bash.exe` — the WSL launcher, which exists on any
machine with WSL and normally precedes `Git\bin` on PATH. Injecting into
it is destructive rather than merely useless: `--rcfile` *replaces*
`~/.bashrc` instead of supplementing it, and the Windows path we pass
does not exist inside the distro, so the user silently loses aliases,
prompt, and PATH. Identify msys bash positively and fail closed — a bare
`bash`/`bash.exe` is declined too, since its PATH lookup is exactly what
we cannot predict. The cost of a false negative is only the absence of a
feature that did not exist before.

`pwd -W` is the identity for msys-only virtual mounts (`/proc`, `/dev`),
which have no Windows path at all, and the `[[ "$d" != /* ]]` test could
not tell a translated path from an untranslated one. Require a drive
letter and report nothing otherwise: the `$PWD` fallback would have
re-emitted the very msys path this branch exists to avoid, landing as
drive-relative `C:\proc` and failing the next spawn. Staying silent
leaves the daemon holding the last usable cwd.

The prior OSC 7 test asserted only that the marker was emitted, so the
payload shape went unchecked; it now round-trips through the daemon's own
`parse_osc7`, including the case the guard suppresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:58:43 +08:00
thomasandClaude Opus 4.8 6c07356ea6 fix(input): keep Kitty full mode off the IME path
The `defer_to_ime` early return sat above `keystroke_to_bytes`, and the
IME path terminates in `write_gap_text`, which writes raw UTF-8 with no
Kitty awareness. So under REPORT_ALL_KEYS_AS_ESC a plain `a` reached the
PTY as `a` instead of the `CSI 97;1;97u` the app negotiated — on macOS,
unconditionally. Before this branch it worked on the default ABC layout,
since the key reached the encoder whenever no IME source was active.

Fold the check into `defer_to_ime` so both routing sites agree: it now
declines under `report_all_keys`, and `prefers_ime_for_printable_keys`
gets the same gate (CJK composition and "escape every key" are mutually
exclusive by construction — an app that asks for the latter gets it).
Disambiguate-only sessions are untouched: `encode_kitty` already
declines unmodified text keys there, so the IME route is equivalent.

The existing Kitty tests call `keystroke_to_bytes` directly and so could
not see this. `defer_to_ime` now compiles under `test` on every platform
and has coverage for the routing rule itself, so all three CI targets
exercise it rather than the macOS runner alone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:53:34 +08:00
thomasandClaude Opus 4.8 2dbeb21b19 fix(terminal): keep contraction apostrophes out of quote pairing
`quote_range` paired quotes by parity, so English prose broke it: in
`it's a test, isn't it` the apostrophe in `it's` reads as an opener and
pairs with the one in `isn't`, and a double-click on either selects
`'s a test, isn'` instead of the stock `it's`.

The existing comment anticipated this and claimed a missing match would
fall through to `None`, but that only holds when the line has a single
apostrophe — prose usually has two. This path also returns before the
`extends` guard, so there was no safety net.

Exclude contraction apostrophes (alphanumeric on both sides) throughout:
clicking one falls through to the stock word, and they count neither
toward the parity nor as a candidate match. `"` and `` ` `` are
unaffected — they don't occur inside words. Genuine possessives like
`the 'dogs' bark` still pair, since a delimiter always has a non-word
char or a line edge on one side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:49:08 +08:00
l0ng-ai 2fe794ef9c Merge pull request #129 from l0ng-ai/fix/windows-git-probe-console-flash
fix(windows): stop console windows flashing on every git status probe
2026-07-19 18:47:02 +08:00
thomasandClaude Opus 4.8 87f865d55a fix(ssh): hide the console for ProxyCommand children too
The daemon is spawned detached with no console of its own, so a
ProxyCommand launched from it (`ssh -W`, `connect.exe`, `cloudflared`)
had Windows allocate one — not a flash but a black window that stayed
up for the whole session.

`hide_console` takes `std::process::Command`; this site builds a
`tokio::process::Command`, which is a distinct type with its own
`creation_flags`. Add `hide_console_tokio` alongside it so the module
comment's claim that every non-PTY Command goes through this file holds
again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:46:52 +08:00
l0ng-ai f5afc71512 Merge pull request #131 from l0ng-ai/fix/windows-titlebar-menu-spacing
fix(windows): align the titlebar ... with the window-control rhythm
2026-07-19 18:45:16 +08:00
thomasandClaude Opus 4.8 4e97253b53 fix(cwd): never run local filesystem operations on a remote pane's cwd
A remote pane's OSC 7 reports a path in the remote's namespace. The
git-status probe, path completion, link resolution, the git-diff and
worktree shell-outs, session persistence and cwd inheritance all took it
as a local path.

Nothing gated this. `refresh_git_status`'s doc claimed remote panes have
no cwd, but that holds only *before* the remote shell's OSC 7 arrives —
after it, a native-SSH pane's remote path was fed to a local `git`, with
only `Path::exists()` incidentally saving it. That guard is weakest
exactly where it matters: on Windows `/home/me/proj` is not an invalid
path but a drive-relative one resolving to `C:\home\me\proj`, so a
machine that has such a directory would report an unrelated repo's branch
and diff as the pane's own. Correctness must not rest on that collision
never happening.

Add `TerminalView::local_cwd()` — the pane's cwd only when the pane is
not remote — and route every local filesystem/Command consumer through
it. Cwd inheritance for new tabs and splits is gated too: an inherited
cwd wins over every fallback in `initial_working_directory`, so a remote
path reached the spawn as a Win32 working directory.

Deliberately unchanged: `tab_cwd`, so "Copy Working Directory" still
copies a remote pane's remote path, which is what it is for.

Not fixed here: on a remote pane, path completion falls back to tty7's
own directory and link resolution stops matching relative paths. Both
degrade rather than mislead now; sourcing either from the remote is a
separate piece of work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:28:01 +08:00
thomasandClaude Opus 4.8 caec744bfa fix(shell-integration): report a Windows path from Git Bash
Git Bash's $PWD is an msys path (`/c/Users/x`, and `/tmp` for mounts with
no drive at all). The daemon runs Windows-side, where `/c/Users/x` is not
absolute but drive-relative, so it resolved to a bogus `C:\c\Users\x`:
`strip_uri_drive_slash` only un-prefixes the `/C:/…` shape.

That silently disabled the git-status probe and path completion in every
Git Bash pane, and broke split/new-tab outright — an inherited cwd wins
over every fallback in `initial_working_directory`, so the next shell was
spawned with a working directory that does not exist. Worse than either,
`/c/Users/x` is a *valid* drive-relative path, so a machine that happens
to have `C:\c\...` would have shown an unrelated repo's status as the
pane's own.

Use `pwd -W`, msys's own translation to the real Windows path, which also
maps msys-only mounts correctly (`/tmp` -> `AppData/Local/Temp`). It has
no leading slash, so add one for the file: URI shape. Falls back to $PWD
if it fails or returns empty, and the branch is taken once at install
time rather than on every prompt.

The live-PTY test now round-trips what Git Bash actually emits through
the daemon's own `parse_osc7` and asserts the path exists, so the two
halves are verified to agree rather than checked in isolation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:26:40 +08:00
l0ng-ai 83676d2e01 fix(input): route macOS text through the IME so synthesized keys keep their text
gpui derives `key_char` by running the event's virtual keycode back through the
current layout; it never reads the event's Unicode payload. That is fine for a
physical keyboard, but remote-control apps synthesize keystrokes as
`CGEventCreateKeyboardEvent(src, 0, ...)` + `CGEventKeyboardSetUnicodeString()`,
putting the real text only in the payload. Keycode 0 is `a`, so every character
typed from a phone arrived in the terminal as `a`.

gpui does divert printable keys to the input context, but only while a composing
input source is active (`is_ime_input_source_active`), so the bug appeared and
vanished depending on the selected input method — and the plain ABC layout, the
macOS default, always lost the text.

Make the IME the single delivery path for text on macOS: `on_key_down` declines
plain printable keys without consuming them, so gpui falls through to
`handleEvent:` and the Unicode payload survives into `commit_text`. Chords are
excluded — Ctrl/Cmd/Fn belong to the encoders, Option to the Meta policy — and a
pending multi-key chord still wins, matching `prefers_ime_for_printable_keys`.

Answer `apple_press_and_hold_enabled()` with false as well. `on_key_down` used to
consume printable keys before gpui consulted it; now that gpui reaches its
held-key branch, false is what keeps auto-repeat instead of handing the key to
the accent palette — a terminal wants `jjj`, not `ĵ`.

Tests that assert on text input now go through a `type_char` helper that follows
the platform: `commit_text` on macOS, `on_key_down` elsewhere. Building a
`KeyDownEvent` by hand exercised a path macOS no longer takes. Production
semantics are unchanged: `input_active()` is already false at a shell vi-mode
prompt and `write_gap_text` has its own `shell_vi_prompt()` branch.

Linux is unaffected and still uses the `key_char` path, since gpui's IBus
integration does not commit plain ASCII back through `replace_text_in_range`.
2026-07-19 18:04:03 +08:00
thomasandClaude Opus 4.8 9b1c7bfcac fix(windows): align the titlebar ... with the window-control rhythm
The three native window controls are 34px tiles, so consecutive glyph
centres sit 34px apart. The overflow ... was inset by `pr_3` (12px),
putting its centre `15 + 12 + 17` = 44px from the minimise glyph — a
10px overshoot that read as the button drifting away from the group
rather than as deliberate separation.

`pr_1` (4px) lands it at 36px. Measured on a 1.5x display: 54.5 physical
px between ... and minimise vs 51px between the window controls, i.e.
within ~2 logical px of the native rhythm, while keeping just enough
slack that it does not read as a fourth window control.

macOS is unaffected (controls are on the left there; `pr_2` still holds).
The `strip_w` 114px reserve is untouched -- this padding lives inside the
strip box, so shrinking it only shifts the ... right and hands the chip
row 8px more.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:54:31 +08:00
thomasandClaude Opus 4.8 a95f890497 feat(shell-integration): support Git Bash on Windows
PowerShell was the only Windows shell that got OSC 133 integration. Git
Bash was excluded by two unrelated gates, neither deliberate:

`shell_kind` stripped `.exe` only for PowerShell, so the `bash.exe` that
`core::shells::find_git_bash` resolves matched nothing — even though the
comment right above it reasons about exactly that suffix. Strip it for
every shell instead.

The Git Bash dropdown row also ships `-i -l`, which tripped
`has_custom_args` and made `setup` decline bash outright. But that guard
exists to protect args the *user* configured; these are tty7's own, from
`detect_shells`. `ShellSpec` now carries who authored its args, so
integration may respell tty7's (`--rcfile … -i` plus the replayed
login-file chain means the same thing) while still leaving the user's
alone. The `-i -l` stay as the fallback for when integration doesn't
apply or fails to set up.

One msys2 detail: the rcfile path is now spelled with forward slashes,
which its runtime accepts just as readily and which carry no second
meaning in the bash string contexts the path can reach.

Verified end to end, not just by construction: a new live-PTY test
spawns the real Git Bash through the real `setup` output and asserts the
full A/B/C/D cycle plus OSC 7 come back. It skips when Git for Windows
isn't installed. Getting it green surfaced two ConPTY-isms worth
recording — the master doesn't reliably EOF when the child exits, and
closing its input side raises a console control event that kills the
shell with STATUS_CONTROL_C_EXIT — both noted at the call sites.

cmd and WSL stay unintegrated, now documented as decisions rather than
gaps: cmd's only hook is PROMPT, which cannot emit C or D, and since
only C clears `at_prompt` an A/B-only shell would leave the line editor
holding the keyboard for the whole of every command. WSL would need
per-distro shell detection and WSLENV path translation to reach the
shell that actually runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:39:19 +08:00
thomasandClaude Opus 4.8 3231d315bf fix(windows): stop console windows flashing on every git status probe
tty7 is a GUI process with no console, so launching a console-subsystem
child makes Windows allocate one for it — a black window that pops up
and vanishes. The git status probe is the worst offender: it shells out
four times (rev-parse --show-toplevel, branch name, --git-dir /
--git-common-dir, diff --numstat) and runs on every pane cwd change,
command end, and agent-turn end. Opening a shell in a repo flashed four
windows.

Add core::proc::hide_console — CREATE_NO_WINDOW on Windows, a no-op on
Unix so callers stay cfg-free — and route every non-PTY shell-out
through it: the status probe, worktree's git, the diff-review git calls,
the codex CLI, and shells.rs's WSL probe (which had its own copy of the
constant, now one source of truth).

PTY children are out of scope; daemon::spawn already passes its own
flags for those.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 16:37:43 +08:00
l0ng-ai 80af906146 fix(terminal): require angle-bracket pairs to hug their contents
`<` and `>` are comparison and redirection operators at least as often as
they are delimiters, and the bracket path returns before the `extends`
guard that keeps every other smart candidate additive — so a bad match
there has no safety net and can select half a line.

Accept an angle pair only when neither delimiter is followed (resp.
preceded) by whitespace. Measured against the repo's own source plus a
corpus of shell commands, logs and diagnostics, that keeps every real
delimiter — `Vec<String>`, `<div class="row">`, `<command>`,
`<user@host>`, `<info>` — and drops the operator matches
(`a < b > c`, `x <= 0 || y > 9`, `WHERE a < 10 AND b > 20`).

Redirections need no handling: `2>&1` and `cmd > out` have no partner to
match, so the scan already failed on them. Other pairs are unaffected —
`( a )` is a legitimate subshell and `[ 1 ]` a legitimate index.
2026-07-18 22:19:04 +08:00
l0ng-ai 142a4f109c test(terminal): cover the grid side of smart selection
The existing tests all drove the pure string helpers; the grid path — where
the index arithmetic is actually hard — had none. Add tests that feed a real
`Term` through the VT parser and assert on the resolved selection:

- OSC 8 hyperlink runs, including one spilling across a soft wrap in both
  directions, and a cell outside the run not picking the link up
- a URL split by a soft wrap, clicked from the head (joins forwards) and from
  the continuation row (joins backwards)
- wide CJK glyphs, where clicking the glyph cell and its trailing spacer must
  resolve to the same word, plus a glyph pushed onto the next row by a
  leading spacer
- a click past the last column resolving to no range

Verified by mutation: breaking the trailing-spacer offset, the hyperlink
wrap walk, or the logical-line backward join each turns one of these red.
2026-07-18 21:50:56 +08:00
l0ng-ai ceb303aa6c fix(terminal): prefer the OS tokenizer over jieba for CJK selection
jieba's dictionary costs ~55 MB resident and ~130 ms to build, and it was
built eagerly on every terminal-view creation regardless of the
`smart_select` setting or whether the user ever selects CJK text.

macOS already ships a Chinese lexicon in CFStringTokenizer that matches
jieba on most prose, is locale-independent (identical output for current /
NULL / zh_CN / en_US), and segments Japanese and Korean properly where
jieba shreds them into single characters. Make the OS tokenizer the
primary path and keep jieba only as the fallback for platforms with no
such API:

- gate the dependency behind `cfg(not(target_os = "macos"))` so the
  embedded dictionary isn't even linked into the macOS build
- never warm eagerly; the first CJK double-click kicks the build off in
  the background and settles for the unsegmented run, so the UI thread
  never blocks on it
- skip jieba for runs containing kana or hangul, where selecting the
  whole run beats per-character tokens

Also honor `Config::smart_select` in the prompt's command editor, which
did bracket pairing, CJK segmentation and mixed-script narrowing even
with the Settings toggle off.
2026-07-18 21:41:58 +08:00
l0ng-ai 88c694df9e feat(terminal): smart double-click selection with CJK segmentation
- Double-click expands to the whole URL, email, file path, sci-notation
  number, identifier chain, OSC 8 hyperlink run, or matching bracket/quote
  pair containing the clicked word. Candidates only ever grow the plain
  word selection, so nothing regresses below the stock word behavior.
- Chinese segments with jieba's dictionary on all platforms; Kana/Hangul
  use CFStringTokenizer on macOS. The table builds lazily on a background
  thread so the first double-click never pays the cost.
- Latin words glued to CJK text narrow to the clicked script's sub-run
  instead of selecting the mixed blob.
- Bracket pairs (ASCII and full-width) and symmetric quotes (parity
  matched) select through their match, in the grid and prompt editor alike.
- Shift+click extends the existing grid selection instead of restarting.
- Word separators are configurable (word_separators, shared by grid and
  prompt editor); new 'Smart selection' toggle in Settings > Terminal.
2026-07-17 19:11:52 +08:00
l0ng-ai 2e4cc77a47 Merge pull request #126 from l0ng-ai/feat/tab-close-overlay
feat(tab-strip): float the close button over the label instead of reserving a slot
2026-07-17 18:11:24 +08:00
l0ng-ai 30e6895dd5 Merge pull request #127 from l0ng-ai/feat/agent-hook-cwd
feat(agent-hooks): follow the agent's own cwd for the sidebar git line
2026-07-17 18:11:12 +08:00
l0ng-ai 29a429d0fd Merge pull request #125 from l0ng-ai/fix/agents-row-note-layout
fix(settings): keep Agents rows aligned and notes terse
2026-07-17 18:10:47 +08:00
l0ng-ai c70f9e0881 feat(agent-hooks): follow the agent's own cwd for the sidebar git line
Claude Code's EnterWorktree chdirs the agent without any shell cd, so the
sidebar's branch/diff line could not see the move on platforms without the
proc-cwd fallback. Forward the cwd field every Claude Code hook payload
already carries through the OSC 777 sentinel, keep it on the pane's agent
session state, and let it take precedence over the proc probe for the git
line. The claim is released on session-end (and when the agent leaves the
foreground), so an exited agent falls back to the pane's real directory.
2026-07-17 18:01:29 +08:00
l0ng-ai 58d0596015 feat(tab-strip): float the close button over the label instead of reserving a slot
Every chip permanently reserved a 6px gap + 20px slot for a close
button that is invisible until hover, which read as a hole between an
ellipsized label and the chip edge, and wasted label width on crowded
strips. Drop the in-flow slot: the label now runs the full chip width,
and on hover the ✕ floats over its right edge on a solid backing in
the chip's own fill with a short gradient run-in, so covered text
fades out and nothing reflows.

The ⌘N hint badge keeps an in-flow slot: it also shows on unhovered
inactive chips, which are transparent over the window background, so
an overlay has no solid colour to back onto.
2026-07-17 17:56:09 +08:00
l0ng-ai 72056f4195 fix(settings): keep Agents rows aligned and notes terse
The install/uninstall outcome note repeated the agent name and full
target path inside the row's shrink-proof control column; a long path
inflated the column past the 640px content cap and crushed the label
column (min_w_0) to zero width, wrapping the title one character per
line.

- Terse action summaries: "Installed" / "Removed" — the row already
  shows the agent and target path. Codex keeps the feature-flag
  failure advice; the launch-refresh log line carries the agent name
  and target path itself now.
- Control column gets items_end so status, buttons, and note share
  one right edge across all agent rows.
- Note is width-capped (max_w_80) and wraps, so long error text can
  never expand the column again.
- Outdated status text shortened to one word; the Update button says
  what to do.
2026-07-17 17:21:22 +08:00
l0ng-ai 96ff361209 chore(release): v26.7.1 v26.7.1 2026-07-17 16:42:28 +08:00
l0ng-ai f7270f2bc0 Merge pull request #124 from l0ng-ai/feat/duo-logo-refresh
feat(brand): "Duo" logo refresh — mint panes mark across all icon assets
2026-07-17 16:36:50 +08:00
l0ng-ai 6af47dfb53 feat(brand): "Duo" logo refresh — mint panes mark across all icon assets
Replace the orange window-and-cursor identity with the "Duo" mark: two
offset session panes (mint #3FDD8C behind, ink #17171A in front) with a
prompt chevron, on a cream tile following the Big Sur icon grid.

- app-icon.svg is the master; app-icon.png, tty7.icns and favicon.ico
  are re-rendered from it (rsvg-convert + iconutil + PIL)
- logo.svg/logo.png/logo@256.png carry the tile-less transparent mark
- tray.svg redrawn as the same mark in template form: solid front pane
  with the chevron masked out, back pane at partial alpha
- bare (non-bundled) macOS binaries now set the Dock icon at runtime
  via NSApplication.setApplicationIconImage, so cargo dev/run shows the
  logo instead of the generic executable icon
- README version badge and the social preview switch to the new brand
  green; social preview copy re-aligned with the current README slogan
2026-07-17 16:31:41 +08:00
l0ng-ai 55b5d0151f Merge pull request #123 from l0ng-ai/fix/flaky-reap-guard-test
test(daemon): poll for the child's exec before asserting its path
2026-07-17 15:52:35 +08:00
l0ng-ai 4d2094ff90 test(daemon): poll for the child's exec before asserting its path
Command::spawn returns after the fork, possibly before the child has
exec'd sleep; until then /proc/<pid>/exe still points at the test
binary itself, so asserting the basename immediately is a race. This
intermittently failed the Linux CI job on main (runs 29549626571,
29560431777, 29563049775). Poll process_path for up to 5s and carry
the last-seen basename into the failure message.
2026-07-17 15:48:25 +08:00
l0ng-ai 5b67d79805 Merge pull request #122 from l0ng-ai/fix/macos-tray-template-icon
fix(tray): keep the macOS icon a template image in every state
2026-07-17 15:25:42 +08:00
l0ng-ai 3f61bbbc34 fix(tray): keep the macOS icon a template image in every state
The attention state left template mode to carry the amber badge, which
forced the glyph to a hardcoded mid-grey that was illegible on many menu
bar appearances. Drop the badge (and the grey recolor) on macOS: the icon
now stays a template glyph the system recolors for the bar, and agent
status lives in the tooltip and tray menu. Windows/Linux keep the amber
corner badge on their colored icon.
2026-07-17 15:23:58 +08:00
l0ng-ai a31db6aa7f Merge pull request #121 from l0ng-ai/feat/theme-follow-system
feat(theme): follow the OS appearance with separate light/dark themes
2026-07-17 15:16:02 +08:00
l0ng-ai df36292db1 test(config): round-trip follow-system fields; docs + comment fixes from review 2026-07-17 15:11:11 +08:00
l0ng-ai 1cd801e902 feat(theme): follow the OS appearance with separate light/dark themes
Add a "Sync with system" mode: when on, the active theme resolves from
two user-picked slots (theme_preset_light / theme_preset_dark) by the
current OS appearance and switches live when the OS mode flips.

- config: theme_follow_system + theme_preset_light/theme_preset_dark
- theme: effective_preset_id() resolver; release the native appearance
  pin while following (it would blind the OS-appearance reads)
- app: observe_window_appearance re-applies the theme on OS flips;
  set_preset writes the slot matching the current appearance while
  following; explicit set_slot_preset for the Settings cards
- settings: sync switch + one card per slot; the picker panel aims at
  the slot whose card opened it

Closes #107
2026-07-17 14:57:48 +08:00
l0ng-ai 627f5fe174 Merge pull request #119 from l0ng-ai/fix/windows-hook-console-flash
fix(agent-hooks): stop console windows flashing on each Windows hook
2026-07-17 14:34:30 +08:00
l0ng-ai d5c4f347ed Merge pull request #120 from l0ng-ai/feat/tab-mark-unread
feat(tabs): add Mark as Unread to the tab context menu
2026-07-17 14:32:30 +08:00
l0ng-ai 37a894fc49 feat(tabs): add Mark as Unread to the tab context menu 2026-07-17 14:23:47 +08:00
l0ng-ai f8baebddfc Merge pull request #118 from l0ng-ai/fix/sidebar-group-worktree
fix(sidebar): group linked worktrees under their main repository
2026-07-17 10:18:56 +08:00
thomasandClaude Opus 4.8 3a70e62078 fix(agent-hooks): stop console windows flashing on each Windows hook
Debug builds are console-subsystem (so println! logging stays visible
while developing the GUI), so every `tty7 agent-hook <agent> <event>`
process Claude Code spawns for a hook gets its own console window. At
end of turn several hooks fire (Stop, SessionEnd, PostToolUse, ...), so
a cluster of terminal windows flashes open and vanishes as each tiny
emitter runs and exits.

The emitter never uses its own console for I/O — stdin is piped and it
writes to the agent's console via AttachConsole — so free the throwaway
console the instant run_agent_hook starts. With no other process
attached, the console and its window are torn down before they can
paint. No-op in release (GUI subsystem, no console) and on Unix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:48:24 +08:00
l0ng-ai 09810c3766 fix(sidebar): group linked worktrees under their main repository
The sidebar's repo grouping keyed on the git work-tree root
(rev-parse --show-toplevel), so every linked worktree of one repository
rendered as its own group. Probe the common git dir alongside and derive
a repository home — the main checkout's root — and group on that
instead. Branch and diff state stay cached per work tree, so each row's
branch line is unaffected.
2026-07-17 09:46:30 +08:00
l0ng-ai e1c0ed332e chore(release): v26.7.0 v26.7.0 2026-07-16 23:40:31 +08:00
l0ng-ai bb4d1a6bc9 Merge pull request #117 from l0ng-ai/feat/windows-clipboard-image-paste
feat(terminal): paste clipboard images as a file path off macOS
2026-07-16 23:30:02 +08:00
l0ng-ai 3155540acd Merge main: carry #113 selection-consume into the SYN fallback of paste_clipboard_image 2026-07-16 23:20:43 +08:00