Commit Graph
82 Commits
Author SHA1 Message Date
l0ng-ai 8bdcaf4290 fix(fonts): name a CJK and emoji fallback the host platform actually ships
The default `font_fallbacks` list was macOS-only -- Menlo, Hasklug Nerd
Font Mono, Maple Mono NF CN, Apple Color Emoji. Fallbacks resolve by
family name against installed fonts, so off macOS the whole chain matched
nothing and every glyph the primary lacked was left to the platform's own
cascade. Bundled Hack maps 1548 codepoints and zero ideographs, so on
Windows that was every Chinese character in every pane, and every emoji.

The fall-through is not only a matter of which face you get. `element.rs`
pins each wide cell to `2 x cell_width`, and Hack advances 0.60205em, so
a two-column slot is 1.2041em -- while every stock CJK face (Microsoft
YaHei, PingFang SC, Noto Sans CJK) advances 1.0em. `force_width`
left-aligns, so the ideograph hugs the left of its slot and the remaining
0.2em shows up as a gap on the right of every character. Measured on
Windows at font_size 15: left bearing 1.49px, right bearing 4.90px.

Branch the defaults per platform, keeping Maple Mono NF CN first
everywhere -- 0.6em Latin, 1.2em CJK, the one exact two-cell fit against
Hack (bearings 3.06px / 3.62px, ink centered). It stays referenced by
name only, never bundled, at ~20MB per weight.

Changing `Config::default` alone would reach nobody who already has a
`config.json`, which is every existing user. So `fallback_chain` appends
the platform's stock faces the same way it already pins Hack: a fallback
is consulted only after everything ahead of it has missed, so appending
can never displace a face the user chose, and the file is never rewritten.

Verified by driving two builds against one config naming only absent
macOS faces: before, the CJK line differed from an explicit Microsoft
YaHei chain by 3571 pixels (the cascade picked something else); after, it
is pixel-identical.
2026-07-26 20:59:11 +08:00
l0ng-ai 78d86b9949 Merge pull request #175 from l0ng-ai/feat/ux-overhaul
feat(ux): rebuild the menu bar, command palette, and Settings IA
2026-07-26 10:12:52 +08:00
l0ng-ai 89b6fc5ae5 Merge pull request #174 from l0ng-ai/feat/grok-hooks
feat(agents): wire the rich status channel into Grok Build
2026-07-26 10:09:21 +08:00
l0ng-ai 4ac7d30178 feat(ux): rebuild the menu bar, command palette, and Settings IA
The menu bar shipped four menus in the order App / Shell / Window / View
with no Edit menu at all, so Copy and Paste existed nowhere but a
right-click, and About / Check for Updates / Hide / Minimize / Help had
no home. It now follows the macOS HIG's standard set.

The palette listed 47 commands in three competing naming styles, ranked
only by catalog order, with no grouping and no way to reach most of what
the tab context menu could do. It now has one documented grammar, a
scored fuzzy ranker, group bands with a frecency-driven Recent, and the
commands it was missing.

Settings had a three-row Shell page indistinguishable from Terminal, a
seven-group Terminal page that had become a junk drawer, two different
groups called "Window", and a search index that had drifted so far from
the rendered rows that "opacity" and "completion" returned nothing.

Also folds copy / cut / paste / undo onto one code path each, which fixed
two real drift bugs: the right-click Paste skipped the image-paste branch
that Cmd+V had, and Copy rendered disabled whenever the selection was in
the prompt editor rather than the grid.
2026-07-25 20:19:00 +08:00
l0ng-ai eced0af754 feat(agents): give Grok its brand avatar
Grok was drawing the generic robot glyph on a slate disc — the fallback
picked back when no usable mark was bundled.

xAI publishes its symbol only as a ~2:1 landscape lockup that bleeds off
its own canvas; traced and fitted to a 24x24 box it is unreadable as a
16px silhouette, which is what the tab chip and sidebar render. So the
bundled mark is lobehub/lobe-icons' square transcription (MIT), drawn for
exactly this avatar use. Its notice rides in the SVG.

The slate accent goes with it. That mid-tone exists for vendors whose
monochrome mark is grey or a gradient (Cursor), because a white field
vanishes on a light theme; a black field has no such problem — it stays
darker than even the darkest theme background and the white mark carries
the badge. Grok brands in black, like Codex, so it keeps that.

Adds a guard test: every CLIAgent::icon_path must resolve through the
asset source. A brand mark means touching two files, and forgetting the
registration costs the agent its avatar silently.
2026-07-25 20:17:24 +08:00
l0ng-ai c76ef87d02 feat(agents): wire the rich status channel into Grok Build
Grok Build exposes a Claude Code-shaped hook surface, so tty7 can now
install into it and give grok panes live session status and
resume-after-restart, not just a brand chip.

- Owned hook file at ~/.grok/hooks/tty7.json (grok loads every JSON file
  there; global hooks need no folder-trust grant), so the user's own
  hooks are never touched.
- Read camelCase payload keys: grok's envelope sends sessionId, and
  without it restore loses the id --resume needs.
- Relabel events that arrive through grok's Claude-compat scan of
  ~/.claude/settings.json, keyed on the GROK_HOOK_EVENT var its hook
  runner injects — otherwise a grok pane reports Claude Code, and having
  both integrations installed emits every turn under two identities.
- Resume via `grok --resume <id>`, stripping the flags that would fight
  the injected id (--resume/--load/--continue/--session-id/--fork-session)
  or relocate the session (--worktree/--worktree-ref).

Notification is subscribed with a matcher for elicitation_dialog only.
Grok dispatches its permission_prompt notification before the permission
system decides, so it fires on essentially every tool call, auto-approved
ones included; escalating that to the amber "needs you" state would flash
the pane and fire a desktop notification on every tool a turn runs.
2026-07-25 19:17:05 +08:00
l0ng-ai 5d814c0d70 feat(config): default the tab bar to the left sidebar 2026-07-25 16:22:26 +08:00
l0ng-ai efd2213cf9 Merge branch 'main' into fix/prompt-ctrl-chords
CHANGELOG: keep both Unreleased sets, with the history-search entry under
Added beside the multi-window ones and the Ctrl+J/M fix in its own Fixed
section.
2026-07-25 15:25:35 +08:00
l0ng-ai 7d7d3ce882 fix(windows): start fresh workspaces with a shell, and say where a closed one went
Review follow-ups on top of the multi-window work.

- A brand-new workspace came up on the home page with no shell, because
  `claim` always hands back an (empty) session and the window treated that
  as "restore this". A first run and `New Workspace` now take the
  first-run path again and spawn a terminal; the launch that exists to
  show the workspace picker asks for an empty window explicitly
  (`FreshStart`).
- The close-window prompt promised sessions "will be restored the next
  time you open tty7", which is no longer what happens — the workspace
  detaches and waits in the picker. Both it and the one-time detach hint
  now point at the title bar's workspace menu rather than the macOS
  Window menu, which does not exist on Windows or Linux.
- `ToggleSftp` read the panel state off the config, which is now only
  what a *new* window starts with; it reads this window's own state.
