agent-ecosystem/docs/diff-view-implementation-plan.md
iliya e160626c64 feat: implement diff view functionality with read-only and accept/reject capabilities
- Introduced a comprehensive implementation plan for the diff view feature, structured in four phases: MVP (read-only), accept/reject per hunk, per-task scoping, and enhanced features.
- Phase 1 includes a read-only diff view per agent, utilizing JSONL data to display file changes.
- Defined new types for file changes and review data, and established IPC channels for fetching member changes and reading file content.
- Developed backend services for extracting file changes and aggregating review data, alongside frontend components for displaying diffs and managing state.
- Subsequent phases will enhance the diff view with accept/reject functionality, task-specific change scoping, and improved user experience features.
2026-02-24 20:29:41 +02:00

34 KiB

Diff View + Accept/Reject -- Plan

Overview

4-phase plan. Phase 1 -> self-contained MVP (read-only diff view per agent). Phase 2 -> accept/reject per hunk with disk writes. Phase 3 -> per-task scoping. Phase 4 -> polish features.


Phase 1: MVP -- Read-Only Diff View Per Agent

Goal: show all file changes made by a team member in a diff review panel, using data from JSONL files. No accept/reject yet.

1.1 Packages to Install

pnpm add diff          # jsdiff v8 -- structuredPatch, applyPatch, parsePatch

@codemirror/merge + react-codemirror-merge deferred to Phase 2. diff is needed immediately for programmatic hunk computation from tool_use.input.

1.2 Types to Define

File: src/shared/types/review.ts (NEW ~120 LOC)

/** Represents one file edit extracted from JSONL */
export interface FileChange {
  filePath: string;          // Absolute path on disk
  toolName: 'Edit' | 'Write' | 'NotebookEdit' | 'Bash';
  toolUseId: string;         // For linking back to JSONL
  timestamp: string;          // ISO timestamp of the tool_use
  memberName: string;         // Agent who made the change
  sessionId: string;
  subagentId?: string;        // null for lead session

  // For Edit tool (main session with toolUseResult)
  originalFile?: string;       // Full file content BEFORE edit (from toolUseResult)
  structuredPatch?: Hunk[];    // Ready-made unified diff hunks (from toolUseResult)

  // For Edit tool (subagent -- no toolUseResult, only tool_use.input)
  oldString?: string;          // tool_use.input.old_string
  newString?: string;          // tool_use.input.new_string
  replaceAll?: boolean;

  // For Write tool
  writeContent?: string;       // Full new file content
  writeType?: 'create' | 'overwrite';

  // Reliability indicator
  confidence: 'high' | 'medium' | 'low';
}

export interface Hunk {
  oldStart: number;
  oldLines: number;
  newStart: number;
  newLines: number;
  lines: string[];   // Each line prefixed with ' ', '+', '-'
}

/** A file with all its changes aggregated */
export interface ReviewFile {
  filePath: string;
  relativePath: string;       // Relative to project root
  language: string;            // Inferred from extension
  changes: FileChange[];       // Ordered by timestamp
  stats: { added: number; removed: number };
  status: 'added' | 'modified' | 'deleted';
}

/** Complete review data for an agent */
export interface AgentReviewData {
  teamName: string;
  memberName: string;
  files: ReviewFile[];
  totalStats: { added: number; removed: number; filesChanged: number };
  extractedAt: string;
  confidence: 'high' | 'medium' | 'low';  // Lowest confidence of all changes
}

Re-export from src/shared/types/index.ts.

1.3 IPC Channels

File: src/preload/constants/ipcChannels.ts (MODIFY -- add 2 lines)

/** Get file changes for a team member (diff review) */
export const REVIEW_GET_MEMBER_CHANGES = 'review:getMemberChanges';

/** Read current file content from disk (for conflict detection) */
export const REVIEW_READ_FILE = 'review:readFile';

1.4 Backend Service

File: src/main/services/team/FileChangeExtractor.ts (NEW ~350 LOC)

Main service that parses JSONL files and extracts FileChange[].

Responsibilities:

  • Uses TeamMemberLogsFinder.findMemberLogPaths() to get JSONL paths
  • For MAIN session JSONL: extracts toolUseResult objects with originalFile + structuredPatch (high confidence)
  • For SUBAGENT JSONL: extracts tool_use.input (old_string, new_string, file_path) from Edit blocks (medium confidence)
  • For Write tools: extracts tool_use.input.content + file_path (medium confidence for overwrite, high for create)
  • Uses diff package's structuredPatch() to compute hunks when structuredPatch is not present in JSONL
  • Caches results with 2-minute TTL (like MemberStatsComputer)
  • Error filtering: skips entries where typeof toolUseResult === 'string' or is_error: true

