Commit Graph
501 Commits
Author SHA1 Message Date
l0ng-ai f3e7bbb364 feat(sidebar): float the row close button instead of reserving a trailing slot
The sidebar row kept a permanent in-flow slot for the hover-only close
button, so titles and branch names truncated while an empty column sat
at the rail's right edge. Move the close affordance out of flow, same
Safari-style float the strip's chips already use: on hover the x sits
over the title line's right end on a solid backing (the row's hover
fill, flattened against the sidebar surface) with a short gradient
run-in, so covered text fades out instead of hard-cutting.

Pinned to the row top rather than vertically centered: on a two-line
row a centered x would straddle both lines and cover the branch line's
+n/-n counts, which are the diff-overlay click target. The cmd-N hint
badge keeps its in-flow slot (deliberate all-rows modal reflow).
2026-07-21 17:43:56 +08:00
l0ng-ai f61bb39f01 Merge pull request #144 from l0ng-ai/feat/agent-resume-launch-flags
feat(agent): carry launch flags onto session resume commands
2026-07-21 12:31:30 +08:00
l0ng-ai ace55cb5be Merge pull request #142 from l0ng-ai/feat/codex-black-brand-field
fix(ui): Codex black-field avatar and 22pt macOS tray icon
2026-07-21 12:26:01 +08:00
l0ng-ai ac27b4a821 Merge pull request #139 from l0ng-ai/dependabot/cargo/cargo-minor-patch-ce6b99fb33
deps: bump the cargo-minor-patch group with 8 updates
2026-07-21 12:24:53 +08:00
l0ng-ai c5b97fdfa9 Merge pull request #140 from l0ng-ai/dependabot/cargo/sha2-0.11.0
deps: bump sha2 from 0.10.9 to 0.11.0
2026-07-21 12:24:45 +08:00
l0ng-ai 84500cdc86 Merge pull request #141 from l0ng-ai/dependabot/cargo/resvg-0.47.0
deps: bump resvg from 0.45.1 to 0.47.0
2026-07-21 12:24:37 +08:00
l0ng-ai e34da7a36e Merge pull request #138 from l0ng-ai/dependabot/github_actions/actions/upload-artifact-7
ci: bump actions/upload-artifact from 4 to 7
2026-07-21 12:24:18 +08:00
l0ng-ai c724e8c257 Merge pull request #137 from l0ng-ai/dependabot/github_actions/actions/download-artifact-8
ci: bump actions/download-artifact from 4 to 8
2026-07-21 12:24:14 +08:00
l0ng-ai 0233f56766 Merge pull request #143 from ayamir/feat/link-file-command
feat(links): open file links with a configurable command
2026-07-21 12:22:42 +08:00
l0ng-ai 6a85964390 style: rustfmt 2026-07-21 12:21:57 +08:00
l0ng-ai e3edcdb153 feat(agent): carry launch flags onto session resume commands
Resume-after-restart replayed a hardcoded per-agent command
(claude --resume <id>), dropping whatever flags the agent was
originally launched with (--dangerously-skip-permissions, --model).

The daemon's foreground poll already reads the agent's argv for
detection; keep it, stream it to the client inside AgentSessionState
(serde-default, wire-compatible both ways), persist it in the session
Leaf, and splice a conservatively-gated flag tail into the resume
command. The gate refuses anything that is not a plain flag-shaped
token sequence and falls back to the bare table command.

