Commit Graph
501 Commits
Author SHA1 Message Date
l0ng-ai 0ac9f8aebf fix(input): normalize CR before the bracketed submit
The unbracketed fallback stripped a CRLF clipboard's stray \r, but the
bracketed branch passed it inside the markers, leaving the line break to
whatever the far side does with a CR in a paste — a blank line under zsh,
a literal ^M under a shell that doesn't translate it. Normalize every
line break to one \n in the same pass that strips ESC, so both branches
start from the same shape.
2026-07-25 11:43:36 +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 a7bbe96b16 fix(settings): keep the stock glyphs on the Settings page
The icon overrides in `assets.rs` are keyed on the asset path, and
`icon_named!` derives `IconName` from those same filenames, so #161's
redraws took effect app-wide rather than only in the detail panel. Three
Settings glyphs came along for the ride and are worse at the 16px that
page uses: both magnifiers (thinner ring, stubbier handle) and the SSH
profile row's "..." — whose filled r=2 dots are weighted for the title
bar's 18px tiles and smear into three blobs when scaled down.

Adds a `stock/` path prefix that bypasses the override table and returns
gpui-component's own glyph, and points those three call sites at it. A
prefix rather than a second copy of the art: nothing is duplicated, and
the page keeps following upstream if these glyphs ever change there.

Everything else keeps the redraw, as intended — the detail panel, the
title bar chrome, the sidebar branch mark, the file tree and the SFTP
browser are all unchanged.
2026-07-25 11:19:04 +08:00
l0ng-ai 433a545444 perf(input): submit a multi-line command as one bracketed paste
Replaying each embedded newline as its own CR made the shell's line
editor run a full prompt cycle per line -- preexec, the user's precmd
chain (git-status prompts, conda), a syntax-highlight pass over the
whole buffer, plus our own OSC 133 D follow-up work. A pasted block
crawled down the screen line by line, as if the command were being
retyped: instant to paste (the local editor never touches the PTY),
then visibly slow to run.

When the shell has bracketed paste enabled, send the buffer as one
paste followed by a single CR. zle keeps the embedded newlines in its
buffer, so backslash / open-quote continuation and heredocs still parse
as one unit -- the PS2 assembly the per-line replay existed for now
happens inside the buffer instead of on the wire -- and the block costs
one prompt cycle whatever its line count. Shells without the mode keep
the per-line fallback.

This is the shape core::agent_prompt::submit_bytes has always used for
agent prompts; the shell path was the odd one out.

Two fixes ride along, both from the same helper:
- ESC is stripped on the submit path (it already was on the clipboard
  paste path), so clipboard text can't smuggle its own ESC[201~ end
  marker or hand zle an editor command.
- A CRLF clipboard no longer turns \r\n into \r\r -- a stray blank
  Enter submitted mid-command.

An empty buffer skips the markers: zsh's bracketed-paste-magic, which
oh-my-zsh turns on, errors on a paste with nothing between them.
2026-07-25 10:48:55 +08:00
l0ng-ai fd3de8a3ed Merge pull request #162 from l0ng-ai/fix/windows-titlebar-chrome
fix(windows): titlebar chrome — ⋯ menu rhythm + clickable sidebar controls
2026-07-25 10:34:56 +08:00
l0ng-ai bf7fbd3dee Merge pull request #161 from l0ng-ai/feat/detail-panel-icons
feat(icons): draw the detail panel and chrome glyphs to one spec
2026-07-25 10:32:16 +08:00
l0ng-ai ace2b416ac style: rustfmt the re-nested sidebar add button
The occlude() wrapper pushed the chrome_tile call one level deeper, past
rustfmt's width for the single-line Button builder.
2026-07-25 10:30:13 +08:00
l0ng-ai 1ff907f4cf feat(icons): draw the detail panel and chrome glyphs to one spec
The detail panel mixed four stock lucide glyphs from four different
weights and frames, and the title bar's "+" and "..." were the stock
thin cross and three stroked r=1 rings sitting next to the app's own
filled dock tiles. Side by side that read as glyphs from unrelated sets.

Thirteen icons now share one spec: stroke 1.9, corner radius 2.6-3.2, a
3.2->20.8 optical span on both axes (circles widen a little, since a
round shape reads smaller at equal geometry), and every node filled
rather than stroked -- a stroked dot hazes over below 16px, which is
where the stock ellipsis and list bullets fell apart. "+" is the one
departure: a bare cross has no enclosing shape to carry weight, so it
runs 2.4 on a tighter span.

