feat: split FileDiff into metadata FileDiff + hunk-bearing FileDiffDetail
Prepares the review components for the backend phase that returns metadata-only file lists from getPhaseReviewDiff. FileDiff now holds only path/status/additions/deletions; FileDiffDetail extends it with hunks. Renames changeType→status and adds 'binary' to the union. Also fixes two pre-existing TypeScript errors: InitiativeReview was passing an unknown `comments` prop to DiffViewer (should be commentsByLine), and ConflictResolutionPanel destructured an unused `agent` variable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ interface ConflictResolutionPanelProps {
|
||||
}
|
||||
|
||||
export function ConflictResolutionPanel({ initiativeId, conflicts, onResolved }: ConflictResolutionPanelProps) {
|
||||
const { state, agent, questions, spawn, resume, stop, dismiss } = useConflictAgent(initiativeId);
|
||||
const { state, agent: _agent, questions, spawn, resume, stop, dismiss } = useConflictAgent(initiativeId);
|
||||
const [showManual, setShowManual] = useState(false);
|
||||
const prevStateRef = useRef<string | null>(null);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FileDiff, DiffLine, ReviewComment } from "./types";
|
||||
import type { FileDiffDetail, DiffLine, ReviewComment } from "./types";
|
||||
import { FileCard } from "./FileCard";
|
||||
|
||||
function getFileCommentMap(
|
||||
@@ -13,7 +13,7 @@ function getFileCommentMap(
|
||||
}
|
||||
|
||||
interface DiffViewerProps {
|
||||
files: FileDiff[];
|
||||
files: FileDiffDetail[];
|
||||
commentsByLine: Map<string, ReviewComment[]>;
|
||||
onAddComment: (
|
||||
filePath: string,
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
Circle,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { FileDiff, FileChangeType, DiffLine, ReviewComment } from "./types";
|
||||
import type { FileDiffDetail, DiffLine, ReviewComment } from "./types";
|
||||
import { HunkRows } from "./HunkRows";
|
||||
import { useHighlightedFile } from "./use-syntax-highlight";
|
||||
|
||||
const changeTypeBadge: Record<
|
||||
FileChangeType,
|
||||
FileDiffDetail['status'],
|
||||
{ label: string; classes: string } | null
|
||||
> = {
|
||||
added: {
|
||||
@@ -32,17 +32,19 @@ const changeTypeBadge: Record<
|
||||
"bg-status-active-bg text-status-active-fg border-status-active-border",
|
||||
},
|
||||
modified: null,
|
||||
binary: null,
|
||||
};
|
||||
|
||||
const leftBorderClass: Record<FileChangeType, string> = {
|
||||
const leftBorderClass: Record<FileDiffDetail['status'], string> = {
|
||||
added: "border-l-2 border-l-status-success-fg",
|
||||
deleted: "border-l-2 border-l-status-error-fg",
|
||||
renamed: "border-l-2 border-l-status-active-fg",
|
||||
modified: "border-l-2 border-l-primary/40",
|
||||
binary: "border-l-2 border-l-primary/40",
|
||||
};
|
||||
|
||||
interface FileCardProps {
|
||||
file: FileDiff;
|
||||
file: FileDiffDetail;
|
||||
commentsByLine: Map<string, ReviewComment[]>;
|
||||
onAddComment: (
|
||||
filePath: string,
|
||||
@@ -80,7 +82,7 @@ export function FileCard({
|
||||
[commentsByLine],
|
||||
);
|
||||
|
||||
const badge = changeTypeBadge[file.changeType];
|
||||
const badge = changeTypeBadge[file.status];
|
||||
|
||||
// Flatten all hunk lines for syntax highlighting
|
||||
const allLines = useMemo(
|
||||
@@ -93,7 +95,7 @@ export function FileCard({
|
||||
<div className="rounded-lg border border-border overflow-clip">
|
||||
{/* File header — sticky so it stays visible when scrolling */}
|
||||
<button
|
||||
className={`sticky z-10 flex w-full items-center gap-2 px-3 py-2 bg-muted hover:bg-muted/90 text-left text-sm font-mono transition-colors ${leftBorderClass[file.changeType]}`}
|
||||
className={`sticky z-10 flex w-full items-center gap-2 px-3 py-2 bg-muted hover:bg-muted/90 text-left text-sm font-mono transition-colors ${leftBorderClass[file.status]}`}
|
||||
style={{ top: 'var(--review-header-h, 0px)' }}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
|
||||
@@ -308,7 +308,7 @@ export function InitiativeReview({ initiativeId, onCompleted }: InitiativeReview
|
||||
) : (
|
||||
<DiffViewer
|
||||
files={files}
|
||||
comments={[]}
|
||||
commentsByLine={new Map()}
|
||||
onAddComment={() => {}}
|
||||
onResolveComment={() => {}}
|
||||
onUnresolveComment={() => {}}
|
||||
|
||||
@@ -165,10 +165,12 @@ function getFileName(path: string): string {
|
||||
return lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
|
||||
}
|
||||
|
||||
const changeTypeDotColor: Record<string, string> = {
|
||||
const changeTypeDotColor: Record<string, string | undefined> = {
|
||||
added: "bg-status-success-fg",
|
||||
deleted: "bg-status-error-fg",
|
||||
renamed: "bg-status-active-fg",
|
||||
modified: undefined,
|
||||
binary: undefined,
|
||||
};
|
||||
|
||||
function FilesView({
|
||||
@@ -310,7 +312,7 @@ function FilesView({
|
||||
const isInView = activeFilePaths.has(file.newPath);
|
||||
const dimmed = selectedCommit && !isInView;
|
||||
const isViewed = viewedFiles.has(file.newPath);
|
||||
const dotColor = changeTypeDotColor[file.changeType];
|
||||
const dotColor = changeTypeDotColor[file.status];
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { FileDiff, FileChangeType, DiffHunk, DiffLine } from "./types";
|
||||
import type { FileDiffDetail, FileDiff, DiffHunk, DiffLine } from "./types";
|
||||
|
||||
/**
|
||||
* Parse a unified diff string into structured FileDiff objects.
|
||||
* Parse a unified diff string into structured FileDiffDetail objects.
|
||||
*/
|
||||
export function parseUnifiedDiff(raw: string): FileDiff[] {
|
||||
const files: FileDiff[] = [];
|
||||
export function parseUnifiedDiff(raw: string): FileDiffDetail[] {
|
||||
const files: FileDiffDetail[] = [];
|
||||
const fileChunks = raw.split(/^diff --git /m).filter(Boolean);
|
||||
|
||||
for (const chunk of fileChunks) {
|
||||
@@ -90,19 +90,19 @@ export function parseUnifiedDiff(raw: string): FileDiff[] {
|
||||
hunks.push({ header, oldStart, oldCount, newStart, newCount, lines: hunkLines });
|
||||
}
|
||||
|
||||
// Derive changeType from header markers and path comparison
|
||||
let changeType: FileChangeType;
|
||||
// Derive status from header markers and path comparison
|
||||
let status: FileDiff['status'];
|
||||
if (hasOldDevNull) {
|
||||
changeType = "added";
|
||||
status = "added";
|
||||
} else if (hasNewDevNull) {
|
||||
changeType = "deleted";
|
||||
status = "deleted";
|
||||
} else if (oldPath !== newPath) {
|
||||
changeType = "renamed";
|
||||
status = "renamed";
|
||||
} else {
|
||||
changeType = "modified";
|
||||
status = "modified";
|
||||
}
|
||||
|
||||
files.push({ oldPath, newPath, hunks, additions, deletions, changeType });
|
||||
files.push({ oldPath, newPath, hunks, additions, deletions, status });
|
||||
}
|
||||
|
||||
return files;
|
||||
|
||||
29
apps/web/src/components/review/types.test.ts
Normal file
29
apps/web/src/components/review/types.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { FileDiff, FileDiffDetail } from './types';
|
||||
|
||||
describe('FileDiff types', () => {
|
||||
it('FileDiff accepts binary status', () => {
|
||||
const f: FileDiff = {
|
||||
oldPath: 'a.png',
|
||||
newPath: 'a.png',
|
||||
status: 'binary',
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
};
|
||||
expect(f.status).toBe('binary');
|
||||
});
|
||||
|
||||
it('FileDiffDetail extends FileDiff with hunks', () => {
|
||||
const d: FileDiffDetail = {
|
||||
oldPath: 'a.ts',
|
||||
newPath: 'a.ts',
|
||||
status: 'modified',
|
||||
additions: 5,
|
||||
deletions: 2,
|
||||
hunks: [],
|
||||
};
|
||||
expect(d.hunks).toEqual([]);
|
||||
expect(d.additions).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -14,15 +14,20 @@ export interface DiffLine {
|
||||
newLineNumber: number | null;
|
||||
}
|
||||
|
||||
export type FileChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
|
||||
/** Metadata returned by getPhaseReviewDiff — no hunk content */
|
||||
export interface FileDiff {
|
||||
oldPath: string;
|
||||
newPath: string;
|
||||
hunks: DiffHunk[];
|
||||
/** 'binary' is new — prior changeType used FileChangeType which had no 'binary' */
|
||||
status: 'added' | 'modified' | 'deleted' | 'renamed' | 'binary';
|
||||
additions: number;
|
||||
deletions: number;
|
||||
changeType: FileChangeType;
|
||||
projectId?: string; // present in multi-project initiatives
|
||||
}
|
||||
|
||||
/** Full diff with parsed hunks — returned by getFileDiff, parsed client-side */
|
||||
export interface FileDiffDetail extends FileDiff {
|
||||
hunks: DiffHunk[];
|
||||
}
|
||||
|
||||
export interface ReviewComment {
|
||||
|
||||
Reference in New Issue
Block a user