NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #1713 by repository stars
Last release 3 days ago
05 Oct 2026
Release timing varies
gaps range from 8 days to 2 months
Nearly every release is documented
notes for 58 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
9 months old
184 releases · first in 2025
One column per month.
fbc93f4 Add project content, live invalidations and conditional viewer layouts
A UI API for web and embedded clients. sidecar api serve exposes the Sessions catalog and live terminals over HTTP and WebSocket, so a web UI, a component embedded in another app, or an agent with curl can use them. It listens on a Unix socket in the state directory for local agents and the CLI, on 127.0.0.1 for browsers, and with --tailnet on a second socket for tailscale serve, trusting only allowed tailnet logins. Browsers pair once with sidecar api open, and other origins with sidecar api pair --origin URL. Host, Origin and mutation guards are always on, and only paired origins get CORS. Each terminal WebSocket is one mobile protocol v0 stream, served exactly as sidecar mobile serve --stdio serves stdin, and GET /api/v0/sessions returns the same document as sidecar mobile sessions --json. sidecar api status shows who is connected and who holds terminal control. The wire contract is docs/reference/ui-api.md. (td-ba925d)
Set up an always-on web UI without editing config. sidecar api service install --ui DIR validates and saves a built UI directory before starting the service; --ui "" clears it. Service status and Local API status show the UI directory in text and JSON; browser and Tailnet status show only whether a UI is configured. An API-only server serves a helpful setup page, and the public docs explain web UI installation and how agents can build their own client against the resources, events, terminal, and viewer relay contracts. The private reference UI adds pnpm run install-local with atomic build switching and an optional --service step. (td-96fa2b)
launchctl returns, so an immediate bootstrap failed with "Input/output error". Install now waits for the old job to leave and retries that refusal briefly.owner_host_id and workspace_id; every other control character is still refused.sidecar mobile sessions --json dropped from about 2.5 s to under 0.4 s and spawns roughly a third of the processes. The UI API shares one collection between concurrent requests while still authorizing every request against current panes and shell reservations, so /sessions and project workspaces answer in about 0.3-0.4 s cold. Concurrent requests no longer time the local host out and report it offline with an empty list. Owner catalogs are charged against the aggregate bounds as they arrive, with at most eight outside the budget at once. (td-67fc32)f0f83fd release: prepare v1.15.1
commit and merge can leave a detached git maintenance writing under .git/objects as the temp directory is removed, which failed TestLoadFileTreeRealGitConflict on CI and stopped the v1.15.0 release workflow before anything was published. Fixture repositories now disable auto-maintenance at init. v1.15.0 was tagged but never released; its changes ship here as v1.15.1.One message to every live agent. sidecar agent broadcast TEXT puts one short prompt in front of every live agent in the caller's project, or every liv
sidecar agent broadcast TEXT puts one short prompt in front of every live agent in the caller's project, or every live agent on this machine with --all, without starting anything. B on the workspace list or Sessions opens the same plan as a checklist: space toggles a row, a/n select all or none, the scope segments re-plan between this project and every project, and the message is a four-line text area where enter opens a line and ctrl+s sends. Each row is a receipt — submitted, skipped, or unknown — not an acknowledgement that the agent did anything with the text. Gated on agent_control (default off). Remote hosts are out of scope; --host is a usage error and --all means this machine. (td-3c3245)Quick Open finds the file that was just written, and ranks the file you meant first. The ctrl+p file list in Files was refreshed only by a watcher on the directories the tree had expanded, so a file an agent wrote into a collapsed directory stayed invisible to the finder until Sidecar restarted — use-cases returned six near-misses and not the USE-CASES.md sitting in the tree. The list now ages out after ten seconds, and an aged list is re-checked with one stat per directory the last walk recorded rather than walked again: a directory's mtime moves whenever an entry is created, removed or renamed inside it, so an unchanged tree costs well under a millisecond and only a tree that moved pays the walk. The matcher is rewritten as well. It aligns each query term by dynamic programming rather than taking the leftmost letters it meets, so cases lights up the word cases instead of a c, an a and an s scattered across three directories and scores the row by that. Words separated by spaces must all match, -, _ and space are interchangeable, a word at the start of a filename beats the same letters mid-path, a shallower path wins a tie, and the files you have open lead an empty query and break near-ties. Results come out in the same order regardless of how the list was walked. At the scanner's 50,000-file cap a broad one-letter keystroke costs a few milliseconds more than it did and a specific query costs less, with a hundred allocations where there were tens of thousands. Pane finders in Workspaces and Sessions share the same cache clock. (td-935e53)
A managed install no longer breaks the sidecar a previous one left on your PATH. Activation synced ~/go/bin/sidecar with cp, and after the first activation that destination is a symlink into the previous dev install, so cp followed it and rewrote that older artifact in place. The artifact then disagreed with its own directory name and metadata, and because macOS had already executed that file, every later exec of it was killed outright — so any launcher still pointing there ran nothing and printed nothing, exit 137. The sync now points at the artifact instead of copying onto the path. Two guards come with it: every launcher ever retargeted is remembered and re-pointed on each activation, so one that no shell happens to resolve today cannot rot unnoticed, and each synced launcher must actually run before the install reports success, because resolving to the right path no longer proves a binary is runnable.
If-Match), board move, a project description endpoint and td info --json base_dir; tasks adds lifecycle dates, quadrant and agent readiness on the Task resource, activate/tag/note-delta endpoints, outline and view reads, and fixes concurrent formal_links edits overwriting each other.Nothing published for this version
5285edc app: host a protocol plugin's tab through the generalized global host
The @ project switcher is a compact list, a card grid, and two dates that mean something. The switcher opens on a compact list with column headings and full-row click targets, switchable to a three-column card grid, and a shared sort control offers Last activity, Name and Date added with a direction. Sort, direction and view persist across launches. Neither date existed before this: ProjectConfig.AddedAt is written once by config.AddProject, the single Load-mutate-Save boundary the TUI and sidecar project add already share, so the CLI records it and project list --json reports it. It means registration with Sidecar, not the directory's birth time and not the first commit, so the control says "Date added" rather than claiming a creation date Sidecar does not have. Last activity is the latest Sidecar event recorded against the project — binding, at launch and on every switch, which sidecar project switch records through the same code; creating a shell in a project does not mark it active and nothing watches terminal output. Reading it touches no filesystem, git or tmux, and writing it happens in a tea.Cmd. Projects registered before this read Unknown and are never backfilled; an unknown date sorts last in both directions, because "not recorded" is not a date and reversing the order must not promote it. internal/projectlist is the state-free core, mirroring internal/workspacelist's vocabulary so the two collection surfaces answer the same question the same way.
Overlay popovers are mouse-interactive, and hover no longer bleeds through them. The switcher's sort dropdown opened on a click but its options could not be clicked, and hovering its chrome lit up the project cards underneath. In the modal library, an overlay now registers a backdrop region over its own bounds that absorbs stray clicks and clears the hover id, indexes its focusables into the modal's focus positions and hit map with viewport clamping, and grows the viewport up to its maximum when the overlay is taller than the modal's natural content so option rows are not clipped. (td-3a434f)
create shell --cwd, and a prompt receipt that says whether the bytes landed. sidecar create shell --project sidecar --cwd ~/code/tui --name publisher starts a managed shell somewhere without changing which project owns it — the two facts are stated separately rather than inferred from each other. Relative paths resolve from the caller's directory, ~ from the caller's home, and the directory must exist before tmux or durable state is touched; the resolved path is the shell's live cwd, its recorded workDir, the provider launch cwd and the cold-restore cwd. --cwd always creates a managed workspace row and is refused with --split, whose live terminal has no durable shell record. Separately, sidecar agent prompt --json now carries receipt.submission (submitted, not_submitted, unknown), receipt.wait, and the pinned receipt.target, on success and inside the error envelope alike. A timeout after delivery is still exit 1 with code timeout, but it now proves the prompt was submitted and only the wait expired — so an agent reading the receipt knows not to send it again. unknown means a write or transport may have landed and is equally unsafe to retry automatically. (td-6b63bf)
Sidecar can bind a Hermes Agent session. sidecar agent integration install hermes drops a plugin into <hermes home>/plugins/sidecar-agent-state/ and adds one line to plugins.enabled in your config.yaml, so a pane running Hermes records which conversation it is running and a cold restart can offer to resume that exact one. The binding lands at process start rather than a turn in, which is earlier than any other agent Sidecar integrates with. Uninstall gives your config.yaml back byte for byte: it removes only Sidecar's own line, leaving your other plugins, your comments and your indentation where they were. Traced against hermes 0.17.0 and corroborated by hermes sessions list printing the same id. (td-73c4ff)
The plugin browser answers the pointer everywhere. Every protocol plugin's tab and pane now has the interactions the rest of Sidecar has: clicking a pane focuses it, clicking a row selects it and a second click opens it, the query row and the View control are click targets, the wheel scrolls the box under the pointer, both the table and the detail have draggable scrollbars, the gap between them is a drag rail whose split is remembered per plugin, and +/- resize it from the keyboard. Tab and Shift+Tab reach the browser through an opt-in focus-ring capability, so Files, Git and Notes keep their own Tab behaviour. The "no action here" flash is gone: an action that does not apply is absent from the hints and inert on its key. The rules are in the design language under Pointer parity. (td-62b81c)
Plugins declare filters, and a page that could not answer everything says why. A collection can declare up to eight choosers or text filters; the host draws them in the View modal, folds the applied scope into the sort pill, persists them with the tab, and sends list only the filters that differ from their defaults. A page may report what it omitted, a failed outcome, and per-source coverage rows; clicking the outcome word or a notice, or pressing c, opens a coverage modal with a Source, State, Elapsed and Reason table and a Retry button. outcome describes only the page's own rows. Recall is global first with a profile chooser over every configured profile, and no longer narrows to the Sidecar project on its own, which is what had made every documents source answer empty. --filter ID=VALUE reaches the same filters from sidecar plugin check, plugin call, sidecar open --plugin and the layout spec. (td-9ca6a7, td-786e42)
Query bars are one field with real editing keys. Every / search bar in Sidecar — the plugin browser, both workspace sidebars, doc search, the file browser's tree and content search, git history search and path filter, both notes searches, Sessions search and both terminal searches — is the same field, backed by the text input the modals use, so option-arrow, option-delete, home, end, cursor movement and paste work in all of them, a clickable × clears the query wherever the row has a hit map, Esc clears then blurs, and the arrow keys hand the keyboard to the list beneath. In the plugin browser, down from the field moves to row one, k on row one returns to the field with a key-repeat guard, and the detail follows the cursor after a short quiet period with a superseded load killed rather than left running — the rule the Files plugin already followed, now named in the design language. (td-e8cceb, td-45898b)
One selector control, and selectable text in every detail pane. The Create Workspace kind chooser is now modal.Select in the modal library: segmented under five choices while they fit, a bordered ❯-cursor list at five or more, scrolling past a visible cap, disabled rows kept visible with their reason, and a border that follows focus like an input's; every View modal uses it, and a modal sizes to its widest control. The plugin browser's detail, td issue cards, resource cards and diff panes are selectable with the pointer — drag, double-click a word, triple-click a line — and alt+c or super+c copies to both the system clipboard and the terminal over OSC 52, through the same engine file panes already used; the help modal names the shift or option bypass for the terminal's own selection. (td-21aafa, td-c6904c, td-2b8f79)
A plugin authoring guide and a Plugins page. docs/guides/active/creating-plugins.md takes an author from a CLI to a plugin that passes sidecar plugin check, with a runnable Python example under docs/guides/examples/hello-plugin/ that a test keeps honest; docs/reference/plugin-protocol.md is the single authority for the frozen contract; and the documentation site's Plugins page replaces the Terminal Resources page, which described a configuration shape that never existed, with the sidecar plugin verbs added to the site's CLI reference. (td-40eb97, td-ade3a3)
Configuration -> Agents -> Integrations is a table. One line per agent Sidecar can install for, with its name, its status, and Install, Update, Repair and Remove always in the same column, painted dimmed on the rows where the service would refuse them. Moving the cursor changes only which row is highlighted, and every action that is on offer can be clicked on any row without selecting that row first. Wide terminals also get the authority tier and the file the integration lives in; narrow ones keep the actions and drop the rest. Underneath the table, a fixed-height detail box follows the cursor with that agent's files, tier and demotion reason, CLI path and version, last lifecycle report, and diagnostic. Agents Sidecar has surveyed and ships nothing for collapse into one line instead of taking a row each. The count of an agent's known gaps is gone: they are gaps in that agent's own hook contract rather than faults in Sidecar, and the page names sidecar agent integration status <agent>, which lists them. (td-20e857)
External plugins are hosted without turning a flag on first. plugin_protocol and terminal_resource_providers both default on. An install with nothing configured still starts nothing — no describe pass is even scheduled when no plugin or provider is configured for the section a flag governs — and turning a flag off still stops every one of that section's child processes while leaving the configuration in place. terminalResources.providers is now a read-only alias of the plugin configuration: it is still read and still dispatched on the frozen sidecar.terminal-resource/v1 identifier, and Sidecar no longer writes the section, so it stays exactly as you wrote it. plugins.<id>.enabled is the documented switch for the embedded panels, with the tasks_plugin and notes_plugin feature flags as read-only aliases for one more minor release: each answers only while its config key is absent, and the Feature Flags page now reports the panel's own answer rather than the flag, so it can no longer disagree with the Panels page. (td-944274)
An empty plugin detail box shows the plugin's next collection. In a Tab placement, where the box stands beside the list, a plugin with a second collection gets that collection drawn there — its title, what it currently says, and its first rows — instead of a card of help text. A plugin's next collection is usually the ledger that explains the list, so "no matches" and "why" are on screen together and an abstained page can be checked where it is read. It is listed once, without a query, and never when its search is required; a plugin with one collection keeps the help line, and a pane, which shows one shape at a time, is unchanged. (td-6c49c5)
A narrow plugin list keeps its rank beside the name. Below the table floor a row still reflows onto two lines, but the columns declared before the primary — a rank, an index — now stay with the primary on line one, and the remaining short columns, the status label and the secondary text fold into line two indented under the name. A list of ranked results reads down its names with the numbers still attached, which is what the M0 mockup drew. (td-6c49c5)
status.label has a stated render bound. The reserved status column never grows past 24 characters, and the plugin protocol reference now says so in its Limits table as the protocol's own bound, beside the frozen 64-character wire bound it does not replace. A plugin author can pick a label that reads rather than discovering the truncation in a pane. (td-6c49c5)
A plugin row now expands under the scope it was found in. get carries params.filters — the applied filter set of the list that produced the row, sent exactly as that list sent it, narrowed against the collection's declaration at the same process boundary. Enter on a row hands the scope to the tab that opens, a restored row tab carries the scope it recorded, sidecar open --plugin ID --collection C ROW --filter id=value and a layout spec row carry theirs, and sidecar plugin check --get and plugin call get take --filter too. The host's get cache is keyed by the applied set, so the same row under two scopes is two questions rather than one cached answer. Without this a row found under a raised-sensitivity profile expanded under the plugin's default profile, which could be a different document or a refusal. (td-6c49c5)
The plugin protocol is frozen as sidecar.plugin/v1. Sidecar sends that identifier and validates the answer against it strictly: there is no alias, and a plugin still answering the pre-freeze sidecar.plugin/v1-draft is a protocol failure with a named reason rather than a silent downgrade. Tolerance belongs on the plugin side — a plugin that accepts either identifier on a request and answers with whichever it was asked keeps working with a Sidecar released before the freeze and with every one after it. The canonical request and response JSON under internal/pluginhost/testdata/protocol/, the reference fixture, the reference, the authoring guide, the runnable example, and the generated CLI reference all name the frozen identifier. (td-6c49c5)
Every agent Sidecar can recognise is now an agent it can start, and you can add your own without waiting for a release. Twelve families gained a launch command: Cline, Devin, Droid, Hermes, Kilo, Kimi, Kiro, Maki, Qoder, Qwen, plus OMP and Mastra Code, which have their first Sidecar identity here. Each one's command, auto-approve flag and resume arguments were read from the provider's own documentation or --help, and where a provider has no auto-approve flag the catalog says so instead of guessing one. The catalog itself moved from Go code to one TOML file per family, embedded in the binary, so a family is a file rather than a rebuild-shaped change.
You can override an agent or add one of your own. Drop a .toml file into agents/ beside your config file (~/.config/sidecar/agents/ by default) and it joins the catalog at startup. A file named after a family Sidecar ships overrides only the fields it states, so command = "claude-next" is a complete file and Claude keeps everything else; a file with a new name adds a whole family, launchable and resumable, at the end of the creation picker. A malformed file is reported and skipped rather than stopping Sidecar. See docs/reference/agent-catalog.md.
The creation pickers offer the agents you actually have. Create Workspace, Create Shell and the Sessions create now list a family when its command is on your PATH, or when you have named it in plugins.workspace.agents. Naming one still offers it whether or not it is installed. Configuration → Agents keeps listing every agent Sidecar knows, marking the ones that are not installed, so nothing is hidden from you, and the CLI still launches any family by name, installed or not. The PATH lookup happens once per process, off every render path.
Kilo Code reports its own lifecycle to Sidecar. sidecar agent integration install kilo writes one Sidecar-owned plugin into Kilo's config directory ($XDG_CONFIG_HOME/kilo/plugin, or $KILO_CONFIG_DIR/plugin), and from then on a Kilo pane's working, blocked, unblocked, idle and session-identity transitions come from Kilo itself rather than from reading its screen. It installs at the advisory tier on real traces of kilo 7.5.9, which is the ceiling this integration can reach: a user interrupt and a provider failure reach Kilo's bus as the same event with a different name, so a cancelled turn is not distinguishable from a failed one, and nothing releases the lane on exit. Kilo loads both plugin/ and plugins/, so a copy in each would report every event twice; Sidecar owns one of them and reports anything with its asset's name in the other as needing repair. Install, inspect, repair and uninstall go through the same adapter contract as the other integrations, so --dry-run shows the exact operations and uninstall removes only what Sidecar owns. It reports lifecycle facts only: never prompts, responses, tool data, paths or credentials.
Kimi Code CLI reports its own lifecycle to Sidecar. sidecar agent integration install kimi adds twelve hook entries to Kimi's own config.toml ($KIMI_CODE_HOME/config.toml, or ~/.kimi-code/config.toml), and from then on a Kimi pane's working, blocked and idle transitions come from Kimi itself rather than from reading its screen. It installs at the advisory tier on real traces of kimi-code 0.40.1, covering work start, tool use, blocking, unblocking, turn completion and cancellation. Blocking is genuinely first-class here: one PermissionResult event fires whether you approve or deny, so a Kimi pane cannot get stuck showing blocked the way an agent with no denial event can. Two things are deliberately not claimed — process exit, because Sidecar already owns process liveness, and session binding, which needs Kimi to become a launchable agent family first. Sidecar owns only the block between its own two marker comments: it refuses to touch a hook of yours placed inside that block, refuses to leave a stray copy of its own outside it, and an uninstall leaves the rest of your config.toml byte-identical. It reports lifecycle facts only: never prompts, responses, tool data, paths or credentials. (td-b0e19a)
OMP (oh-my-pi) reports its own lifecycle to Sidecar. sidecar agent integration install omp drops one Sidecar-owned extension into OMP's own extensions directory (~/.omp/agent/extensions, or wherever PI_CONFIG_DIR, an OMP profile or PI_CODING_AGENT_DIR puts it), and from then on an OMP pane's working, blocked, unblocked, idle and session-identity transitions come from OMP itself rather than from reading its screen. It installs at the advisory tier on real traces of omp 18.1.8. Blocking is first-class: one tool_approval_resolved event fires whether you approve or deny, so an OMP pane cannot get stuck showing blocked, and the ask tool blocks the pane on a question of its own. A turn that ends is not published immediately -- OMP can end one run and start another at once, so idle is debounced, and a provider error OMP is about to retry is held at working for a grace period rather than shown as a block you would have to do something about. Two things are deliberately not claimed: cancellation, which OMP does distinguish but its upstream mapping does not read, and process exit, which OMP's shutdown event carries nothing to support. Sidecar refuses to install when PI_CODING_AGENT_DIR has collapsed Pi's and OMP's extension directories into one, because both agents would then load both extensions and each would be reporting the other's pane; it says so on the status as well as in the refusal. Uninstall removes only the file Sidecar wrote and leaves any neighbouring extension exactly as it found it. It reports lifecycle facts only: never prompts, responses, tool data, question text, provider error text, paths or credentials. (td-11db02)
The Devin CLI binds its pane to the exact conversation running in it. sidecar agent integration install devin adds one session-identity hook entry to Devin's own config.json ($XDG_CONFIG_HOME/devin/config.json, or ~/.config/devin/config.json) under each of the six events Devin can carry a session id on, so a cold restart can offer to resume that exact conversation rather than guessing which one the directory belongs to. State still comes from reading the pane, in Sidecar and in Herdr alike. Nothing is claimed on trust: the entry ships untraced, at screen fallback, because no released Devin was available to fire it here, and sidecar agent integration status devin says so in its own words. Sidecar owns only its own entries: an entry of yours that merely resembles one is never adopted, overwritten or deleted, your file is backed up before it is rewritten, and an uninstall leaves the rest of it exactly as it was. It reports the conversation's identifier only: never prompts, responses, tool data or credentials. (td-73c4ff)
Droid binds its pane to the exact conversation running in it. sidecar agent integration install droid adds one session-identity hook entry to ~/.factory/settings.json, so a cold restart can offer to resume that exact conversation. State still comes from reading the pane. If you keep a ~/.factory/hooks.json, Droid reads its hooks from there and ignores the ones in settings.json entirely, so sidecar agent integration status droid tells you the entry would never fire and names the file; Sidecar does not move your hooks between files for you. It ships untraced, at screen fallback, because no released Droid was available to fire it here. Sidecar owns only its own entry: yours is never adopted, overwritten or deleted, your file is backed up before it is rewritten, and an uninstall leaves the rest of it exactly as it was. (td-73c4ff)
The Qoder CLI binds its pane to the exact conversation running in it. sidecar agent integration install qodercli adds one session-identity hook entry to ~/.qoder/settings.json (or $QODER_CONFIG_DIR/settings.json), so a cold restart can offer to resume that exact conversation. State still comes from reading the pane. Only the user-level file is touched: a per-project copy would follow a checkout into other people's clones. It ships untraced, at screen fallback, because no released Qoder CLI was available to fire it here. Sidecar owns only its own entry, your file is backed up before it is rewritten, and an uninstall leaves the rest of it exactly as it was. (td-73c4ff)
Qwen Code binds its pane to the exact conversation running in it. sidecar agent integration install qwen adds one session-identity hook entry to ~/.qwen/settings.json (or $QWEN_HOME/settings.json), so a cold restart can offer to resume that exact conversation. State still comes from reading the pane. Only the user-level file is touched. It is the one of these four that is proved against a released build: qwen-code 0.23.0 fires the hook before you have even chosen a provider to authenticate with, so the binding lands the moment the session opens. Sidecar owns only its own entry, your file is backed up before it is rewritten, and an uninstall leaves the rest of it exactly as it was, including the keys Qwen itself writes there afterwards. (td-73c4ff)
Antigravity panes bind to their own conversation. sidecar agent integration install antigravity writes one PreInvocation entry into ~/.gemini/config/hooks.json under a sidecar hook block, so a managed shell running agy records exactly which Antigravity conversation is in it and a cold restore can offer that one back. Antigravity has no session-start event, so the binding lands on the session's first model call rather than at startup. Lifecycle state still comes from screen detection, which is what the session-identity tier means. Uninstall removes only Sidecar's own entry, wherever in the file it ended up, and leaves every other named hook block untouched. (td-73c4ff)
GitHub Copilot CLI panes bind to their own session. sidecar agent integration install copilot writes one SessionStart entry into ~/.copilot/settings.json (or $COPILOT_HOME/settings.json), so a managed shell running copilot records which session is in it. Copilot is not installed on any machine Sidecar has surveyed, so this port is built entirely from Herdr's own installer and is untraced; sidecar agent integration status copilot says so in its known gaps rather than implying more confidence than there is. (td-73c4ff)
Cursor Agent panes bind to their own session. sidecar agent integration install cursor writes one sessionStart entry into ~/.cursor/hooks.json, so a managed shell running cursor-agent records which session is in it. The entry goes to ~/.cursor and not to CURSOR_CONFIG_DIR, because cursor-agent's hook loader reads the former and never consults the latter. A version header is written only into a hooks.json Sidecar creates, so uninstall gives a file you already had back exactly as it was. (td-73c4ff)
grok panes bind to their own session. sidecar agent integration install grok writes one SessionStart entry into ~/.grok/hooks/sidecar.json (or under $GROK_HOME), a directory grok merges every .json from, so a managed shell running grok records which session is in it. grok also loads hooks from ~/.claude/settings.json and ~/.cursor/hooks.json, so all three Sidecar entries fire inside one grok session and exactly one of them binds: a report whose claimed agent is not the one in the pane is refused rather than recorded. Uninstall removes only Sidecar's entry, so a hook you added to that file yourself is kept. One thing to expect: grok creates its session on your first prompt rather than at startup, so a pane you open and leave alone is not bound until you send something. (td-73c4ff)
Mastra Code reports its own lifecycle to Sidecar. sidecar agent integration install mastracode adds eleven hook entries to Mastra Code's own ~/.mastracode/hooks.json, and from then on a Mastra Code pane's working, blocked and idle transitions come from the agent itself rather than from reading its screen, along with which thread the pane is on. It installs at the advisory tier on real traces of mastracode 0.38.0, covering work start, tool use, blocking, unblocking, turn completion, cancellation and session binding. Blocking is first-class: one PermissionResult event fires whether you approve or deny, and the traced case is a denial, so a Mastra Code pane cannot get stuck showing blocked the way an agent with no denial event can. Two things are deliberately not claimed: process exit, because Mastra Code's SessionEnd carries no reason and could not tell an exit from any other ending, and sub-agent activity, which no traced turn produced. The session binding is on AgentStart rather than on the session-start event other agents use, because Mastra Code's own session-start event carries a placeholder rather than a thread id; binding it would have made every Mastra Code shell claim the same conversation. The three hooks on events where Mastra Code can refuse a turn are written so a Sidecar failure can never do that. Sidecar owns only the entries whose command is its own: your hooks in the same file are preserved in place, and an uninstall leaves the rest of hooks.json byte-identical. It reports lifecycle facts only: never prompts, responses, tool data, paths or credentials. (td-c060a5)
Porting an agent integration from Herdr is a written procedure. .claude/skills/port-herdr-integration/SKILL.md takes an agent from "upstream moved" to a merged port in eleven steps: reading the weekly sync report, picking the port shape, keeping the provider's own knowledge verbatim while swapping the transport, the ownership rules an installer has to obey against somebody else's configuration file, every registry the port has to appear in with the test that guards each, how a capability tier is earned from traces rather than copied from Herdr's table, and the hazards a live proof run hits. docs/guides/active/adding-new-agent-clis.md points at it before Step 4's hand-written path, so a provider Herdr already covers is not built twice. (td-615d9f)
The session-identity installer's shared test suite covers all eight agents. The suite that pins how an integration behaves against a configuration file you already own -- it never adopts an entry that merely resembles Sidecar's, never writes through a symlink, converges on exactly its own entries when repairing, and gives your file back when it uninstalls -- was discovering its subjects from a hard-coded list of four. Devin, Droid, the Qoder CLI and Qwen Code had been outside all nine of those checks since they shipped. They are in now, and the list cannot go stale again because the suite discovers agents the same way the shipped code does. (td-615d9f)
The Herdr sync review cannot report a ported agent as unported. The weekly review's integration table reads which agents Sidecar has ported from the port records themselves, and nothing had been checking that the two line up. They line up by Herdr's agent id, which is not always its directory name, so a mismatch would have silently listed a working integration as one nobody has built. (td-615d9f)
The Integrations table's rows give way before its detail box does. The Configuration detail pane truncates at its bottom edge, so a page taller than the pane lost whatever was last -- which on this route was the detail box the cursor drives, the outcome of the last install, and the closing note. The rows now window instead: the column header above them and everything below them stays pinned, the rows scroll to keep the cursor's own row on screen, and a row the window hid takes its action pills' click targets with it. On a pane too short for any window to make the whole page fit -- fifteen agents on a 60x24 terminal -- the rows still give way down to three, and the rest of the page truncates as it did before, so the cursor's own row is on screen at every size. (td-20e857, td-73c4ff)
A truncated path in Configuration keeps its filename. clampEnd passed the target width to ansi.TruncateLeft, whose count is how many columns to remove rather than how many to keep, so a 43-column path in the Projects page's path column came back as ten columns rather than the thirty-four it had room for. A column exactly one wide came back empty for the same reason, because the ellipsis was TruncateLeft's own prefix and it writes that only when the string is longer than the count. (td-20e857)
Saving no longer drops a plugin section Sidecar does not manage. A key under plugins that this build does not know — one written by a newer Sidecar, or by hand — used to be discarded the next time anything saved the configuration. It is now merged, the way an unknown top-level key already was. The first save after upgrading sorts the subkeys under plugins once, the way the top level of the file has always been sorted, so expect one cosmetic diff there and no lost settings. An external plugin id that one of Sidecar's own surfaces already answers to (tasks, notes, td-monitor, git-status, file-browser, conversations, workspace-manager, sessions, activity) is refused by sidecar plugin add and by configuration validation, naming the surface, instead of painting two tabs with one identity. (td-944274)
An integration for an agent Sidecar can recognise but not start can now bind the conversation in its pane. sidecar agent report-session --kind KIND resolved the kind through the launchable agent families only, so a hook for a detection-only family was refused as an agent kind Sidecar does not know, even when Sidecar itself had installed the integration sending it. The lookup now also consults the families Sidecar recognises in a pane, which is the right question for a verb that reports about a pane rather than about a launch.
A relocated Claude Code is found by the integration installer. sidecar agent integration status|install|uninstall claude now resolves Claude's configuration home the way Claude itself does, from CLAUDE_CONFIG_DIR when that names an absolute directory and ~/.claude otherwise. Before this, a user who moved that directory saw not-installed for a Claude full of hooks, an install that wrote its entry where Claude never reads, and an uninstall that could not find its own entry. A relative or whitespace-only value keeps the default, because Claude refuses to run at all when the resolved home is not absolute. (td-73c4ff)
A worktree created non-interactively can now be deleted non-interactively through the same lifecycle as the TUI. sidecar worktree delete TARGET --plan --json reports the exact checkout, dirtiness, remote availability, branch-cleanup choices, and pinned branch and HEAD without changing anything; using the returned absolute path and re-running with --expect-branch BRANCH --expect-head-oid OID --yes closes its Sidecar worktree session and rooted managed shells before removing the directory. Local and remote branch cleanup remain explicit flags, as the confirmation's unchecked boxes are, and a failed create's exact pending-creation journal is cleared only after deletion succeeds. A rooted shell that refuses teardown is reported as a warning after the checkout is removed, while requested branch and journal cleanup still finish. The shared refusal rules still protect main, bare, detached, locked, missing, and prunable worktrees. (td-85b0c4)
Nothing published for this version
Nothing published for this version
Nothing published for this version
3085db8 feat(agentactivity): score the process tree past generic runtimes
Pi reports its own lifecycle to Sidecar. sidecar agent integration install pi writes one Sidecar-owned extension into Pi's user-level extension directory ($PI_CODING_AGENT_DIR/extensions, or ~/.pi/agent/extensions), and from then on a Pi pane's working, idle and session-identity transitions come from Pi itself rather than from reading its screen. It installs at the advisory tier on real traces of pi 0.84.3, which is the ceiling Pi can reach: it ships no permission system, so a blocked lane does not exist to be reported. Turn completion is taken from agent_settled and never from agent_end, because Pi can follow agent_end with an automatic retry or a compaction; tool use and process exit are deliberately not claimed. Install, inspect, repair and uninstall go through the same adapter contract as the other integrations, so --dry-run shows the exact ops and uninstall removes only the file Sidecar owns. It reports lifecycle facts only: never prompts, responses, tool data, paths or credentials.
An agent installed as a plain #!/usr/bin/env node shim now gets a state badge. Detection used to match the pane's argv[0] basename only, so an npm-installed CLI left the interpreter in argv[0], tmux reported node, and neither identity input named the agent — the pane was never claimed at all. Sidecar now scores the whole foreground process group the way Herdr does: it knows the generic runtimes (sh bash zsh fish tmux node bun cmd powershell pwsh, plus python[3[.N]]), unwraps one using its own argv, prefers the process group leader, and ranks a non-runtime match above a runtime one. A pane running node /usr/local/bin/qwen is Qwen. Node package layouts upstream recognises by path are recognised too, so a Pi or Qwen CLI launched by its dist/cli.js is named without guessing from screen text.
sidecar agent start --kind pi no longer times out. Pi rewrites its own process title, so tmux reports the pane as node while the process is really named pi; Pi's process check accepted only the literal pi and refused its own correctly identified pane before a single detection rule ran. A resolved process identity now settles which provider a pane is, and it settles it in both directions — the same check stops one agent's detection rules being evaluated against another agent's screen, which a bare node allowance previously permitted. Pi deliberately gains no bare-node allowance of its own: its published rules are a single literal "Working…", which on any Node pane would be a wrong answer rather than a missing one.
SIDECAR_AGENT names the agent in a pane where the process cannot be seen. Export it on a wrapper command — a container, a sandbox, anything that hides the real process — and Sidecar uses the named provider's detection rules for that pane. It is a hint and nothing more: real process evidence always wins over it, and it can never grant or revoke an agent's lifecycle authority, so setting it in someone else's pane cannot switch off their session binding. One bound on macOS: a wrapper that is a SIP-protected system binary, such as sandbox-exec or ssh, publishes no readable environment to anyone, so it cannot be hinted through; a wrapper you installed yourself can.
0f111ff chore(deps): pin tasks v1.17.0
The Git tab reads a remote-bound project's repository. Bound to [host] Project from @, Git shows that machine's changed files with their staged, unstaged and untracked state, its branch and upstream, its ahead/behind counts, its repository state (merging, rebasing, detached), its commit list and graph, its commit details, and its stash and branch lists. Patches come from the host and render through this machine's own diff parser and viewer, so side-by-side, wrap, the minimap and the full-screen diff are unchanged. Author and path filters run on the host, over its whole log; a subject search runs here over the rows already loaded, the same split a local project makes. Every write refuses by name and changes nothing on either machine: stage, unstage, commit, amend, discard, push, pull, fetch, branch switch, stash, init and open-in-editor all name the host, and the footer offers only what the surface can do. Open in GitHub, yank commit and yank id work, because the remote URL is the host's fact and the browser and clipboard are this machine's. There is no watcher across the boundary: status refreshes on r and on the host's own snapshot signal. A bound workspace the host cannot resolve as a worktree says so instead of falling through to this machine's offer to run git init. Hosts serve it with the new read-only sidecar repo status|diff|history|commit|refs, advertised as repoReadV1; a host too old for it says so and names the machine to update. Writing to a bound repository is not part of this: it is a refusal table with a row per gesture, waiting for host verbs that do not exist yet. (td-7a1393)
The Files tab browses a remote-bound project. Selecting [host] Project from @ already bound this TUI to that host; Files now shows that machine's tree and that machine's file bytes, never a same-named checkout on this disk. Directories expand against the host, and opening the tree costs one round trip for the root plus everything you had expanded rather than one per level. ctrl+p finds by name from the host's own catalog. Writes, git blame, file info, project search, and reveal-in-file-manager have no host verb behind them and refuse naming the host rather than silently doing nothing — the footer offers only what the surface can actually do. There is no filesystem watcher across the boundary: the tree refreshes on the host's own snapshot and on r, and claims nothing more. Hosts serve it with the new read-only sidecar content tree, advertised as contentTreeV1; a host too old for it says so and names the machine to update. (td-bc57bb)
A bound @ destination is the screen for relayed sidecar open / layout. Selecting [host] Project already bound this TUI as that host's project workspace; a host agent that runs sidecar open or sidecar layout now lands on that workspace when this instance holds the geometry lease, not only on Sessions and never on a same-named local twin. Sessions landing is unchanged when you are actually looking at that row. Off-screen still declines and never queues. (td-af932a)
Sessions can hide a remote's rows, and says so on the control that brings them back. The View flyout grows a ⇅ show remotes toggle with a checkbox per registered machine, so a busy host can be set aside without disconnecting it or editing config — the connection stays up and its notifications still arrive. A hidden machine leaves both projections at once, health row included, and the sort pill carries a struck host glyph (with a count once more than one machine is hidden) so missing rows are never a mystery. The choice persists, and it is dropped for a machine that is later de-registered. Hiding is not deleting: the machine's rows stay in the catalog, so pins, live terminal splits and the remembered selection all survive being hidden and come back with it — while its rows, cards, create targets and relayed pane requests are all off-screen for as long as it is. The flyout's Filter: none line is gone from both the global and project surfaces; the filter is reported only when a query is actually doing something.
Every agent's state detection now runs Herdr's published detection manifests instead of Sidecar's hand-written rules. All ten providers Sidecar identifies — Claude Code, Codex, OpenCode, Cursor, Grok, Pi, Copilot, Amp, Antigravity and Muse — are classified by the vendored manifests, so an upstream rule fix arrives without anyone translating a regex. Claude's half-circle busy spinner (2.1.228 and newer) is recognised, so a working pane no longer has to be inferred from the absence of an idle prompt; MCP elicitation dialogs, the /btw overlay, "Waiting for N background agents" and "N MCP tasks still running" read as work in progress; a Muse pane sitting on an unanswered "Do you trust this workspace?" prompt reads as blocked instead of as a finished turn; a Copilot pane waiting for background agents reads as working; Cursor's approval prompts are recognised by the control lines every one of them renders rather than by a regex per prompt shape, so a prompt Cursor adds next release is already covered; and Codex's first-run "Do you trust the contents of this directory?" prompt, a Claude "Allow Claude to use …?" permission prompt, and a Codex approval prompt whose option line carries the composer glyph all read as blocked rather than idle. The things Sidecar knows that upstream does not are kept as data overlays with a fixture each — Claude's model picker still holds the prior state instead of reading as a finished turn, a Codex turn parked on a background terminal or in the middle of a tool call still reads as working, and an Antigravity pane on an unanswered "requesting permission for:" prompt reads as blocked — and the pane's status is still gated by Sidecar's stricter process check before any rule is evaluated.
From a Sidecar-managed pane on a host you are viewing, sidecar open and sidecar layout land on this machine. The geometry lease names the screen: if you are looking at that Sessions row from here, the pane opens here; if you walked over to the host's own Sidecar, the open stays there. Relayed open never queues — a row that is not on screen, or a lease holder that is disconnected or too old, exits 4 with the reason rather than applying later against whatever is selected. There is still no sidecar open --host; an agent on the host runs the same command it runs locally. (td-3d2e0d, td-4971ac, td-716ba6, td-15344e, td-4c955f)
n on a remote Sessions row lists that host's files, diffs, issues, and notes, and a Terminal split creates the tmux session on the host. Pickers stay empty until the host catalog arrives rather than filling from a same-named local twin. Resource rows come from the host's content describe matchers, not this machine's provider snapshot. layout apply of a new kind: shell pane uses the same host tmux path. (td-3fe778)
A click in a remote Sessions pane opens that host's file, issue, note, diff, or resource, not a same-named local twin. Selecting a registered host's row and clicking a reference resolves and loads on the machine that owns the workspace, then renders here in the same Document, Issue, Note, Diff, and Resource panes local workspaces use. Nested links stay on that host; a failed remote read never falls back to this filesystem; HTTP(S) still opens in the local browser. The host must advertise ContentReadV1 — an older Sidecar still streams its terminals, and the click names the machine to update. There is no sidecar open --host; sidecar content resolve|read|describe is the internal transport over the SSH connection already held. Inline edit, file finder, and project search stay unavailable on a remote source, because those would walk this machine. (td-89c1cb, td-87358d, td-925cf9)
A host whose login shell prints one line to stdout is no longer stuck at "not-protocol". The serve stream refused the first non-JSON line, so a motd, a version nag, or a wrapper's log line meant no hello, no snapshot, no Sessions rows and no @ destinations for that machine — with an error naming the exact cause it would not handle. Output before the first protocol message is now skipped, bounded, including JSON that is not a protocol message so a profile logging {"level":"info"} cannot be mistaken for the stream starting. Nothing is skipped after the stream has proven itself, and a stream that is simply not this protocol still fails, quoting what the host actually wrote. A version this viewer refuses still reports as a version mismatch. (td-055768)
A Claude pane waiting on background subagents no longer announces that the turn is done. Claude Code 2.1.257 reports that state in two places and Sidecar was reading neither: the Waiting for N background agents to finish row is no longer the last line above the prompt box once an Update installed · Restart to update banner sits between them, and the footer that used to say · N shells · now says · ← N agents ·. With both signals missed, the prompt box alone read as a finished turn. Both are read now, and a waiting row left in the scrollback by an earlier turn does not hold the pane on the working lane.
A pane's detection window no longer drops its topmost visible row. Detection reads the last N rows of a capture where N is the pane's height, and the newline that terminates a capture was being counted as a row — so on a real tmux capture, which pads the visible region to the full pane height and then terminates with a newline, the window started one row too low. Rules anchored on the first row of the screen, such as Codex's first-run trust prompt, could not match.
The agent verbs resolve a sibling worktree from a managed shell or worktree session without flags, and refusals name the fix. An explicit target is still searched across every registered project, but when the same name exists in several, the caller's own project breaks the tie — the one SIDECAR_SHELL belongs to, or the one owning the worktree session tmux reports; outside a Sidecar session the ambiguity lists the projects and names --project / --shell. --project now also accepts what create worktree --json hands back — the worktree's path or basename resolves to the project that created it — and the create results carry project, the slug every verb accepts. A read-only lookup with --project no longer refuses because several Sidecar instances are showing that project; that check belongs to open, which has to land on one of them. (td-c906c1)
sidecar agent list reports each live pane once, under the project that owns it. Every registered project that could see a worktree's checkout used to emit it — a worktree opened as its own project, a subdirectory of the repository registered as a project, a state directory with no checkout path at all — so one pane appeared under six project keys, twice with no name, and an explicit worktree target was "ambiguous across 3 Sidecar sessions". Each worktree root now has one owner: the project that created it, then the project whose checkout it is, then a project that merely discovered it through Git; a project with no checkout path owns no worktree rather than claiming the working directory. (td-ebd72c)
create worktree and create shell start a catalog family with provider arguments, and create worktree --agent with --run records the family. Arguments after -- follow the family's launch command, as agent start -- ARGS takes them: sidecar create worktree orchestrate --agent claude -- --model fable; the name comes before --, and a configured launch override that contains shell syntax refuses them rather than handing them to the wrong process. --agent with --run on a worktree is now the layering create shell had — the family is recorded, the caller's command owns the launch — instead of a refusal that left --run "claude --model fable" with no agentType on record. Usage refusals under --json are {"error":{"code":"usage",...}} on stderr rather than a prose line and the full help text. (td-a658ed)
sidecar agent read without --source now reads the visible screen, matching its documented default. Omitting the flag previously failed with source "" is not a terminal capture. (td-152978)
Sessions no longer lists a remote project's main checkout just because Sidecar is running there. sidecar host serve cannot exclude the host's own TUI pane the way a local collector excludes TMUX_PANE, so that pane's cwd used to mark the main worktree LIVE. The TUI is chrome, not a session, and a remote main checkout with no agent is hidden the same way the project sidebar already hides it locally. Linked worktrees, managed shells, and an agent actually running on main still appear.
The manifest_detection flag is gone. It ran Herdr's rules in shadow beside Sidecar's own and logged the differences; with every provider now classified by the manifests there is no second lane to compare against, so the flag, its entry on the flags page, and the shadow log it wrote have all been removed.
Tmux 3.4 is now Sidecar's explicit compatibility floor, with 3.7c continuously tested. One checksum-pinned manifest drives local source builds and an oldest/latest CI matrix, including real private-server coverage for control mode, terminal rendering, paste, metadata and shell lifecycle. A separate latest-client/minimum-server proof models a Homebrew upgrade without touching the live default server and verifies capture fallback when tmux explicitly declines cross-version control mode. Future stable upgrades are one manifest change plus the same repeatable proof. (td-22399a)
project drop and project reopen join project complete, a closed project is stamped on the section itself, and the Tasks tab hides closed projects until C reveals them.Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
The read-only worktree planning proof now ignores Git's transient bookkeeping wherever the fixture repository sits beneath its snapshot root. Backgrou
Release verification no longer intermittently loses its private tmux server between agent-control integration tests. The suite now keeps one inert session alive for the package lifetime, so one test cleaning up its last working session cannot race the next test's server startup. The package still uses its own socket and tears down only that isolated server.
The remote-host reconnect test now observes a stable recovered connection instead of a transient state. Its successful fake stream stays open like the real host protocol, so loaded CI cannot miss the online state between an immediate end-of-stream and the following reconnect.
Shells lost to a tmux restart come back. After a reboot or a tmux server crash, Sidecar recreates the managed shells that were running, under their own names and in their own working directories, once the first frame is on screen. sidecar session status shows what it would do before it does anything — every shell named as reattach, recreate-shell, resume-agent, manual, skip or refuse, with the reason and whether it would run an agent — and sidecar session restore performs exactly that plan, with --dry-run, --shell, --agents and --yes. sidecar session policy sets it per shell (--inherit, --shell, --resume, --never), so a long-running server, a disposable helper and a sensitive agent session can differ without changing the machine default. Nothing arbitrary is replayed: a --run command, dev server or test watcher is never restarted, a working directory that no longer exists is a refusal rather than a fallback to some other directory, and a tmux session name held by something else is a refusal rather than something Sidecar closes to take the name. Conversations are a separate decision from terminals: plugins.workspace.sessionRestore.resumeAgents defaults to ask, so a reboot restores your shells and then asks once, in one grouped summary, before resuming anything that can spend money or change a repository. (td-e78e17)
A Sidecar-managed shell can be bound to the exact agent conversation running in it. A provider's own hook calls sidecar agent report-session --kind KIND (--id ID | --path ABS_PATH), and Sidecar records which native conversation that pane is in. That binding is what makes a cold restart able to offer to resume that conversation, and what makes sidecar agent read --source transcript return it. Sidecar never guesses one: an unbound shell gets transcript_unavailable, because "the newest conversation in this directory" is wrong often enough to matter and looks identical to being right. Session values are redacted by default — agent list and agent get report only whether a shell is bound and whether an official integration vouched for it, and the value appears for your own shell or with --include-session-ref, since list output routinely lands in logs and CI artifacts. shells.json moves to schema version 3 to hold the binding, additively: a record that has never run an agent serializes exactly as version 2 wrote it. (td-8ec2cc)
Codex and Claude Code can tell Sidecar which conversation they are in. sidecar agent integration install codex (or claude) adds one Sidecar-owned entry to that provider's own hook configuration, and from then on each new session reports its identity to the shell it is running in. They install at session-identity tier and stay there: these hooks say which conversation is running, never what state it is in, so screen and process detection remain the only authority for whether an agent is working, blocked, or done. Installation preserves every unrelated hook and every unrelated setting, --dry-run shows the exact ops, and uninstall removes only Sidecar's entry. Codex needs its hook trusted before it will run: Sidecar writes the trust record itself, and if that ever stops matching, the failure is Codex's own visible one-time "Hooks need review" prompt rather than a hook that silently never fires. (td-8ec2cc)
Every agent resume command now comes from one registry. The Conversations plugin had the only table of how to resume each provider, as a switch building shell strings. It now lives in agentcatalog as structured argv, so the Conversations UI, the CLI, and cold restore share it and cannot drift, and a session identifier is an argument vector entry rather than text spliced into a command line. A reported identifier that would read as a flag is refused outright, both when it is validated and again when a resume is built from it. The UI keeps its current user-confirmed behavior and the command you see is unchanged. (td-8ec2cc)
Agent state can come from deterministic provider integrations instead of screen inference alone. The new lifecycle contract records working, idle, blocked and terminal outcomes in a bounded JSONL store, resolves competing sources through one authority policy, and exposes the result through sidecar agent report, end, release and explain. The bundled OpenCode integration reports full lifecycle state; Codex and Claude Code report session identity. Install, inspect, update, repair and remove integrations from Configuration or the matching sidecar agent integration commands, all through the same service. Integrations report lifecycle facts only: they never send prompts, responses, tool data, paths, credentials, or notification policy. (td-43a93f)
A managed agent can be driven without taking over its terminal. Behind the agent_control feature flag, sidecar agent start, prompt, wait, read and send-keys operate on one pinned shell, validate the complete request before writing anything, and refuse when the pane is busy, replaced, blocked, or owned by something else. Prompt delivery uses the same ordered paste and key encoding as the embedded terminal, waits use a pooled tmux control client with bounded polling fallback, and read offers visible, recent, unwrapped, detection and transcript sources. This is the non-interactive counterpart to watching and answering an agent in the TUI. (td-7de1af)
A pane you have opened can be moved. M from any pane — or the new ⊞ button on the pane header, left of the close × — opens one reposition modal: h/j/k/l move a draft of the layout, z zooms, enter commits the whole sequence atomically, esc discards it. The pane is pulled out and grafted back, so its tabs, scroll position, selection and any live terminal travel with it, and so does the share of the box you dragged it to. All three pane hosts have it: project Workspaces, the global Sessions browser, and the content decks beside Files, Git and Notes. M was chosen over m, which is already render and merge-workflow in two of the twelve pane contexts, and one key must mean one thing in every pane. (td-2ec104)
sidecar layout move gives agents the same capability in one call. sidecar layout move 2.1 --to 1.2 moves by cell, --to 3 appends to a column (opening one past the last), and --focused --to left|right|up|down uses the identical direction rule the modal's keys compile through, so the CLI and the keyboard cannot drift. It was always possible to rearrange a layout with layout get plus layout apply --spec, but that means reconstructing every pane on screen to move one of them, and every pane you reconstruct is a pane you can get wrong. Like get and apply it never queues, refuses rather than squeezing, and reports a move with nothing to do as unchanged (exit 0) rather than calling it moved. With --sessions it changes this machine's viewer tree even for a row whose workspace is on another host, and sends no layout mutation to that host. (td-2ec104)
Three shell verbs stopped being things only the TUI could do. sidecar shell rename --target <session> renames a shell you are not sitting in — until now rename resolved "which shell am I" from the ambient tmux environment, so there was no way to rename any other one. sidecar shell send --target <session> --run/--type <command> sends a command into an existing shell, matching the --run/--type split create shell already had. And sidecar create worktree --plan resolves a worktree plan and prints it as JSON without creating anything, so a caller can show branch, path, source OID and whether a setup hook will run before committing to it. Sidecar owns shells.json, so a capability reachable only through the TUI was a gap for every agent, not only for remote hosts. shell send guards its own boundary: the underlying send-keys has no protection beyond a blank-command check, so the verb refuses a session that is not a live record or a registered worktree session for the resolved project, and refuses one recorded on a different tmux server rather than typing into whatever answers that name on this one. (td-677dde)
A configured project can be used before it has ever been opened. --project resolved only through project state directories that already existed on disk, so a project listed in config.projects.list but never opened returned unknown project. It now falls back to the configured list and registers the project the same way first-open does. (td-677dde)
Sidecar can watch and drive sessions on another machine over SSH. Behind the sidecar_remote_hosts feature flag, installing Sidecar on the host and registering it with sidecar host add makes that machine's projects, shells, worktrees, agent states and live panes appear in Sessions beside local ones. Selecting a row opens the real remote tmux pane with history, search, selection and ordered input intact; cross-host geometry leases let either machine take back its own viewport by typing. There is no daemon or listening port: Sidecar starts an ephemeral host process over SSH stdio, reuses tmux control mode for pane traffic, and names unreachable, missing-binary, missing-tmux, login-output and version-skew failures with their fixes. The Remote Hosts configuration page and host add/list/set/remove/probe commands share one validated registry and apply changes without an app restart. (td-f10d6f, td-998e58, td-42b724, td-141917)
Notifications can reach you inside Sidecar and outside the terminal. Alerts now land in a persistent JSONL-backed notification centre with stacked toasts, an unread header indicator, keyboard and mouse actions, target links, per-source rules and a sidecar notify CLI for posting, listing, dismissing and configuring them. Native macOS and Linux notifications, built-in or custom sounds, quiet hours, background-only delivery and live config reload all run behind delivery adapters. A Sidecar running through SSH can forward a remote transition to the viewing machine or emit through the outer terminal, while deduplication and claim rules prevent the same event from alerting twice. (td-7b9ccc, td-eb6475, td-58679c)
Agents on another machine can be driven the same way as agents on this one. Every sidecar agent verb — list, get, start, prompt, wait, read, send-keys — plus session status and session restore now take --host ID and run on that registered host, as one sidecar <verb> --json invocation over the ssh connection Sidecar already keeps open. There is no second protocol and no new daemon: the verbs were headless, target-taking and --json from the start, so carrying them to another machine is transport rather than design. What you get back is the host's own answer, including its refusals — a blocked agent on another machine reports agent_blocked with the host's own sentence, not a local approximation of it, because the rules run on the machine that owns the pane. Two failures the local vocabulary could not describe honestly get their own codes: host_unavailable when the machine could not be reached at all (nothing was attempted, so trying later is the fix) and version_skew when the host's Sidecar does not know the verb (update one of the two binaries). Conversation identifiers stay where they belong: a remote agent list or agent get tells you whether a shell is bound and whether an official integration vouched for it, never what it is bound to, unless you ask with --include-session-ref. Nothing remote is ever written into this machine's shells.json. A remote verb requires an explicit target, because the "current shell" shorthand names a shell on this machine and two machines running a same-named project generate the same tmux session names. Cold restore runs on the host too — a viewer asks for it and watches, and never rebuilds another machine's state locally. Behind the existing sidecar_remote_hosts and agent_control flags. (td-a55114)
A tmux server crash no longer erases your shell records. When a tmux server dies, every managed session disappears in the same instant. Sidecar's liveness check asked, once per shell, "is this one gone?" — got a true-looking answer every time, and tombstoned the whole file. That is how a crashed server took a user's sidecar and braid shell lists with it. The liveness path no longer asks for a deletion in that situation at all: when no tmux server is running, or when a shell's last confirmed server is not the one running now, the record is kept and marked as something a restore can bring back. A shell that exits inside a server that is still up is still tombstoned, and still recoverable with sidecar shell restore, because that is a terminal you closed. Both paths that could delete were fixed, including the project workspace one, which reaped per shell with no protection at all. (td-e78e17)
A late report from an agent that has already exited can no longer overwrite its successor's session binding. This one was found by building the proof rather than by a test. The generation a reporting hook derives for itself falls back to the pane's root process when it cannot trace its own ancestry back to the pane, and the independently-derived "what occupies this pane now" falls back to the pane's root process when nothing is running there. Those are exactly the two conditions that hold for a hook left behind by a provider that has exited: it has been reparented away from the pane, and the pane is empty. Both sides fell back, compared equal, and the stale report was accepted — in precisely the case the check exists to reject. The reporting side now has no fallback: a hook that cannot prove which provider it belongs to is refused rather than believed. Two fallbacks agreeing is not evidence. (td-8ec2cc)
agent prompt with the text left off no longer prompts your own shell with a shell's name. sidecar agent prompt reviewer read reviewer as the prompt text and typed it into the shell the caller was sitting in, because one positional argument means "prompt this shell". A lone argument that resolves to a managed target is now a usage error naming what is missing. Empty or blank prompt text is a usage error too — it exited 5, the code for a semantic refusal, where every other malformed command line exits 2, and a caller could not tell "I built this wrong" from "the agent would not take it". (td-2cea15)
layout apply --json and layout move --json write only JSON to stdout. Both wrote the structured result object and then appended the human per-pane lines after it, so sidecar layout move 2.1 --to 1.2 --json | jq failed on the trailing text — which is the only reason to ask for --json at all. The flag's own help already promised "one structured result object to stdout", and layout get --json already kept that promise. The two projections are alternatives now: without --json you get the human lines and nothing else, with it you get the object and nothing else. Both carry the same per-item verdicts, cells and reasons, so nothing is lost by choosing one. (td-0e2d12)
The reposition modal asks before discarding a live inline edit. Opening it released every input surface the content deck owns, and for an inline edit that release killed the tmux session holding an unsaved buffer without a word. Every other caller of that release is a surface teardown — a plugin switch, a scope change, shutdown — where the editor has nowhere left to be drawn; the modal is the one caller that keeps the deck on screen and returns to it, so nothing forced the buffer to die. It now raises the editor's existing Save/Discard/Cancel dialog, the same one clicking away from the editor raises, and opens the modal on the pane you asked for once you have answered. Both doors onto the modal — M and the header ⊞ — inherit it. (td-0e2d12)
A power loss during an agent lifecycle report no longer costs two reports instead of one. The lifecycle log is append-only JSONL, and a machine dying mid-append leaves a final line with no newline. The next report was appended straight onto that fragment, welding two records into one line that neither could be read from — so the crash cost the report it interrupted and the next healthy one, while the write reported success. A pane that had reached its third report came back believing it was on its first. The log is now re-framed when it does not end where a line should. (td-b2370e)
Validation errors no longer exit with the code that means "you passed a bad flag." create worktree, create shell --name and shell rename --target returned exit 2 both for an unusable command line and for a perfectly well-formed one whose value was rejected — a branch that already exists, a display name already in use. A caller cannot tell those apart, and the remote-host viewer read the second as version skew and told the user to upgrade Sidecar. Input rejection is now exit 5; exit 2 keeps meaning a usage error. Documented in each command's exit-code table. (td-677dde)
A display name or worktree name starting with - works. shellstate.NormalizeName accepts a leading dash, so -wip was a legal name that the argument parser then read as an unknown option. shell rename --target, shell send and create worktree now stop flag parsing at --. (td-677dde)
notify config set is inside the isolation gate. It rewrites the whole config file but carried no mutating mark, leaving it the one mutating verb a misconfigured proof run could still drive against the real ~/.config/sidecar/config.json. (td-677dde)
Embedded terminal backgrounds no longer flicker, truncate, or flood at particular widths. Captures now preserve blank rows with capture-pane -N, every drawn cell takes its background from tmux, and Sidecar paints a row's carried background through its trailing cells without letting pane padding leak into the child's pen. The old content-based canvas heuristic is gone, so a terminal's colour no longer changes because its text or width happened to trigger a guess.
features.SidecarRemoteHosts, default off)Phase C: a remote host can be changed, not only watched. Creating a shell, creating a worktree through its confirmation, seeding an agent, and renaming either kind now work on a remote row from the Sessions browser. Mutations run as one-shot sidecar <verb> --json invocations over the ssh connection that already carries the observation stream, so the serve protocol stays one-directional and read-only by construction. Rows arrive through the host's next snapshot rather than being invented locally. Delete, merge and navigation still refuse — their implementations resolve paths against the local filesystem. That was also a live bug in O (open in Git), which had no guard at all and sent a remote path into a local worktree switch; on a machine with the same checkout layout that succeeds against the wrong repository. (td-677dde)
A confirmed worktree plan is pinned to its commit. create worktree --expect-source-oid OID refuses with exit 5 when the base ref no longer resolves to the confirmed commit, and the remote confirmation passes its plan's OID back on Create — so an agent pushing to the branch while the user reads the modal yields a refusal naming both commits, not a worktree silently built from the new head. The local modal already had this guard from executing its stored plan; the remote path re-ran the command from raw arguments and did not. Also from the pre-merge review: a setup hook failing with its own command not found is no longer misread as an uninstalled Sidecar, a login profile emitting 32+ structured-log lines can no longer push a successful result out of the decode window, host-derived error text is stripped of terminal escape bytes before display, disabled hosts refuse mutations up front by name instead of failing as "removed or retargeted", the rename modal shows an in-flight state and swallows the double Enter that raced two renames on the host, a stale reply from one host no longer clears another host's pending selection, switching the create form from a local to a remote project clears the local repo's branch list, and Merge is hidden on remote rows rather than offered and then refused. (td-677dde)
Rendered Markdown in Files can be selected and copied as rendered text. Drag selection now works in both the primary Files preview and file panes open
Rendered Markdown in Files can be selected and copied as rendered text. Drag selection now works in both the primary Files preview and file panes opened beside it, including rows whose layout differs from the Markdown source. Copying sends ANSI-free visible text to the native and terminal clipboards, while link clicks, drag-over-link selection, scrollbars, raw previews, and collapsed-tree previews keep their existing behavior. (td-a2b617)
Release verification no longer intermittently loses its private tmux server between agent-control integration tests. The suite now keeps one inert session alive for the package lifetime, so one test cleaning up its last working session cannot race the next test's server startup. The package still uses its own socket and tears down only that isolated server.
The remote-host reconnect test now observes a stable recovered connection instead of a transient state. Its successful fake stream stays open like the real host protocol, so loaded CI cannot miss the online state between an immediate end-of-stream and the following reconnect.
Shells lost to a tmux restart come back. After a reboot or a tmux server crash, Sidecar recreates the managed shells that were running, under their ow
Shells lost to a tmux restart come back. After a reboot or a tmux server crash, Sidecar recreates the managed shells that were running, under their own names and in their own working directories, once the first frame is on screen. sidecar session status shows what it would do before it does anything — every shell named as reattach, recreate-shell, resume-agent, manual, skip or refuse, with the reason and whether it would run an agent — and sidecar session restore performs exactly that plan, with --dry-run, --shell, --agents and --yes. sidecar session policy sets it per shell (--inherit, --shell, --resume, --never), so a long-running server, a disposable helper and a sensitive agent session can differ without changing the machine default. Nothing arbitrary is replayed: a --run command, dev server or test watcher is never restarted, a working directory that no longer exists is a refusal rather than a fallback to some other directory, and a tmux session name held by something else is a refusal rather than something Sidecar closes to take the name. Conversations are a separate decision from terminals: plugins.workspace.sessionRestore.resumeAgents defaults to ask, so a reboot restores your shells and then asks once, in one grouped summary, before resuming anything that can spend money or change a repository. (td-e78e17)
A Sidecar-managed shell can be bound to the exact agent conversation running in it. A provider's own hook calls sidecar agent report-session --kind KIND (--id ID | --path ABS_PATH), and Sidecar records which native conversation that pane is in. That binding is what makes a cold restart able to offer to resume that conversation, and what makes sidecar agent read --source transcript return it. Sidecar never guesses one: an unbound shell gets transcript_unavailable, because "the newest conversation in this directory" is wrong often enough to matter and looks identical to being right. Session values are redacted by default — agent list and agent get report only whether a shell is bound and whether an official integration vouched for it, and the value appears for your own shell or with --include-session-ref, since list output routinely lands in logs and CI artifacts. shells.json moves to schema version 3 to hold the binding, additively: a record that has never run an agent serializes exactly as version 2 wrote it. (td-8ec2cc)
Codex and Claude Code can tell Sidecar which conversation they are in. sidecar agent integration install codex (or claude) adds one Sidecar-owned entry to that provider's own hook configuration, and from then on each new session reports its identity to the shell it is running in. They install at session-identity tier and stay there: these hooks say which conversation is running, never what state it is in, so screen and process detection remain the only authority for whether an agent is working, blocked, or done. Installation preserves every unrelated hook and every unrelated setting, --dry-run shows the exact ops, and uninstall removes only Sidecar's entry. Codex needs its hook trusted before it will run: Sidecar writes the trust record itself, and if that ever stops matching, the failure is Codex's own visible one-time "Hooks need review" prompt rather than a hook that silently never fires. (td-8ec2cc)
Every agent resume command now comes from one registry. The Conversations plugin had the only table of how to resume each provider, as a switch building shell strings. It now lives in agentcatalog as structured argv, so the Conversations UI, the CLI, and cold restore share it and cannot drift, and a session identifier is an argument vector entry rather than text spliced into a command line. A reported identifier that would read as a flag is refused outright, both when it is validated and again when a resume is built from it. The UI keeps its current user-confirmed behavior and the command you see is unchanged. (td-8ec2cc)
Agent state can come from deterministic provider integrations instead of screen inference alone. The new lifecycle contract records working, idle, blocked and terminal outcomes in a bounded JSONL store, resolves competing sources through one authority policy, and exposes the result through sidecar agent report, end, release and explain. The bundled OpenCode integration reports full lifecycle state; Codex and Claude Code report session identity. Install, inspect, update, repair and remove integrations from Configuration or the matching sidecar agent integration commands, all through the same service. Integrations report lifecycle facts only: they never send prompts, responses, tool data, paths, credentials, or notification policy. (td-43a93f)
A managed agent can be driven without taking over its terminal. Behind the agent_control feature flag, sidecar agent start, prompt, wait, read and send-keys operate on one pinned shell, validate the complete request before writing anything, and refuse when the pane is busy, replaced, blocked, or owned by something else. Prompt delivery uses the same ordered paste and key encoding as the embedded terminal, waits use a pooled tmux control client with bounded polling fallback, and read offers visible, recent, unwrapped, detection and transcript sources. This is the non-interactive counterpart to watching and answering an agent in the TUI. (td-7de1af)
A pane you have opened can be moved. M from any pane — or the new ⊞ button on the pane header, left of the close × — opens one reposition modal: h/j/k/l move a draft of the layout, z zooms, enter commits the whole sequence atomically, esc discards it. The pane is pulled out and grafted back, so its tabs, scroll position, selection and any live terminal travel with it, and so does the share of the box you dragged it to. All three pane hosts have it: project Workspaces, the global Sessions browser, and the content decks beside Files, Git and Notes. M was chosen over m, which is already render and merge-workflow in two of the twelve pane contexts, and one key must mean one thing in every pane. (td-2ec104)
sidecar layout move gives agents the same capability in one call. sidecar layout move 2.1 --to 1.2 moves by cell, --to 3 appends to a column (opening one past the last), and --focused --to left|right|up|down uses the identical direction rule the modal's keys compile through, so the CLI and the keyboard cannot drift. It was always possible to rearrange a layout with layout get plus layout apply --spec, but that means reconstructing every pane on screen to move one of them, and every pane you reconstruct is a pane you can get wrong. Like get and apply it never queues, refuses rather than squeezing, and reports a move with nothing to do as unchanged (exit 0) rather than calling it moved. With --sessions it changes this machine's viewer tree even for a row whose workspace is on another host, and sends no layout mutation to that host. (td-2ec104)
Three shell verbs stopped being things only the TUI could do. sidecar shell rename --target <session> renames a shell you are not sitting in — until now rename resolved "which shell am I" from the ambient tmux environment, so there was no way to rename any other one. sidecar shell send --target <session> --run/--type <command> sends a command into an existing shell, matching the --run/--type split create shell already had. And sidecar create worktree --plan resolves a worktree plan and prints it as JSON without creating anything, so a caller can show branch, path, source OID and whether a setup hook will run before committing to it. Sidecar owns shells.json, so a capability reachable only through the TUI was a gap for every agent, not only for remote hosts. shell send guards its own boundary: the underlying send-keys has no protection beyond a blank-command check, so the verb refuses a session that is not a live record or a registered worktree session for the resolved project, and refuses one recorded on a different tmux server rather than typing into whatever answers that name on this one. (td-677dde)
A configured project can be used before it has ever been opened. --project resolved only through project state directories that already existed on disk, so a project listed in config.projects.list but never opened returned unknown project. It now falls back to the configured list and registers the project the same way first-open does. (td-677dde)
Sidecar can watch and drive sessions on another machine over SSH. Behind the sidecar_remote_hosts feature flag, installing Sidecar on the host and registering it with sidecar host add makes that machine's projects, shells, worktrees, agent states and live panes appear in Sessions beside local ones. Selecting a row opens the real remote tmux pane with history, search, selection and ordered input intact; cross-host geometry leases let either machine take back its own viewport by typing. There is no daemon or listening port: Sidecar starts an ephemeral host process over SSH stdio, reuses tmux control mode for pane traffic, and names unreachable, missing-binary, missing-tmux, login-output and version-skew failures with their fixes. The Remote Hosts configuration page and host add/list/set/remove/probe commands share one validated registry and apply changes without an app restart. (td-f10d6f, td-998e58, td-42b724, td-141917)
Notifications can reach you inside Sidecar and outside the terminal. Alerts now land in a persistent JSONL-backed notification centre with stacked toasts, an unread header indicator, keyboard and mouse actions, target links, per-source rules and a sidecar notify CLI for posting, listing, dismissing and configuring them. Native macOS and Linux notifications, built-in or custom sounds, quiet hours, background-only delivery and live config reload all run behind delivery adapters. A Sidecar running through SSH can forward a remote transition to the viewing machine or emit through the outer terminal, while deduplication and claim rules prevent the same event from alerting twice. (td-7b9ccc, td-eb6475, td-58679c)
Agents on another machine can be driven the same way as agents on this one. Every sidecar agent verb — list, get, start, prompt, wait, read, send-keys — plus session status and session restore now take --host ID and run on that registered host, as one sidecar <verb> --json invocation over the ssh connection Sidecar already keeps open. There is no second protocol and no new daemon: the verbs were headless, target-taking and --json from the start, so carrying them to another machine is transport rather than design. What you get back is the host's own answer, including its refusals — a blocked agent on another machine reports agent_blocked with the host's own sentence, not a local approximation of it, because the rules run on the machine that owns the pane. Two failures the local vocabulary could not describe honestly get their own codes: host_unavailable when the machine could not be reached at all (nothing was attempted, so trying later is the fix) and version_skew when the host's Sidecar does not know the verb (update one of the two binaries). Conversation identifiers stay where they belong: a remote agent list or agent get tells you whether a shell is bound and whether an official integration vouched for it, never what it is bound to, unless you ask with --include-session-ref. Nothing remote is ever written into this machine's shells.json. A remote verb requires an explicit target, because the "current shell" shorthand names a shell on this machine and two machines running a same-named project generate the same tmux session names. Cold restore runs on the host too — a viewer asks for it and watches, and never rebuilds another machine's state locally. Behind the existing sidecar_remote_hosts and agent_control flags. (td-a55114)
A tmux server crash no longer erases your shell records. When a tmux server dies, every managed session disappears in the same instant. Sidecar's liveness check asked, once per shell, "is this one gone?" — got a true-looking answer every time, and tombstoned the whole file. That is how a crashed server took a user's sidecar and braid shell lists with it. The liveness path no longer asks for a deletion in that situation at all: when no tmux server is running, or when a shell's last confirmed server is not the one running now, the record is kept and marked as something a restore can bring back. A shell that exits inside a server that is still up is still tombstoned, and still recoverable with sidecar shell restore, because that is a terminal you closed. Both paths that could delete were fixed, including the project workspace one, which reaped per shell with no protection at all. (td-e78e17)
A late report from an agent that has already exited can no longer overwrite its successor's session binding. This one was found by building the proof rather than by a test. The generation a reporting hook derives for itself falls back to the pane's root process when it cannot trace its own ancestry back to the pane, and the independently-derived "what occupies this pane now" falls back to the pane's root process when nothing is running there. Those are exactly the two conditions that hold for a hook left behind by a provider that has exited: it has been reparented away from the pane, and the pane is empty. Both sides fell back, compared equal, and the stale report was accepted — in precisely the case the check exists to reject. The reporting side now has no fallback: a hook that cannot prove which provider it belongs to is refused rather than believed. Two fallbacks agreeing is not evidence. (td-8ec2cc)
agent prompt with the text left off no longer prompts your own shell with a shell's name. sidecar agent prompt reviewer read reviewer as the prompt text and typed it into the shell the caller was sitting in, because one positional argument means "prompt this shell". A lone argument that resolves to a managed target is now a usage error naming what is missing. Empty or blank prompt text is a usage error too — it exited 5, the code for a semantic refusal, where every other malformed command line exits 2, and a caller could not tell "I built this wrong" from "the agent would not take it". (td-2cea15)
layout apply --json and layout move --json write only JSON to stdout. Both wrote the structured result object and then appended the human per-pane lines after it, so sidecar layout move 2.1 --to 1.2 --json | jq failed on the trailing text — which is the only reason to ask for --json at all. The flag's own help already promised "one structured result object to stdout", and layout get --json already kept that promise. The two projections are alternatives now: without --json you get the human lines and nothing else, with it you get the object and nothing else. Both carry the same per-item verdicts, cells and reasons, so nothing is lost by choosing one. (td-0e2d12)
The reposition modal asks before discarding a live inline edit. Opening it released every input surface the content deck owns, and for an inline edit that release killed the tmux session holding an unsaved buffer without a word. Every other caller of that release is a surface teardown — a plugin switch, a scope change, shutdown — where the editor has nowhere left to be drawn; the modal is the one caller that keeps the deck on screen and returns to it, so nothing forced the buffer to die. It now raises the editor's existing Save/Discard/Cancel dialog, the same one clicking away from the editor raises, and opens the modal on the pane you asked for once you have answered. Both doors onto the modal — M and the header ⊞ — inherit it. (td-0e2d12)
A power loss during an agent lifecycle report no longer costs two reports instead of one. The lifecycle log is append-only JSONL, and a machine dying mid-append leaves a final line with no newline. The next report was appended straight onto that fragment, welding two records into one line that neither could be read from — so the crash cost the report it interrupted and the next healthy one, while the write reported success. A pane that had reached its third report came back believing it was on its first. The log is now re-framed when it does not end where a line should. (td-b2370e)
Validation errors no longer exit with the code that means "you passed a bad flag." create worktree, create shell --name and shell rename --target returned exit 2 both for an unusable command line and for a perfectly well-formed one whose value was rejected — a branch that already exists, a display name already in use. A caller cannot tell those apart, and the remote-host viewer read the second as version skew and told the user to upgrade Sidecar. Input rejection is now exit 5; exit 2 keeps meaning a usage error. Documented in each command's exit-code table. (td-677dde)
A display name or worktree name starting with - works. shellstate.NormalizeName accepts a leading dash, so -wip was a legal name that the argument parser then read as an unknown option. shell rename --target, shell send and create worktree now stop flag parsing at --. (td-677dde)
notify config set is inside the isolation gate. It rewrites the whole config file but carried no mutating mark, leaving it the one mutating verb a misconfigured proof run could still drive against the real ~/.config/sidecar/config.json. (td-677dde)
Embedded terminal backgrounds no longer flicker, truncate, or flood at particular widths. Captures now preserve blank rows with capture-pane -N, every drawn cell takes its background from tmux, and Sidecar paints a row's carried background through its trailing cells without letting pane padding leak into the child's pen. The old content-based canvas heuristic is gone, so a terminal's colour no longer changes because its text or width happened to trigger a guess.
features.SidecarRemoteHosts, default off)Phase C: a remote host can be changed, not only watched. Creating a shell, creating a worktree through its confirmation, seeding an agent, and renaming either kind now work on a remote row from the Sessions browser. Mutations run as one-shot sidecar <verb> --json invocations over the ssh connection that already carries the observation stream, so the serve protocol stays one-directional and read-only by construction. Rows arrive through the host's next snapshot rather than being invented locally. Delete, merge and navigation still refuse — their implementations resolve paths against the local filesystem. That was also a live bug in O (open in Git), which had no guard at all and sent a remote path into a local worktree switch; on a machine with the same checkout layout that succeeds against the wrong repository. (td-677dde)
A confirmed worktree plan is pinned to its commit. create worktree --expect-source-oid OID refuses with exit 5 when the base ref no longer resolves to the confirmed commit, and the remote confirmation passes its plan's OID back on Create — so an agent pushing to the branch while the user reads the modal yields a refusal naming both commits, not a worktree silently built from the new head. The local modal already had this guard from executing its stored plan; the remote path re-ran the command from raw arguments and did not. Also from the pre-merge review: a setup hook failing with its own command not found is no longer misread as an uninstalled Sidecar, a login profile emitting 32+ structured-log lines can no longer push a successful result out of the decode window, host-derived error text is stripped of terminal escape bytes before display, disabled hosts refuse mutations up front by name instead of failing as "removed or retargeted", the rename modal shows an in-flight state and swallows the double Enter that raced two renames on the host, a stale reply from one host no longer clears another host's pending selection, switching the create form from a local to a remote project clears the local repo's branch list, and Merge is hidden on remote rows rather than offered and then refused. (td-677dde)
Nothing published for this version
a6246a9 Fix global quick open result routing
The workspace sidebar reads as a list of cards instead of a wall of text. Two-line workspace entries sat flush against each other, so line 2 of one row and line 1 of the next were adjacent and the eye had nothing to anchor on; section headers were dim muted text with no glyph and no divider, which made a section boundary the hardest transition on the pane to see. Rows now carry a blank line between them and one before each heading, and headings render flush-left and uppercase with a category glyph — 📌 PINNED, ● LIVE, ○ IDLE, ◆ NEEDS ATTENTION, ● WORKING — followed by a horizontal rule that runs to the section's action button. A project-grouped heading takes that project's stable theme hue for both glyph and title, while category and time headings stay neutral. The selected row keeps a full-width fill on both surfaces and changes only its text hierarchy when focus leaves, so a selection is never ambiguous. All of it lives in internal/workspacelist, so the project Workspaces sidebar and the global Sessions browser inherit the same presentation, including the scroll, wheel and scrollbar math that had to learn about the new row heights. (td-fd674e)
sidecar --version now reports the commit, build date, and build profile. A released binary previously printed only sidecar version v1.9.0, which is the one build that cannot be traced back to source from the outside, so a bug report from a Homebrew install gave no way to tell which commit it came from. Release builds now carry ShortCommit and the build date from the release pipeline, make build and make install stamp the commit and dirty state from the working tree, and anything built without ldflags falls back to debug.ReadBuildInfo. The first line of the output is unchanged and stays a single sidecar version <v> line, because scripts/dev-install.sh matches it by prefix and scripts/verify-release-archives.sh compares it exactly; the new detail sits on indented lines below it. Unmanaged make install builds deliberately still do not stamp a release version, so the update checker keeps offering updates on locally built binaries. Based on the work of @justin13888 in #228. (#227)
A file opened beside Files keeps the shortcuts it has in Files. A document pane composed next to a plugin answered a thin subset of the document keys: E external editor, ctrl+p Find, f project Search, I Info, y contents, Y path, selection copy and select-all, and + / - resize all did nothing, and esc hid the pane instead of clearing a selection. The pane now uses the shared workspace-doc context and answers every file-facing key the primary preview does wherever the shared viewer owns the capability, with Find and Search built on the same internal/panesearch surfaces the project and global Workspace document panes use, rooted at that deck's project and loading their result back into the focused pane. Because the finder, ripgrep and git-info messages are broadcast types Files also consumes, each is now tagged with the deck and leaf that issued it, so one pane can no longer swallow another's scan. y copies the visible selection when there is one and the file otherwise — the Files rule, now implemented once in docview rather than decided separately by each host. Files-only tree operations such as rename and full-screen blame stay with the primary surface and are deliberately not forwarded to a different file behind the focused pane.
A file finder opened in the global Sessions preview no longer loses its results when the selection moves. The scan and search messages carry no root and no surface identity, so a result could land in whichever pane happened to be current — and moving the Sessions selection to another workspace while a scan was in flight left the originating pane with nothing. Each search command now carries its workspace ID and is routed back to that workspace's pane, live or cached, and previewDocSearchMsg joins the async set the model keeps delivering while the selection is elsewhere.
An apostrophe in a task description no longer breaks agent launch on macOS. The generated start.sh passed the prompt through a heredoc nested inside $(...), and bash 3.2 — still what /bin/bash is on macOS — mis-tracks single-quote state while scanning for the closing paren. An odd number of apostrophes in the prompt turned the whole script into a syntax error, so fix today's bug failed to launch while don't break the user's code worked, which is why this survived so long: an even count accidentally re-balances the lexer, including in the test that was supposed to cover it. A " or a ) in the prompt was broken by the same pattern, with ) silently truncating the prompt and leaking the heredoc delimiter into the agent's argv. The prompt is now staged in a tmpfile written by a top-level heredoc and read back with a plain cat, which parses correctly on every bash. Thanks to @imsickofmaps for the diagnosis and the fix, and to @jennings, who reported the same bug and sent an equivalent fix in #225 back in March, months before this landed. (#262)
Tasks moves to v1.16.0, and the Tasks tab can show finished work. C on the Outline reveals DONE and CANCELLED rows in place instead of hiding them, and the same key on the Projects tab nests closed children under their project rather than pruning them and hoisting the open ones. Hidden rows stay honest either way: section badges read 4 · 1 closed, a search whose matches are all closed says so instead of rendering blank, and reordering anchors on the nearest visible sibling so a keypress can't rewrite the file without moving anything on screen. Delegation stamps — tasks show's (since …), the TUI detail pane, the claim-conflict message — now render in the configured timezone and time format, with the year shown only when it isn't the reader's own, so an old handoff can't read as yesterday's. Storage and --json keep the exact UTC instant.
td moves to v0.65.0, which lets a hosted session approve again. Hosted td-sync handlers have no on-disk BaseDir, so review-policy resolution skipped the resolver and fell back to strict mode: an implementation-involved browser session was offered only reject in available_transitions even though td's documented default is trusted and an attributed approval is valid there. Policy resolution is now centralized across review, approve, close, and transition discovery, and honors process-wide environment overrides in hosted contexts. The release also fixes td's own deploy pipeline — a failed remote build used to be masked into a green health check against the container it had failed to replace — and adopts Sidecar's blocking golangci-lint gate.
Nothing published for this version
735dc1c deps: bump tasks to v1.15.0
Embedded agent terminals now keep the host terminal's background and honor synchronized redraws. Default-background cells no longer fall through to Sidecar's own canvas, which made Codex look like a dark rectangle compared with the same session in Ghostty; Sidecar asks the host for its real background and uses it only where the child selected the terminal default, while explicit Claude, Grok, and Cursor canvases still win. Canvas inference now also requires vertical reach through the live content, so a localized Codex or Cursor composer cannot become the whole-pane background merely because a one-column rewrap moved another painted row across the viewport edge. Cursor's CLI also brackets composer updates with DEC mode 2026, and Sidecar now holds intermediate emulator frames until that transaction closes instead of flashing partially cleared status and input rows on each keystroke, with a one-second fail-safe for a malformed child. On macOS, Cursor launched as agent is identified from its foreground process group's symlink-resolved argv[0] even when tmux reports the shared runtime node, using the same process-first rule as Herdr without reopening the screen-text false positives. (#313, td-3b8972)
A shell record that was written by a newer Sidecar is no longer quietly rewritten without the parts this build could not read. shells.json has carried a version field since the beginning and nothing ever read it, so a file from a newer binary would parse into the fields this one knows, lose the rest on the next write, and look fine doing it — the same failure the shell-record durability work is about, one level up in the format itself. The version is now 2 and every writer checks it: a manifest from the future is refused with a message naming both versions, and the file is left byte-identical. A version 1 file upgrades in place on its first write, keeping every field. Reads are unaffected at any version, because a read cannot lose anything. (td-362a41)
Forgotten shell records stop accumulating forever. Forgetting a shell moves its definition to a tombstone so sidecar shell restore can put it back, and until now nothing ever removed one — a long-lived project's shells.json grew by a record for every shell ever forgotten. Tombstones now expire after shells.tombstoneRetention in config.json, which takes a Go duration, a day count ("30d"), or "forever", and defaults to 14 days. Expiry runs at the writer boundary, so the file is bounded without a background sweeper. One deliberate consequence: once a record's window passes, Sidecar no longer remembers the forget, so a tmux session of that name that is still running becomes an ordinary adoptable row again rather than staying invisible. (td-362a41)
N marks the selected open task as a GTD next action in one keypress from the list or detail view, and it refuses politely on proposed and done tasks rather than mutating from an input context. An empty Next tab now explains itself — how many dated items are waiting on Agenda, counted by the same query the Agenda tab paints, and how to mark one — instead of rendering a blank pane beside a full Agenda. The Inbox tab buckets approvals and accepted rows under project headings so a triage pass stays inside one theme, with headings as unselectable chrome the a/r walk steps past. tasks move files a PROPOSED task into a project without the reject-and-re-propose dance that used to mint a new id.404dfa9 Merge pull request #312 from marcus/global-terminal-panes
Quit on Sessions, and the panes come back. The global Sessions browser already restored the tab, but the selected row and anything composed beside it died with the process. Those now live on global state.json: the last row, each composed pane tree, and which leaf had focus. A terminal split reattaches to its still-running tmux session with scrollback intact; a row you only previewed writes nothing. A missing document, issue, unknown kind, or dead split drops that leaf and collapses its split rather than failing the restore. (td-e5a987)
sidecar layout get and apply answer for Sessions. --sessions [ROW] targets the global surface — the selected row by default, or a durable inventory ID / display name with the same ambiguity rules as --shell. Off-screen is exit 4. Get and apply share the project workspace's report shape and all-or-nothing verdict path, so a tree an agent cannot read or compose is no longer a Sessions-only hole. (td-b6178f)
The Tasks tab keeps up with work done anywhere else. A Sidecar left open all day only ever reread the local task database, so a task created in the hosted browser, or by a teammate, or on another machine, stayed invisible until some unrelated td command happened to pull it in. td v0.64.0 moves that responsibility into the monitor itself: it subscribes to the server's event stream and applies changes within about a second, falling back to cheap status probes and then to timed sync where a stream cannot be held. Work done in the tab — creating, starting, approving, closing, logging — now pushes as soon as it commits instead of waiting for something else to flush it. Sidecar wires none of this; it comes with the dependency.
The create modal is steerable with the arrow keys alone. Choosing a pane kind used to mean Shift+Tab up to the list, arrowing to the row, then Tab back down to the Name field — three gestures before the one that mattered. Up and down now reach the kind list from wherever focus sits, so n, a couple of arrows and Enter is the whole interaction, and the fields that give the arrows a meaning of their own — the Project, Base Branch and Agent combos — still keep them for their dropdowns. The hint line says so.
The global Sessions browser offers its configured resource providers. Its preview has placed Resource panes all along — sidecar open <locator> opened one there, and the surface resolves the target through the same core the project workspace does — but the create modal never listed the provider rows. They shared a flag with Terminal split, which this surface genuinely cannot offer (one terminal producer, bound to the selected row), so a passive row was gated on a live-terminal capability it does not need. The flag now means only what its name says, and the two surfaces' catalogs differ by exactly the Terminal split row.
The kind list reads as one block. Its rows were chrome-sized to their own text, so the fill ended wherever that row's description happened to, and the list showed a ragged right edge whose shape was an accident of the longest line. Every row now spans the modal's whole content column, and a disabled row keeps the list's fill under its muted text rather than punching a hole in it.
Nothing published for this version
346b09c Merge pull request #309 from marcus/pane-control
sidecar layout get and sidecar layout apply. Opening a working set of panes used to mean a sequence of sidecar open calls, each placed by a policy the agent could not see and could not address, with no way to read back what was on screen first. layout get --json now answers with the current layout — a columns-of-rows grid projection, every pane's kind, targets, tabs and tmux session, the geometry, and the caps and floors an apply will be held to. layout apply composes onto it, either additively (repeatable --pane descriptors) or as a full layout (--spec, or - for stdin) that replaces the screen. Apply is all-or-nothing: the host resolves every descriptor, builds the whole trial tree, fit-tests it against the pane floors, and either commits atomically or declines with the reason naming the first violation and leaves the layout byte-identical. The ack's items array carries a verdict per requested pane — opened, retargeted, carried, or declined with its own reason — so one round trip shows everything wrong with a refused spec rather than one error at a time. A spec must account for every live terminal by name and is declined if it omits one: apply never destroys a live session implicitly. Unlike open, layout requests never queue — a queued atomic apply would validate against a tree that no longer exists, and a stale get answer is worse than a refusal. (td-e9a089, td-89033c)sidecar open <target> --at <col>[.<row>] places a pane at an explicit grid cell. --split expresses a preference and silently no-ops when the open retargets an existing pane; --at expresses a requirement, so a cell that cannot be honored exactly declines rather than landing the pane somewhere else. The two are mutually exclusive. Cells are 1-based col.row against the same columns-of-rows vocabulary layout get prints, and the same requirement semantics apply on all three surfaces that host panes. (td-89033c)n used to offer Shell, Worktree and Terminal split; opening a file, a diff, a td issue, a note or a configured resource provider beside your work had no keyboard path at all. The modal now lists every pane kind — growing from a horizontal toggle into the vertical list with aligned descriptions once the row count earns it — and continues to a target picker for the kinds that need one: fuzzy path match for files, recent commits and refs for diffs, in-progress and recent issues from td, recent notes, and a locator field for each configured provider instance. Every pane kind shows the placement row, so one click both chooses where the pane goes and creates it. Disabled rows stay visible with the reason inline instead of vanishing. The pickers resolve to exactly the target shape the CLI produces, so the modal is an entry point rather than a second implementation, and the rows work identically from the project workspace and the global Sessions browser. (td-2962e3)n now opens it from a focused Document, Issue, Note, Diff or Resource pane, on both the project workspace and the global Sessions browser. It is the same key the sidebar and the terminal preview already answer with "make me a new thing", so the answer no longer changes with focus. Two consequences, both deliberate: the Diff pane's n / N next-change pair moved to > / < — the shifted forms of its , / . file steps, so the pair reads as one hierarchy — and a live input surface inside a pane still wins, so a committed in-file search keeps n for its next-match while it is up. The terminal preview keeps o, because n there belongs to the list's create. Extending the same entry to Notes, Files and Git under ctrl+n is planned separately (docs/plans/active/pane-switcher-everywhere.md). (td-18e1c1)LiveLeafCap is unchanged at two live terminals. (td-afa959)BUMP=major|minor|patch make release derives the next version from the latest tag, stamps ## [Unreleased] to ## [vX.Y.Z] - <today>, commits release: prepare vX.Y.Z, pushes main, and publishes — the version is stated exactly once, or zero times when BUMP implies it. Previously the operator had to hand-edit the changelog heading to a specific version and then repeat that same version on the command line via RELEASE_VERSION, and nothing caught the two disagreeing. scripts/release.sh now refuses an empty [Unreleased] section, a tree dirty beyond CHANGELOG.md, a tag that already exists, and a RELEASE_VERSION that contradicts an already-stamped heading, naming both versions in that last case. make release-dry-run prints the derived plan and exits before any mutation. RELEASE_VERSION=vX.Y.Z make release still works unchanged as the explicit-version path. Ported from tasks, which already carried this flow. (td-0dda74)5c67366 Merge PR #308 from gvorwaller: reopen td database after sync replacement
shells.json, taking every shell's
display name, working directory, agent type and skip-perms flag with it — and
those records are the only thing that can rebuild a shell, so deleting them
deleted the recovery path. On 2026-08-22 that emptied five projects at once.
Sidecar now models the tmux server as an identity of its own (socket
inode+ctime and the server #{pid}), because liveness is a joint property of
a shell and the server it was observed on: when the server is replaced, every
signal changes at once for a reason that has nothing to do with any one shell.
"No server running" is read as a fact about the server rather than a listing
of zero sessions, startup reconciliation is additive-only, and a shell that
is not running degrades to an offline row you can press Enter on to recreate.
No code path can now write a manifest with fewer entries than it read except
the two single-identity removals, and that is enforced by a test at the writer
boundary rather than by convention.sidecar shell list, forget, and restore. Forgetting a shell record
was previously reachable only by pressing a key in the TUI, and Sidecar owns
these records outright — nothing underneath it can reconstruct a display name
or agent type — so the capability is owed a non-interactive path. forget
moves a record to a tombstone rather than dropping it, restore puts it back
with its display name, agent type, skip-perms and working directory intact,
and list shows live and forgotten records on both the human and --json
surfaces. The tmux session itself is never started or killed; this is the
record, which is the part Sidecar owns.View(), project Workspace
and global Sessions share one bounded content-link resolution and row
analysis path, ordinary frames no longer build diagnostic cell grids,
presentations with no consumer-visible change are suppressed, and global
terminal updates no longer rebuild the workspace list. Where that still
missed the budget, per-feed publication became adaptive: every byte is still
consumed immediately, with an immediate leading frame, at most 30 sustained
frames per second, and a guaranteed trailing frame. The isolated fixture
measures 8.7% visible against 0.4% hidden with output-to-frame p95 of 34 ms.
Scrollback, selection, links, cursor and mouse modes, resize and input
latency are unchanged.td sync. td sync installs snapshots by
atomically replacing .todos/issues.db, and the embedded monitor's SQLite
connection kept pointing at the unlinked inode, where every later write failed
with SQLITE_READONLY — correct file permissions, unwritable database.
Sidecar now notices when issues.db names a different file, closes the stale
handle, reopens against the current one, and replays the keypress that
triggered the check. Thanks to @gvorwaller (#308).<provider> to be ready" for a provider that already was, and
each one had to be refreshed by hand. Restore now asks for the tab that is on
screen — one call, not one per remembered tab — and a request made before any
provider is wired up leaves the tab armed instead of failing it, so provider
readiness resolves it without the user touching anything. A project or
worktree switch republishes providers to the rebuilt surfaces too, which had
been silently dropping both the matchers and the resolver.github.com/marcus/tasks v1.13.0 -> v1.14.0 (Sidecar-style modals in the
Tasks TUI: boxed fields, real buttons, fixed footer, draggable scrollbars).github.com/marcus/td stays at v0.63.0.a1def50 demo: a sample document that exercises every content-link kind
m / Render) was the one Sidecar reading surface where a td-* id, a
path/file.go:42 reference, a commit hash, a URL, a sidecar:// intent, or a
provider key went dead — toggling Render removed the links you were about to
click, while the byte-identical Workspace document pane kept them live. It now
scans what was drawn, the same way Notes and the document viewer already do.[ZMS-37161](https://<site>.atlassian.net/browse/ZMS-37161) — how a ticket
normally appears in a brief — could never reach a provider: the key is only in
the label, and an issue-key matcher can never match a whole browse URL. With
the destination's host listed in that instance's claimHosts, the label now
opens the Resource card and keeps its browser hyperlink, so cmd-click still
reaches the ticket. Only frames Sidecar's own Markdown renderer drew are
eligible; a program writing to a terminal still means what its destination
says. claimHosts is now documented in the provider protocol reference../scripts/demo.sh ships a sample document exercising every content-link
kind that works without an external provider.b657a3c Merge pull request #263 from youdie006/fix/search-focus-gg
bounded renders short
runs — diff hunks, highlighted notices — and drops the background of any run
longer than the cap, so an application that paints most of its output one
colour degrades to plain text instead of repainting the pane. never
suppresses carried backgrounds entirely; auto keeps the previous
canvas-detection behaviour. Configurable per surface as
plugins.workspace.terminalBackgrounds and terminalBackgroundSpanMax.terminal-overrides entry,
idempotently. The claim names sidecar's own TERM rather than every terminal
type, and is only made when COLORTERM says the terminal renders direct
colour — terminal-overrides is a server option that outlives sidecar and
applies to every session on the server, so a blanket claim would corrupt
colour in terminals sidecar never opened. Failure is never fatal: a colour
hint cannot block opening a shell.tmux_interactive_input,
tmux_inline_edit, files_auto_refresh, plugin_content_panes, and
terminal_resource_providers were settable only by hand-editing
config.json. Flags that Panels & Integrations already owns are shown
read-only with a jump to the control that owns them, so the flag and the
plugin's own enabled key cannot drift apart.K. A remembered space whose tabs are no
longer available falls back rather than showing an empty surface.create shell opens beside the session.g / G can be typed into the conversations session search box instead
of jumping the list.encoding/json/v2 is the default
implementation; sidecar's adapter parsing, config, and state round-trip
unchanged.make lint works in a go.work checkout again. It computed GOTOOLCHAIN
from a go list -m that did not set GOWORK=off, so the workspace answered
for tasks and td as well and the recipe expanded to three version words.TMUX_PANE, which could
silently drop a scripted pane from an inventory assertion after a tmux server
restart renumbered panes.fcf1cbd docs(plans): deprecate workspace-windowing-system in favor of terminal-splits plan
git init -b main after a click or i, then reloads git context
without a restart.go install if brew is missing). Sidecar never uses sudo and does not
treat a package-manager exit as success. Diagnostics for missing standalone
Tasks points at Panels rather than claiming Sidecar will not install it.n / + once
inventory has loaded. An unattached worktree preview is padded and has a
Start Agent button that opens the create-workspace form.[ ] around the row uses the same Primary colour as a
focused input so Tab on the toggle is visible without stealing the selection.--run, and split names are scoped to the
owning workspace.sidecar create CLI. sidecar create shell, sidecar create shell --split <dir>, and sidecar create worktree let an agent open shells, splits, and
worktrees in the running app, with worktree context, journalling, and ack
matching.claimHosts so resource providers can claim built-in URL spans
while keeping their OSC-8 hyperlinks.--auto, derived from
workspaceops rather than hardcoded per launcher.go.work, and make install-status reports the sibling revisions compiled in.scripts/demo.sh — modular, fully ephemeral demo environments (multi-project,
single-project, and fresh onboarding presets) that build a fresh binary from the
working tree and clean up on exit.b306179 chore(overview): remove unused createRenderSize helper
ccc6f69 Merge pull request #296 from marcus/worktree-modals
fefb6ea Add plan for a single Create Workspace modal.
The footer toast is gone. Notifications stack in the corner, live in a centre you can open from any surface, and take numbered jumps to the thing they name. Clicking a file, a commit, a td id, or a note link opens a pane beside the plugin you were already looking at, instead of yanking you away. Markdown follows the active Sidecar theme. Notes is no longer a beta.
ctrl+n, Shells [+], and autoCreateShell still skip
the form.d. The centre is
an app-level right panel (N / alt+n) with per-source sections, unread
dots, wheel scrolling, and a header indicator next to the gear. Settled agent
lane transitions post here; so does anything that used to flash the footer.
A quieter status-flash tier exists for things that should not take a toast
slot. Expiry, stacking, and per-source behaviour live on the existing config
screen.sidecar notify for agents. post, list, and dismiss write the same
log the UI reads. A post appears as a toast in the running instance and stays
in the centre until dismissed; with no instance running it is stored and
shown at the next start. --target kind:value[:line][@project] attaches
numbered calls to action (issue, task, commit, file, session, url), including
a jump into another checkout.path:line, td id, commit hash, or sidecar://… link underlines
only when it can be activated in the current project. Clicking it keeps the
active plugin on screen and opens the same Document, Issue, Diff, or Resource
pane Workspaces already uses: first pane to the right, another kind stacked
in that column, the same kind as a tab. Plugin content panes are on by
default. Tab walks the plugin, then the panes, in visual order; q/esc
hides the focused pane and x closes a tab./ searches the focused file
(incremental, n/N, wrap-aware highlights); the same tmux-PTY editor the
Files plugin uses now edits a doc pane in place. ctrl+p / ctrl+f reach
the file finder from the browser as well.dark preset. Fenced code uses the same Chroma style as
file previews. Switching a theme restyles Markdown that is already on screen
without discarding scroll, selection, tabs, or search.td init
gets a setup path rather than a dead screen. Create and delete are optimistic,
mouse editing is native (multi-click, click-after-EOL, pane-local $EDITOR
forwarding), and the built-in editor honors Mac/Emacs keys plus a persisted
Built-in / $EDITOR preference. Layout, filter, and save chrome are tighter;
informal numbered outlines render as lists; edit padding matches the
markdown view.make install-worktree / make install-local make sidecar on PATH
run the build they just activated. Homebrew is still the managed
link; copies that win PATH (typically ~/go/bin from unmanaged
make install) are pointed at the same artifact so
make install-worktree && sidecar is one build. make install-status
still reports resolution without mutating it.sidecar open land in the worktree
they named.make lint is the GitHub lint job: full codebase, GOOS=linux,
GOWORK=off, golangci-lint v2.12.2. The old --new-from-merge-base
gate missed unused leftovers whose function bodies were not edited.internal/contentlink owns link recognition and sidecar:// routing;
internal/passivedeck owns the app-level content deck both plugin hosts and
the workspace surfaces bind to. There is still one compositor and one pane
frame.internal/docview. Inline edit for Files, Notes,
and doc panes shares internal/inlineedit.sidecar open) go
through one state-free activation service, with a single pending-target slot
so a project switch and a landing cannot race.b3ba2b5 release: take tasks v1.11.0, and gate releases on sibling pins
tasks moved to v1.11.0, two releases on from the v1.9.0 v1.1.0 shipped
against. It brings three-part delegation (mode and note) through the data
model, the CLI, the HTTP surface, and a delegate modal; a user-configurable
delegation vocabulary; approving and completing a proposal in one step; and
expanded relative date input.tasks pinned two minors behind and nothing noticed, because
go.work resolves td and tasks to the local checkouts — so the pins in
go.mod are the one thing a local build never exercises. The release
preflight now reads those requirements with GOWORK=off, compares each
github.com/marcus/* module against its newest published tag, and refuses to
tag when any of them is behind. make sync-deps is the fix it points at: it
pins every sibling to its latest release and tidies, so the correction is one
command rather than a per-module go get the operator has to remember.d5552f5 Merge pull request #291 from marcus/worktree-text-selection-phase1
Text you can see is now text you can select and copy — on every surface, into every clipboard — and the machinery behind panes gained two shared seams so project and global workspaces stay one model rather than two lookalikes.
docview.Model in two places — the
project workspace and the global Workspaces browser — so the selection binds
to the viewer once and both surfaces inherit it, the way live refresh already
does. Rows are wrapped and tab-expanded at layout time, in the column space
they are actually drawn in, so the columns a selection names are the columns
on screen and the selection engine never sees wrapping. The line-number
gutter is kept beside the text rather than inside it, so it can never be
selected or copied.internal/clip is
now the single path text takes: the system clipboard natively, and the
terminal's own over OSC 52. Copy-on-select is a setting rather than an
assumption, and one control owns it.sidecar-modern Chroma syntax theme, with red keywords and teal types,
plus easier-to-see text selection in that theme.@ never reached the project switcher.
Click and wheel rules moved onto the shared workspacediff.View, and host
globals now pass through when a content leaf is focused.internal/livepanes owns the live-refresh watcher lifecycle that had been
written out once per pane kind per surface — six near-identical copies, and
six chances for a new pane kind to silently never refresh. Adding a kind is
now one Binding entry per surface.make worktree-init lets any harness make a git worktree Go-buildable, and
Sidecar-created worktrees run it automatically.Your coding agent can read these notes before it upgrades. Set up the MCP server →