Skip to content

feat(vscode): builder diffs, terminal image paste, backlog & Builders-tree UX#771

Merged
waleedkadous merged 10 commits into
mainfrom
feat/vscode-updates
May 19, 2026
Merged

feat(vscode): builder diffs, terminal image paste, backlog & Builders-tree UX#771
waleedkadous merged 10 commits into
mainfrom
feat/vscode-updates

Conversation

@amrmelsayed
Copy link
Copy Markdown
Collaborator

@amrmelsayed amrmelsayed commented May 19, 2026

Integration branch aggregating several independently-developed feature commits for the VS Code extension, plus one protocol-docs reconcile. Reviewed best per-commit (do not squash — keep the individual commits).

VS Code extension

  • Per-builder changed files, inline — builder rows expand to their changed-file list vs the default branch; click a file → 2-way diff. SCM-style decorations (file icon + colored status letter, theme-driven gitDecoration.*), since the built-in Git decorator can't see the gitignored .builders/ worktrees. 15s git throttle per expanded builder. (55912a40, 7b06bf0d)
  • Codev: View Diff — command + builder right-click opening the worktree's whole delta vs the default branch in the Multi Diff Editor, base served by a read-only codev-diff: provider (git -C <worktree>). Handles A/M/D/R/C + binary. (3b253ecc)
  • Accordion mode for the Builders tree — expanding one builder collapses the others; codev.buildersAutoCollapse setting (default on) + title-bar toggle. Stable row ids fix expansion resetting each overview poll. (4c5fbda2)
