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.
Every chip permanently reserved a 6px gap + 20px slot for a close
button that is invisible until hover, which read as a hole between an
ellipsized label and the chip edge, and wasted label width on crowded
strips. Drop the in-flow slot: the label now runs the full chip width,
and on hover the ✕ floats over its right edge on a solid backing in
the chip's own fill with a short gradient run-in, so covered text
fades out and nothing reflows.
The ⌘N hint badge keeps an in-flow slot: it also shows on unhovered
inactive chips, which are transparent over the window background, so
an overlay has no solid colour to back onto.
The install/uninstall outcome note repeated the agent name and full
target path inside the row's shrink-proof control column; a long path
inflated the column past the 640px content cap and crushed the label
column (min_w_0) to zero width, wrapping the title one character per
line.
- Terse action summaries: "Installed" / "Removed" — the row already
shows the agent and target path. Codex keeps the feature-flag
failure advice; the launch-refresh log line carries the agent name
and target path itself now.
- Control column gets items_end so status, buttons, and note share
one right edge across all agent rows.
- Note is width-capped (max_w_80) and wraps, so long error text can
never expand the column again.
- Outdated status text shortened to one word; the Update button says
what to do.
Replace the orange window-and-cursor identity with the "Duo" mark: two
offset session panes (mint #3FDD8C behind, ink #17171A in front) with a
prompt chevron, on a cream tile following the Big Sur icon grid.
- app-icon.svg is the master; app-icon.png, tty7.icns and favicon.ico
are re-rendered from it (rsvg-convert + iconutil + PIL)
- logo.svg/logo.png/logo@256.png carry the tile-less transparent mark
- tray.svg redrawn as the same mark in template form: solid front pane
with the chevron masked out, back pane at partial alpha
- bare (non-bundled) macOS binaries now set the Dock icon at runtime
via NSApplication.setApplicationIconImage, so cargo dev/run shows the
logo instead of the generic executable icon
- README version badge and the social preview switch to the new brand
green; social preview copy re-aligned with the current README slogan
Command::spawn returns after the fork, possibly before the child has
exec'd sleep; until then /proc/<pid>/exe still points at the test
binary itself, so asserting the basename immediately is a race. This
intermittently failed the Linux CI job on main (runs 29549626571,
29560431777, 29563049775). Poll process_path for up to 5s and carry
the last-seen basename into the failure message.
The attention state left template mode to carry the amber badge, which
forced the glyph to a hardcoded mid-grey that was illegible on many menu
bar appearances. Drop the badge (and the grey recolor) on macOS: the icon
now stays a template glyph the system recolors for the bar, and agent
status lives in the tooltip and tray menu. Windows/Linux keep the amber
corner badge on their colored icon.
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
The panel's open files, tree roots/expansion/selection, and visibility now
live on Tab.code (same contract as Tab.diff_overlay): only the active tab's
panel renders, switching tabs shows that tab's own panel (or none), and
closing the tab drops its state. Hiding via Esc keeps the tab's open files.
Shared infrastructure stays app-global: directory-listing and gitignore
caches (path-keyed, tab-agnostic), the LSP registry, and single watchers
over the union of every tab's roots / open files. External-change reloads
and diagnostics now fan out to every buffer of the path across tabs.
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.
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>
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.
Pasting a screenshot into a coding-agent pane (Claude Code &co.) did
nothing on Windows/Linux. The clipboard-image branch only forwarded SYN
(0x16), relying on the agent to read the OS clipboard itself when it sees
Ctrl+V. That works on macOS, but Claude Code silently drops raw
screenshots off macOS (anthropics/claude-code#26679), so nothing landed.
gpui already hands us the image bytes via ClipboardEntry::Image, so off
macOS we now stage the image to a temp file and paste its shell-escaped
path — the same route drag-and-drop uses, which agents attach reliably.
Windows screenshots arrive as BMP (CF_DIB), which agent vision rejects,
so those are transcoded to PNG; PNG/JPEG/GIF/WebP pass through untouched.
The temp filename is keyed on gpui's content hash so re-pasting one image
reuses a single file. macOS keeps the higher-fidelity SYN path, and a
staging failure there falls back to SYN too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A stray foreign A/B mark mid-command re-delivers the same capture; when
that capture names no agent (a wrapper script the matcher can't see
through), re-applying its None wiped an identity the sentinel events had
established. Gate the Windows apply on the capture changing, document
the bare-C trade-off (our own PowerShell fallback emits it), and cover
the gate, %0A multi-line decode, and whitespace-only payloads in tests.
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>
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.
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.
The group key now reads the first pane's cwd instead of the focused
pane's, so switching focus between splits in different repos never
relocates the row (the branch line still follows focus). Also documents
the repo-grouped sidebar and the sidebar_grouping setting in
features.md / features.zh-CN.md.
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.
The tray reused logo.svg (window outline + title-bar rule + colored
cursor block), which turns to mush at 18pt in template mode: the color
flattens to alpha and the icon reads as a card/wallet, not a terminal.
Replace it with a dedicated tray.svg — a bold window outline with a
prompt chevron and underscore, drawn for menu-bar size. logo.svg is
untouched; the tray was its only code consumer.
- Diff overlay paints the gradient/opacity-aware window background instead
of the stale representative solid.
- Config hot-reload now re-runs apply_theme with the window (blur flip,
traffic-light re-pin) and re-syncs the Appearance opacity slider, so
hand-edits to config.json / theme files take effect fully.
- Document the theme background/window settings in features docs (EN + zh-CN).
- 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.