The Windows 133;C typed-command capture is forgeable by terminal
output, so it contributes identity only, never flags. Copilot gains a
resume entry (copilot --resume <id>, hooks already report its session
id) and Amp's threads continue verified to accept global flags.
2026-07-21 12:16:11 +08:00
l0ng-ai 7eba9153e3 fix(tray): override hardcoded 18pt NSImage size to 22pt on macOS 2026-07-21 10:45:24 +08:00
ayamir 3f7442f6ce feat(links): open file links with a configurable command 2026-07-20 19:08:55 +08:00
l0ng-ai 6d9a2b3659 fix(brand): use black field for Codex tab avatar 2026-07-20 18:14:36 +08:00
dependabot[bot] e91e706138 deps: bump resvg from 0.45.1 to 0.47.0
Bumps [resvg](https://github.com/linebender/resvg) from 0.45.1 to 0.47.0.
- [Release notes](https://github.com/linebender/resvg/releases)
- [Changelog](https://github.com/linebender/resvg/blob/main/CHANGELOG.md)
- [Commits](https://github.com/linebender/resvg/compare/v0.45.1...v0.47.0)

---
updated-dependencies:
- dependency-name: resvg
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 09:35:36 +00:00
dependabot[bot] 33bebccaa0 deps: bump sha2 from 0.10.9 to 0.11.0
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.9 to 0.11.0.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.9...sha2-v0.11.0)

---
updated-dependencies:
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 09:35:10 +00:00
dependabot[bot] 6848054da3 deps: bump the cargo-minor-patch group with 8 updates
Bumps the cargo-minor-patch group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [keyring](https://github.com/open-source-cooperative/keyring-rs) | `4.1.4` | `4.1.5` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.52.3` | `1.53.0` |
| [jieba-rs](https://github.com/messense/jieba-rs) | `0.10.2` | `0.10.3` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.103` | `1.0.104` |
| [serde](https://github.com/serde-rs/serde) | `1.0.228` | `1.0.229` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.150` | `1.0.151` |


Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `keyring` from 4.1.4 to 4.1.5
- [Release notes](https://github.com/open-source-cooperative/keyring-rs/releases)
- [Commits](https://github.com/open-source-cooperative/keyring-rs/compare/v4.1.4...v4.1.5)

Updates `tokio` from 1.52.3 to 1.53.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.3...tokio-1.53.0)

Updates `jieba-rs` from 0.10.2 to 0.10.3
- [Release notes](https://github.com/messense/jieba-rs/releases)
- [Commits](https://github.com/messense/jieba-rs/compare/v0.10.2...v0.10.3)

Updates `anyhow` from 1.0.103 to 1.0.104
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.103...1.0.104)

Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.228...v1.0.229)

Updates `serde_json` from 1.0.150 to 1.0.151
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.150...v1.0.151)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-minor-patch
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-minor-patch
- dependency-name: keyring
  dependency-version: 4.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-patch
- dependency-name: tokio
  dependency-version: 1.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-minor-patch
- dependency-name: jieba-rs
  dependency-version: 0.10.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-patch
- dependency-name: anyhow
  dependency-version: 1.0.104
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-patch
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-patch
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 09:34:47 +00:00
dependabot[bot] b9f6cbb78a ci: bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 09:29:18 +00:00
dependabot[bot] 0577f7d481 ci: bump actions/download-artifact from 4 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 09:29:15 +00:00
l0ng-ai 3c7cff09ca Merge pull request #134 from l0ng-ai/feat/wsl-shell-integration
feat(shell-integration): support WSL
2026-07-19 20:48:47 +08:00
thomasandClaude Opus 4.8 ef4160d398 style: rustfmt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:35:00 +08:00
thomasandClaude Opus 4.8 c780799c31 fix(wsl): tag the shell we launch, not the one we were asked for
`wsl_remote_context` read the per-spawn `ShellSpec` override, but the
program actually spawned comes from `choose_shell`, which falls back to
`config.json`'s `shell` when there is no override.

So `{"shell": {"program": "wsl.exe", "args": []}}` reached `setup_wsl`
— empty args, so nothing custom to preserve — and the distro began
reporting its own cwd over OSC 7, while `wsl_remote_context(None)` left
the pane untagged. `local_cwd` then accepts `/home/me/proj` and hands it
to the local git probe, which Windows resolves drive-relative to
`C:\home\me\proj`: exactly the collision the tag exists to prevent, and
newly reachable because integration is what makes such a pane report a
cwd at all.

`choose_shell` now runs in `build_spawn_config` and its result feeds both
the tag and `build_shell_command`, so the two cannot describe different
shells. Taking `ChosenShell` rather than `ShellSpec` is what keeps it
that way — the resolved type is only available after the fallback.

Also drop the second distro parser: the tag now reads the distro through
`shell_integration::wsl_distro`, which additionally understands
`--distribution=NAME`. Both are handed the same argv, so one parser is
the only way they agree.

736 tests pass, clippy warning count unchanged (124).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:22:34 +08:00
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