Skip to content

Code tab: diff review & file browser #514

@srid

Description

@srid

Problem

The agent edits code in the terminal; the human needs a fast, low-friction way to look at those changes and push feedback back into the agent's context — without leaving kolu for GitHub or a separate editor.

  1. "What did the agent (or I) just change?" — answered by the set of files with a diff, not the full tree.
  2. "What does the change look like?" — answered by a unified diff view of one file.
  3. "This line is wrong / please also do X" — answered by inline comments on the diff.
  4. "Tell the agent" — answered by pasting a reference into the terminal so the agent picks it up on its next turn.

Beyond diffs, users also need to orient in an unfamiliar codebase the agent is working in — see the repo's structure, find files quickly, and know at a glance what's modified. The Code tab serves both needs: reviewing changes and navigating the project.

Everything is driven by git state and terminal coupling — both already first-class in kolu.

Diff review phases

Each phase is independently shippable and delivers a user-visible capability.

Phase 1 — See local diff for one changed file ✅ Shipped in #515

User-facing change: Right-panel tab (replacing / renaming the both current "Files" and "Git" slot) lists files with uncommitted changes in the terminal's cwd. Click one to see its unified diff inline. No PR diff, no comments yet.

Value: "What did the agent just touch?" answered without dropping to a terminal.

Libraries / prior art:

  • Git data: existing kolu server-side git plumbing (already used by GitTab).
  • Diff rendering: @codemirror/merge or diff2html — both SolidJS-compatible with thin wrappers; prefer whichever ships syntax-highlighted unified view out of the box.

Phase 2 — Toggle between local diff and branch diff ✅ Shipped in #520

User-facing change: Same view, with a toggle between "local changes" (working tree vs HEAD) and "branch diff" (working tree vs merge-base(HEAD, origin/<defaultBranch>)). The file list and diff contents reflect the active mode. The resolved base ref is labelled in the UI (e.g. "vs origin/master"). The tab was renamed from "Review" to "Code" — the broader name accommodates future modes (file tree browser) under the same tab. Mode sub-tabs use icons (pencil for local, branch-fork for branch diff).

Value: Review what's about to ship on this branch, not just what's uncommitted. Same answer GitHub's "Files changed" tab gives for a PR, computed locally from git refs — no forge API, no GitHub dependency.

Design notes:

  • Forge-agnostic by design. Base is resolved via the existing detectDefaultBranch() (origin/HEADorigin/mainmaster). No PR lookup, no gh/glab calls. A future per-branch override can be added if the default is wrong often, but kolu should never need to know what forge the repo is hosted on.
  • Merge-base, not base tip. Three-dot semantics (git diff origin/<base>...HEAD). Handles the "I merge origin/master into my branch several times per PR" workflow correctly: each merge advances the merge-base, so the diff shows only the branch's net contribution, never double-counting merged commits.
  • Working tree, not HEAD, on the left side. Matches "total damage this branch will ship" and makes local-mode a strict subset. Single git diff <merge-base> -- <file> call — same shape as the phase 1 git diff HEAD -- <file>, just a different ref.
  • No auto-fetch. Compute against whatever origin/<base> currently points at; if the user wants a fresh base they fetch in the terminal.
  • Explicit errors, not graceful degradation, for the two states where branch mode is nonsensical: no origin/HEAD configured, detached HEAD.

Libraries / prior art: No new libs — same rendering, different git queries. Reuses detectDefaultBranch() from packages/server/src/git.ts and the existing getDiff() plumbing in git-review.ts.

Phase 3 — File tree layout for changed files ✅ Shipped in #538

User-facing change: The flat list of changed files (phases 1–2) becomes a collapsible directory tree. Files are grouped by their parent directories — packages/server/src/git-review.ts renders under packages › server › src › instead of as one long path. Click a directory node to expand/collapse; click a file to open its diff (same as today).

Value: The flat list is fine for 5–10 files. A 30-file branch diff (lockfile update, cross-cutting refactor) makes the list unusable — you can't see structure, can't collapse irrelevant directories, and can't tell which packages were touched at a glance. Tree layout gives spatial structure without changing any data model.

Scope: Client-only. The server's getStatus response is the same flat GitChangedFile[]; the client groups by path segments. No new RPC, no schema change.