Two shapes changed metaphor rather than just proportion: Info drops the
circled "i" -- the most-drawn icon there is, and one that says "help" as
readily as "details" -- for a panel with two lines written in it, a
picture of what the tab opens; Changes drops lucide's long arc slung
between two floating rings for an actual commit graph, which also fixes
the 11px branch mark on every sidebar row.

The override is keyed on the asset *path*, and `icon_named!` derives
`IconName` from those same filenames, so each arm redraws its glyph
app-wide rather than only in the panel. Two consequences are handled
here rather than left to discover:

  - `folder` and `file` are redrawn too. Overriding only `folder-open`
    would have split the file tree down the middle -- expanded rows on
    the new spec, collapsed rows and every file still stock.
  - the Settings nav's About row keeps its circled "i", now carried by
    its own `circle-info.svg`. There the glyph labels a section, and a
    document shape says nothing about *About*.

Because the paths resolve on their own, the glyphs need no call-site
changes: `Icon::new(IconName::Search)` already renders the new art.
Call sites stay on the typed enum rather than stringly paths, which a
rename would break silently.
2026-07-25 10:27:23 +08:00
thomasandClaude Opus 4.8 a00abe1c09 fix(windows): un-swallow the sidebar collapse/new-tab buttons
The rail's control row is a `WindowControlArea::Drag` so the window can be
dragged by it. On Windows that maps to HTCAPTION, so the OS claims clicks as
window-drags before GPUI hit-tests — the "+" and collapse tiles never fired
their `on_click`, and collapsing the sidebar did nothing. (Fine on macOS,
where titlebar dragging doesn't gate child hit-testing, so it looked correct
there.)

Wrap both tiles in an `occlude()` div — a BlockMouse hitbox that stops
hit-testing on the button — exactly as the title-strip chrome tiles already
do.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 10:12:52 +08:00
thomasandClaude Opus 4.8 7d190a8298 fix(windows): restore the ⋯ menu's native window-control rhythm
The de3896c chrome redesign moved the right-corner chrome into
`window_chrome()` and reset its non-macOS right padding to `pr_3` (12px),
silently reverting the `pr_1` fix from 9b1c7bf. That put the "⋯" glyph centre
~45px from the minimise glyph — visibly adrift from the native window
controls, which sit on a 34px rhythm.

Restore `pr_1` (4px) so the "⋯" centre lands ~37px out, reading as part of the
same row with just enough slack not to be mistaken for a fourth window
control. macOS is unaffected (controls are on the left; the content-inset
padding still holds).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 10:12:52 +08:00
l0ng-ai 73588663a0 fix(chrome): give every icon tile the same soft hover fill
The chrome tiles — sidebar collapse, the detail-panel toggle, the "⋯" menu,
the panel's own tabs — had no hover state at all. `chrome_tile_variant` set
one, but gpui-component derived the custom variant's hover from its resting
`color` and ignored the colour the caller named; that resting colour is
transparent, and transparent shaded by transparent is still transparent. The
fork now reads `hover`/`active` (l0ng-ai/gpui-component@469ab7a, picked up in
Cargo.lock), so the value here paints for the first time.

With it painting, the old `sidebar_accent.opacity(0.55)` was too faint to see
on a light background. Hover is now that fill at full strength — a 12% mix,
exactly #E2E2E2 on the default light theme, the same grey the sidebar rows
use — and selected/pressed drop one step to `list_active` so a lit toggle
still reads as on under the pointer.

The sidebar's "+" was a `ghost()` button, whose hover is a heavier grey mixed
a different way; it joins the tiles so the whole chrome answers alike.
2026-07-24 18:25:02 +08:00
l0ng-ai 09abfd5741 Merge pull request #160 from l0ng-ai/perf/file-tree-async
perf(file-tree): list directories off the render thread
2026-07-24 18:12:56 +08:00
l0ng-ai fb7d373494 Merge pull request #159 from l0ng-ai/refactor/drop-lsp
refactor(editor): drop the LSP client entirely
2026-07-24 18:09:27 +08:00
l0ng-ai 8a4168ca5c fix(file-tree): re-walk on the dotfile toggle, retire dropped watches
Two things the async rewrite let slip.

The search walk bakes the dotfile setting in — hidden and ignored entries
never reach the hits — so the eye toggle did nothing while a query was up:
`retarget` only compared the query string, and the query hadn't moved. It
now compares the setting too, and flipping it re-walks.

`file_tree_refresh_roots` returning early on an unchanged root set also
skipped the watcher rebuild, which spans *every* tab's roots — so closing a
tab left its root watched for the rest of the session. The cache-drop still
hangs off the per-tab check; the rebuild now compares the union itself.
2026-07-24 18:09:05 +08:00
l0ng-ai 182298a223 perf(file-tree): list directories off the render thread
The tree did its filesystem work inside the paint. `ensure_loaded` ran a
`read_dir` plus a `.gitignore` chain compile for every root and every
expanded directory that wasn't cached, and a non-empty search box replaced
that with a breadth-first walk of up to 2000 directories — re-run on every
keystroke, because the search input notifies per `InputEvent::Change`. On
a cold cache or a large repo that is a visibly stalled frame.

Render now only reads caches. A miss becomes a queued load that runs on
the background executor and lands with a `cx.notify()`, so a
just-expanded directory fills in on the next frame rather than blocking
this one — the same trade every editor makes.

- `TreeLoader` owns the filesystem half, including its own `.gitignore`
  matchers (`Arc`, not `Rc`, so they can cross threads). It is seeded from
  the UI thread's compiled matchers and hands back the ones it compiled,
  so the cache still warms. The ignore semantics are unchanged: deepest
  match wins, `!` whitelists un-ignore, `.git` is always ignored.
- `Loads` tracks what is in flight and what a watcher event superseded
  while it flew, so a repaint can't spawn a second load for the same
  directory and a listing that predates a change can't install itself.
- The search is debounced 200ms, walks on the background executor, and
  carries a generation so a slow walk can't overwrite a newer query's
  answer. Previous hits stay on screen while the next walk flies rather
  than blanking per keystroke.
- `file_tree_refresh_roots` returns early when the root set is unchanged.
  It is called from render whenever the roots are empty, and it clears the
  caches and notifies — so a tab that can produce no roots spun the
  render loop.

Watcher events deliberately do *not* restart an open search. Doing so
starves it: the watcher's own debounce delivers a batch about as often as
the search debounce waits, so under sustained churn (a build writing into
`target/`, which the watcher reports because it knows nothing about
gitignore) every walk bowed out before reading a single directory and the
list stayed empty. A snapshot that is stale until the next keystroke is
the better failure.
2026-07-24 17:57:44 +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 266aeeb732 Merge pull request #158 from l0ng-ai/worktree-code-panel
feat(right-panel): docked detail panel, code editor overlay and LSP
2026-07-24 17:28:29 +08:00
l0ng-ai 564f52e76f test(lsp): use a host-shaped absolute path in the URI round-trip
`Url::from_file_path` rejects a POSIX literal on Windows, so the test
panicked there instead of exercising the percent-encoding it is about.
2026-07-24 17:19:21 +08:00
l0ng-ai f6ba623af0 fix(procinfo): the Windows process table reads Proc::parent
`winproc::Proc` names the parent link `parent`, not `ppid` — the
Windows arm of `process_table` had never been compiled, so it took a CI
run to find it.
2026-07-24 17:12:57 +08:00
l0ng-ai fd1062f564 fix(right-panel,editor): restore the git dependency and address review findings
The branch had `gpui-component` pointed at a sibling checkout by absolute
path, which is why every CI job failed at manifest load. Point it back at
the fork's `tty7` branch (now carrying the custom-button label-color fix
the chrome tiles depend on) with the `tree-sitter-languages` feature, and
re-lock.

Review fixes on top:

- **Changes tab churned.** `right_panel_invalidate` dropped the cached
  diff on every `GitStatusCache` notification — including unrelated
  repos' — so the list blanked to "Loading…" and spawned a fresh
  `git diff` several times a second while a pane produced output.
  Replaced by `right_panel_refresh_changes`, which compares branch and
  totals first and re-probes in place, mirroring the diff overlay.
- **Changes tab could wedge on "Loading…".** A probe dropped because the
  cwd changed mid-flight left `diff_cwd` set and `diff` empty, and the
  render path only spawns when the cwd *changes* — so nothing re-probed.
  Spawn when nothing is cached and nothing is in flight.
- **Find references blocked the UI thread.** `cx.spawn_in` runs on the
  main thread; the up-to-200 `read_to_string`s for the row previews now
  run on the background executor, as the comment already claimed.
- **LSP frames could be lost or reordered at startup.** `send` checked
  `ready` outside the `queued` lock, so a frame could park behind a
  handshake that had just finished and never go out. `ready` now flips
  under that lock in `mark_ready_and_flush`.
- `MarkScanner`'s ESC-in-payload branch bypassed the payload cap, so a
  stream of bare ESCs inside an unterminated OSC grew the buffer without
  bound.
- The file tree's search frontier used `Vec::remove(0)`; a wide tree made
  that quadratic. `VecDeque`.
- `procs()` documented a pane check it didn't make; it takes the pane id
  and makes it.
- Four doc comments had been orphaned onto newly inserted functions
  (`pty`, `smooth_scroll`, `foreground_agent`, `file_expanded`).
2026-07-24 17:07:07 +08:00
l0ng-ai f5d2ab3e0e Merge remote-tracking branch 'origin/main' into worktree-code-panel 2026-07-24 17:06:20 +08:00
l0ng-ai f25acb0eab Merge pull request #157 from l0ng-ai/style/settings-close-button-size
style(settings): size the page's close button to the title bar's rhythm
2026-07-24 16:58:39 +08:00
l0ng-ai 5c17d2bc7b Merge pull request #156 from l0ng-ai/fix/windows-theme-panel-close
fix(settings): the theme panel's close button did nothing on Windows
2026-07-24 16:58:35 +08:00
l0ng-ai c94793a99a Merge pull request #155 from l0ng-ai/fix/windows-group-header-cursor
fix(sidebar): the repo group header showed a plain arrow on Windows
2026-07-24 16:58:32 +08:00
l0ng-ai de3896cbdf feat(right-panel): redesign the detail panel and chrome icons
Rework the detail panel's four tabs and the window chrome to read as a
deliberate, commercial-grade surface while keeping the terminal's calm.

- Chrome icons: filled-block panel toggles; Outline gets a list glyph,
  Changes a git-branch, Info a redrawn mark; glyphs go to 18px on the full
  foreground instead of the faint 15px secondary tone.
- Panel body: labelled Session / Processes / Ports bands, mono values,
  pid/port pills, status dots on Outline, neutral M/U badges on Changes,
  live counts in the headers, and roomier rows. Hue only ever lands on the
  git add / remove / fail semantics, everything else stays neutral grey.
- Fixes: the corner "…" no longer jumps a pixel when the panel opens (the
  top strip mirrors the TitleBar's hidden bottom border); that strip now
  drags the window and double-click zooms, like the rail's; and the process
  list stops flickering — the poll guard now spans the whole 2s cycle
  instead of being restarted by every repaint between ticks.
2026-07-24 16:43:06 +08:00
thomasandClaude Opus 4.8 743984c8ad style(settings): size the page's close button to the title bar's rhythm
The settings page's `×` sits at the window's top-right corner — the spot
the native window controls own, whose tiles are 34px. As a `small` icon
button it was 24px, which reads undersized standing there alone once the
overlay has covered the real title bar.

Give it the shape the title bar's own corner button already uses: 30px
square, a 15px glyph, `rounded_lg` (see the "⋯" in `tab_strip`). Matching
that beats the component's medium default (32px) — it is the size this
app already puts in this corner. `top` drops 6 → 5 so the taller button
still centres in the title bar's band.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 16:14:10 +08:00
thomasandClaude Opus 4.8 08f8d16a88 fix(settings): the theme panel's close button did nothing on Windows
The settings overlay covers the real title bar, so it lays its own drag
band across the top 40px -- absolute, full width, tagged
`WindowControlArea::Drag`. The theme panel docks to the window's top edge
beside it, which puts its header's `×` inside that band.

On Windows the band is `HTCAPTION`. gpui resolves the control area in
`Window::hit_test` by walking hitboxes top-down and stopping only at a
`HitboxBehavior::BlockMouse`; a plain `Button` isn't one, so the band's
hitbox stayed in `mouse_hit_test.ids`, the hit-test callback answered
`Drag`, and the OS took every press on the `×` as a window-drag. The
button's `on_click` never fired.

`occlude()` on the button ends the walk there, which is the same fix the
tab-strip chips carry and the one the page's own `×` has had since it was
written. Scoped to the button so the rest of the header still drags the
window. No-op on macOS and Linux.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:51:25 +08:00
thomasandClaude Opus 4.8 79e2cbc09e fix(sidebar): the repo group header showed a plain arrow on Windows
The group header is drag-only -- it does nothing on click -- so it hovered
under `cursor_grab()`, an open hand that says "pick me up". gpui's Windows
backend has no mapping for `CursorStyle::OpenHand`: `load_cursor` matches
IBeam, Crosshair, PointingHand, the resize family and OperationNotAllowed,
then falls everything else through to `IDC_ARROW`. Win32 has no open-hand
system cursor to map it to either.

So on Windows the one affordance the header has read as "nothing to do
here", while the rows beside it (`cursor_pointer()` -> `IDC_HAND`) looked
interactive. Point there instead: not as apt as the open hand, but it is
the same cursor the rows use and it does say the header responds. macOS
and Linux keep the hand -- both backends implement OpenHand.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:45:29 +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 db02cb7ce6 Merge pull request #153 from l0ng-ai/feat/sidebar-titlebar-drag
Make the sidebar's top strip drag the window
2026-07-24 12:42:50 +08:00
l0ng-ai 200196ad69 feat(sidebar): make the rail's top strip drag the window
In vertical tab-bar mode the sidebar is a full-height left column and
the real TitleBar only spans the right column, so the rail's
title-bar-height top strip was dead space — you couldn't grab the window
by it. Give it the title bar's behaviour: drag to move, double-click to
zoom, driven the same way TitleBar (and the settings overlay's stand-in
strip) does it, so a plain click and a double-click still land intact.
2026-07-24 12:41:40 +08:00
l0ng-ai 6b266045cb Merge pull request #151 from l0ng-ai/feat/live-drag-reorder
Live drag-to-reorder for tabs and repo groups
2026-07-24 12:40:18 +08:00
l0ng-ai 22774e3bdc Merge pull request #152 from l0ng-ai/feat/ssh-remote-shell-integration
feat(ssh): shell integration for native SSH panes (zsh/bash/fish)
2026-07-24 12:38:12 +08:00
l0ng-ai 7cfdba1772 test(ssh): skip the real-shell parse checks on Windows
On Windows a bare `bash` resolves through PATH to
`C:\Windows\System32\bash.exe` — the WSL launcher, not a shell. With no
distro installed it exits non-zero with an empty stderr, which is
indistinguishable from "the shell rejected this script", so the checks
failed the Windows job while reporting nothing to explain why.

`is_msys_bash` guards the production path against the same trap; the test
had no such guard.

Nothing is lost by gating them to unix: these scripts are destined for a
remote POSIX host, so their syntax has nothing to do with the platform
running the test, and the macOS and Linux jobs already exercise them.
2026-07-24 12:04:05 +08:00
l0ng-ai 4f4e29dee4 fix(completion): keep a signature's static candidates in remote panes
A remote pane passes no local cwd, and the argument-position branch answered
that by returning nothing at all. The reasoning in its comment only covered
filesystem paths — offering this machine's filenames for a remote command line
— but the code took `complete_signature` down with them, and a signature's
subcommands and flags are static text describing the *command*, not the
machine. `git push` is just as true over SSH.

The cost was not merely a missing menu. A position with no candidates hands
the line to the shell (`handoff_tab_to_shell`, #136), which clears the inline
editor for the rest of that prompt — so every `git <Tab>` in a remote pane
looked like the editor had crashed. Every git position measured as a handoff:
`git `, `git c`, `git checkout `, `git status`.

Generators stay disabled without a cwd, and that exclusion matters more than
the path one: a generator is a local `/bin/sh -c`, so `git checkout <Tab>`
would have offered the branches of whatever repo tty7's own cwd sat in. A
wrong filename fails loudly when it runs; a wrong branch name is plausible
enough to be accepted. Positions that are generator-only now yield nothing and
hand the Tab to the remote's own compsys, which can actually see that host.
2026-07-24 11:57:53 +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 8c095da835 feat(tabs): live drag-to-reorder for the strip, sidebar rows and repo groups
Reordering used to be a swap on release: gpui's floating drag card followed
the cursor and `on_drop` moved the tab to whichever slot it landed on, with
the list itself frozen for the whole drag. Replace it with a reorder the list
performs live, and add repo group blocks as a second draggable surface.

The new `ui::reorder` module owns the geometry. A drag freezes every slot's
measured bounds and the grab point; each frame the surface reads the pointer,
asks which slot the held item now belongs in, and renders the list in that
order. The held item stays in the list (dimmed, painted over its neighbours)
and tracks the cursor pixel for pixel; the items it displaces slide 140ms.
Slot selection uses half-overlap — the held item's trailing edge past a
neighbour's centre going forward, its leading edge going back — so a tall
group block and a short one swap symmetrically.

Commit no longer goes through `on_drop`, which only fires when the pointer is
over that element at release: every frame the surface records the whole-tab
permutation a release would produce, and the root applies it when the drag
ends, wherever the cursor is. `apply_tab_order` becomes the single commit
path, replacing `move_tab`/`move_group`; it also lays every group out
contiguously, which fixes group-order corruption when a group's tabs were
non-contiguous in the tab vector.
2026-07-24 11:56:25 +08:00
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