image
  • Live-status dot for active builders — green filled dot instead of the press-to-start-looking play icon; blocked keeps the amber bell. (50aac68f)
  • Image paste into Codev terminals (VSCode extension: image paste into Codev terminal #736)Cmd+Alt+V / Ctrl+Alt+V in a focused Codev terminal uploads a clipboard image to Tower and injects the saved path (web-/built-in-terminal parity). Codev terminals are Pseudoterminal-backed so VSCode's image bridge never fires; this reimplements it (per-OS clipboard read). Initially bound to Cmd+V (822385d5); that corrupted native multi-line text paste, so it was moved to a dedicated chord and Cmd+V left fully native (0d37fae5).
  • Backlog hides builder-active issues — an issue with an active builder no longer appears in the Backlog or its count, preventing a duplicate spawn; matches the web dashboard. hasBuilder is machine-local. (78165263)
  • CHANGELOG [Unreleased] documents the above in the user-facing voice. (3a1d9e30)

Preview

image

Protocol docs (out-of-theme, included on the branch)

  • docs(pir): reconcile PIR protocol/prompts with the 3-way consultation that actually ships (the DEFAULT_CONFIG precedence makes PIR run 3-way despite its CMAP-2 prose). Docs-only; the resolver/config bug is deliberately left out of scope. (78c50e28)

Verification

  • tsc + eslint + esbuild clean; 54 unit tests passing (clipboard-image, backlog, view-diff seams, etc.).
  • Image paste validated end-to-end manually (upload → path injection); the Cmd+V text-paste regression is fixed by construction (Cmd+V no longer bound by the extension).
  • VSCode extension: image paste into Codev terminal #736 covered here; closing on merge.

Closes #736

PIR's protocol.json declares verify.models [gemini, codex] (CMAP-2),
but the shipped DEFAULT_CONFIG porch.consultation.models
[gemini, codex, claude] always wins over a protocol's verify.models:
loadConfig seeds every config from DEFAULT_CONFIG before merging, so
config.porch.consultation.models is never undefined and the resolver's
'=== undefined -> protocol fallback' branch is unreachable. Every PIR
run is therefore 3-way, silently contradicting the protocol's own
CMAP-2 prose and its '## Consultation Footprint' invariant.

This change does NOT fix that resolver/config precedence bug (a
system-wide change, deliberately out of scope here). It only aligns
PIR's docs + prompts with the behavior that actually ships:

- CMAP-2 / 'CMAP' wording -> 3-way consultation (Gemini, Codex, Claude)
  in protocol.json, protocol.md, builder-prompt.md,
  prompts/implement.md, prompts/review.md, consult-types/pr-review.md
- review.md architect-notification snippet now also captures and
  reports the claude verdict alongside gemini/codex
- removed the misleading 'leave porch.consultation.models unset to keep
  CMAP-2' paragraph — that mitigation never worked given the above

verify.models is left [gemini, codex] (inert due to the precedence
bug); not changed to 3 models here to avoid widening scope into the
config-resolver territory left untouched on purpose. Applied to both
codev-skeleton/ (shipped to consumers) and codev/ (this repo's
instance) so the two stay in lockstep. No behavioral/code change.
Adds a codev.viewDiff command + right-click action that opens a builder
worktree's delta vs the default branch in the Multi Diff Editor.

The previously-removed review-diff.ts failed because it resolved base
content through git:-scheme URIs, which VSCode's built-in Git extension
can't serve for a worktree hidden in the host repo's .gitignore'd
.builders/ dir. This backs the base side with our own read-only
codev-diff: TextDocumentContentProvider (running git -C <worktree>
directly), so resolution no longer depends on Git-extension worktree
discovery. The right side is a plain file: URI; base content is keyed by
the immutable merge-base SHA. Handles A/M/D/R/C and binary files.

Coexists with Open Worktree in New Window. Pure parsing/URI logic is
factored out and unit-tested (16 tests in view-diff.test.ts).
Codev terminals are Pseudoterminal-backed, so VSCode's image-paste
bridge never fires — an image-only clipboard is dropped before the
extension sees it (text-only handleInput). This was the last terminal
feature gap vs the web dashboard.

Cmd/Ctrl+V in a focused Codev terminal now reads the clipboard image
per-OS, POSTs it to Tower's workspace-scoped paste-image endpoint, and
injects the returned path into the PTY. Every non-image path (no image,
clipboard tool missing, read error, Tower down, non-Codev terminal)
delegates to VSCode's built-in workbench.action.terminal.paste, so
text / bracketed paste cannot regress by construction.

- core: TowerClient.pasteImage(workspacePath, bytes, mime) — posts to
  /workspace/<enc>/api/paste-image (the handler is workspace-scoped;
  a global /api/paste-image has no route). Direct fetch (request<T>()
  forces application/json); ArrayBuffer body for cross-lib BodyInit.
- clipboard-image.ts: macOS osascript (no brew dep), Linux wl-paste /
  xclip (Wayland vs X11 via WAYLAND_DISPLAY), Windows PowerShell;
  ENOENT->tool-missing, clean+empty->no-image; injectable for tests.
- terminal-adapter: writeNotice() renderer-only status line.
- terminal-manager: getActiveManagedPty()/isCodevTerminalActive().
- extension: codev.pasteImage command + onDidChangeActiveTerminal ->
  codev.terminalFocused context key (scopes the Cmd+V rebind so it
  never shadows paste outside a focused Codev terminal).
- failure notice surfaces the reason ([Image upload failed: <reason>]).
- 9 unit tests for the clipboard-image seam (30 passing total).
Each builder row in the Builders tree is now expandable to a
second-level list of its changed files vs the default branch; clicking
a file opens its 2-way diff (codev-diff base <-> worktree file). The
existing multi-file View Diff command is unchanged.

view-diff.ts is refactored to share its engine: the inline git sequence
becomes exported getBuilderChanges(); a diffUrisForChange() seam wraps
the URI builders. Both the command and the tree use them — no behavior
change to the command.

BuilderDiffCache (15s TTL, keyed by builder id) guards against running
git on every overview/SSE tick: VSCode re-queries an expanded node's
children on each refresh, so the TTL caps git to ~1 spawn / 15s per
expanded builder; collapsed builders never call getChildren.

Adds BuilderFileTreeItem (status-colored diff icons, row-click ->
codev.openBuilderFileDiff) and placeholder rows for no-worktree /
git-error / no-changes. 50 unit tests pass (+4 diffUrisForChange).
Replace the diff-* codicons with VSCode's native Source Control look:
the file-type icon (from resourceUri) plus a colored one-letter status
badge and tinted label, via a FileDecorationProvider — the same API the
built-in Git decorator uses (it can't see the gitignored .builders/
worktrees, so we supply our own).

BuilderDiffCache now also owns a global fsPath->status registry with
per-builder path tracking and an onDidChangeDecorations(Uri[]) event, so
decorations refresh in lockstep with the file list under the same 15s
TTL. One shared cache instance is injected into both BuildersProvider
and BuilderFileDecorationProvider from extension.ts.

Letter/color mapping mirrors the Git extension (A/M/D/R/C/T/U) and uses
gitDecoration.* theme tokens so the user's theme drives exact colors.
50 unit tests still pass; type-check + lint clean.
Replace the 'play' icon (reads as a press-to-start button) with
'circle-filled' in green — the running-process/live-status idiom — so an
active builder looks like it's working, not awaiting a click. Blocked
rows keep the amber bell. Distinct from Backlog's issues/account icons.
The extension Backlog listed every issue Tower puts in data.backlog,
including ones that already have an active builder — clicking one would
spawn a second builder for the same issue. The web dashboard already
hides these (BacklogList.tsx: items.filter(i => !i.hasBuilder)); the
extension ignored the server-computed OverviewBacklogItem.hasBuilder.

Add a single-sourced spawnableBacklog(items) => items.filter(!hasBuilder)
in views/backlog.ts, applied in BacklogProvider.getChildren() before the
existing assignee (mine/rest) ordering, and reused for the 'Backlog (N)'
title count in extension.ts so the count can't desync from the visible
rows (the count is computed at a separate call site). View-layer only —
no core/types/Tower change; hasBuilder already ships in the overview
payload. Filter behaviour now matches the web dashboard exactly (the
extension keeps its own assignee-first ordering + count badge).

4 unit tests for spawnableBacklog (54 passing total).
Expanding one builder auto-collapses the others so a reviewer can't
have diffs from unrelated worktrees open at once. On a builder expand,
run the built-in collapseAll then reveal(expand:true) on that builder;
guarded by an openBuilderId idempotency check (reveal re-fires the
expand event regardless of timing) plus a reconciling debounce.

Builder rows now set a stable TreeItem.id = builderId — also fixes a
latent bug where label churn (wait-time/phase) regenerated the id every
overview poll and reset the user's expansion. getParent() added (reveal
requirement).

