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 → tracked in #616

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.

Now tracked in #616, which bundles this phase's server-side streaming work with item 1 of #615 (CodeTab's client-side transport insulation). The two only pay off together: shipping the server stream without the client wrapper is wasted motion, and shipping the client wrapper without the stream is a pure alias with zero user value.

Phase 6 — Inline comments on diff lines

User-facing change: 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.

Value: Capture targeted feedback as you read the diff, without losing your place.

Libraries / prior art:

  • Comment anchoring UI: whichever diff library from Phase 1 exposes line-level event hooks; otherwise a thin overlay.
  • Storage: simple file (e.g. a path under a kolu-managed directory keyed by repo + branch). No new dep.

Phase 7 — Paste comment reference into the agent terminal

User-facing change: 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.

Value: Closes the loop: human feedback flows back into the agent's context without a new protocol, a new config, or a new integration. Matches kolu's "terminal is the universal interface" thesis — kolu is just typing on the user's behalf.

Libraries / prior art: No new libs — kolu already owns the PTY and already injects text (e.g. shell-integration preexec marks).

Phase 8 — Robustness (binary files, large diffs)

Phase 1 shipped two correctness/perf gaps worth tracking explicitly so they don't silently decay.

Binary file diffs. readHeadContent / readWorkingContent read every path as UTF-8 and feed the result to the diff viewer. A binary file (image, compiled artifact, .wasm, etc.) becomes either a Buffer-to-string munge or a hard error — neither is a useful review. Detection is cheap: git diff --numstat reports -\t-\t<path> for binary files. The change: short-circuit getDiff on a - numstat line and return a sentinel { hunks: [], binary: true } (schema change); the client renders a "Binary file — not displayable" placeholder instead of trying to syntax-highlight bytes.

Large-file virtualization. The current DiffView renders the full hunk list into the DOM at once. A file with tens of thousands of changed lines (lockfile, generated SDK, a mass-rename) will stall the frame. @git-diff-view/solid has no virtualization story; either we pre-truncate server-side (return the first N hunks plus a "truncated" marker) or we adopt a virtual-scroll wrapper around the diff rows. Acceptable for now — most review targets are human-scale edits — but worth tracking.

Value: Phases 1–7 are usable as-is for the 95% case. Phase 8 makes it not fall over on the 5%.

Libraries / prior art: No new libs for binary detection (git diff --numstat is already in the tree). Virtualization likely needs a SolidJS virtual-list primitive (@solid-primitives/virtual or similar) or a library upgrade; decide when the first real complaint lands.

Phase 9 — Fuzzy file search + Cmd+O

User-facing change: Cmd+O opens a fuzzy file finder overlay. Debounced query → ranked results with match highlighting. Selecting a result opens the file in the tree. Command palette "Browse files" entry.

  • fs.search endpoint — server-side fuzzy matching (e.g. fzf-style scoring)

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

Phase 10 — Git status decorations in file tree

User-facing change: The file tree (Phase 4) shows per-file git status: color-coded dots/badges (green = added, blue = modified, red = deleted). Status propagates to parent directories (dot if any descendant is modified).

  • Extend fs.listDir response with per-file git status, or add a separate fs.gitStatus endpoint parsing git status --porcelain.

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

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.
  • Phase 6 is where the product starts being agent-native; it's also where the storage shape needs a deliberate decision (per-branch file? per-commit?).
  • Phase 7's "paste the path" simplicity is the design: resist inventing a kolu-specific agent-context protocol when the terminal already is one.
  • 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.

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