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) {
|
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 [showManual, setShowManual] = useState(false);
|
||||||
const prevStateRef = useRef<string | null>(null);
|
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";
|
import { FileCard } from "./FileCard";
|
||||||
|
|
||||||
function getFileCommentMap(
|
function getFileCommentMap(
|
||||||
@@ -13,7 +13,7 @@ function getFileCommentMap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DiffViewerProps {
|
interface DiffViewerProps {
|
||||||
files: FileDiff[];
|
files: FileDiffDetail[];
|
||||||
commentsByLine: Map<string, ReviewComment[]>;
|
commentsByLine: Map<string, ReviewComment[]>;
|
||||||
onAddComment: (
|
onAddComment: (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import {
|
|||||||
Circle,
|
Circle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 { HunkRows } from "./HunkRows";
|
||||||
import { useHighlightedFile } from "./use-syntax-highlight";
|
import { useHighlightedFile } from "./use-syntax-highlight";
|
||||||
|
|
||||||
const changeTypeBadge: Record<
|
const changeTypeBadge: Record<
|
||||||
FileChangeType,
|
FileDiffDetail['status'],
|
||||||
{ label: string; classes: string } | null
|
{ label: string; classes: string } | null
|
||||||
> = {
|
> = {
|
||||||
added: {
|
added: {
|
||||||
@@ -32,17 +32,19 @@ const changeTypeBadge: Record<
|
|||||||
"bg-status-active-bg text-status-active-fg border-status-active-border",
|
"bg-status-active-bg text-status-active-fg border-status-active-border",
|
||||||
},
|
},
|
||||||
modified: null,
|
modified: null,
|
||||||
|
binary: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const leftBorderClass: Record<FileChangeType, string> = {
|
const leftBorderClass: Record<FileDiffDetail['status'], string> = {
|
||||||
added: "border-l-2 border-l-status-success-fg",
|
added: "border-l-2 border-l-status-success-fg",
|
||||||
deleted: "border-l-2 border-l-status-error-fg",
|
deleted: "border-l-2 border-l-status-error-fg",
|
||||||
renamed: "border-l-2 border-l-status-active-fg",
|
renamed: "border-l-2 border-l-status-active-fg",
|
||||||
modified: "border-l-2 border-l-primary/40",
|
modified: "border-l-2 border-l-primary/40",
|
||||||
|
binary: "border-l-2 border-l-primary/40",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FileCardProps {
|
interface FileCardProps {
|
||||||
file: FileDiff;
|
file: FileDiffDetail;
|
||||||
commentsByLine: Map<string, ReviewComment[]>;
|
commentsByLine: Map<string, ReviewComment[]>;
|
||||||
onAddComment: (
|
onAddComment: (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@@ -80,7 +82,7 @@ export function FileCard({
|
|||||||
[commentsByLine],
|
[commentsByLine],
|
||||||
);
|
);
|
||||||
|
|
||||||
const badge = changeTypeBadge[file.changeType];
|
const badge = changeTypeBadge[file.status];
|
||||||
|
|
||||||
// Flatten all hunk lines for syntax highlighting
|
// Flatten all hunk lines for syntax highlighting
|
||||||
const allLines = useMemo(
|
const allLines = useMemo(
|
||||||
@@ -93,7 +95,7 @@ export function FileCard({
|
|||||||
<div className="rounded-lg border border-border overflow-clip">
|
<div className="rounded-lg border border-border overflow-clip">
|
||||||
{/* File header — sticky so it stays visible when scrolling */}
|
{/* File header — sticky so it stays visible when scrolling */}
|
||||||
<button
|
<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)' }}
|
style={{ top: 'var(--review-header-h, 0px)' }}
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ export function InitiativeReview({ initiativeId, onCompleted }: InitiativeReview
|
|||||||
) : (
|
) : (
|
||||||
<DiffViewer
|
<DiffViewer
|
||||||
files={files}
|
files={files}
|
||||||
comments={[]}
|
commentsByLine={new Map()}
|
||||||
onAddComment={() => {}}
|
onAddComment={() => {}}
|
||||||
onResolveComment={() => {}}
|
onResolveComment={() => {}}
|
||||||
onUnresolveComment={() => {}}
|
onUnresolveComment={() => {}}
|
||||||
|
|||||||
@@ -165,10 +165,12 @@ function getFileName(path: string): string {
|
|||||||
return lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
|
return lastSlash >= 0 ? path.slice(lastSlash + 1) : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTypeDotColor: Record<string, string> = {
|
const changeTypeDotColor: Record<string, string | undefined> = {
|
||||||
added: "bg-status-success-fg",
|
added: "bg-status-success-fg",
|
||||||
deleted: "bg-status-error-fg",
|
deleted: "bg-status-error-fg",
|
||||||
renamed: "bg-status-active-fg",
|
renamed: "bg-status-active-fg",
|
||||||
|
modified: undefined,
|
||||||
|
binary: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FilesView({
|
function FilesView({
|
||||||
@@ -310,7 +312,7 @@ function FilesView({
|
|||||||
const isInView = activeFilePaths.has(file.newPath);
|
const isInView = activeFilePaths.has(file.newPath);
|
||||||
const dimmed = selectedCommit && !isInView;
|
const dimmed = selectedCommit && !isInView;
|
||||||
const isViewed = viewedFiles.has(file.newPath);
|
const isViewed = viewedFiles.has(file.newPath);
|
||||||
const dotColor = changeTypeDotColor[file.changeType];
|
const dotColor = changeTypeDotColor[file.status];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<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[] {
|
export function parseUnifiedDiff(raw: string): FileDiffDetail[] {
|
||||||
const files: FileDiff[] = [];
|
const files: FileDiffDetail[] = [];
|
||||||
const fileChunks = raw.split(/^diff --git /m).filter(Boolean);
|
const fileChunks = raw.split(/^diff --git /m).filter(Boolean);
|
||||||
|
|
||||||
for (const chunk of fileChunks) {
|
for (const chunk of fileChunks) {
|
||||||
@@ -90,19 +90,19 @@ export function parseUnifiedDiff(raw: string): FileDiff[] {
|
|||||||
hunks.push({ header, oldStart, oldCount, newStart, newCount, lines: hunkLines });
|
hunks.push({ header, oldStart, oldCount, newStart, newCount, lines: hunkLines });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive changeType from header markers and path comparison
|
// Derive status from header markers and path comparison
|
||||||
let changeType: FileChangeType;
|
let status: FileDiff['status'];
|
||||||
if (hasOldDevNull) {
|
if (hasOldDevNull) {
|
||||||
changeType = "added";
|
status = "added";
|
||||||
} else if (hasNewDevNull) {
|
} else if (hasNewDevNull) {
|
||||||
changeType = "deleted";
|
status = "deleted";
|
||||||
} else if (oldPath !== newPath) {
|
} else if (oldPath !== newPath) {
|
||||||
changeType = "renamed";
|
status = "renamed";
|
||||||
} else {
|
} else {
|
||||||
changeType = "modified";
|
status = "modified";
|
||||||
}
|
}
|
||||||
|
|
||||||
files.push({ oldPath, newPath, hunks, additions, deletions, changeType });
|
files.push({ oldPath, newPath, hunks, additions, deletions, status });
|
||||||
}
|
}
|
||||||
|
|
||||||
return files;
|
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;
|
newLineNumber: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FileChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
|
/** Metadata returned by getPhaseReviewDiff — no hunk content */
|
||||||
|
|
||||||
export interface FileDiff {
|
export interface FileDiff {
|
||||||
oldPath: string;
|
oldPath: string;
|
||||||
newPath: string;
|
newPath: string;
|
||||||
hunks: DiffHunk[];
|
/** 'binary' is new — prior changeType used FileChangeType which had no 'binary' */
|
||||||
|
status: 'added' | 'modified' | 'deleted' | 'renamed' | 'binary';
|
||||||
additions: number;
|
additions: number;
|
||||||
deletions: 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 {
|
export interface ReviewComment {
|
||||||
|
|||||||
Reference in New Issue
Block a user