Skip to content

feat(detail): interactive git file & diff viewer in task detail view#620

Draft
bborn wants to merge 3 commits into
mainfrom
task/4474-git-aware-file-diff-viewer-in-task-detai
Draft

feat(detail): interactive git file & diff viewer in task detail view#620
bborn wants to merge 3 commits into
mainfrom
task/4474-git-aware-file-diff-viewer-in-task-detai

Conversation

@bborn

@bborn bborn commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What

A keyboard-driven, git-aware file & diff viewer in the task detail view (v to open), now with an interactive review layer that loops feedback back to the task's live agent — no PR round-trip.

Read-only viewer

  • File tree (left): changed files scoped to the task's worktree — committed, staged, unstaged, and untracked — vs the branch's base (merge-base against the source/default branch). Status-colored (A/M/D/?).
  • Content pane (right): the selected file's unified diff with chroma syntax highlighting. tab toggles a rendered view of the working-tree file, with glamour-rendered markdown for .md files.
  • / navigate files, j/k/wheel scroll, esc closes.

Interactive review

  • Line cursor () in diff mode (j/k); c opens an inline comment anchored to the current file + the quoted diff line.
  • Comments accumulate per task (persist across closing/reopening the viewer) and show a count badge.
  • s composes them into one structured message and sends it to the executor's tmux pane (claudePaneID), so the live agent addresses them in the worktree. When no agent is running (e.g. a done task), it falls back to the clipboard.

Why it's safe for the detail view

  • The "claude/shell panes" are tmux panes; the viewer renders entirely inside the existing detail box, so those panes and their layout are untouched.
  • All viewport writes go through setViewportContent(), and every bit of viewer + review state (active, selected, cursor, comments, input, status) is folded into viewSignature(), so the View() render cache stays correct.
  • File-list and per-file content load on goroutines (matching setupPanesAsync); the comment input is routed like the other live text inputs in app.go, with the viewer's async result messages exempted by type.
  • Live-pane delivery uses tmux send-keys -t <claudePaneID> -l … (the persisted pane id, since the detail view joins that pane into the UI session) — the same primitive as the existing executor.SendLiteralTextToPane nudge.

Files

  • internal/ui/diffviewer.go (new) — viewer + review state, git helpers, async loaders, chroma/glamour rendering, line cursor, comment compose + send/clipboard.
  • internal/ui/diffviewer_test.go (new) — helpers, git-integration (temp repo), DetailModel integration, compose/cursor/anchor/comment-input/clipboard, cache-signature.
  • internal/ui/detail.godiff field; viewSignature/View/renderContent/renderHelp/viewport-width/Update wiring.
  • internal/ui/app.gov keybinding + viewer/comment-input key routing.
  • go.modalecthomas/chroma/v2 and atotto/clipboard promoted to direct deps.

Verification

go build ./..., go vet, gofmt -l, go test ./internal/ui/ + ./internal/executor/, and golangci-lint run (v2.8.0, matching CI) — all green. Full QA with screenshots posted as PR comments (isolated-instance TUI + VHS, no live daemon touched).

🤖 Generated with Claude Code

Press `v` in the task detail view to open a keyboard-driven, read-only
viewer of what the task's agent changed — without leaving the TUI:

- A changed-file tree (left) scoped to the task's worktree, listing files
  the branch changed vs its base (merge-base of the source/default branch),
  including committed, staged, unstaged, and untracked files.
- A content pane (the existing viewport) showing the selected file's unified
  diff with chroma syntax highlighting; `tab` toggles to a rendered view of
  the working-tree file, with glamour-rendered markdown for .md files.
- up/down navigate files, j/k/wheel scroll the diff, esc closes.

The viewer renders entirely inside the existing detail box, so the tmux
claude/shell panes and their layout are untouched. All content writes go
through setViewportContent() and every bit of viewer display state is folded
into viewSignature(), so the View() render cache stays correct. File-list
and per-file content loading happen on goroutines (matching the existing
async pane-setup pattern) to keep the UI thread responsive on large diffs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bborn

bborn commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

QA evidence — git-aware file & diff viewer

Driven against an isolated ty instance (throwaway DB + a seeded done task whose worktree is a real git repo with a mix of committed / uncommitted / untracked changes vs main). Screenshots rendered with VHS at a real terminal size (not tmux capture-pane).

1. Detail view — new v review changes hint in the help bar (existing detail content/layout unchanged)
detail

2. Viewer open — changed-file tree (status-colored) + selected file's unified diff
Tree lists all four changes incl. the untracked CHANGELOG.md (shown as added vs /dev/null). Header shows Diff vs main; viewer help bar: ↑/↓ file · j/k/wheel scroll · tab diff/rendered · esc close.
viewer-open

3. Code diff — to server.go, chroma-highlighted unified diff (red removed / green added)
code-diff