Prerequisite for the file browser. The full file-tree browser (a 3rd icon sub-tab showing the repo's entire file tree, not just changed files) needs the same tree component. Building it here for the diff file list first means the tree browser mode inherits a battle-tested component rather than building one from scratch.

Phase 4 — Full file tree browser ✅ Shipped in #555

User-facing change: A 3rd icon sub-tab under the Code tab shows the repo's entire file tree (not just changed files). Lazy-loaded directory tree rooted at the terminal's repo root. Reuses the tree component built in Phase 3.

Server:

  • fs.listDir endpoint — input: { terminalId, path }, output: { entries: { name, isDirectory, path }[] }. Resolve against the terminal's CWD/repo root, readdir the requested path, return sorted entries (directories first, then files, alphabetical). Path traversal guard: requested path must be under the terminal's repo root.

Client:

  • Tree view with async loadChildren calling fs.listDir, rooted at meta().git?.repoRoot ?? meta().cwd. File/folder icons, expand chevrons, manual refresh button in the tab header.

Value: Browsable, lazy-loaded file tree alongside the terminal — useful when you want to see the full repo structure, not just what changed.

Phase 5 — Live file watching ✅ Shipped in #786

User-facing change: The file list, diff viewer, and file tree browser auto-update as the agent works. No more manual refresh — the Code tab stays current while the agent edits files in the terminal.

  • fs.onChange streaming endpoint (oRPC event iterator) using fs.watch with debounce
  • Auto-invalidates the changed-file list, the currently-viewed diff, and the file tree for changed directories

Value: Without live watching, the user has to manually refresh to see what the agent just did — a paper cut that compounds every time the agent makes a change. Keeping the Code tab reactive makes the diff review and file browsing experience feel alive.

Libraries / prior art: No new libs expected. fs.watch (or chokidar / @parcel/watcher if fs.watch proves too flaky on Linux) for file-system events; debounce to avoid thrashing on rapid writes. The main risk is fs.watch reliability on Linux under parallel workers — scope to the repo root and use a generous debounce to keep it simple.

Shipped in #786, which delivered the four streaming endpoints (git.onStatusChange, git.onDiffChange, fs.onListAllChange, fs.onReadFileChange) on top of a refcounted shared watcher layer (@parcel/watcher for the working tree; .git/HEAD / logs/HEAD / index watchers for the git axes), plus the createReactiveSubscription client wrapper and the CodeTab migration in one PR. The manual button is gone. The interim tracker #616 (which had bundled this phase with item 1 of #615) was closed as a duplicate on the way to #786.

Phase 6 — Inline comments on diff lines ❌ Dropped — superseded by #708's right-click path:line copy

Original scope: Click a line in the diff to attach a comment. Comments persist across sessions for the current repo/branch. Visible alongside the line like a GitHub review.

Why dropped: Phases 6 and 7 existed to answer "this line is wrong / please also do X" and then "tell the agent." #708's useLineSelection hook + CodeContextMenu already solve that loop without any persistence layer: select a line in the diff or file viewer, right-click → Copy <path>:<line>, paste into the agent terminal. The user's feedback is the message they paste — no separate comment store, no anchoring UI, no per-branch file format to design. Net cost saved: one storage shape decision, one anchoring UI, and the cross-session reconciliation problem that comes with both.

Phase 7 — Paste comment reference into the agent terminal ❌ Dropped — superseded by #708's right-click path:line copy

Original scope: A "send to agent" action inserts plain text into the active terminal's stdin — e.g. the absolute path of the file under review (plus whatever pointer lets the agent find the comments). The agent reads it on its next turn like any other user message.

Why dropped: Same answer as Phase 6. With Phase 6's persistent-comment model dropped, there's nothing for Phase 7 to ferry — the right-click Copy <path>:<line> action from #708 already produces the exact pointer the agent needs, and the user pastes it themselves with Cmd+V into the terminal kolu owns. Phase 7's "kolu types on the user's behalf" framing turned out to be unnecessary: a one-keystroke paste is the same UX without any new mechanism.

Phase 8 — Robustness (binary files, large diffs)

Phase 1 shipped two correctness/perf gaps. #708 incidentally closed half of one of them. The two remaining items are now tracked as their own issues — they're independent of each other and of the rest of this roadmap:

Phase 9 — Fuzzy file search ✅ Shipped in #708

User-facing change: Fast file filter across the entire repo without leaving kolu. Type into the file tree's filter input → the tree narrows live to matching paths.

Value: Fast file finder across the entire repo without leaving kolu.

How it shipped: @pierre/trees (adopted in #708) brings client-side path filtering as a first-class feature of the tree component, so no fs.search server endpoint or fzf-style scoring was needed — Pierre's built-in matcher operates on the already-loaded path set. The originally scoped Cmd+O overlay didn't ship; the in-tree filter delivered the value at lower cost. (#791 later moved the filter input into the Code tab's toolbar, but the search capability itself arrived with Pierre.)

Phase 10 — Git status decorations in file tree ✅ Shipped in #708

User-facing change: The file tree shows per-file git status with color-coded badges; status propagates to parent directories so a folder reflects whether anything underneath it has changed.

Value: At-a-glance view of what's changed in the repo, right in the file tree.

How it shipped: @pierre/trees (adopted in #708) renders git-status decorations natively when handed status data. The tree is fed by git.onStatusChange (see Phase 5 / #786) so decorations stay live as the agent edits files. No separate fs.gitStatus endpoint or fs.listDir schema extension was needed.

Non-goals

  • Not an editor. The Code tab is read-only; edits happen in the agent's terminal.
  • Not a GitHub review replacement. Comments live locally and feed the agent loop; they do not post to GitHub.
  • Not forge-aware. The Code tab operates on git refs only (HEAD, working tree, origin/<defaultBranch>). No GitHub / GitLab / Gitea / Bitbucket API calls.

Notes

  • Phases 1–2 are pure read; the tab is now named "Code" (was "Review" in phase 1, renamed in phase 2 to accommodate the file browser mode under the same tab).
  • Phase 2 intentionally drops the original "PR diff" framing. A PR diff is one forge's presentation of a branch diff; kolu computes the same answer from local refs and stays forge-neutral.
  • Phase 3 (file tree layout) is the prerequisite for Phase 4 and beyond — the tree component is built here for the diff file list first, then reused for the full file browser.
  • Phase 4 (file tree browser) ships next because it completes the "orient in the codebase" story — users need to see the full repo structure, not just changed files.
  • Phase 5 (live watching) follows immediately so neither the diff view nor the file tree require manual refresh.
  • Phases 6 and 7 (inline comments + agent paste) were dropped after feat(code-tab): swap file tree + diff view for @pierre/trees and @pierre/diffs #708 shipped right-click Copy <path>:<line> from both the diff and the file viewer. Selecting a line and pasting the resulting pointer into the agent terminal closes the same feedback loop without a comment store, an anchoring UI, or a kolu-specific agent-context protocol. Phase 7's "paste the path" simplicity was the design — Pierre delivered it directly.
  • Phases 4, 9, 10 were migrated from File browser: phased implementation plan #479 (file browser phased plan), which is now closed. The original Phase 0 (3-column layout, Add resizable right panel with live metadata inspector #483) and Phase 0.5 (tab infrastructure, Right panel multi-tab infrastructure #501) shipped as prerequisites; the diff viewer (479's Phase E) shipped as Phases 1–2 here.
  • The @pierre/trees + @pierre/diffs adoption (feat(code-tab): swap file tree + diff view for @pierre/trees and @pierre/diffs #708) absorbed Phases 9 and 10 as a side-effect of replacing the hand-rolled tree and diff renderers with Pierre's. Pierre ships path filtering and git-status decorations as first-class tree features, so neither phase needed the originally scoped server endpoints (fs.search, fs.gitStatus) — the Cmd+O overlay was replaced by Pierre's in-tree filter, and decorations ride on the existing git.onStatusChange stream. Code tab: chip + popover picker with inline file-name filter #791 later restructured the surrounding toolbar (chip + popover mode picker, externalized filter input), but the search and decoration capabilities themselves arrived in feat(code-tab): swap file tree + diff view for @pierre/trees and @pierre/diffs #708.

Supersedes / closes the approach attempted in #503. Absorbs the remaining scope from #479.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions