Commit Graph
756 Commits
Author SHA1 Message Date
l0ng-ai c2d4e7f2c8 feat(settings): open settings in a window of its own
Settings used to cover the workspace it was opened from, hiding the
terminal a setting was being tried on. It now opens a separate window.

- The state and page stay on the workspace's Tty7App; a thin
  SettingsWindow view borrows it to draw, observes it to repaint, and
  sets its own rem size. The state is built against the new window, so
  its inputs and subscriptions belong to it.
- Opening again raises the window instead of closing it. Esc, Cmd-W and
  the close button go through close_settings_checked, so unsaved forms
  and theme drafts still prompt; closing hands focus back to the
  workspace window. The window goes when its owner is released.
- open_settings_then / close_settings_then run follow-ups in the right
  window: loading an SSH form happens in the settings window, and
  Connect opens its tab in the workspace window.
- Window opacity, blur and backdrop are pushed to every window, since
  they are now changed from a window that is not the workspace.
- Tests keep the in-window overlay so they can drive settings through
  their single test window.
2026-09-23 22:18:54 +08:00
l0ng-ai 826c42f6bd fix(scm): give the folded History bar air above the window edge
Folded, the history section is the last line in the window and sat flush
against the bottom edge, inside the window's rounded corner. Pad it 2px
above and 4px below; the expanded section is unchanged, so its
commits-per-height pin still holds.
2026-09-23 22:06:38 +08:00
l0ng-ai 0988c57d5c fix(ui): restore the sidebar's rounded-rectangle corners
The v5 restyle turned the tab rows, the search field and the workspace
switcher into full pills, which read too round at this density. Put
back CARD_RADIUS on the rows, rounded_lg on the search field and
rounded_md on the switcher (and its rename field), keeping the 28px
row height. PILL_RADIUS has no users left and goes with them.
2026-09-23 22:02:45 +08:00
l0ng-ai a3d64bfcc9 fix(ui): drop the solid accent from the switcher's picked row
The workspace switcher painted its picked row a hard-coded #1768CF with
white text, in the workspace list, the tab list and the New Workspace
host dropdown. It bypassed the theme and was the loudest block on
screen. Use the popover's neutral selected step and keep each row's own
inks, so a taken-over warning still reads as one.
2026-09-23 21:50:04 +08:00
l0ng-ai 859c286c6a fix(panel): lead the right panel's tab row on Windows
The tabs sat after the row's flex spacer, so they hugged the window
controls instead of opening the row the way they do on macOS.
2026-09-23 21:49:27 +08:00
l0ng-ai ae0d129816 Merge pull request #946 from l0ng-ai/feat/v5-ui-restyle
feat(ui): restyle the workspace after the v5 design
2026-09-23 21:38:10 +08:00
l0ng-ai d18f797a6c Merge pull request #945 from l0ng-ai/fix/942-wsl-files-panel
fix(files): keep a WSL tree in step with its distro (#942)
2026-09-23 21:38:06 +08:00
l0ng-ai 27406e472d Merge pull request #943 from l0ng-ai/fix/watch-close-off-ui-thread
Stop waiting on remote round trips from the UI thread
2026-09-23 21:37:57 +08:00
l0ng-ai f1648117b7 feat(ui): restyle the workspace after the v5 design
- Rails share the window fill and are separated by a hairline only;
  the current tab is a neutral selected step plus a semibold title
  instead of the blue navigation wash (the unused role is removed).
- Default Light theme takes ink #0F1419 and accent #1F6BF0; the caret
  follows the accent.
- Sidebar: 28px pill rows with 18px avatars (two-line rows keep card
  corners), pill search field and workspace switcher, group headings
  in body ink with the chevron shown only when folded.
- Right panel: word tabs (Info / Changes / Files) with an underline
  bar on a closing hairline. The Changes count is dropped before any
  label is cut, and the panel floor grows with the measured labels so
  the chrome tiles are never pushed off the edge.
- Headings are no longer uppercased; popovers use a 10px radius.
- docs/design-system.md updated to match.
2026-09-23 21:35:28 +08:00
l0ng-ai 1215eba16d fix(ui): drop the trailing tiles while a docked document holds the right edge
On macOS the panel toggle and app menu tiles sat at the end of the terminal
column's strip whenever the detail panel was closed, including when a docked
diff or file column stood to its right, leaving them in the middle of the
window beside the document's header. Hide them while a document is docked and
stop reserving their width for the tab chips.
2026-09-23 19:28:39 +08:00
l0ng-ai 8fa9651814 fix(files): keep a WSL tree in step with its distro (#942)
Three things went wrong with a Files panel rooted on a distro's \\wsl$
share:

- A drop brought a `name:Zone.Identifier` file along with every
  downloaded file. fs::copy is CopyFileEx on Windows, which copies the
  NTFS alternate data streams, and the share has no streams to keep one
  in, so it lands as a file of its own. A copy onto a WSL share now
  moves the contents only; one onto NTFS still goes through fs::copy
  and keeps the mark.
- A file removed from the shell in the distro stayed in the tree. The
  share accepts a ReadDirectoryChangesW and never reports a change, so
  the watch looked healthy and was deaf. Directories on a WSL share are
  now watched by a notify PollWatcher every 2s instead, made the first
  time one is needed.
- Right-clicking a row did not select it, so nothing marked the row the
  menu was about. A right click now selects the row without opening it.
2026-09-23 19:26:26 +08:00
l0ng-ai 439f19ff85 Stop waiting on remote round trips from the UI thread
Dropping a RemoteWatch sent WatchClose with a blocking call, and the last
handle is usually let go on the UI thread: from Tty7App::render via
scm_sync_watchers, and from scm_watch_opened when an open lands after its
subscription moved on. Every window froze for a round trip each time, up
to WatchClose's 5 s deadline on a quiet link. Sampling a live instance
caught ~1.1 s of such stalls in 10 s.

- ControlClient::post sends a request without registering for its reply;
  the reader already drops replies nobody is waiting for.
- RemoteWatch::drop posts WatchClose instead of calling it.
- pane_workspace_for built the full SSH spec, keychain lookups included,
  only to strip the secrets again. It now builds it from NoCredentials,
  which takes the securityd trips off pane_liveness::sweep.

Claude-Session: https://claude.ai/code/session_01YX786Hyr4ivijWxv66zVkf
2026-09-23 19:00:54 +08:00
l0ng-ai 800c6bf724 Merge pull request #933 from l0ng-ai/feat/comfortable-ui-surfaces
feat(ui): refine panel readability and workspace surfaces
2026-09-23 16:59:54 +08:00
l0ng-ai 386ccc3d81 Merge pull request #939 from l0ng-ai/feat/720-font-smoothing
feat(fonts): make macOS stroke thickening configurable
2026-09-23 16:58:03 +08:00
l0ng-ai 296a8a3c3f Merge pull request #938 from l0ng-ai/feat/760-stable-workspace-numbers
feat(workspaces): number workspaces in a stable order (#760)
2026-09-23 16:57:59 +08:00
l0ng-ai da6c8624de Merge pull request #937 from l0ng-ai/feat/723-remote-tree-download
feat(file-tree): download a remote file from the tree's context menu
2026-09-23 16:57:54 +08:00
l0ng-ai a012321542 Merge pull request #936 from l0ng-ai/feat/892-741-codebuddy-cursor-hooks
feat(agents): CodeBuddy CLI support and Cursor CLI hooks
2026-09-23 16:57:50 +08:00
l0ng-ai 2eb1a79d76 Merge pull request #935 from l0ng-ai/feat/754-dock-preview-wrap-actions
feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions
2026-09-23 16:57:45 +08:00
l0ng-ai 2aa28d3982 Merge pull request #934 from l0ng-ai/feat/867-direct-tab-cycling
feat(tabs): step to the next/previous tab without the switcher (#867)
2026-09-23 16:57:41 +08:00
l0ng-ai 75480cac05 fix(ui): improve panel readability at narrow sizes 2026-09-23 16:51:59 +08:00
l0ng-ai 57ca21dd68 feat(workspaces): number workspaces in a stable order (#760)
SelectWorkspace1-9 and the Window menu numbered workspaces by most
recent use, so switching to one moved it to slot 1 and reshuffled the
rest. Number them by the order this client first had them instead
(the append-only views list), skipping synced remote references until
they are opened; opening one moves it to the end so it takes the next
free number. The switcher keeps its MRU list and shows each row's
number.
2026-09-23 15:38:28 +08:00
l0ng-ai 592058acc7 feat(fonts): make macOS stroke thickening configurable (#720)
Add a macOS-only `font_thicken` key (default true) and a Settings row
under Appearance > Terminal text. When off, AppleFontSmoothing is pinned
to 0 in this process's NSArgumentDomain before gpui's text system first
reads it, so glyphs render at the face's own weight. The volatile domain
is in-memory only: nothing is persisted and no other app is affected.
gpui caches the preference in a OnceLock, so a change applies after a
restart; no gpui fork change is needed.
2026-09-23 15:23:10 +08:00
l0ng-ai 43d2b902bb feat(file-tree): download a remote file from the tree's context menu
A remote workspace's Files tree took uploads by drag-and-drop but had no
way to bring a file back. Right-click a file -> Download now reads it
through Host::read_file and saves it into this machine's Downloads
folder, named and numbered the way the SFTP panel's Download does.

Capped at the same ~63 MB as uploads (one control frame); an oversized
file is refused up front with the limit and nothing is transferred.
Local trees keep Reveal in that slot; folders are not offered.

Closes #723
2026-09-23 15:22:57 +08:00
l0ng-ai fe7b6e0b76 feat(agents): wire Cursor CLI (cursor-agent) into agent hooks (#741)
Cursor's ~/.cursor/hooks.json is a flat hook map — `command` directly on
each entry under `hooks.<event>` — so it reuses the flat writer Crush
introduced. Two things are Cursor-specific:

- The file needs `"version": 1` at its root or Cursor ignores it. tty7
  now writes it when it creates the file or finds it missing, never
  overwrites a version the user set, and reports a versionless file that
  holds our hooks as Outdated so refresh repairs it.
- Payloads name the session `conversation_id` (only sessionStart repeats
  it as `session_id`), and most events carry `workspace_roots` instead of
  `cwd`. Both are now read as aliases, which is what Copy Session ID and
  resume were missing.

Events: sessionStart, beforeSubmitPrompt, postToolUse, stop, sessionEnd.
cursor-agent does not fire beforeSubmitPrompt today (a known gap on
Cursor's side), so postToolUse also reports prompt-submit: the first tool
call opens the turn and Cursor's stop, which the CLI does fire, closes it.
A turn with no tool calls never shows as working. None of Cursor's
permission hooks are installed, since those would block on the empty
stdout tty7's hook prints.
2026-09-23 15:21:34 +08:00
l0ng-ai 962fd87ea5 feat(tabs): step to the next/previous tab without the switcher (#867)
Add SelectNextTab / SelectPrevTab, which move to the neighbouring tab in
the order the strip or sidebar shows them, wrapping, with no popup.
Defaults: Cmd+Shift+] / Cmd+Shift+[ on macOS, Ctrl+PgDn / Ctrl+PgUp
elsewhere. The tmux preset's prefix n / p now bind these (tmux
next-window semantics) instead of the MRU switcher, which never
auto-committed there.

NextTab / PrevTab keep their config names and Ctrl+Tab, but are labelled
Recent Tab Switcher on the Keybindings page; the palette's Next/Previous
Tab entries now show the SelectNextTab chord they actually run.
2026-09-23 15:20:34 +08:00
l0ng-ai 9f842975ce feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions (#754)
The code panel's Preview/Edit and Wrap buttons only toggled state in their
click handlers, so there was no way to reach them from the keyboard. Register
both as actions, unbound by default like ToggleDocumentFill and the
DocumentWidth* actions, list them in the command palette and the Keybindings
page, and route the buttons through the same methods.

Instead of config keys for the initial state, the last-used state is
remembered (editor_soft_wrap / editor_markdown_preview), the way diff_view
and scm_graph_expanded already are. A file opened at a line target always
opens as source so the cursor is visible.
2026-09-23 15:18:58 +08:00
l0ng-ai 60296567f9 feat(ui): unify floating surfaces and soften workspace chrome 2026-09-23 15:17:56 +08:00
l0ng-ai 0d0dc597ee feat(agents): add CodeBuddy CLI integration (#892)
CodeBuddy Code takes Claude Code's hooks as-is: the same nested
`hooks.<Event>[].hooks[{type, command}]` shape in ~/.codebuddy/settings.json
(or $CODEBUDDY_CONFIG_DIR/settings.json), the same event names, and the same
session_id/cwd payload fields. So it rides the shared hook-map installer and
needs no payload aliases.

The event table follows Qoder rather than Claude: CodeBuddy has a
first-class PermissionRequest and Elicitation, so it gets no Notification
hook, and StopFailure ends a turn like Stop. CodeBuddy also emits
SessionStart with source "compact" mid-turn, which is filtered out the same
way Qoder's is so the pane does not drop back to idle.

Detection covers all three npm bins (codebuddy, codebuddy-code, cbc).
Resume is `codebuddy --resume <id>` and fork appends --fork-session; stale
session and worktree flags are dropped from the replayed launch argv, and
--no-session-persistence disables both commands.

Icon, en/ja/zh names and search keywords, and docs are updated.
2026-09-23 15:04:06 +08:00
l0ng-ai d474e4ade3 feat(sftp): open the Files panel at the shell's current directory
Reopening the SFTP panel went back to the last browsed folder; a fresh
open now prefers the pane shell's cwd, falling back to the last browsed
folder, then the login directory. Following a pane switch with the panel
still open keeps each pane's last browsed folder.

Also pin the test config dir in the sftp gpui harness so filtered runs
do not trip the real-config guard, and fix docs/remote/sftp.mdx, which
said the panel opens on the remote home directory.

Closes #826
2026-09-23 15:03:04 +08:00
l0ng-ai 611c87af0c Merge pull request #931 from l0ng-ai/feat/palette-update-server
feat(palette): add "Update tty7 server" for this computer and the remote host
2026-09-23 12:34:29 +08:00
l0ng-ai ecdbf16655 Merge pull request #928 from l0ng-ai/fix/919-settings-text-layout
fix(settings): keep keybinding action names on one line (#919)
2026-09-23 12:34:24 +08:00
l0ng-ai 2571ee50db Merge pull request #926 from l0ng-ai/fix/891-remote-directory-group
fix(sidebar): group native SSH tabs by their remote folder (#891)
2026-09-23 12:32:22 +08:00
l0ng-ai b9f77a4b63 Merge pull request #925 from l0ng-ai/fix/896-wsl-files-panel
fix(files): list a WSL pane's cwd through its distro's \\wsl$ share (#896)
2026-09-23 12:32:19 +08:00
l0ng-ai 2506078d77 Merge pull request #924 from l0ng-ai/fix/820-ssh-password-prompt-loop
fix(ssh): retire a routed password prompt its attempt gave up on (#820)
2026-09-23 12:32:17 +08:00
l0ng-ai 35891e1b64 feat(palette): add "Update tty7 server" for this computer and the remote host
Local: restarts the local daemon onto the app's build through the existing
restart flow and confirmation, or says it is already current when the probed
daemon reports this build and no mismatch is pending.

Remote: a new RouteAction::UpdateServer makes the local daemon force-install
this build's server over one already speaking our dialect
(Installer::replace_forced: upload to a temp name, probe, rename, then
cycle_daemon), for SSH and WSL. Offered only when the window's workspace is
on a host whose server we install; gated on the local daemon advertising
FEATURE_UPDATE_SERVER, since an older one cannot decode the action.

Claude-Session: https://claude.ai/code/session_01YX786Hyr4ivijWxv66zVkf
2026-09-23 12:31:17 +08:00
l0ng-ai 67d6f3a839 fix(settings): keep keybinding action names on one line (#919)
On Linux the Keybindings page wrapped action names one to three CJK
glyphs per line and spilled them over the rows below, even though the
keycaps beside them left plenty of room. The label was a shrinkable
block sized to its own measured text, so any layout pass that measured
it narrower than it finally ended up left its painted lines wrapped at
that narrower width.

An action name is a single line: make it nowrap, and beside the keycaps
let it take the space they leave (flex_1) instead of sizing to its text,
so neither its box nor its lines depend on that measurement. Stacked
rows keep the whole row for the name as before.

The Agents half of the report (a long Codex install-failure note
squeezing the agent name to a glyph per line) is #897, already fixed on
main by #898 and not yet in a stable release.
2026-09-23 12:02:13 +08:00
l0ng-ai 45fdd53d1a fix(sidebar): group native SSH tabs by their remote folder (#891)
A native SSH pane is owned by this machine's daemon, so its paths are
kept away from every Host call and it never gets a git_status_cwd. The
sidebar only grouped from git_status_cwd, which left every native SSH
tab in Scratch under repo-or-directory grouping, even though the remote
shell reports its cwd over OSC 7.

Fall back to that reported cwd for native SSH panes (absolute POSIX
paths only) and resolve it as a settled 'no repo': repo-or-directory
files the tab under the folder, repo grouping keeps it in Scratch.
Typed 'ssh' and WSL panes are unchanged.

Fixes #891
2026-09-23 11:48:51 +08:00
l0ng-ai 14fc84ae98 fix(files): list a WSL pane's cwd through its distro's \\wsl$ share (#896)
A pane running wsl.exe reports its cwd as a POSIX path from OSC 7, but its
host is this machine. The Files panel rooted the tree at that path verbatim
and handed it to the local read_dir, so on Windows /home/me was read as
C:\home\me and the panel showed "Could not be read"; drops into the tree
failed the same way since they target the rooted directory.

Root the tree at TerminalView::files_cwd instead: a cwd the pane's host
resolves is used as-is, a WSL pane's POSIX cwd goes through the distro's
\\wsl$ share (the same mapping Tab completion already uses), and a cwd no
host here can read (a shell ssh'd onward) roots nothing instead of an
unreadable directory.
2026-09-23 11:39:11 +08:00
l0ng-ai b635fa57fb fix(ssh): retire a routed password prompt its attempt gave up on (#820)
#827 stopped a declined prompt from being asked again, but left the
prompts themselves with no notion of whether anyone was still waiting on
them. A routed auth prompt sat in the mailbox, in the parked queue behind
the sheet on screen, or on screen itself, until somebody answered it —
even after the connection attempt that raised it had timed out and moved
on. Answering it sent the secret into a dropped channel.

That is the report's sequence. A link drops while nobody is at the
keyboard; each reconnect attempt raises a password prompt and times out
unanswered, and before #827 the supervisor dialled again and again, so one
dead prompt per attempt piled up behind the first sheet. The user comes
back, types the password into a sheet nobody is listening to, the next one
comes up, one of them happens to be the live attempt and connects — and
the dead ones keep coming up however they are closed. #827 ends the
attempt loop on a timed-out password, but a key passphrase declined by
timeout still falls through to other methods and a transient failure, and
a single stale sheet is still left on screen either way.

A PendingAuth now carries a weak handle whose only strong count lives in
the responder for as long as it waits, so it can say when it has been
abandoned. The pump drops abandoned prompts instead of raising or parking
them, and takes down an on-screen routed sheet whose asker has gone,
moving on to whatever else is asking. The GUI also waits no longer than
the daemon's handshake does (the broker's 120s rather than 180s), so the
sheet comes down when the attempt behind it actually fails, not a minute
later.
2026-09-23 11:31:00 +08:00
l0ng-ai 3c8b573a78 fix(ui): un-zooming a pane keeps focus in that pane (#869)
Un-zoom routed focus through the tab's `last_focused`, which only
focus-in writes, so any gap between that record and the pane actually
zoomed put the cursor in a different pane when the split came back.
Since #843 the record is kept current on focus-in, which is why the
reported sequence no longer reproduces on main, but the zoomed pane is
the answer outright: hand it to the tab before focusing, so un-zoom no
longer depends on the record having caught up.

Zooming with focus off the panes (a palette just closed, the tab strip)
also zoomed the tab's first leaf rather than the pane the tab
remembers; it now falls back to `focus_target`, the same pane a switch
back to the tab would focus.
2026-09-23 11:17:39 +08:00
l0ng-ai 5cf4ac5efa revert(sidebar): paint the agent disc solid again, mark in white
The last two passes at the avatar — a flat theme disc, then a bare mark
painted in the brand colour — each gave something up the solid disc had:
the flat disc lost the hue that tells one agent from another down a
column of rows, and the bare mark read lighter and less settled than the
disc beside the status dot. Neither was better than where it started.

Back to a solid fill of the agent's brand with the mark in its own ink,
and the hairline `needs_edge` adds for Codex and Grok's pure black on a
dark window. The shell avatar goes back to its muted disc. `mark_ink`
goes with the style it served; the toolbar changes from the same PR stay.
2026-09-23 11:02:43 +08:00
l0ng-ai 9b4dd53547 Merge pull request #916 from l0ng-ai/style/sidebar-marks-and-chrome
style(ui): give agent marks their brand colour back, quiet the toolbar
2026-09-23 09:33:20 +08:00
l0ng-ai fea497433d style(ui): give agent marks their brand colour back, quiet the toolbar
The sidebar avatar dropped its brand-coloured disc for a flat theme one,
which left a column of identical grey marks: hue had been the fast way to
tell one agent from another down twenty rows, and the disc was the only
thing carrying it. Painting the mark itself restores the reading at a
third of the coloured area, so it no longer competes with the status dot
beside it — colour says who, the dot says what it wants. `mark_ink`
keeps the hue and only lifts a value that cannot be seen on the surface
under it, which is Codex and Grok's pure black on a dark theme.

The toolbar above it had the opposite problem: `+` and the panel toggle
were drawn at `sidebar_foreground`, the rung a tab title uses, so the two
controls were the darkest marks in the sidebar. They drop to
`muted_foreground`, level with the workspace chip, and the hover fill the
variant already carried answers the pointer.

The glyphs themselves: the panel icons fill their compartment so they
read as a sidebar rather than a split box, and the plus carries a sixth
more stroke than the closed shapes beside it — an open form at the family
weight reads both larger and fainter, which is what made it the odd one
out. Its cap now lands on a half pixel rather than a whole one; the
whole-pixel rungs are 10px and 12px in a 16px box, and neither is the
size.

Claude-Session: https://claude.ai/code/session_01FG2s9mbZu6LbjjmU54X7kt
2026-09-23 09:10:30 +08:00
l0ng-ai ae89ff0110 Merge main and preserve SSH credential saving in settings workflows 2026-09-23 08:54:49 +08:00
l0ng-ai a26dab532a Align settings terminology and documentation navigation 2026-09-23 08:47:15 +08:00
l0ng-ai acb8075c64 Unify tty7 server terminology across settings and restart flows 2026-09-23 08:35:45 +08:00
l0ng-ai ec08949625 Keep About focused on app information and update status 2026-09-23 08:30:53 +08:00
l0ng-ai fd9f589c80 Merge pull request #914 from l0ng-ai/feat/right-panel-refresh
style(ui): unify native colors, icons, and sidebar hierarchy
2026-09-23 00:39:03 +08:00
l0ng-ai 4ebc1423c1 Merge pull request #906 from l0ng-ai/fix/901-alt-digit-bindings
fix(keybindings): give Alt+1..9 a way out, and stop one bad line resetting the file (#901)
2026-09-23 00:34:23 +08:00
l0ng-ai a4dbdc7bf1 Redesign settings navigation, search, and editing workflows 2026-09-23 00:24:38 +08:00