File: src/main/services/team/ReviewAggregator.ts (NEW ~150 LOC)

Transforms FileChange[] into AgentReviewData:

  • Groups changes by filePath
  • Computes per-file stats (lines added/removed)
  • Infers file status (added/modified/deleted)
  • Computes relative paths from project root
  • Infers language from file extension (reuse shared utility)

1.5 IPC Handler

File: src/main/ipc/review.ts (NEW ~120 LOC)

Follows exact same pattern as teams.ts:

  • let fileChangeExtractor: FileChangeExtractor | null = null;
  • let reviewAggregator: ReviewAggregator | null = null;
  • initializeReviewHandlers(extractor, aggregator)
  • registerReviewHandlers(ipcMain) / removeReviewHandlers(ipcMain)
  • wrapReviewHandler<T>() -- same as wrapTeamHandler
  • Handlers:
    • handleGetMemberChanges(event, teamName, memberName) -> IpcResult<AgentReviewData>
    • handleReadFile(event, filePath) -> IpcResult<string> (reads current file from disk, with path validation)

File: src/main/ipc/handlers.ts (MODIFY)

  • Import and register review handlers

File: src/main/ipc/guards.ts (MODIFY -- if needed for new validations)

1.6 Preload Bridge

File: src/preload/index.ts (MODIFY)

  • Add review namespace to exposed API:
review: {
  getMemberChanges: (teamName: string, memberName: string) =>
    invokeIpcWithResult(REVIEW_GET_MEMBER_CHANGES, teamName, memberName),
  readFile: (filePath: string) =>
    invokeIpcWithResult(REVIEW_READ_FILE, filePath),
}

File: src/shared/types/api.ts (MODIFY)

  • Add ReviewAPI interface
  • Add review: ReviewAPI to ElectronAPI

File: src/renderer/api/httpClient.ts (MODIFY)

  • Add review HTTP fallback stubs

1.7 Zustand Store

File: src/renderer/store/slices/reviewSlice.ts (NEW ~120 LOC)

export interface ReviewSlice {
  // State
  reviewData: AgentReviewData | null;
  reviewLoading: boolean;
  reviewError: string | null;
  selectedReviewFile: string | null;   // filePath

  // Actions
  fetchMemberChanges: (teamName: string, memberName: string) => Promise<void>;
  selectReviewFile: (filePath: string | null) => void;
  clearReview: () => void;
}

File: src/renderer/store/index.ts (MODIFY -- add slice) File: src/renderer/store/types.ts (MODIFY -- add to AppState)

1.8 UI Components

File: src/renderer/components/team/review/ReviewPanel.tsx (NEW ~180 LOC)

Main container component. Layout:

+----------------------------------+
| ReviewPanel                       |
| [member-name] +142 -38  [Close]  |
+----------+-----------------------+
| FileTree | DiffContent            |
|          |                        |
| src/     | file: auth.ts          |
|   auth.ts| @@ -1,5 +1,42 @@      |
|   +87 -2 | + import jwt ...       |
|          |                        |
| test/    | @@ -42,3 +42,8 @@     |
|   auth.. | - const OLD = ...      |
|   +42 -0 | + const NEW = ...      |
+----------+-----------------------+

Props: teamName: string, memberName: string, onClose: () => void

File: src/renderer/components/team/review/ReviewFileTree.tsx (NEW ~150 LOC)

Left sidebar with file list:

  • Grouped by directory
  • Per-file stats (+added / -removed)
  • Active file highlight
  • Click to select file
  • Collapsible directory groups

File: src/renderer/components/team/review/ReviewDiffContent.tsx (NEW ~120 LOC)

Right panel showing the diff for selected file:

  • Header with filename, language badge, stats
  • Uses improved DiffViewer (Phase 1 keeps the LCS approach but adds useMemo + proper line numbers)
  • Handles multiple changes to same file (shows them sequentially)
  • Shows confidence indicator for low/medium confidence changes
  • Collapse/expand unchanged code regions

File: src/renderer/components/team/review/ReviewEmptyState.tsx (NEW ~30 LOC)

Empty state when no changes found.

1.9 Integration Point

File: src/renderer/components/team/members/MemberCard.tsx (MODIFY)

Add "Review Changes" button to member card:

<button onClick={() => openReviewPanel(memberName)}>
  <GitCompareArrows className="size-4" /> Review
</button>

File: src/renderer/components/team/TeamDetailView.tsx (MODIFY)

Add ReviewPanel rendering (slide-in panel or dialog). Wire up state:

{reviewMember && (
  <ReviewPanel
    teamName={teamName}
    memberName={reviewMember}
    onClose={() => setReviewMember(null)}
  />
)}

1.10 Existing DiffViewer -- Migration Strategy

The existing DiffViewer.tsx in src/renderer/components/chat/viewers/ is used for inline Edit tool display in chat history. It stays UNCHANGED in Phase 1.

The new review components are in a separate team/review/ directory and do NOT modify DiffViewer. In Phase 2, when CodeMirror is introduced, both surfaces will be migrated.

1.11 Service Registration

File: src/main/services/team/index.ts (MODIFY -- add 2 exports) File: src/main/services/index.ts (MODIFY -- re-export)

1.12 Language Detection Utility

File: src/shared/utils/languageDetection.ts (NEW ~50 LOC)

Extract the EXTENSION_LANGUAGE_MAP and inferLanguage() from DiffViewer.tsx into a shared utility. Both DiffViewer and ReviewDiffContent will import from here. DiffViewer.tsx gets modified to import instead of duplicating.

1.13 Testing Strategy

File: test/main/services/team/FileChangeExtractor.test.ts (NEW ~250 LOC)

  • Test parsing Edit tool_use with toolUseResult (main session)
  • Test parsing Edit tool_use without toolUseResult (subagent)
  • Test parsing Write create / overwrite
  • Test error filtering (failed edits, rejected edits)
  • Test caching behavior

File: test/main/services/team/ReviewAggregator.test.ts (NEW ~100 LOC)

  • Test grouping changes by file
  • Test stats computation
  • Test relative path calculation
  • Test file status inference

File: test/main/ipc/review.test.ts (NEW ~80 LOC)

  • Test input validation (teamName, memberName)
  • Test error wrapping

File: test/shared/utils/languageDetection.test.ts (NEW ~40 LOC)

1.14 Phase 1 Summary

Category New Files Modified Files Estimated LOC
Types 1 2 ~120
Backend services 2 2 ~500
IPC handler 1 2 ~120
Preload/API 0 3 ~40
Store 1 2 ~120
UI components 4 2 ~480
Shared utils 1 1 ~50
Tests 4 0 ~470
Total 14 14 ~1,900

1.15 Risks

Risk Probability Mitigation
Subagent JSONL lacks toolUseResult -- hunks inaccurate HIGH (known) Use diff.structuredPatch(old_string, new_string) for subagents; show confidence badge
Large files slow LCS diff MEDIUM Phase 1 uses diff package (Myers algorithm), not hand-rolled LCS; add useMemo
Write tool missing originalFile HIGH (known) Show "file created" or "file overwritten" without diff; add note
Multiple JSONL files per member LOW Already handled by findMemberLogPaths()

1.16 Dependencies

Phase 1 is self-contained. No dependency on other phases.


Phase 2: Accept/Reject Per Hunk

Goal: interactive diff UI with per-hunk Accept/Reject buttons. Reject writes modified file back to disk.

2.1 Packages to Install

pnpm add @codemirror/merge          # Diff UI with acceptChunk/rejectChunk
pnpm add react-codemirror-merge     # React wrapper
pnpm add @codemirror/state          # Core dependency
pnpm add @codemirror/view           # Core dependency
pnpm add @codemirror/lang-javascript  # Syntax highlight
pnpm add @codemirror/lang-python
pnpm add @codemirror/lang-css
pnpm add @codemirror/lang-html
pnpm add @codemirror/lang-json
pnpm add @codemirror/lang-markdown
pnpm add @codemirror/lang-rust
pnpm add @codemirror/lang-sql
pnpm add @codemirror/theme-one-dark   # Dark theme matching our palette
pnpm add node-diff3                   # Three-way merge for conflict detection

2.2 New Types

File: src/shared/types/review.ts (MODIFY -- add ~80 LOC)

/** Per-hunk review decision */
export type HunkDecision = 'accepted' | 'rejected' | 'pending';

/** Review state for a single file */
export interface FileReviewState {
  filePath: string;
  hunkDecisions: HunkDecision[];  // One per hunk, indexed
  viewed: boolean;
  hasConflict: boolean;
  conflictDetails?: string;
}

/** Request to apply review decisions to disk */
export interface ApplyReviewRequest {
  teamName: string;
  memberName: string;
  filePath: string;
  hunkDecisions: HunkDecision[];
  originalFile: string;          // Base version for patch computation
  currentDiskContent: string;    // For conflict detection
}

export interface ApplyReviewResult {
  success: boolean;
  conflictDetected: boolean;
  conflictDetails?: string;
  newContent?: string;
}

2.3 IPC Channels

File: src/preload/constants/ipcChannels.ts (MODIFY)

/** Apply review decisions (write to disk) */
export const REVIEW_APPLY_DECISIONS = 'review:applyDecisions';

/** Get file-history backup content */
export const REVIEW_GET_BACKUP = 'review:getBackup';

2.4 Backend Services

File: src/main/services/team/ReviewApplier.ts (NEW ~200 LOC)

Core logic for writing accepted/rejected hunks to disk:

Accept hunk:   No-op (file already has the change)
Reject hunk:   Compute reverse patch for that hunk, apply to current file
Reject all:    Write originalFile to disk
Partial:       Apply only accepted hunks from originalFile base

Implementation:

  1. Read current file from disk
  2. If current != expected (agent version), run 3-way merge:
    • base = originalFile (before agent edit)
    • ours = result of applying only accepted hunks to originalFile
    • theirs = current disk content
    • Use node-diff3.diff3Merge() for conflict detection
  3. If no conflict: write merged result
  4. If conflict: return conflict details to UI, do NOT write

File: src/main/services/team/BackupReader.ts (NEW ~60 LOC)

Reads ~/.claude/file-history/{sessionId}/{backupFileName} backup files. Used as fallback when originalFile is not in JSONL (Write tool case).

2.5 IPC Handler

File: src/main/ipc/review.ts (MODIFY -- add 2 handlers)

  • handleApplyDecisions(event, request: ApplyReviewRequest) -> IpcResult<ApplyReviewResult>
    • Validates all fields
    • Calls ReviewApplier
    • Path traversal validation (prevent writing outside project dir)
  • handleGetBackup(event, sessionId, backupFileName) -> IpcResult<string>
    • Validates sessionId format
    • Reads backup file content

2.6 Preload / API

File: src/preload/index.ts (MODIFY) File: src/shared/types/api.ts (MODIFY -- extend ReviewAPI) File: src/renderer/api/httpClient.ts (MODIFY)

2.7 Zustand Store

File: src/renderer/store/slices/reviewSlice.ts (MODIFY -- add ~80 LOC)

// Additional state
fileReviewStates: Record<string, FileReviewState>;
applyingReview: boolean;
applyError: string | null;

// Additional actions
setHunkDecision: (filePath: string, hunkIndex: number, decision: HunkDecision) => void;
acceptAllHunks: (filePath: string) => void;
rejectAllHunks: (filePath: string) => void;
acceptAllFiles: () => void;
rejectAllFiles: () => void;
applyReviewDecisions: (filePath: string) => Promise<ApplyReviewResult>;
markFileViewed: (filePath: string) => void;

2.8 UI Components

File: src/renderer/components/team/review/CodeMirrorDiffView.tsx (NEW ~250 LOC)

Replaces the simple DiffViewer in the review panel with CodeMirror merge view:

  • MergeView from @codemirror/merge with mergeControls: true
  • Theme integration with CSS variables (dark/light)
  • collapseUnchanged for hiding unchanged regions
  • allowInlineDiffs for character-level highlighting
  • Custom mergeControls renderer for Accept/Reject buttons matching our design system
  • goToNextChunk/goToPreviousChunk wired to keyboard shortcuts
  • Read-only mode (user cannot edit the code, only accept/reject)
  • Emits onHunkDecision(hunkIndex, decision) callback

File: src/renderer/components/team/review/ReviewToolbar.tsx (NEW ~80 LOC)

Bottom toolbar:

  • "Reject All" / "Accept All" buttons
  • Unified / Split toggle
  • Stats summary (e.g. "3/7 hunks accepted")
  • Apply button (writes to disk)

File: src/renderer/components/team/review/ConflictDialog.tsx (NEW ~80 LOC)

Dialog shown when conflict is detected:

  • Shows conflict details
  • Options: "Force reject (overwrite)", "Skip this file", "Cancel"

File: src/renderer/components/team/review/ReviewFileTree.tsx (MODIFY)

Add per-file status indicators:

  • Checkmark (all accepted)
  • X (all rejected)
  • Partial (mixed)
  • Warning (conflict detected)
  • Eye icon (viewed/unviewed)

