prompt roadmap

August 30, 2026 · View on GitHub

Sinclair is a terminal emulator written in Rust.

  • Feature target: a complete, modern terminal feature set
  • Architecture: Zed-style (https://github.com/zed-industries/zed) — a cargo workspace of small, focused crates under crates/, GPUI for the UI layer.

Conventions (non-negotiable):

  • File names: all lowercase, never use spaces, -, or _. Multi-word concepts become nested module directories (src/grid/scrollback.rs, never src/grid_scrollback.rs).
  • Small, hyper-focused files.
  • Functional style: free functions and plain structs/enums; avoid trait-object heavy designs unless genuinely needed.
  • Each crate declares its own dependencies in its own Cargo.toml.

Phases

  • 1. Core terminal modelvt crate: vte-based parser handling, cell/color/sgr, grid + scrollback, primary/alt screens, scroll regions, tab stops, cursor save/restore, charsets, modes (DECAWM, DECTCEM, DECOM, 1049, 2004), OSC title, DSR responses. pty crate: openpty, shell spawn, resize, read/write. config crate: key = value config file. theme crate: ANSI palette, truecolor, builtin schemes. All unit-tested, cargo test --workspace green.
  • 2. Event loop integration — wire pty output through vt in a background thread; terminal state behind a lock; damage tracking; headless integration tests (spawn shell, run commands, assert grid contents).
  • 3. GPUI app shell — depend on gpui (zed repo git dep); window, monospace grid text rendering, cursor, keyboard input encoding (incl. modifiers) written to pty, resize handling.
  • 4. Selection & clipboard — mouse selection (normal/word/line), copy/paste, bracketed paste, scrollback scrolling, scroll-to-bottom on input. Pulled forward from phase 7: SGR/X10 mouse reporting (1000/1002/ 1003/1006) since app-side mouse plumbing lands here, with shift-bypass for selection.
  • 5. Tabs & splits — workspace/pane tree model, keybindings, pane resize, tab bar UI.
  • 6. Config & themes deep pass — live reload, all core options (fonts, padding, cursor style, shell, keybind = trigger=action, window opts), theme option + builtin scheme library. (Deferred to later phases: font-feature application → phase 8; GUI runtime verification of keybind dispatch.)
  • 7. Advanced VT — mouse reporting modes (phase 4), OSC 52 clipboard, OSC 4/10/11/12 color queries, focus reporting ?1004, synchronized output ?2026 (with a 150ms stuck-sync safety timeout in the app), DA2, OSC 8 hyperlinks (vt model + cmd-click-to-open + underline), the kitty keyboard protocol, XTGETTCAP. All five kitty enhancement flags are encoded — disambiguation, event types, alternate keys, associated text, and all-keys-as-escape-codes including the bare modifier keys — over the full functional-key table.
  • 8. Fonts — font fallback chain (font-family repeats), emoji (via the fallback chain + system fallback), ligatures (contiguous-run shaping + calt), font-feature config (+liga/-calt/ss01/cv01=2 → gpui FontFeatures), box-drawing glyph rendering (custom geometry for light lines/junctions, block elements, shades, eighth blocks). Remaining for a later polish pass: heavy/double/dashed/rounded box lines (currently fall back to font glyphs).
  • 9. Shell integration — OSC 133 semantic prompt marking (A marks prompt rows, tracked into scrollback), OSC 7 cwd reporting (phase 6), jump-to-prompt (jump_to_prompt:N action, default cmd+up/down). Deferred: automatic shell-integration script injection (the bash/zsh/ fish hooks that emit these sequences) is shell-side packaging, not terminal logic.
  • 10. Images & extras — URL detection (vt::url, cmd-click opens a detected URL when there is no OSC 8 link), search in scrollback (vt::search engine + an in-app overlay: cmd+f, live highlight, n/N navigation, jump-to-match). Image protocols: sixel (DCS) and the whole kitty graphics protocol (APC) are decoded and GPU-composited into the grid, anchored to the text they arrived with — every transmission medium, image numbers, the full delete table, cropping, cell-box scaling, z-index, unicode placeholders, and animation.
  • 11. Performance & polish — parser throughput benchmark (vt/tests/throughput.rs, including settled resize and scrollback-footprint scenarios), row-level snapshot/text-shaping reuse, 60 Hz output coalescing, resize debounce, bounded hot scrollback/graphics/input/OSC data, a shared GPU texture LRU, event-driven config reload, and public session/render counters. See docs/performance.md for budgets and repeatable checks.
  • 12. Feature coverage audit — feature-by-feature coverage map in docs/parity.md, with implemented areas, partial areas, and the remaining gaps prioritized.
  • 13. Plugin foundation — manifest plugins under ~/.config/sinclair/plugins/*/plugin.toml (plus explicit plugin = path config entries). Plugins contribute command actions with optional default keybindings, resolved through the same keymap pipeline as built-ins. Command targets: focused pane, new tab, right split, down split. Deferred: richer extension hosts for themes, languages, and UI surfaces.

Status log

  • 2026-06-12: workspace scaffolded; phase 1 implementation started.

  • 2026-06-12: phase 1 complete — vt/pty/config/theme implemented, 196 tests green. Phase 2 started: terminal crate (pty↔vt runtime) + damage tracking in vt.

  • 2026-06-12: phase 2 complete — terminal runtime + vt damage/bell/title signals, 229 tests green. Toolchain updated to rustc 1.96. gpui build recipe proven (see gpui.md). Phase 3 started: gpui app shell + input crate.

  • 2026-06-12: phase 3 complete — windowed terminal works end to end, 283 tests green, launch smoke test passed, rendering visually verified (colors, title, cursor, real zsh session). Phase 4 started: selection, clipboard, scrollback scrolling, mouse reporting.

  • 2026-06-12: phase 4 complete — selection (cell/word/line), cmd-c + copy-on-select, scrollback viewing with stable offset, SGR/X10 mouse reporting with shift-bypass, alternate scroll. 351 tests green across 15 suites, smoke test clean. Phase 5 started: workspace crate (pane tree)

    • tabs/splits in app.
  • 2026-06-12: phase 5 complete — tabs + splits live (pane tree crate, tab bar, divider drag, directional focus, cmd-t/w/d bindings, per-pane sessions). 395 tests green across 17 suites. Phase 6 started: config deep pass (full option set, keybind parsing, live reload) + builtin scheme library + app application of all of it.

  • 2026-06-15: phase 6 complete — live config reload wired into the app (app/reload.rs bridges the config watcher to the gpui foreground; WorkspaceView::reload rebuilds colors/font/size/padding/cursor/ copy-on-select and pushes an Appearance to every pane). Config keybindings now drive the app: hardcoded gpui bindings removed, config::resolve feeds a RunBind(index) data action through one dispatcher (app/keys.rs maps triggers → gpui keystrokes, unit-tested; clear_key_bindings + rebind on reload). New actions wired: font size inc/dec/reset (live re-measure), scroll page/top/bottom, clear screen (form feed), new_split left/up, goto_split prev/next, goto_tab (incl. negative), move_tab, toggle_fullscreen, copy/paste via focused pane. 460 tests green. Pending: live GUI verification of dispatch; font-feature application (phase 8).

  • 2026-06-15: phase 7 batch 1 — advanced VT replies/modes landed in vt (pure, unit-tested): focus reporting (?1004) with report_focus/ focus_reporting; synchronized output (?2026) with synchronized_output accessor; OSC 52 clipboard set (base64 decode in term/report.rs, surfaced as terminal::Event::Clipboard, written to the system clipboard by the pane); OSC 4/10/11/12 color queries answered from host-installed ReportColors (set_report_colors, fed from the theme on spawn + reload); DA2. App wiring: focus in/out listeners drive ?1004; pane installs report colors; clipboard event writes to gpui clipboard. 475 tests green. Remaining phase 7: OSC 8 hyperlinks, kitty keyboard protocol, XTGETTCAP; app frame-gating for ?2026 (needs a stuck-sync timeout).

  • 2026-06-15: phase 7 batch 2 — OSC 8 hyperlinks and kitty keyboard. Hyperlinks: vt/hyperlink.rs interns links; Cell gains a 2-byte Option<HyperlinkId> (NonZeroU16 niche); OSC 8 open/close sets the pen link; Terminal::cell_hyperlink; RIS clears the registry. App: cmd-click on a linked cell calls cx.open_url (visual underline still TODO). Kitty: vt/kitty.rs per-screen flag stack with CSI >/=/< u push/set/ pop + CSI ? u query + kitty_keyboard_flags() accessor (per-screen, RIS-reset, all tested); input/kitty.rs encodes the disambiguated keys (Escape always; ctrl/alt or all-keys text → CSI codepoint;mods u), deferring everything else to legacy. Limits: press-only (no release/ repeat events from gpui, so event-type/alternate-key/text flags are tracked but not encoded); nav/function keys keep legacy spellings. TermState gains kitty_flags, fed from the terminal. 496 tests green.

  • 2026-06-15: phase 7 complete — XTGETTCAP via DCS (vt/term/dcs.rs, hook/put/unhook; answers Co/colors, TN, RGB, and common booleans; hex helpers in report.rs); ?2026 frame-gating in the pane (withhold repaint while synchronized, 150ms stuck-sync safety timer via gpui timer+spawn); OSC 8 link underline in the element snapshot (linked cells get UNDERLINE, spaces included). 503 tests green. Phase 8 (fonts) next.

  • 2026-06-15: phase 8 complete — fonts. font-family now accumulates into a fallback chain (config::Options::primary_font/font_fallbacks); app/font.rs builds the gpui Font with primary + FontFallbacks + FontFeatures (parsing +liga/-calt/ss01/cv01=2) + base style, used by startup and live reload (closes the phase-6 font-feature TODO). Emoji ride the fallback chain + system fallback; ligatures work via same-style run shaping plus calt. app/boxdraw.rs renders box-drawing and block glyphs with custom quad geometry (light lines ─│┌┐└┘├┤┬┴┼, full/half blocks, shades ░▒▓, lower/left eighth blocks); the element snapshot diverts these cells to box_quads. 517 tests green. Phases 1–8 done; remaining: 9 (shell integration), 10 (images/extras), 11 (perf), 12 (parity audit).

  • 2026-06-15: phases 9–12. Phase 9: OSC 133;A prompt marking on Row (travels into scrollback), Terminal::prompt_lines, jump_to_prompt:N action (cmd+up/down). Phase 10: vt::url detection (cmd-click opens a bare URL), vt::search engine + cmd+f overlay with live highlight and n/N navigation; kitty/sixel image sequences swallowed without corruption (rendering deferred). Phase 11: vt/tests/throughput.rs benchmark (~103 MiB/s), snapshot buffer pre-sizing. Phase 12: docs/parity.md full coverage map. All phases 1–12 complete; remaining work is the prioritized gap list in parity.md (chief: image rendering, resize reflow, damage-clipped paint). Test count 529 green.

  • 2026-06-15: configuration UX. Native macOS menu bar (set_menus, items reuse config actions). Settings panel (cmd+,) writes changes back to the config file via config::upsert and live-reloads. Added a reusable text-input model (app/textedit.rs, tested) and wired editable fields into settings (font family, shell, foreground, background) and the search query (caret + cursor keys). Keymap now emits gpui's secondary modifier for cmd, so every ⌘ binding is ⌘ on macOS / Ctrl on Linux+Windows. 539 tests green.

  • 2026-06-18: command macros + MCP server. New macros crate: a keystroke Recorder (captures typed command lines, segmented on Enter) and plain per-macro text storage under ~/.config/sinclair/macros. Actions macro_record (toggle capture; names/saves via the rename modal) and macro:<name> (replay into the focused pane, paced off OSC 133 prompt marks with a fixed-delay fallback). New mcp crate: a dependency-light Model Context Protocol server over stdio (initialize/tools/list/ tools/call). sinclair mcp bridges tool calls to the running instance over the existing single-instance socket (ipc.rs grew a JSON request/response protocol; mcpbridge.rs maps ops onto the active WorkspaceView). Tools: run_command, read_screen, list_macros, run_macro, list_tabs, focus_tab. 575 tests green.

  • 2026-06-23: UI, working-directory, and packaging pass (v0.3.0). About panel (app/about.rs): app menu → About Sinclair shows the icon, version, and the release date (stamped from the HEAD commit by app/build.rs), plus a repo link. Window chrome: the window opens with a transparent native title bar and app/titlebar.rs draws the strip — tabs folded in, drag-to-move (start_window_move), macOS traffic-light inset, and on Linux app-drawn minimize/maximize/close controls + resize edges (client-side decorations). Working directory: shells default to $HOME (was inheriting the launcher's cwd, e.g. / from Finder), and new windows now inherit the focused pane's cwd like new tabs/splits already did. Tests relocated: every crate's unit tests moved from inline #[cfg(test)] modules into a sibling tests/ directory, compiled in-crate via #[path] includes with autotests = false; genuine integration tests (vt, terminal) declared as explicit [[test]] targets. Linux releases: scripts/linux.sh builds .tar.gz/.deb/.AppImage for x86_64 and aarch64, wired into the release workflow plus a linux.yml validation workflow. 583 tests green.

  • 2026-06-24: Relay agent mesh. New relay crate (bundled sidecar binary, not in-process): a local mesh that lets coding-agent sessions (Claude Code, Codex, …) coordinate — a SQLite bus, MCP Streamable-HTTP transport, direct/channel/ broadcast messaging, and a free wait-loop (one blocking SSE call, so idle agents cost nothing). The relay CLI manages the daemon (start/stop/ restart/status) and launches agents under it (launch, foreground or --background monitored workers; ps/kill/feed), all keyed to a --home state dir. Sinclair integration: a Settings → AI section (master AI toggle, MCP-server toggle, Relay enable, start-on-launch, address, default agent — all config keys, no env vars), a Relay menu (Launch Agent…, Open Feed) that opens agents/feeds into splits, and app/relay.rs which starts/stops the bundled daemon off settings. scripts/bundle.sh ships relay beside sinclair in the .app. Roles: relay role {list,info,create,edit,delete} with an $EDITOR drop-in and TOML files layered project → user → built-in (seven built-ins embedded); launch --role injects the brief and applies the role's channels/agent/model as overridable defaults. See docs/relay.md.

  • 2026-06-24: tiles, teams, and the AI menu. Tiles (app/tiles.rs): pane-layout presets (columns/rows/grid/main-bottom/main-right, generated for any N as a binary split tree) applied via the Workspace menu into a fresh tab; Save Current Layout… captures the focused tab's tree and stores it as JSON under ~/.config/sinclair/layouts/ (named via the rename modal). Teams (relay team {list,info,create,edit,delete}, --json for Sinclair; layered project → user → built-in, two built-ins): a layout + ordered roster. The keybind menu became an AI menu (shown when AI is enabled) with Launch Agent / Open Feed and a Teams ▸ submenu that opens a team as a tiled set of agents (each pane runs relay launch <member> --role <role>). New actions tile:, save_layout, open_team:. See docs/relay.md.

  • 2026-06-24: multi-tool agents. Three integration tiers in relay's adapter table (cli/agent.rs): MCP-native (claude via --mcp-config, codex via -c mcp_servers.relay.url — both verified streamable-HTTP MCP), bridged (ollama), and terminal (--cmd/gemini). New relay agent ollama bridge (cli/bridge.rs): a tool-using loop against /api/chat that participates over a new plain-HTTP control plane (/control/register|wait|send), factored with the MCP tools onto a shared bus.rs. Settings → AI gained an Agent tools group with per-tool enable toggles (agent-claude|codex|ollama|gemini) and a Test button that probes reachability (CLI --version or the Ollama port) off-thread. See docs/relay.md.

  • 2026-06-24: New Agent modal + movable utility windows. app/newagent.rs: a modal to add an agent to the current workspace — provider dropdown (enabled tools), name, and a role preset (from relay role list --json) or a custom description toggle. On create it spawns the agent into a split via the workspace WindowHandle (view.update(|v, window, cx| ...), the same path the MCP bridge uses) and saves the definition; AI → Agents lists saved agents (relaunch on click) plus Define Agent…. The Settings and New Agent windows got a transparent-titlebar drag strip (start_window_move, inset past the macOS traffic lights) so they can be moved.

  • 2026-06-24: macOS-style text-field keys. app/textkeys.rs centralizes a keymap shared by every modal (rename, settings, new agent): Cmd+←/→ to line ends, Option+←/→ by word, Cmd/Option+Backspace to delete line/word, Ctrl+A/E/K, Esc to dismiss, Cmd+W to close. TextEdit gained word/line nav + delete ops (unit-tested).

  • 2026-07-01: recording export (1.6.0). New export crate renders an asciinema .cast to GIF or MP4/MOV/WebM: a cast::Reader (the recorder's inverse), an idle-cap/speed timeline, replay through vt, a software rasterizer (bundled JetBrains Mono) plus a global-palette GIF encoder and an ffmpeg video encoder. sinclair export drives it from the CLI; Export Recording (File menu + command palette, export_recording) runs it in the background on the latest recording and notifies when the file is ready. On macOS an optional fidelity renderer (app/fidelity.rs) shapes and rasterizes through gpui's CoreText text system for ligatures, exact fonts, and box-drawing geometry. Also fixed the New OS Tab picker: focus the image field on open (deferred to first paint) and match its colors to the guise theme so it no longer looks disabled.

  • 2026-07-02: terminal power-feature pass (1.10.0–1.11.0). A broad sweep toward parity with modern terminals, every feature configurable and wired into Settings. Selection & navigation: smart selection (vt::semantic), hint mode (hints, cmd+shift+u), vi/copy mode (copy_mode, cmd+shift+space), middle-click paste, focus-follows-mouse. Command blocks: copy_command_output (cmd+shift+o) over OSC 133 marks. Fuzzy pickers on the guise Spotlight: clipboard history (cmd+shift+y), global search across tabs (cmd+alt+f), emoji/symbol (cmd+alt+e), snippets/workflows (cmd+alt+s), launch profiles (cmd+alt+p). Automation & privacy: regex output triggers (desktop notify), secret redaction on every copy path (both regex-backed, unit-tested). Reading aids: per-line timestamps (new monotonic vt committed_lines + a capture ring) and line annotations (annotate, cmd+alt+a). Appearance: auto light/dark theme (observe_window_appearance), minimum-contrast (WCAG, previously a dead setting), background-opacity, background-image, pane badge. Agent glance: an Activity sidebar dashboard (working/attention/idle per tab). See docs/parity.md; the remaining large/blocked items (persistent detachable sessions, kitty graphics, ssh multiplexing) are noted there.

  • 2026-07-02: Notes is now built-in, backed by a bundled Rust vault server, and the plugin runtime PATH is fixed. The Notes plugin required bun at runtime — and failed with "spawn bun: No such file or directory" whenever the app was launched from Finder/Dock (a GUI launch inherits a bare PATH, not the login shell's). Two fixes: (1) app/src/envpath.rs adopts the login shell's PATH at startup so every spawned tool (plugin runtimes, agent CLIs, git/docker plugins) is found; (2) the Bun Notes plugin is replaced by a self-contained notes sidecar crate — an axum HTTP+WS server (the vault core ported to std::fs, notify file-watching, the CM6 web app embedded via include_dir) with a launcher mode that plugs into the existing boot webview flow. Notes is now File → Notes with no runtime dependency; shipped and signed beside sinclair on macOS and Linux. See crates/notes/readme.md.

  • 2026-07-02: agent-native plugins (phase 1 of the plugin push). Plugins can now contribute tools to Sinclair's MCP server via a new [[tool]] manifest section (id, description, param = "name | type | description | required"). Every plugin tool joins the built-in terminal tools that sinclair mcp exposes, named <plugin-id>_<tool-id>, so any MCP client (Claude, or a relay-session agent) can call it. Tool calls route directly to the plugin's [runtime] with a tool request (no GUI needed), keeping the bridge process trivial. The bundled sysinfo plugin ships a working sysinfo_stats tool as the reference. This is the differentiator's first beat: a plugin isn't just UI for the user, it's a capability for their agents. Next: generalize surfaces, then the WASM runtime.

  • 2026-07-02: plugin push phases 2–4. (2) Surface generalization — PluginWebView is decoupled from plugin::Plugin behind a WebviewSurface descriptor (Url/Entry/ Boot; Boot is Runtime or Server), shared by plugin webviews and built-ins. Notes now uses a first-class surface with Boot::Server(ensure_server), retiring the synthetic-plugin + launcher-mode hack; the notes binary is server-only. (4) Capability declarations — plugins declare capability = "…" from a known set (commands/screen/network/filesystem/clipboard/notify), shown in the Plugin Manager ("accesses: …"); advisory under the process runtime, the vocabulary the WASM runtime will enforce; bundled plugins declare theirs. (3) WASM runtime foundation — manifest [runtime] type = "wasm" + wasm = "…" parses and validates; invoking one returns a clear not-yet-executable error; the full engine design (WIT host world, capability-gated imports, guest toolchain, migration) is in docs/plugins.md. Phases 2 and 4 ship; phase 3's engine is the scoped next build.

  • 2026-08-04: Plugin system down to one runtime. Three runtimes served the plugins (spawn-per-event subprocess, warm stdio server, WASM) and the expensive one carried a single plugin while five still shelled out to bun. A new capability-gated exec host function - the host spawns, never through a shell, and hands back the output - let git/docker/sysinfo move into the sandbox, and promptdesigner followed using per-plugin storage plus a visible apply command instead of silently rewriting the user's rc file. Both subprocess tiers deleted; bun/node gone from the plugin path entirely. Trigger invoke now calls a plugin's tool, so a wasm plugin can finally receive events. fetch and clipboard stopped being stubs. Plugin-contributed web views retired with their JS bridge; Notes went back to being a first-party surface over the bundled sidecar. See docs/plugins.md.

  • 2026-07-04: AI + input polish, shipped across 1.17–1.21. AI: an "Optimize tokens" setting compacts prompts sent to agents; quick-launch menu items for each configured provider (with reachability auto-verify); per-provider CLI flags (agent-claude-args, …, e.g. --dangerously-skip-permissions); a Team Builder window (manual or AI-guided) backed by a non-interactive relay team save; and agents are available on ai-enabled alone (Relay starts on demand). Input: fish-style autosuggestions — inline ghost text (accept with →/End), a completion popup, Tab-cycling, and an AI fallback, sourced from history/common-commands/paths/assist, driven by a new OSC 133;B input mark and per-pane history in vt. Workspace: tabs tear off into new windows (guise PaneGroup), keeping the live shell. Self-update: Sinclair checks GitHub on launch + hourly and offers to update — deferring to Homebrew/apt for managed installs, or swapping a manual .dmg/AppImage in place and relaunching via gpui's restart hook (auto-update).

  • 2026-07-06: agent status, session resume, and git worktrees (1.23.0). Semantic agent state across the session. Status dots: every pane self-reports working/blocked/done/idle, drawn as a colored dot on the tab (new guise on_item_dot hook) and rolled up in the Activity sidebar; mesh agents show their state in the Relay panel. Hooks: sinclair agent-status <state> reports over the single-instance socket, addressed by a SINCLAIR_PANE token injected into every session; sinclair agent-hooks install|uninstall [--project] wires Claude Code's lifecycle hooks (idempotent install, surgical uninstall). Mesh: relay gains report_status/wait_status tools and a status column, riding the /control snapshot + events stream. Session resume closes step 4 of docs/pauseresume.md — an agent pane persists its launch command + native session id and relaunches resumed on restore, while plain shells restore as before. Git worktrees: worktree_create/open/list/remove as keybind actions and MCP/IPC verbs that open a tab at the worktree, with new worktree_created/worktree_removed plugin triggers. See docs/parity.md.

  • 2026-07-24: kitty graphics + keyboard event types (1.30.0). The pinned vte has no APC callback, so ESC _G … ST never reached a Perform and the graphics protocol was unimplementable. A byte-level APC pre-parser (vt/term/apc.rs) now captures the block before vte discards it, and vt/graphics.rs decodes the payload: direct base64 in RGB (f=24), RGBA (f=32), or PNG (f=100), zlib-compressed (o=z) and chunked (m=1). Transmit/display/delete/query are answered with OK/error responses honoring the quiet level, and placement reuses the sixel compositor so images scroll with the grid; decode is bounded by dimension, pixel-count, and decompressed-size caps. File / shared-memory transmission, animation, unicode placeholders, and z-index/cropping stay deferred. Keyboard: the kitty protocol's press/repeat/release event types are encoded when a client sets report_event_types (gpui delivers key-up and flags auto-repeat with is_held). See docs/parity.md.

  • 2026-07-24: unattended agent teams. Teams launch unattended: a new relay launch --skip-permissions resolves each agent's own bypass after the role picks it (--dangerously-skip-permissions for claude, approval_policy="never" for codex, --yolo for gemini; a --cmd template and the ollama bridge are left alone), and relay-team-autonomy passes it to every member — nobody sits in each split to answer a prompt. Team members also now inherit the per-provider flags (agent-claude-args, …) that only solo launches used to get. Teams open in a window of their own (relay-team-window): the lead is the window's first pane and the rest split off it, one member per pane, each tab named after its member, focus on the lead, and the layout you were working in untouched. A team window is excluded from session save/restore — session.json holds one window (last writer wins), so a roster must not overwrite your real work, and replaying every member's relay launch would re-register a team the daemon may still be running. Known gap: the permission bypass does not cover Claude Code's separate first-run folder trust dialog, which still stops every pane the first time a team runs in an unfamiliar directory.

  • 2026-07-27: the shared project sandbox. A project's panes and its whole agent team can now run inside one container instead of on the host, turning relay-team-autonomy's permission bypass from a promise into a boundary. Self-contained: docker or podman is the only dependency — no VS Code, no devcontainer CLI, no Sinclair image to pull. The container crate grew Sandbox (a detached, keepalive-backed container with mounts, env, user, network, and resource ceilings), Recipe (a generated Dockerfile that layers the agent CLIs onto a configurable base, tagged by recipe hash so a rebuild only happens when the recipe changes), Mount, adopt (label discovery plus ownership, so a container VS Code created is entered and never removed), and a devcontainer.json reader on config's JSONC parser. The project is identity-mounted — the same absolute path inside and out — which is what keeps git worktrees valid from both sides and removes any need for a path translation layer. Relay stays on the host: relay launch --sandbox changes only the final exec, rewriting the bus URL to the engine's gateway host and handing the agent its MCP config at the path the container mounts it. Opening a team brings the container up first. UI: a Sandbox submenu under both File and AI with a live status line, a Sandbox section in the Containers panel, fourteen sandbox-* settings in Settings → AI, and six bindable actions. Known gap: a worker started with the MCP spawn tool runs on the host — the daemon is not told which container a session belongs to. See docs/sandbox.md.

  • 2026-07-27: tab renaming, tab context menu, and window dragging (issue #15). Renaming a tab was already an action, a View-menu item, a palette entry, and a documented keybinding — and the dialog never painted: its host element had no size, so the modal's absolutely-positioned backdrop, drawn in the deferred pass, had nothing to resolve against. The dialog was created and re-rendered every frame, off screen; the feature looked missing because it was invisible. tab-title-show-host was dead in the same way — parsed, written to settings.json, a toggle in Settings and a promise in the help text, and no consumer. Tab labels now honour it, with a strip that only fires on something genuinely shaped like user@host: (so https://example.com:8080 and nvim: main.rs survive) and never rewrites a label the user typed; the flag rides in a cell the reload path updates, so toggling needs no restart. Right-clicking a tab now opens a menu — rename, split, close, close others — built by the host from a new PaneGroupEvent::ContextMenu; its handlers defer their action, since dispatching straight into the same window re-enters an update in progress. Window dragging: every tab bar along the layout's top edge now drags, not just the top-right one, and the space reserved for the window controls drags rather than being dead padding. Dev containers: mounts and remoteUser from a project's devcontainer.json are now applied, not just parsed. See docs/devcontainers.html.

  • 2026-08-03: configurable side columns. The two side drawers used to be the same thing twice — one identical activity rail on each side listing every panel, and one panel visible at a time per side. Each side is now its own dock: an ordered stack of collapsible sections, composed independently, Zed-dock style. The rail stays but lists only that side's sections, so the left and right columns are finally different columns; several sections can be open at once, sharing the leftover height and each scrolling on its own. Composition lives in app/src/root/dock.rs, deliberately gpui-free so the interesting part is testable (crates/app/tests/dock.rs): resolving config tokens, revealing a section wherever the user actually put it, and moving one dock to the other. Sections persist as tokens, never indices — a plugin panel carries an index into a list that shifts when the plugin set changes, so storing one would quietly rebind a saved slot to a different plugin. An unresolvable token (uninstalled plugin) is skipped and left in the file, so a reinstall restores its slot. Action::Sidebar gained reveal semantics: a bare side toggles the dock, <side>:<token> expands the section on whichever side holds it, which is what keeps sidebar:left:containers working after Containers has been moved right. Five new settings keys (sidebar-left, sidebar-right, sidebar-collapsed, sidebar-left-width, sidebar-right-width) and a Settings → Sidebar designer that edits them through the same compose_docks the workspace renders from, so the two can never disagree. Two new sections: Worktrees (the repo's git worktrees, click to open one in a tab, on the existing worktree.rs plumbing) and Notes (the vaults the Notes editor has opened; the editor itself stays a webview, since a markdown editor is unusable in a 260px column). Both cache on expand rather than resolving during render — one is a git subprocess and the other a file read.

    Footprint: no new dependencies (the Notes section reads the sidecar's vaults.json directly rather than depending on the notes crate, which is a binary-only tokio+axum server). A closed dock builds no elements at all, so the default costs nothing. The chrome of an open dock allocates nothing per repaint: rail icons and section headers click by (side, index) through dock::toggle_at instead of formatting a token payload only to parse it back and re-resolve it, built-in labels/glyphs are SharedString::new_static over constants (label_upper), and the plugin lookups iterate to nth rather than collecting a Vec<&Plugin> to index once. Measured: +0.1MB RSS to open both docks with six sections; CPU during sustained terminal output is unchanged within noise (6.2% open vs 6.3% closed — the grid renderer dominates and gpui coalesces repaints); 200 toggle cycles move RSS by 0.5MB.

  • 2026-08-29: the kitty protocols, in full. Graphics moved into vt/graphics/ (control model, decode, media, store) with dispatch in vt/term/gfx.rs, and now answers all eight actions. Transmission gained the file, temp-file and shared-memory media with O=/S= windows — the one place vt touches the OS, confined to graphics/media.rs, because a terminal that will not read them does not implement the protocol. Images gained numbers alongside ids, the whole delete-specifier table, source cropping, cell-box scaling, cell offsets, z-index (including below the cell background), placement ids, and animation (frames, gaps, composition modes, loops, playback state).

    Unicode placeholders needed somewhere to put a cell's decoded coordinates. Rather than widen every cell, the zero-width slot became a tagged u32 holding either one combining mark or a placeholder's row, column and image-id high byte — the same four bytes a char took, so no grid or scrollback block grew (size_of::<Cell>() is 24 either side). The renderer keeps one texture per image and clips with a content mask, so a crop, a cell offset, a scrolled-off top and a placeholder's single-cell slice are all the same operation.

    Keyboard: all five enhancement flags over the full functional-key table — F13–F35, keypad, media, lock and modifier keys, the last synthesised from the window layer's modifier-state changes. The modifier parameter carries super, hyper, meta and the locks, so cmd+arrow reports instead of vanishing. The remaining limits are the window layer's, not the encoding's: gpui reports no hyper, meta, or num lock, and does not name keypad keys separately on macOS.

    Three resource bugs found auditing the result, all the shape a runaway stream produces: the pane byte budget was recomputed by walking every frame of every image on every transmit (quadratic — a flood of small images cost 93us apiece on accounting alone; the store now keeps a running total, and the same flood costs 0.01s where it cost 0.40s); virtual placements were charged to nothing at all, and animation frames were unbounded; and an on-screen animation asked for a repaint every vsync however far off its next frame was, where it now sleeps until the frame is due. Measured after: 8 panes idle at ~95MB (~200KB per pane), 60k scrollback rows ~13MB, idle threads all blocked, parser ~104 MiB/s.

    Also: an open-modifier click on a filesystem path opens it with the desktop's default handler rather than only revealing it, and hovering underlines it — but only once the candidate resolves against the pane's cwd, so text that merely looks like a path stays inert. And unified-tab-bar (on) keeps the tabs-as-titlebar layout; off gives an ordinary titlebar with the tab bars beneath it. The split controls sit at the titlebar's right edge either way, acting on the focused pane when they are in a shared one.