- `SelectWorkspace1..9` were unbindable: registered as actions but absent
  from the keymap tables. Added with no default chord (⌘1–9 is the tab
  row's).
- `theme_commands`' doc comment had been captured by a function inserted
  above it, and the Window menu's slot→action mapping was a second copy
  of the title-bar chip's.
- CHANGELOG: drop the ⌘1–9 claim (no such binding ships), and document
  the chrome tile sizing that rode along with this branch.
2026-07-25 15:07:47 +08:00
thomasandClaude Opus 5 443f04f3c6 fix(prompt): stop swallowing ^J, and let ^R go to the shell on request
The local command editor consumed every Ctrl chord at the prompt, matched
or not, so two things the shell owns quietly stopped working (#163).

^J and ^M carry accept-line's control codes — Enter by another name — but
fell into `apply_readline_ctrl`'s no-op arm, so the keys did nothing at
all. Route them through the same path Enter takes, via a shared
`accept_line`, so the completion picker and the history menu treat them
identically.

^R was recognized, but only ever opened tty7's own history menu, with no
way back to a `bindkey`ed widget (fzf, percol). Add `history_search`
(default on, Settings → Terminal → Keyboard): with it off, the edited
line is handed to the shell and the raw ^R follows it, so whatever is
bound there answers. The "shell integration never engaged" notice stays
quiet in that case — ^R reaching the PTY is then the point, not a gap.

`handoff_tab_to_shell` generalizes to `handoff_line_to_shell(chord)` to
carry the ^R handoff; the Tab path is a thin wrapper over it and its
behavior is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:07:31 +08:00
l0ng-ai 7789dc4c36 Merge branch 'main' into feat/multi-window
Conflict in the sidebar's control row: main wrapped both tiles in
`occlude()` so Windows' HTCAPTION drag doesn't swallow their clicks, while
this branch moved their geometry onto `chrome_tile_sized` / the shared
`TILE_*` constants. Kept both — occluded wrappers around helper-sized tiles.
2026-07-25 14:12:58 +08:00
l0ng-ai e41afaf857 feat(windows): one window per workspace
tty7 had exactly one window, so `main` opened it inline and every app-wide
duty — tray, menus, the quit hook — lived in `Tty7App`'s constructor. This
splits those apart: a *workspace* is the persistent identity (tabs, splits,
cwds, name) and a *window* is a transient view onto exactly one of them.

- `ui::windows` — the app-level window registry and the single place that
  opens a window. Exactly one window per workspace is enforced there: the
  daemon gives each pane a single subscriber, so a second window on the same
  panes would silently steal the first's output. `open` focuses the existing
  window instead. New windows cascade so one never lands on top of another.
- `WorkspaceStore` owns session.json, so windows never race each other as
  writers. Closing a window *detaches* — panes keep running in the daemon
  and the entry stays for the picker; `StopWorkspace` kills the sessions and
  keeps the layout; `DeleteWorkspace` also forgets it.
- Window menu lists every workspace with a monogram badge and a liveness
  dot, ⌘1–9 for the first nine. Same list in the palette; closed ones also
  appear in a home-page picker with a coarse relative age.
- Sidebar collapse and right-panel visibility move onto `Tty7App`, so
  toggling one window's chrome leaves the others alone; the config value
  becomes what a new window starts with. Panel *width* stays shared — a
  width is a preference, not a view state.
- Tray, menus, and the quit hook now walk the registry rather than
  belonging to a single window.

Protocol goes to v2: `RemoteKind::Wsl` is a new enum variant, which is not
the additive change it looks like — the enums carry no `#[serde(other)]`, so
a v1 peer fails the whole decode and drops the pane's connection. The
handshake now catches that skew and offers a restart.
2026-07-25 14:10:44 +08:00
l0ng-ai 0ebe68a453 feat(ssh): fold port forwarding and SFTP into the detail panel
Both SSH tools floated over the terminal: a tunnel icon and an SFTP icon
pinned top-right, opening a 460px popover and a bottom dock. They are
pane facts, so they now live where the pane's other facts already are.

Port forwarding becomes a Forwards band on the Info tab, under Ports —
one says what the pane listens on locally, the other what it routes
across the connection. Rows take the panel's language: a mono kind
letter, the bound port as the same chip a listening port gets, hover to
remove, click to edit. The add form is inline, stacked to fit the
column. The list re-lists on the Info tab's existing 2s poll, so a
forward that dies remotely turns red on its own.

SFTP becomes the Files tab's remote mode: the tab follows the detail
pane, showing a local repository tree or that machine's filesystem. Same
browsing model as before (breadcrumb, filter, `..`-led list, per-row
right-click) relaid out for ~260px — the toolbar collapses to refresh
plus a `⋯`, and the permissions column moves into the chmod form, which
now names the mode it is editing. The header carries the hostname:
the tab swaps between two filesystems as the pane changes, and it can
rename and delete.

Transfers become a footer on the panel column rather than a tray inside
SFTP. It sits below every tab, so reading Info doesn't hide a running
upload, and stays pane-scoped rather than aggregating every pane, which
would quietly make the panel a window-level transfer centre.

Opening the browser gained a step: the shell's cwd needs tty7's shell
integration on the remote, which a freshly-connected host rarely has, so
it fell through to `/`. A new SftpOp::Realpath resolves the login
directory instead. Per-pane positions are recorded on arrival, so a
first landing at `/` can no longer be remembered as a preference.

With nothing floating over the terminal any more, the ⌘F find bar gets
its top-right slot back — it used to be suppressed while those icons
were up.
2026-07-25 11:25:06 +08:00
l0ng-ai 9a4d818b78 refactor(editor): drop the LSP client entirely
Opening a `.rs` file in the code panel silently spawned rust-analyzer,
which then indexed the whole workspace — hundreds of megabytes of RAM and
a busy core — with no setting to turn it off. A terminal emulator should
not do that to its user on a click, and rather than add a flag to disable
something nobody asked for, the integration goes.

Removed: the JSON-RPC client and reader thread (`ui::lsp`), the per-server
registry, the completion / hover / definition providers installed on the
buffer, document sync (didOpen/didChange/didSave/didClose), diagnostics,
Go to Definition (F12), Find References (⇧F12) and its drawer, and the
status bar's server indicator. With them go the `lsp-types`, `ropey` and
`url` dependencies — all three were used only by this code (they remain in
the lock file as transitive deps of gpui-component and gpui, which is
expected).

Kept, and deliberately so:

- **Syntax highlighting**, which is tree-sitter, not LSP: gpui-component's
  `tree-sitter-languages` feature, `InputState::code_editor(language)` and
  `language_for_path` are all untouched. It is static, in-process, and
  costs nothing beyond parsing the open buffer.
- ⌘S save, dirty tracking, the external-change watcher and its conflict
  banner, markdown preview, soft wrap, and open-from-the-file-tree.

The module header now records *why* there is no language server, so the
next person to reach for one finds the reasoning instead of a gap.

Net −975 lines.
2026-07-24 17:56:02 +08:00
l0ng-ai 649fdef51e Merge branch 'main' into worktree-code-panel
# Conflicts:
#	src/core/config.rs
#	src/ui/mod.rs
#	src/ui/tab_sidebar.rs
#	src/ui/tab_strip.rs
2026-07-24 15:15:08 +08:00
l0ng-ai 403cfd47a1 feat(right-panel): docked detail panel with Info, Changes and Files tabs
Add a right-hand detail column showing what the active pane is, not what
it prints: session facts plus its process tree and listening ports
(daemon-side procinfo, pull-based via QueryProcs), the working-tree diff,
and the file tree. Tab row lives in the title bar, body in right_panel.

Also record OSC 133 command marks client-side so the panel's Outline can
list a pane's commands and scroll back to one, keyed on row text since
absolute scrollback indices drift once history fills.
2026-07-24 15:07:35 +08:00
l0ng-ai 27c1cdbf3f feat(ssh): bootstrap shell integration into native SSH sessions
Native-SSH panes reported no OSC 133, so the inline line editor, exit-code
marks and cwd tracking were all inert there — the daemon's OSC sniffer was
already wired up for them and simply never received anything.

Every existing integration configures a *local* process spawn (ZDOTDIR, a
bash --rcfile, fish's -C). An SSH channel offers no spawn to configure, only
the string an `exec` request carries, so the remote path recreates those same
files on the remote side and execs through them. The integration bodies are
reused verbatim rather than forked.

The bootstrap can't be shell-agnostic: sshd runs it as `$SHELL -c <string>`,
so a POSIX script is parsed by fish and a fish script by zsh. Rather than
contort one expression into parsing identically everywhere, spend a probe
round-trip (`echo __tty7_shell; echo $SHELL` — no substitution, assignment or
grouping, so it is valid in all of them) and then emit the dialect we know we
are talking to. The probe is memoized on the connection key, so extra tabs to
an open host cost nothing.

The probe's negative answer is load-bearing: a remote whose login shell is
unrecognized — or that isn't POSIX at all, where `$SHELL` echoes back
unexpanded — falls through to the plain shell request it always used.

Every arm ends by exec'ing the user's own shell, including the failure paths,
so a remote with a read-only $TMPDIR loses the integration and not the session.
zsh only gets ZDOTDIR pointed at the throwaway dir once all four redirectors
are confirmed written; a half-populated dir would silently cost the user their
dotfiles. The dir removes itself on the first precmd, by which point every
startup file has been read.

Add a per-profile switch, on by default and defaulting to on for profiles
saved before it existed, for remotes we *can* integrate but shouldn't.
2026-07-24 11:57:41 +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 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 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 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
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
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
thomas 9023e1ada8 Merge remote-tracking branch 'origin/main' into feat/gitbash-shell-integration 2026-07-19 19:15:46 +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 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
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 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 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 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 f9ed31c0af refactor(code-panel): full-body overlay instead of docked side columns
The file tree + editor now render as one overlay covering the terminal
(settings/diff-overlay style): toggling never resizes the terminal (no PTY
resize/reflow) and the editor gets the full body width. The tab sidebar stays
visible and switching tabs re-roots the tree; focus follows the panel.

- Merge ToggleFileTree/ToggleEditor into one ToggleCodePanel action (cmd-shift-e,
  Esc closes, palette "Code Panel").
- Add the one on-screen entry point: a title-bar tile next to the overflow
  menu, lit while the overlay is up (present in both tab-bar modes).
- Drop the editor width divider and the file tree's standalone open flag.
2026-07-17 10:52:21 +08:00
l0ng-ai 5d58aae9d1 Merge remote-tracking branch 'origin/main' into worktree-code-panel 2026-07-17 10:26:06 +08:00
l0ng-ai acb461a094 feat(code-panel): local file tree, code editor panel, and LSP client
- File tree (left column): lazy per-directory listing with notify-driven
  refresh, gitignore chain matching (dimmed italics), keyboard nav, inline
  new-file/new-folder/rename, context menu (open / cd / insert path /
  attach-to-agent / copy path / reveal / delete), multi-root from the active
  tab's pane cwds, rows draggable into the terminal as ExternalPaths.
- Code editor (right column): gpui-component CodeEditor mode (tree-sitter
  highlighting, line numbers, folding, find/replace), file tabs with dirty
  markers, cmd-S save, external-change reload with conflict banner, markdown
  preview, soft-wrap toggle.
- LSP: stdio JSON-RPC client per (server, workspace root) for rust-analyzer /
  gopls / pyright / tsserver / clangd; completions, hover, diagnostics,
  same-file cmd-click definitions, F12 cross-file goto, shift-F12 references
  drawer.
2026-07-17 10:25:45 +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 89774b1800 Merge pull request #115 from l0ng-ai/feat/windows-agent-detection
feat(agent): detect coding agents on Windows via shell-integration command capture
2026-07-16 22:46:45 +08:00
l0ng-aiandl0ng-ai 8209fa1823 feat(nightly): unattended nightly build channel with prerelease-aware update check (#114)
* feat(nightly): unattended nightly build channel with prerelease-aware update check

* fix(nightly): upload assets before pruning stale ones; pin nightly-to-nightly no-prompt in tests

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-07-16 22:40:22 +08:00
thomasandClaude Opus 4.8 6067f6b651 feat(agent): emit hook status events on Windows via ancestor console
The Windows agent status dot (working/waiting/done) never showed because
`write_to_controlling_tty` was a `false` stub on Windows — the hook's OSC
777 sentinel event was never injected into the pane's PTY, so the daemon
never learned the agent's turn state.

The hook has no `/dev/tty` on Windows, and agents (Claude Code, a Node app)
spawn hooks with CREATE_NO_WINDOW, giving the hook its own *hidden* console
— so a naive `CONOUT$` write succeeds into a dead-end buffer. Mirror the
Unix "write the agent's tty" strategy at the console layer: walk up the
parent chain to the shell tty7 spawned (the ancestor whose parent is the
tty7.exe daemon — the process actually on the pane's ConPTY), FreeConsole
off the hidden one, AttachConsole to the shell's, and write CONOUT$ there.
The OSC bytes then flow through ConPTY to the daemon exactly like the
shell-integration marks. Falls back to spraying every ancestor console
when the shell can't be pinned down.

Adds the windows-sys Win32_System_Console feature for Attach/FreeConsole.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 21:16:03 +08:00
l0ng-ai 775f0b7024 feat(agent): detect coding agents on Windows via shell-integration command capture
ConPTY has no foreground process group, so the Unix process-table poll
(pgid -> argv) has no Windows equivalent and foreground_agent was a stub
returning None. Follow Warp's approach instead: the shell integration
captures the submitted command line at preexec and carries it
percent-encoded on the OSC 133;C mark; the daemon detects the agent from
that string.

- shell_integration: all four bodies (zsh/bash/fish/PowerShell) append
  the submitted line to 133;C, truncated to 512 chars and escaped
  (% ESC BEL CR NL). PowerShell guards the surrogate-splitting truncation
  and wraps the emission in try/catch (EscapeDataString throws on lone
  surrogates under .NET Framework / PS 5.1).
- pane: ShellState.command stores the capture (cleared on D only, so a
  stray foreign A/B mid-command can't wipe the chip); on Windows
  apply_signals feeds it through the new detect_from_command_with.
- cli_agent: detect_from_command_with tokenizes a typed command line
  (quotes, & call operator, case-insensitive) and reuses the argv
  detection; base_stem handles backslash paths and .exe/.cmd/.bat/.ps1.
- ForegroundProbes::agent is now Option<Option<CLIAgent>>: None means
  'no process-table view' (native SSH, Windows) and is never applied,
  fixing the 0.5s poll wiping event-branded agents on native-SSH panes.
2026-07-16 20:23:47 +08:00
l0ng-ai 98b4282a39 Merge pull request #110 from l0ng-ai/feat/sidebar-repo-grouping
feat(sidebar): group vertical tabs by git repository
2026-07-16 18:26:19 +08:00
l0ng-ai eec003e4e7 feat(sidebar): visual-order ⌘N, persisted groups, deduped headers
Three follow-ups to the repo grouping:

- ⌘N now targets the N-th row as displayed (activate_visual over the
  same sections the render uses), so the badge digits read 1…9 top to
  bottom under grouping instead of scattering tab-vector indices.
- SessionTab persists the sticky sidebar group, so a restored session
  renders grouped on the first frame instead of starting flat and
  reshuffling as git probes land; reopen-closed-tab keeps its group too.
- Same-named repo roots disambiguate their headers by extending parent
  components until distinct (work/app vs fork/app); unique names stay
  short. Grouping/naming logic moved to pure functions with unit tests.
2026-07-16 18:14:08 +08:00
l0ng-ai 4d70cad3aa feat(sidebar): group vertical tabs by git repository
Group the left tab sidebar's rows under a per-repo header, keyed on the
git work-tree root only (not the branch), so a branch switch or an
in-repo cd never relocates a tab — only changing repos does. Non-repo
tabs collect in a trailing Scratch group; the list stays flat when no
tab is in a repo.

Each row is two lines: the title on its own (never crowded out) with the
branch and +N/-N diff on an indented second line. A sticky per-tab group
key holds across an in-flight probe so the list doesn't flicker on a cd.
New sidebar_grouping setting (repo default / none) toggles it.
2026-07-16 17:51:43 +08:00
l0ng-ai 4aeed6fa46 Merge pull request #106 from l0ng-ai/feat/theme-background-visuals
feat(theme): render gradient and image backgrounds, global window opacity/blur
2026-07-16 16:44:01 +08:00
l0ng-ai 1a50cf0e60 feat(theme): render gradient and image backgrounds, global window opacity/blur
- Gradient backgrounds (vertical/horizontal two-stop) now actually render;
  previously only the first stop painted as a solid.
- Background images composite over the background fill (cover-fit, per-image
  opacity), under all content.
- Window opacity/blur become global Appearance settings (config overrides
  layered over per-theme defaults), editable for every theme with a
  follow-theme reset; the theme editor gains a background-image picker and
  image-opacity slider.
- The window is created non-opaque and stays that way: on macOS 26 flipping
  a window to transparent after creation never reaches the compositor, so
  translucent themes rendered against black instead of the desktop. Fully
  opaque themes paint alpha-1.0 content, which is visually identical.
- gpui-component's Root no longer paints a second (stale) background layer,
  and the terminal surface no longer repaints the theme background — the
  app root is the single owner, so alpha is applied exactly once.
- to_yaml now serializes background_image, so editing a theme in-app no
  longer silently drops its image.
2026-07-16 16:03:28 +08:00
l0ng-ai 040f4e35a1 feat(tray): system tray icon with agent status menu
A cross-platform tray / menu bar status item: the icon flips to an
attention state when any coding agent blocks on input, and its menu
lists agent panes (brand avatar + status dot, click to reveal),
switches the notification policy, forces an update check, and offers
Quit and Stop Daemon alongside the session-keeping plain quit.

macOS/Windows use tray-icon (muda menus, main-thread NSStatusItem);
Linux deliberately uses ksni (pure-Rust SNI over zbus) instead of
tray-icon's GTK+libappindicator backend so the AppImage stays lean,
with a slow-backoff retry for SNI hosts that appear after login.
Bitmaps are rasterized at runtime with resvg (already in the tree).

Gated by show_tray_icon (default on) with a Settings toggle; the 1s
foreground poll re-reads it, so toggles and config.json hot-reloads
apply live.
2026-07-16 14:08:03 +08:00