/* Review workspace — split diff view with accept/reject per file and comments */ import { useMemo, useState, useCallback } from "react"; import { DiffViewRaw } from "./DiffView"; export type ReviewStatus = "pending" | "accepted" | "rejected"; interface ReviewFile { path: string; diff: string; status: ReviewStatus; comments: ReviewComment[]; } interface ReviewComment { id: string; line: number; text: string; author: string; timestamp: Date; } interface ReviewWorkspaceProps { files: ReviewFile[]; onAcceptFile?: (path: string) => void; onRejectFile?: (path: string) => void; onApproveAll?: () => void; onRejectAll?: () => void; overallStatus?: "pending" | "approved" | "changes-requested"; } function FileReviewCard({ file, onAccept, onReject, onComment, }: { file: ReviewFile; onAccept?: () => void; onReject?: () => void; onComment?: (line: number, text: string) => void; }) { const [commentText, setCommentText] = useState(""); const [showCommentInput, setShowCommentInput] = useState(false); const statusColors: Record = { pending: "var(--text-quiet)", accepted: "var(--color-success)", rejected: "var(--error)", }; const statusLabels: Record = { pending: "Pending Review", accepted: "Accepted", rejected: "Rejected", }; return (
{/* File header with actions */}
{file.path} {statusLabels[file.status]}
{file.status === "pending" && (
)}
{/* Diff content */}
{/* Comments */} {file.comments.length > 0 && (

Comments ({file.comments.length})

{file.comments.map((c) => (
L{c.line}

{c.text}

{c.author} ·{" "} {c.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })}
))}
)} {/* Comment input */} {file.status === "pending" && (
{showCommentInput ? (
setCommentText(e.target.value)} placeholder="Add a comment..." className="flex-1 rounded-lg px-2.5 py-1.5 text-[11px] outline-none" style={{ background: "var(--claude-canvas)", border: "1px solid var(--border-strong)", color: "var(--text-primary)", }} onKeyDown={(e) => { if (e.key === "Enter" && commentText.trim()) { onComment?.(1, commentText.trim()); setCommentText(""); setShowCommentInput(false); } if (e.key === "Escape") { setCommentText(""); setShowCommentInput(false); } }} />
) : ( )}
)}
); } export function ReviewWorkspace({ files: initialFiles, onAcceptFile, onRejectFile, onApproveAll, onRejectAll, overallStatus = "pending", }: ReviewWorkspaceProps) { const [files, setFiles] = useState(initialFiles); const [view, setView] = useState<"list" | "unified">("list"); // Sync with external files prop useMemo(() => { setFiles(initialFiles); }, [initialFiles]); const handleAccept = useCallback( (path: string) => { setFiles((prev) => prev.map((f) => f.path === path ? { ...f, status: "accepted" as const } : f, ), ); onAcceptFile?.(path); }, [onAcceptFile], ); const handleReject = useCallback( (path: string) => { setFiles((prev) => prev.map((f) => f.path === path ? { ...f, status: "rejected" as const } : f, ), ); onRejectFile?.(path); }, [onRejectFile], ); const acceptedCount = files.filter((f) => f.status === "accepted").length; const rejectedCount = files.filter((f) => f.status === "rejected").length; const pendingCount = files.filter((f) => f.status === "pending").length; return (
{/* Review summary header */}

Code Review

{files.length} file{files.length !== 1 ? "s" : ""} changed {acceptedCount} accepted {rejectedCount} rejected {pendingCount > 0 && ( {pendingCount} pending )}
{pendingCount > 0 && onApproveAll && ( )} {pendingCount > 0 && onRejectAll && ( )}
{/* View toggle */}
{/* File review cards */} {files.length === 0 ? (

No files to review. Changes will appear here.

) : view === "list" ? (
{files.map((file) => ( handleAccept(file.path)} onReject={() => handleReject(file.path)} /> ))}
) : ( /* Unified view: concatenate all diffs */
{files .filter((f) => f.diff) .map((f) => (
{f.path} {f.status}
))}
)} {/* Bottom actions bar */} {files.length > 0 && pendingCount === 0 && (
All files have been reviewed. {acceptedCount > 0 && ` ${acceptedCount} accepted.`} {rejectedCount > 0 && ` ${rejectedCount} rejected.`}
)}
); } export type { ReviewFile };