Commit Graph
259 Commits
Author SHA1 Message Date
l0ng-ai 142e5afef4 Merge pull request #149 from l0ng-ai/fix/stale-sidebar-git-status
fix(git-status): refresh the sidebar counts on window focus and agent tool calls
2026-07-23 17:18:14 +08:00
l0ng-ai 2f99dad0fc Merge pull request #150 from l0ng-ai/chore/fix-cargo-lock-drift
chore(deps): resync Cargo.lock with Cargo.toml and lock it in CI
2026-07-23 17:18:01 +08:00
l0ng-ai 7ab19f14f6 fix(git-status): throttle opportunistic probes per repo, not per cwd
The counts a probe produces are repo-wide — `git diff --numstat HEAD`
ignores the subdirectory it ran in — so panes at `repo/`, `repo/src` and
`repo/docs` were three ways of asking one question, and a window
activation spent one full-repo diff on each.

Count the throttle against the work-tree root once a probe has resolved
one for the cwd, and stamp the clock when the probe is *claimed* rather
than when it lands: without that, panes claiming in the same instant all
pass a throttle nothing has answered yet, which is exactly the shape a
window activation has. In-flight dedup stays keyed by cwd, since it
brackets a specific spawn that finish_probe has to release.
2026-07-23 17:09:20 +08:00
l0ng-ai a42ed82a14 ci(release): build the tagged commit with --locked too
The release workflow is a plain checkout of the tag — nothing rewrites
Cargo.toml there, so the lockfile guard the CI build just gained applies
just as well, and a release is the build you least want silently
re-resolving dependencies. Only nightly stays unlocked: it stamps
Cargo.toml's version, which makes the lock's own root entry stale by
design.
2026-07-23 17:03:44 +08:00
l0ng-ai 9c90044c05 chore(deps): resync Cargo.lock with Cargo.toml and lock it in CI
Two lockfile-only dependabot bumps (#139, #140) raised resvg to 0.47.0 and
sha2 to 0.11.0 in Cargo.lock without touching Cargo.toml, which asks for
`resvg = "0.45"` and `sha2 = "0.10"`. Under cargo's 0.x rules the minor
version is the major, so neither requirement accepts the locked version and
the lockfile has been self-contradictory ever since:

    $ cargo metadata --locked
    error: cannot update the lock file ... because --locked was passed

Nothing failed loudly — CI never passed `--locked` — so the cost landed on
contributors instead: every local cargo invocation rewrote the lock, leaving
a permanently dirty working tree to discard before each commit.

Resyncing drops the duplicates too. gpui-component already pulls resvg
0.45.1, so the tree no longer builds two copies each of resvg, usvg,
tiny-skia, tiny-skia-path, kurbo, svgtypes, roxmltree, imagesize and
polycool.

CI now builds and tests with `--locked` so the next such drift fails in the
PR rather than in a working tree. The release and nightly workflows keep
their unlocked builds on purpose: both stamp Cargo.toml's version and depend
on cargo refreshing the lock's root entry.
2026-07-23 16:26:21 +08:00
l0ng-ai 8955b1545f fix(git-status): refresh the sidebar counts on window focus and tool calls
The sidebar's `+N -N` only refreshed on three rare edges: the pane changing
directory, a command ending, and an agent turn ending. Edits made anywhere
else produced no signal at all, so the counts sat stale — a long agent turn
showed nothing until it finished minutes later, and a file edited in another
editor never registered until the user happened to run a command in the pane.

Two new triggers close the gap:

- Window activation re-probes every pane. Coming back to the window is the
  only cue we get that the tree moved while the user was elsewhere, and the
  sidebar lists every tab, so refreshing just the focused pane isn't enough.
- An agent's tool completions re-probe mid-turn. `AgentSessionState` gains an
  `activity` counter because `ToolComplete` is deliberately a status no-op
  during normal work, leaving status-watchers unable to see it.

Both go through a new throttled claim on `GitStatusCache` that drops triggers
instead of queueing them, so a busy agent or a window full of panes collapses
into one shell-out per repo per 1.5s rather than a `git` storm.

Also: fold the probe's two `rev-parse` calls into one (it now asks for
toplevel, git-dir and common-dir together), which makes `repo_home` a pure
function and unit-testable; and land probe results in the shared cache
independently of the pane entity, so a pane closed mid-probe can't wedge the
cwd-keyed in-flight claim for every other pane in that directory.
2026-07-23 16:17:32 +08:00
l0ng-ai 174700b77b Merge pull request #148 from l0ng-ai/fix/quit-with-last-window
fix(window): quit when the last window is closed from the home page
2026-07-23 12:56:32 +08:00
l0ng-ai c4381812ae Merge pull request #145 from l0ng-ai/feat/sidebar-floating-close
feat(sidebar): float the row close button instead of reserving a trailing slot
2026-07-23 12:56:29 +08:00
l0ng-ai 0e76a79685 fix(window): quit when the last window is closed from the home page
Closing the window with zero tabs took the early-return path in
on_window_should_close, which skipped the cx.quit() the confirmed path
runs. gpui does not quit on last-window-close and no on_reopen handler is
registered, so the process stayed alive with no window and clicking the
Dock icon did nothing (#147). Defer the quit onto the next tick so the
close completes first, matching the confirmed path.
2026-07-22 21:04:40 +08:00
l0ng-ai b98e9c4ea5 chore(release): v26.7.2 v26.7.2 2026-07-21 18:50:41 +08:00
l0ng-ai cfe11ebf0c Merge pull request #146 from l0ng-ai/fix/tab-completion-fallthrough
fix(completion): fall through to shell completion, dir-only candidates, opt-out
2026-07-21 18:39:23 +08:00
l0ng-ai 6753b9697b fix(completion): fall through to shell completion, dir-only candidates, opt-out (#136)
Three fixes for tty7's Tab completion:

- Tab is no longer swallowed when the engine has no candidates: the
  locally edited line is handed off to the shell (text shipped raw,
  cursor walked back, Tab sent) and the local editor suspends until the
  next prompt cycle, so shell-native completion (compsys, fzf-tab, ...)
  answers instead. The handoff release keys off a new entered-prompt
  cycle counter rather than the raw Prompt-frame seq, so same-prompt
  redraws (PS1-embedded 133;B re-emissions) cannot re-engage the editor
  while zle still holds the handed-off text.
- cd/pushd/popd/rmdir complete directories only in the no-signature
  path fallback; Fig 'folders' templates narrow signature slots the
  same way. Symlinks now classify by their target.
- New tab_completion config field (default true) plus a Settings ->
  Terminal -> Keyboard toggle; when off every Tab goes to the shell.
2026-07-21 18:25:49 +08:00
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