Behavior is gated on a persisted setting codev.buildersAutoCollapse
(default true), toggled from a Builders header button whose icon/tooltip
reflect state (fold = on, unfold = off), the Command Palette, or
Settings. 54 unit tests green; type-check + lint clean.
Rebinding Cmd+V to the image-paste command corrupted normal multi-line
text paste in Codev terminals: every paste took an async detour
(readClipboardImage spawns a subprocess) then re-dispatched
workbench.action.terminal.paste, which races VSCode's atomic paste
handling for a Pseudoterminal (the [Pasted text #N] chip interleaved
with raw characters). Codev terminals are Pseudoterminal-backed, so
VSCode's native text paste is correct on its own — the regression was
self-inflicted by intercepting Cmd+V.

Move image paste to a dedicated shortcut and stop touching Cmd+V:
- keybinding: codev.pasteImage -> Cmd+Alt+V (mac) / Ctrl+Alt+V (Win/
  Linux), consistent with the Cmd/Ctrl+Alt+<letter> Codev family;
  Ctrl+Shift+V deliberately avoided (native terminal-paste chord on
  Win/Linux). Cmd+V is no longer bound by the extension at all.
- paste-image.ts: image-only. Removed builtinPaste() and every
  workbench.action.terminal.paste re-dispatch (the race source). No
  image / tool missing / read error / Tower down -> a toast; text
  paste is never our concern now.

Text paste is now 100% native VSCode Pseudoterminal paste (incl.
bracketed multi-line) by construction. Image upload + path injection
unchanged (web-parity). 54 tests passing.
…ycle

Covers every extension-facing change on the branch: per-builder
changed-files (expandable rows + SCM-style decorations), Codev: View
Diff, accordion mode for the Builders tree, live-status dot for active
builders, image paste via Cmd+Alt+V, and the Backlog hiding
builder-active issues. Written in the existing developer-facing voice.

PIR protocol-docs reconciliation is intentionally excluded (not an
extension change). Internal parallel-development provenance is omitted
— the changelog documents user-facing effects, not process.
@amrmelsayed amrmelsayed requested a review from waleedkadous May 19, 2026 09:41
@waleedkadous waleedkadous merged commit 2bb8a1f into main May 19, 2026
6 checks passed
waleedkadous added a commit that referenced this pull request May 19, 2026
Bugfix release fixing the multi-architect routing regression (#774) that
broke sibling-architect affinity end-to-end since v3.0.5: detectCurrentBuilderId
opened the empty worktree-local state.db via the singleton getDb(), causing
the canonical builder ID lookup to miss and the sender ID to drop the
'builder-' prefix. Downstream lookupBuilderSpawningArchitect then bypassed the
affinity branch and routed every builder->architect message to main, regardless
of which sibling spawned the builder.

The fix in detectCurrentBuilderId opens the workspace's state.db readonly
directly, mirroring the per-workspace-handle pattern in state.ts:380.
End-to-end verified before tagging: ob-refine-spawned builder's message
landed on ob-refine's terminal, not main's.

Also includes Amr's VSCode extension round (#771): per-builder file trees,
View Diff command, accordion mode for the Builders tree, image paste into
Codev terminals, Backlog dedup for issues with active builders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VSCode extension: image paste into Codev terminal

2 participants