File: src/renderer/components/team/review/ReviewDiffContent.tsx (MODIFY)

Replace inline diff rendering with CodeMirrorDiffView component.

2.9 CodeMirror Theme

File: src/renderer/components/team/review/codemirrorTheme.ts (NEW ~80 LOC)

Custom CodeMirror theme that maps to our CSS variables:

  • --diff-added-bg, --diff-removed-bg
  • --code-bg, --code-border
  • Font family matching our monospace stack
  • Accept/Reject button styling

2.10 Existing DiffViewer Migration

At this point, the chat viewer's DiffViewer.tsx can optionally be migrated to use CodeMirror as well. This is NOT required for the review feature but improves consistency. If done:

  • DiffViewer.tsx becomes a thin wrapper around CodeMirror (read-only, no accept/reject)
  • LCS algorithm removed
  • Bundle size increase ~130KB (CodeMirror core) -- acceptable since already loaded for review

Recommended: keep old DiffViewer in chat view for now (it works, it's lightweight). Only the review panel uses CodeMirror.

2.11 Testing Strategy

File: test/main/services/team/ReviewApplier.test.ts (NEW ~200 LOC)

  • Test reject single hunk
  • Test reject all hunks
  • Test partial accept/reject
  • Test conflict detection (file changed after agent edit)
  • Test three-way merge resolution
  • Test path traversal prevention

File: test/main/services/team/BackupReader.test.ts (NEW ~60 LOC)

  • Test reading backup files
  • Test missing backup graceful handling

2.12 Phase 2 Summary

Category New Files Modified Files Estimated LOC
Types 0 1 ~80
Backend services 2 0 ~260
IPC handler 0 1 ~60
Preload/API 0 3 ~30
Store 0 1 ~80
UI components 4 2 ~490
Theme 1 0 ~80
Tests 2 0 ~260
Total 9 8 ~1,340

2.13 Risks

Risk Probability Mitigation
CodeMirror bundle size (~130KB) LOW Lazy import; only loaded when review panel opens
Three-way merge conflicts hard to resolve MEDIUM Show clear conflict UI; always offer "force" option
originalFile missing for some edits MEDIUM Fall back to file-history backups; show warning
CodeMirror theme integration complex LOW Start with one-dark theme, customize incrementally
react-codemirror-merge API changes LOW Pin version; wrapper is thin

2.14 Dependencies

  • Requires Phase 1 (review data extraction)
  • Phase 2 review decisions are per-agent only (not per-task)

Phase 3: Per-Task Scoping

Goal: show diffs scoped to a specific task, not just an agent. Integrate review into the kanban board task cards.

3.1 No New Packages

All needed packages installed in Phases 1-2.

3.2 Types

File: src/shared/types/review.ts (MODIFY -- add ~50 LOC)

/** Time window for task-scoped change extraction */
export interface TaskTimeWindow {
  taskId: string;
  memberName: string;
  startTimestamp: string | null;   // First activity related to task
  endTimestamp: string | null;     // Task completion or latest activity
  confidence: 'high' | 'medium' | 'low';
  markers: TaskMarker[];
}

export interface TaskMarker {
  type: 'task_start' | 'task_complete' | 'task_create' | 'task_update' | 'mention';
  timestamp: string;
  source: string;  // JSONL line info
}

/** Review scoped to a task */
export interface TaskReviewData extends AgentReviewData {
  taskId: string;
  taskSubject: string;
  timeWindow: TaskTimeWindow;
}

3.3 IPC Channels

File: src/preload/constants/ipcChannels.ts (MODIFY)

/** Get file changes scoped to a task */
export const REVIEW_GET_TASK_CHANGES = 'review:getTaskChanges';

3.4 Backend Service

File: src/main/services/team/TaskTimeWindowResolver.ts (NEW ~250 LOC)

Resolves the time window for a task by scanning JSONL files:

  1. Use TeamMemberLogsFinder.findLogsForTask() to find relevant JSONL files
  2. Scan each file for task markers:
    • TaskCreate with matching task ID -> start marker
    • TaskUpdate with status in_progress -> start marker
    • TaskUpdate with status completed -> end marker
    • SendMessage referencing task ID -> activity marker
    • Comment mentioning #taskId -> activity marker
  3. Build TaskTimeWindow from earliest start to latest end
  4. Confidence levels:
    • HIGH: both explicit start + end markers found
    • MEDIUM: only start OR end found, other inferred from timestamps
    • LOW: no explicit markers, only mentions -- wide time window

File: src/main/services/team/FileChangeExtractor.ts (MODIFY -- add ~80 LOC)

New method: extractChangesForTask(teamName, taskId, timeWindow)

  • Same JSONL parsing as per-agent
  • Filters tool_use blocks by timestamp within timeWindow
  • Additional heuristic: if task owner is known, only include that member's changes

3.5 IPC Handler

File: src/main/ipc/review.ts (MODIFY)

Add handleGetTaskChanges(event, teamName, taskId) handler.

3.6 Preload / API

Same pattern: extend ReviewAPI, update preload/index.ts, update httpClient.ts.

3.7 Zustand Store

File: src/renderer/store/slices/reviewSlice.ts (MODIFY)

// Additional state
taskReviewData: TaskReviewData | null;
taskReviewLoading: boolean;

// Additional action
fetchTaskChanges: (teamName: string, taskId: string) => Promise<void>;

3.8 UI Components

File: src/renderer/components/team/review/TaskReviewPanel.tsx (NEW ~100 LOC)

Wraps ReviewPanel with task-specific header:

  • Task subject + ID
  • Time window visualization (start -> end)
  • Confidence badge
  • Same file tree + diff content as ReviewPanel (reuses components)

File: src/renderer/components/team/kanban/KanbanTaskCard.tsx (MODIFY)

Add "Review Changes" button on task cards that are in review or done columns:

{(task.kanbanColumn === 'review' || task.status === 'completed') && (
  <button onClick={() => openTaskReview(task.id)}>
    <GitCompareArrows className="size-3.5" /> Changes
  </button>
)}

File: src/renderer/components/team/dialogs/TaskDetailDialog.tsx (MODIFY)

Add "View Changes" tab/section to task detail dialog.

3.9 Testing Strategy

File: test/main/services/team/TaskTimeWindowResolver.test.ts (NEW ~200 LOC)

  • Test finding task markers in JSONL
  • Test HIGH confidence (both markers)
  • Test MEDIUM confidence (partial markers)
  • Test LOW confidence (only mentions)
  • Test multiple sessions contributing to same task

File: test/main/services/team/FileChangeExtractor.task.test.ts (NEW ~120 LOC)

  • Test time-window filtering
  • Test cross-session task changes

3.10 Phase 3 Summary

Category New Files Modified Files Estimated LOC
Types 0 1 ~50
Backend services 1 1 ~330
IPC handler 0 1 ~30
Preload/API 0 3 ~20
Store 0 1 ~30
UI components 1 2 ~100
Tests 2 0 ~320
Total 4 9 ~880

3.11 Risks

Risk Probability Mitigation
Time window too wide (catches unrelated changes) MEDIUM Use confidence levels; show warning for LOW confidence
Task timestamps not in JSONL HIGH (known) Rely on tool_use timestamps from JSONL, not task JSON file
Multiple agents working on same task LOW Show all contributing agents, grouped by member
Task markers hard to find in large JSONL MEDIUM Reuse fileMentionsTaskId() for fast scanning

3.12 Dependencies

  • Requires Phase 1 (FileChangeExtractor)
  • Optionally uses Phase 2 (accept/reject) but works without it (read-only task review)

Phase 4: Enhanced Features

Goal: polish, keyboard navigation, "viewed" tracking, multi-edit timeline, git fallback for Bash changes.

4.1 Packages to Install

pnpm add simple-git    # Git operations for Bash change detection

4.2 Feature A: Multiple Edits to Same File (Timeline View)

File: src/renderer/components/team/review/FileEditTimeline.tsx (NEW ~120 LOC)

When a file has multiple FileChange entries:

  • Show a horizontal timeline of edits
  • Each node = one edit (with timestamp, agent name)
  • Click node to see that specific diff
  • "Final" shows cumulative diff

4.3 Feature B: Keyboard Navigation

File: src/renderer/hooks/useReviewKeyboardNav.ts (NEW ~80 LOC)

Keyboard shortcuts (while review panel is focused):

  • j / k -- next/previous file
  • n / p -- next/previous hunk
  • a -- accept current hunk
  • r -- reject current hunk
  • A (shift+a) -- accept all hunks in file
  • R (shift+r) -- reject all hunks in file
  • v -- toggle viewed
  • Escape -- close review panel

Integrates with CodeMirror's goToNextChunk / goToPreviousChunk.

4.4 Feature C: "Viewed" File Tracking

File: src/renderer/store/slices/reviewSlice.ts (MODIFY -- ~20 LOC)

Persistent "viewed" state per file (stored in IndexedDB via idb-keyval):

  • Key: review:{teamName}:{memberName}:{filePath}
  • Value: { viewed: boolean, viewedAt: string }
  • Badge in file tree: eye icon / number of unviewed files

4.5 Feature D: Git Fallback for Bash Changes

File: src/main/services/team/GitDiffProvider.ts (NEW ~150 LOC)

For changes made via Bash (git apply, sed, etc.):

  1. Get project's git repo path from team config
  2. Use simple-git to run git log --author --since --until --stat filtered by session timestamps
  3. For each changed file: git diff <before-sha>..<after-sha> -- <file>
  4. Convert to FileChange[] with confidence: 'medium'

Integration: called by FileChangeExtractor when toolName === 'Bash' and git is available.

4.6 Feature E: Split/Unified View Toggle

File: src/renderer/components/team/review/CodeMirrorDiffView.tsx (MODIFY)

Add orientation prop:

  • 'a-b' (side-by-side / split view)
  • Unified view via custom rendering

Store user preference in localStorage.

4.7 Testing Strategy

File: test/main/services/team/GitDiffProvider.test.ts (NEW ~100 LOC) File: test/renderer/hooks/useReviewKeyboardNav.test.ts (NEW ~80 LOC)

4.8 Phase 4 Summary

Category New Files Modified Files Estimated LOC
Backend services 1 1 ~150
UI components 1 1 ~120
Hooks 1 0 ~80
Store 0 1 ~20
Tests 2 0 ~180
Total 5 3 ~550

4.9 Risks

Risk Probability Mitigation
simple-git not available on all systems MEDIUM Feature is optional fallback; graceful degradation
Git diff timestamps don't match JSONL exactly MEDIUM Use wide time window (+/- 60s) for matching
Keyboard navigation conflicts with existing shortcuts LOW Scope to review panel focus only

4.10 Dependencies

  • Requires Phase 2 (CodeMirror for split/unified toggle, keyboard nav)
  • Git fallback can be done independently

Complete File Manifest

All New Files (32 total)

Phase File LOC
1 src/shared/types/review.ts ~120
1 src/shared/utils/languageDetection.ts ~50
1 src/main/services/team/FileChangeExtractor.ts ~350
1 src/main/services/team/ReviewAggregator.ts ~150
1 src/main/ipc/review.ts ~120
1 src/renderer/store/slices/reviewSlice.ts ~120
1 src/renderer/components/team/review/ReviewPanel.tsx ~180
1 src/renderer/components/team/review/ReviewFileTree.tsx ~150
1 src/renderer/components/team/review/ReviewDiffContent.tsx ~120
1 src/renderer/components/team/review/ReviewEmptyState.tsx ~30
1 test/main/services/team/FileChangeExtractor.test.ts ~250
1 test/main/services/team/ReviewAggregator.test.ts ~100
1 test/main/ipc/review.test.ts ~80
1 test/shared/utils/languageDetection.test.ts ~40
2 src/main/services/team/ReviewApplier.ts ~200
2 src/main/services/team/BackupReader.ts ~60
2 src/renderer/components/team/review/CodeMirrorDiffView.tsx ~250
2 src/renderer/components/team/review/ReviewToolbar.tsx ~80
2 src/renderer/components/team/review/ConflictDialog.tsx ~80
2 src/renderer/components/team/review/codemirrorTheme.ts ~80
2 test/main/services/team/ReviewApplier.test.ts ~200
2 test/main/services/team/BackupReader.test.ts ~60
3 src/main/services/team/TaskTimeWindowResolver.ts ~250
3 src/renderer/components/team/review/TaskReviewPanel.tsx ~100
3 test/main/services/team/TaskTimeWindowResolver.test.ts ~200
3 test/main/services/team/FileChangeExtractor.task.test.ts ~120
4 src/main/services/team/GitDiffProvider.ts ~150
4 src/renderer/components/team/review/FileEditTimeline.tsx ~120
4 src/renderer/hooks/useReviewKeyboardNav.ts ~80
4 test/main/services/team/GitDiffProvider.test.ts ~100
4 test/renderer/hooks/useReviewKeyboardNav.test.ts ~80

All Modified Files (across all phases)

File Phases Changes
src/shared/types/review.ts 1,2,3 Type additions
src/shared/types/index.ts 1 Re-export
src/shared/types/api.ts 1,2,3 ReviewAPI interface
src/preload/constants/ipcChannels.ts 1,2,3 Channel constants
src/preload/index.ts 1,2,3 Bridge methods
src/main/ipc/handlers.ts 1 Register review handlers
src/main/ipc/review.ts 2,3 Additional handlers
src/main/services/team/index.ts 1,2,3,4 Barrel exports
src/main/services/index.ts 1 Re-export
src/renderer/api/httpClient.ts 1,2,3 HTTP fallback
src/renderer/store/index.ts 1 Add slice
src/renderer/store/types.ts 1 AppState type
src/renderer/store/slices/reviewSlice.ts 2,3,4 State extensions
src/renderer/components/team/members/MemberCard.tsx 1 Review button
src/renderer/components/team/TeamDetailView.tsx 1 Panel integration
src/renderer/components/team/review/ReviewFileTree.tsx 2 Status indicators
src/renderer/components/team/review/ReviewDiffContent.tsx 2 CodeMirror swap
src/renderer/components/team/kanban/KanbanTaskCard.tsx 3 Review button
src/renderer/components/team/dialogs/TaskDetailDialog.tsx 3 Changes tab
src/renderer/components/chat/viewers/DiffViewer.tsx 1 Extract languageDetection
src/main/services/team/FileChangeExtractor.ts 3,4 Task scope + git fallback
src/renderer/components/team/review/CodeMirrorDiffView.tsx 4 Split/unified toggle

Estimated Total

Phase New Files Modified Files LOC Packages
1 MVP 14 14 ~1,900 diff
2 Accept/Reject 9 8 ~1,340 @codemirror/*, node-diff3
3 Per-Task 4 9 ~880 --
4 Enhanced 5 3 ~550 simple-git
Total 32 34 ~4,670 14 packages

Implementation Order Recommendation

Week 1:  Phase 1 (MVP read-only diff view)
         - Day 1-2: Types + FileChangeExtractor + ReviewAggregator + tests
         - Day 3: IPC handler + preload bridge
         - Day 4-5: UI components + store + integration

Week 2:  Phase 2 (Accept/Reject)
         - Day 1-2: CodeMirror integration + theme
         - Day 3: ReviewApplier + conflict detection + tests
         - Day 4: Toolbar + ConflictDialog
         - Day 5: Polish + testing

Week 3:  Phase 3 (Per-Task) + Phase 4 start
         - Day 1-2: TaskTimeWindowResolver + tests
         - Day 3: Task review UI + kanban integration
         - Day 4-5: Phase 4 features (keyboard nav, viewed tracking)

Week 4:  Phase 4 completion + polish
         - Day 1-2: Git fallback
         - Day 3: File edit timeline
         - Day 4-5: Integration testing, edge cases, performance tuning

Architecture Decision Records

ADR-1: Separate review:* IPC namespace vs extending team:*

Decision: Separate review:* namespace. Reason: Review is a distinct concern with its own service lifecycle. Mixing into teams.ts (already 1400+ LOC) would make it harder to maintain. Following the existing pattern where team:* channels are for team CRUD/messaging and new domains get their own namespace.

ADR-2: diff (jsdiff) for hunk computation vs raw structured patch from JSONL

Decision: Use JSONL structuredPatch when available (main session Edit), fall back to diff.structuredPatch() for subagents. Reason: JSONL data is most reliable (computed by CLI at edit time). But subagent JSONL lacks it, so we need programmatic fallback. diff v8 has 47M weekly downloads and proven reliability.

ADR-3: CodeMirror vs @pierre/diffs

Decision: @codemirror/merge. Reason: Native acceptChunk() / rejectChunk() API, mature ecosystem (580K downloads), MIT license, TypeScript support, active maintenance. @pierre/diffs is newer (Sep 2025), has no explicit license, and Shadow DOM complicates theme integration.

ADR-4: Keep existing DiffViewer in chat view

Decision: Do NOT replace chat DiffViewer with CodeMirror in Phase 2. Reason: Chat DiffViewer is read-only and lightweight (~370 LOC). Adding CodeMirror bundle to every chat view is unnecessary. Review panel loads CodeMirror lazily only when opened. Migration can be done later if needed.

ADR-5: Per-agent first, per-task second

Decision: Phase 1-2 are per-agent only. Per-task added in Phase 3. Reason: Per-agent is 100% reliable (each agent has its own JSONL). Per-task requires time-window inference (~85% reliability). Ship reliable feature first, add task scoping as enhancement.