Commit Graph
501 Commits
Author SHA1 Message Date
l0ng-ai b51b238aef Merge pull request #102 from ayamir/fix/shell-vi-mode
feat(terminal): support shell vi mode
2026-07-16 15:02:40 +08:00
l0ng-ai f4e63d076e fix(daemon): detect shell vi mode via durable signals
zsh: plugins like zsh-vi-mode rebind ^[ to their own widgets, so
sniffing the Esc widget for vi-cmd-mode missed them. Key off the main
keymap link instead (bindkey -A viins main), which both plain
bindkey -v and zsh-vi-mode establish.

bash: [[ -o vi ]] misses vi mode configured only in ~/.inputrc
(set editing-mode vi flips readline without the shell option); read
readline's actual mode via bind -v instead.
2026-07-16 14:56:51 +08:00
l0ng-ai 418433395b fix(terminal): release gap hold at vi prompts without a stale typeahead record
Text typed during a command gap is held for the next prompt's local
editor, but a vi prompt never engages the editor: the hold sat until the
150ms timer dumped it and recorded it as typeahead, and that record
could never drain during vi prompts. It lingered and flushed at the
next emacs-mode prompt, firing a spurious ^U and resurrecting the
long-consumed gap text into the editor.

A vi prompt now releases held gap input raw (the shell's line editor
owns it) and drops any pending typeahead record without its wipe.
2026-07-16 14:56:51 +08:00
l0ng-ai e89cab2e65 Merge pull request #105 from l0ng-ai/feat/tray-icon
feat(tray): system tray icon with agent status menu
2026-07-16 14:42:51 +08:00
l0ng-ai 780be2cedc fix(tray): review fixes — Linux build, non-blocking SNI updates
- sni: import gpui::AppContext (Linux build broke on background_spawn)
- sni: push updates through a background updater task instead of blocking
  the foreground poll loop on a DBus round-trip; bursts coalesce to the
  newest snapshot, and channel close (Backend drop) shuts the service down
- reveal: keep the target pane maximized if it already is; only a
  maximized sibling gets cleared
- icon: center non-square SVGs when rasterizing (was top-left aligned)
- stale comments: 'gave_up' poll-loop reference, prompt button name
2026-07-16 14:29:21 +08:00
l0ng-ai a4683399dd style(tray): cargo fmt 2026-07-16 14:09:42 +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
ayamir 8180d22166 fix(terminal): support shell vi mode 2026-07-16 13:41:43 +08:00
l0ng-ai f8435c435a Merge pull request #104 from l0ng-ai/feat/settings-copy-polish
feat(settings): polish copy and keep SSH security defaults always visible
2026-07-16 13:36:48 +08:00
l0ng-ai bed17435a8 fix(settings): review follow-ups — unify warn-before-closing label, add search entry, note verify-off risk 2026-07-16 13:35:50 +08:00
l0ng-ai 3762259d1e feat(settings): polish copy and keep SSH security defaults always visible 2026-07-16 13:24:55 +08:00
l0ng-ai 0d728c7983 docs(tabs): fix stale rename comment, note titlebar_double_click platform gap 2026-07-16 12:52:13 +08:00
l0ng-ai 3a5bebfdb0 Merge pull request #103 from l0ng-ai/feat/tab-double-click-zoom
feat(tabs): double-click on tab zooms window instead of renaming
2026-07-16 12:48:23 +08:00
l0ng-ai 3dbbfb47c5 feat(tabs): double-click on tab zooms window instead of renaming
Double-clicking a tab label in the title-bar strip now forwards to
window.titlebar_double_click(), matching the rest of the titlebar, instead
of entering inline rename. The sidebar drops double-click rename too for
consistency. Renaming remains available via the tab context menu's
"Rename Tab" item, which is now its only entry point.
2026-07-16 12:38:34 +08:00
l0ng-ai 0b72b752d7 Merge pull request #101 from ayamir/fix/terminal-cursor-shape
fix(terminal): respect app cursor shape
2026-07-16 12:12:44 +08:00
ayamir 95493d50b6 fix(terminal): respect app cursor shape 2026-07-16 11:59:07 +08:00
l0ng-ai b079f91caf chore(release): v0.17.0 v0.17.0 2026-07-16 11:54:10 +08:00
l0ng-ai 501fd4a7de docs(readme): rewrite in minimal style, reposition as terminal workbench
Slim the READMEs to an index (why / install / what's inside / benchmarks);
move feature details, keybindings, and performance notes to docs/features.md
(en + zh-CN). New tagline: a terminal workbench — shells, sessions, SSH,
coding agents. Sync the Cargo.toml description.
2026-07-16 11:45:33 +08:00
l0ng-ai e5dab7ee44 Merge pull request #100 from l0ng-ai/feat/per-tab-split-diff-overlay
feat(diff-overlay): per-tab lifecycle and side-by-side view
2026-07-16 11:33:49 +08:00
l0ng-ai 4cdd413f5d feat(diff-overlay): per-tab lifecycle and side-by-side view
The overlay was a single app-global slot rendered over whatever tab was
active: switching tabs left a stale diff occluding the new tab's body,
with focus on the terminal underneath so Esc couldn't even close it.

Move the overlay state onto Tab so it follows product intuition: switching
away hides it, switching back restores it (re-probing if the status cache
disagrees with the shown snapshot), and closing the tab drops it.
focus_active now hands focus to the returning tab's overlay so Esc keeps
working; probe landings update every tab showing the same cwd.

Also replace the unified diff body with a GitHub-PR-style side-by-side
view: removed lines fill the left column, added lines the right, aligned
positionally within each change block; context lines land on both sides
and leftovers pair with a muted empty cell.
2026-07-16 11:27:03 +08:00
l0ng-ai 29a51edc0e Merge pull request #99 from l0ng-ai/fix/claude-dot-stuck-waiting
fix(agent-status): recover from Waiting once an approved tool completes
2026-07-16 11:10:26 +08:00
l0ng-ai 1509e92ce8 fix(agent-status): recover from Waiting once an approved tool completes
Claude Code has no 'permission replied' hook, so after the user approved
a mid-turn permission prompt the dot stayed amber for the rest of the
turn. Install a PostToolUse hook mapped to a new tool-complete sentinel
event: the first tool that finishes after approval flips the state back
to Working (guarded on Waiting, so completions during normal work are a
no-op and a straggler can't overwrite Done). Same shape as Warp's
ToolComplete transition.

Old daemons drop the unknown event; existing installs surface as
Outdated in Settings -> Agents with an Update button, so the stale
'points at another tty7' copy is reworded.
2026-07-16 11:05:57 +08:00
l0ng-ai c6d57be076 Merge pull request #98 from l0ng-ai/feat/unread-count-dot
feat(tabs): show unread pane count in the avatar status dot
2026-07-16 10:45:55 +08:00
l0ng-ai ee04928ea4 Merge pull request #96 from l0ng-ai/feat/tab-context-menu-worktree
feat(tabs): per-tab context menu with git worktree tabs
2026-07-16 10:40:28 +08:00
l0ng-ai 4b9fdcde43 Merge pull request #97 from l0ng-ai/style/macos-popup-restyle
style(ui): macOS Sonoma-style popup panels
2026-07-16 10:35:22 +08:00
l0ng-ai 4cf91e778b feat(tabs): show unread pane count in the avatar status dot
A split tab can finish several agent turns while you're away, but the
unread marker was a tab-level bool: three unread results looked exactly
like one. Replace the same-hue target ring with a count badge — when a
tab has unread finished turns, the green Done dot swells into a disc
with the number of unread panes, and shrinks back to a plain dot once
every pane has been seen.

- Tab::agent_result_unread (bool) becomes Tab::agent_unread_count
  (usize), counting Done-and-unread panes; still zero while a busier
  pane (working/waiting) owns the corner.
- status_dot takes the count and renders the numbered badge, centred on
  the read dot's corner point so the swell reads as the dot growing in
  place; count clamped to 9 so an absurd split can't overflow the disc.
- Sidebar rows reuse the same avatar, so both layouts pick it up.
2026-07-16 10:35:20 +08:00
l0ng-ai 0033e957b5 fix(worktree): compare canonicalized paths so Windows CI passes
Windows' std::fs::canonicalize returns \\?\-prefixed verbatim paths while
git reports plain absolute paths, so occupied()'s starts_with never matched
(silently disabling the shared-checkout guard) and two path-equality test
asserts failed. Canonicalize both sides in occupied(); strip the verbatim
prefix in the tests.
2026-07-16 10:32:09 +08:00
l0ng-ai f9dba021fc style(ui): macOS Sonoma-style popup panels — pill highlight, taller palette search, whole-row viewport
- Bump gpui-component fork (562341a, ff8af95): menus get a 6px rounded pill
  highlight inset by 5px panel padding, inset hairline separators, a 10px
  panel radius with a floatier shadow; searchable lists get a taller
  Spotlight-style search row with 15px query text.
- palette: card matches the menu panel (10px radius, shadow_xl); rows are
  fixed-height inset pills; the list viewport holds a whole number of rows
  so the card's bottom edge never cuts the last visible row mid-height.
2026-07-16 10:25:23 +08:00
l0ng-ai 27669acf42 fix(worktree): skip the cleanup offer while another pane still lives in the checkout
New tabs inherit the current cwd, so two tabs sharing one managed worktree
is common; removing the checkout on the first close would yank the
directory out from under the surviving shell. Before offering, scan every
leaf of every surviving tab (splits included) and stay quiet if any cwd
still sits inside the worktree.
2026-07-16 10:24:04 +08:00
l0ng-ai 3ec1577a17 feat(tabs): per-tab context menu with worktree tabs
Right-clicking a tab chip (strip) or row (sidebar) opens a context menu:
rename, New Worktree Tab, splits, copy working directory, and a close
group (tab / others / to the right or below).

New Worktree Tab opens a sheet pre-filled with a generated two-word
name (used for both the directory and the new branch) and the current
branch as the start point — all three editable, with a live path
preview. The checkout lands in the repository's own
.tty7/worktrees/<name>, kept out of git status by an auto-written
self-ignoring .tty7/.gitignore. The entry only appears when the tab's
cwd is inside a git repository.

Closing a tab that sat in a managed worktree offers to remove the
checkout (and its branch when fully merged); dirty checkouts default
to keeping and make the discard explicit.
2026-07-16 10:03:13 +08:00
l0ng-ai 8d4b067a82 chore(release): v0.16.1 v0.16.1 2026-07-15 23:00:52 +08:00
l0ng-ai ac9755e7cf style(sidebar): rustfmt the diff-count click handler 2026-07-15 23:00:05 +08:00
l0ng-ai 0536841687 Merge pull request #95 from l0ng-ai/fix/sidebar-diff-click-target
fix(sidebar): limit the diff overlay toggle to the +N/-N counts
2026-07-15 22:44:49 +08:00
l0ng-ai 919e9a0b2e fix(sidebar): limit the diff overlay toggle to the +N/-N counts
Clicking anywhere on a sidebar row's git line (branch icon, branch name,
counts) toggled the diff overlay, so ordinary clicks on the lower half of
the row popped the overlay instead of activating the tab. Only the +N/-N
readout is the click target now; the icon and branch name fall through to
the row's normal activate handler.
2026-07-15 22:44:11 +08:00
l0ng-ai f678469aef chore(release): v0.16.0 v0.16.0 2026-07-15 22:06:23 +08:00
l0ng-ai f26045fbb5 style(diff-overlay): rustfmt the diff overlay module 2026-07-15 22:05:20 +08:00
l0ng-ai 6438d21886 Merge pull request #92 from l0ng-ai/feat/sidebar-diff-overlay
feat(git): click a sidebar git line to open a working-tree diff overlay
2026-07-15 22:01:03 +08:00
l0ng-ai 68cc4680d4 feat(git): click a sidebar git line to open a working-tree diff overlay
The sidebar row's third line (branch + counts) is now a click target: it
opens a read-only, GitHub-style unified diff of that pane's repo as an
overlay covering the terminal body. The sidebar stays visible, so other
tabs' git lines remain one click away — peek at another session's
changes without switching tabs.

- terminal/git_diff: parse `git diff HEAD` into files/hunks/lines
  (renames, binary, quoted paths, per-file line cap), plus the
  untracked list `diff` can't see; probed off-thread, read-only via
  the shared GIT_OPTIONAL_LOCKS=0 helper.
- ui/diff_overlay: per-file cards with collapsible hunk bodies (big
  files collapse by default), line-number gutters, green/red tints,
  Esc / close / re-click toggles; auto-refreshes off the existing
  GitStatusCache triggers when shown counts go stale.
2026-07-15 22:00:19 +08:00
l0ng-ai 8e47d4c231 Merge pull request #94 from l0ng-ai/feat/remember-window-size
feat(window): remember window size and position across launches
2026-07-15 21:59:48 +08:00
l0ng-ai d4450c4199 Merge pull request #93 from l0ng-ai/fix/agent-hook-tty-stdin
fix(agent-hooks): skip stdin read when it is a tty
2026-07-15 21:59:44 +08:00
l0ng-ai 19430424f5 Merge pull request #91 from l0ng-ai/fix/ring-replay-geometry
fix(daemon): segment the replay ring by geometry so attach replays wrap correctly
2026-07-15 21:59:40 +08:00
l0ng-ai 25c8ea6b79 feat(window): remember window size and position across launches
Persist the window's final geometry to window.json on quit (written by
the app-quit hook, tracked live by a bounds observer that records the
restore bounds while fullscreen). On launch, a normal startup window
reopens at the remembered size and position instead of the hardcoded
centered 1440x900; a remembered window that no longer touches any
display keeps its size but re-centers, and degenerate or malformed
state falls back to the default.

Gated by a new remember_window_size config (default on) with a switch
in Settings -> Window & Tabs and a search entry. The geometry is
written unconditionally so toggling the setting back on restores the
most recent quit rather than stale pre-toggle state.

Closes #89
2026-07-15 21:51:31 +08:00
l0ng-ai 0334c97a65 fix(daemon): cap the replay ring's segment count
Drag-resizing a pane whose TUI redraws on every SIGWINCH cuts a tiny
segment per column change; such segments never fill RING_CAP, so over a
long-lived pane's life they accumulated without bound and attach replay
(one full client-side reflow per Size) degraded linearly. Past
MAX_RING_SEGMENTS (64) the two oldest segments now merge, replaying the
older bytes at the newer geometry — like the byte cap, precision
degrades from the oldest scrollback first.

Also correct the client reader's pending_size comment: the one-lock
guarantee is per Size/Snapshot pair, not across the whole replay.
2026-07-15 21:50:03 +08:00
l0ng-ai ffcb990d33 fix(agent-hooks): skip stdin read when it is a tty
The agent-hook emitter reads its JSON payload from stdin until EOF,
which assumes the spawner pipes a payload and closes the stream (as
Claude Code's hooks do). OpenCode's plugin runner spawns the emitter
with stdin inherited from the pane's terminal instead: a tty never
reaches EOF, so the read blocked forever, hanging the plugin's async
init and leaving OpenCode's TUI blank on startup — while also
swallowing the user's keystrokes.

Skip the stdin read when stdin is a terminal; a bare event without
session_id/message is already a supported payload.

Fixes #88
2026-07-15 21:48:45 +08:00
l0ng-ai 846032183d chore: remove repomix skill and config 2026-07-15 21:23:31 +08:00
l0ng-ai b33fcc7035 chore(docs): drop the docs directory and stale plan files 2026-07-15 21:21:48 +08:00
l0ng-ai 363d2592f6 fix(daemon): segment the replay ring by geometry so attach replays wrap correctly
The replay ring stored raw PTY bytes with no geometry history and attach
replayed all of it at the final recorded size. Any resize during a session
(pane split, window drag) meant older bytes re-wrapped at the wrong width
on replay, so a TUI's cursor-up redraws (Claude Code's inline renderer is
the canonical case) landed mid-frame and every redraw leaked stale rows
into the reattached pane's scrollback -- duplication that never existed
live (10 markers live vs 45 replayed in the regression scenario).

The ring is now a sequence of geometry-tagged segments: resize seals the
current segment (retagging an empty tail in place), cap eviction drops
emptied segments, and attach replays a Size -> Snapshot pair per segment.
The client reader already applies each Size to its grid right before the
paired Snapshot advances (pending_size), so it reflows between segments
exactly where the live client did -- no client changes needed.
2026-07-15 21:15:24 +08:00
l0ng-ai 1756bcc3a5 chore(release): v0.15.0 v0.15.0 2026-07-15 19:48:01 +08:00
l0ng-ai 5a1f4c8181 style: cargo fmt 2026-07-15 19:43:08 +08:00
l0ng-ai 30894a7946 refactor(palette): drop the Claude-only hook install command
Settings > Agents now owns hook installs with per-agent state (#87);
the palette entry covered only Claude and duplicated that path, so
remove it rather than grow one palette command per agent. Also drop a
leftover unused HookAgent import in the settings render.
2026-07-15 19:31:17 +08:00