4. Rendered markdown — README.md + tab toggles [file] mode → glamour render
Styled headings, code spans, blockquote, list.
md-rendered

Verified

  • ✅ Open a task → browse changed files (tree) → read diffs inline; committed, uncommitted, and untracked files all listed.
  • ✅ Markdown renders (tab → glamour); code/diffs syntax-highlighted (chroma).
  • ↑/↓ navigate the tree, j/k/wheel scroll the content (scroll hint shown), esc closes back to normal task content.
  • ✅ No regression to the existing detail panes/layout — viewer renders inside the detail box; the tmux claude/shell panes are untouched. All viewport writes go through setViewportContent() and viewer state is folded into viewSignature(), so the render cache stays correct (covered by TestViewSignatureChangesWithViewerState + existing detail_cache_test.go).

Automated checks

go build ./..., go vet, gofmt -l, go test ./internal/ui/, and golangci-lint run (v2.8.0, matching CI) — all green.

Rendered via the scripts/qa isolated-TUI + VHS harness; no live daemon/DB touched.

Turn the read-only diff viewer into an interactive review surface that
loops feedback straight back to the task's live agent — no PR round-trip.

- A vim-style line cursor in diff mode (j/k); the cursor line is where a
  comment anchors. File/rendered mode keeps j/k as scroll.
- `c` opens an inline comment input (anchored to the current file + the
  quoted diff line); comments accumulate per task and show a count badge.
- `s` composes the collected comments into one structured message and
  sends it to the executor's tmux pane (m.claudePaneID, since the detail
  view joins that pane into the UI session). When no agent is running
  (e.g. a done task) it falls back to copying the review to the clipboard
  so nothing is lost.

All new state (cursor, comments, input, status) is folded into
viewSignature() so the View() render cache stays correct, and the comment
input is routed like the other live text inputs in app.go (with the
viewer's async result messages exempted by type). Adds atotto/clipboard
as a direct dep. Tests cover compose/clipboard/cursor/anchor/input flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bborn

bborn commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

QA evidence — interactive review comments (follow-up commit)

The viewer is now interactive: leave comments on the diff and send them straight to the task's live agent (the same tmux send-keys path the executor already uses), with a clipboard fallback when no agent is running. Same isolated-instance + VHS harness as above.

1. Inline comment input — vim-style line cursor () + footer text field
j/k move the cursor; c opens the input anchored to the cursor's diff line. Footer: comment › … · enter save · esc cancel.
comment-input

2. Comment saved — · 1 comment count badge + status
Comments accumulate per task (persist across closing/reopening the viewer); header shows the count, footer confirms Added comment (1 pending).
comment-saved

3. Send (s) — clipboard fallback for a task with no live executor
This QA task is done (no running agent), so the review is copied to the clipboard: No live executor — copied 1 comments to clipboard. For a processing/blocked task the same action sends the composed review to the joined claudePaneID so the agent addresses it in the worktree.
sent-status

How the send works

  • The collected comments are composed into one structured single-line message (Code review on <branch> (N comments): [1] file @ line: body … Please address these in the worktree.).
  • Delivered via tmux send-keys -t <claudePaneID> -l <text> + Enter — targeting the persisted pane id (not the daemon session) because the detail view joins the executor pane into the UI session. This mirrors the existing executor.SendLiteralTextToPane nudge path.
  • No live executor → clipboard.WriteAll of a readable multi-line version.

Verified

  • j/k move the line cursor (diff mode); c opens an input anchored to the file + quoted diff line; enter saves, esc cancels.
  • ✅ Comments accumulate with a visible count and persist across viewer close/reopen; s sends + clears them.
  • ✅ Clipboard fallback when no agent is running (shown); live-pane send composes + targets claudePaneID.
  • ✅ Render cache intact — cursor/comments/input/status folded into viewSignature(); comment input routed like other live inputs in app.go (async result msgs exempted by type).

Automated checks (re-run)

go build ./..., go vet, gofmt -l, go test ./internal/ui/ + ./internal/executor/, golangci-lint run (v2.8.0) — all green. New tests: compose (single/multi-line), cursor movement + anchor, comment input save/cancel, clipboard send, and viewSignature changes for review state.

Follow-up note: the live-agent send is exercised by the same primitive as the existing executor nudge; the headless QA shows the clipboard fallback. A full live-agent demo (tier-2 ty-qa-agent harness) can be added if desired.

@bborn bborn changed the title feat(detail): read-only git file & diff viewer in task detail view feat(detail): interactive git file & diff viewer in task detail view Jun 24, 2026
The new `v` diff/file review viewer is rendered inline in the TUI pane
(chroma + glamour) and has no native GUI/web-API equivalent, so the
GUI-covers-TUI parity test must treat it as intentionally TUI-only —
mirroring the existing SpotlightSync entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant