diff --git a/apps/web/src/components/review/CommitNav.tsx b/apps/web/src/components/review/CommitNav.tsx deleted file mode 100644 index 9085c80..0000000 --- a/apps/web/src/components/review/CommitNav.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { GitCommitHorizontal, Plus, Minus, FileCode } from "lucide-react"; -import type { CommitInfo } from "./types"; - -interface CommitNavProps { - commits: CommitInfo[]; - selectedCommit: string | null; // null = "all changes" - onSelectCommit: (hash: string | null) => void; - isLoading: boolean; -} - -export function CommitNav({ - commits, - selectedCommit, - onSelectCommit, - isLoading, -}: CommitNavProps) { - if (isLoading || commits.length === 0) return null; - - return ( -
-
- {/* "All changes" pill — only when multiple commits */} - {commits.length > 1 && ( - <> - onSelectCommit(null)} - /> -
- - )} - - {/* Individual commit pills - most recent first */} - {commits.map((commit) => ( - onSelectCommit(commit.hash)} - isMono - /> - ))} -
-
- ); -} - -interface CommitPillProps { - label: string; - sublabel: string; - stats?: { files: number; add: number; del: number }; - isActive: boolean; - onClick: () => void; - isMono?: boolean; -} - -function CommitPill({ - label, - sublabel, - stats, - isActive, - onClick, - isMono, -}: CommitPillProps) { - return ( - - ); -} - -function truncateMessage(msg: string): string { - const firstLine = msg.split("\n")[0]; - return firstLine.length > 50 ? firstLine.slice(0, 47) + "..." : firstLine; -} diff --git a/apps/web/src/components/review/ReviewSidebar.tsx b/apps/web/src/components/review/ReviewSidebar.tsx index 378e6a3..6da6d37 100644 --- a/apps/web/src/components/review/ReviewSidebar.tsx +++ b/apps/web/src/components/review/ReviewSidebar.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { MessageSquare, FileCode, @@ -5,8 +6,12 @@ import { Minus, Circle, CheckCircle2, + GitCommitHorizontal, + Layers, } from "lucide-react"; -import type { FileDiff, ReviewComment } from "./types"; +import type { FileDiff, ReviewComment, CommitInfo } from "./types"; + +type SidebarView = "files" | "commits"; interface ReviewSidebarProps { files: FileDiff[]; @@ -14,6 +19,8 @@ interface ReviewSidebarProps { onFileClick: (filePath: string) => void; selectedCommit: string | null; activeFiles: FileDiff[]; + commits: CommitInfo[]; + onSelectCommit: (hash: string | null) => void; } export function ReviewSidebar({ @@ -22,11 +29,107 @@ export function ReviewSidebar({ onFileClick, selectedCommit, activeFiles, + commits, + onSelectCommit, }: ReviewSidebarProps) { + const [view, setView] = useState("files"); + + return ( +
+ {/* Content panel */} +
+ {view === "files" ? ( + + ) : ( + + )} +
+ + {/* Icon strip — right edge */} +
+ setView("files")} + /> + setView("commits")} + badge={commits.length > 1 ? commits.length : undefined} + /> +
+
+ ); +} + +/* ─── Icon Tab ─────────────────────────────────────────── */ + +function IconTab({ + icon: Icon, + label, + active, + onClick, + badge, +}: { + icon: typeof FileCode; + label: string; + active: boolean; + onClick: () => void; + badge?: number; +}) { + return ( + + ); +} + +/* ─── Files View ───────────────────────────────────────── */ + +function FilesView({ + files, + comments, + onFileClick, + selectedCommit, + activeFiles, +}: { + files: FileDiff[]; + comments: ReviewComment[]; + onFileClick: (filePath: string) => void; + selectedCommit: string | null; + activeFiles: FileDiff[]; +}) { const unresolvedCount = comments.filter((c) => !c.resolved).length; const resolvedCount = comments.filter((c) => c.resolved).length; - - // Build a set of files visible in the current diff view const activeFilePaths = new Set(activeFiles.map((f) => f.newPath)); return ( @@ -113,9 +216,101 @@ export function ReviewSidebar({ ); } -/** Show filename with parent directory for context */ +/* ─── Commits View ─────────────────────────────────────── */ + +function CommitsView({ + commits, + selectedCommit, + onSelectCommit, +}: { + commits: CommitInfo[]; + selectedCommit: string | null; + onSelectCommit: (hash: string | null) => void; +}) { + if (commits.length === 0) { + return ( +
+ No commits +
+ ); + } + + return ( +
+

+ Commits +

+ + {/* "All changes" — only when >1 commit */} + {commits.length > 1 && ( + + )} + + {/* Commit list */} + {commits.map((commit) => { + const isActive = selectedCommit === commit.hash; + return ( + + ); + })} +
+ ); +} + +/* ─── Helpers ──────────────────────────────────────────── */ + function formatFilePath(path: string): string { const parts = path.split("/"); if (parts.length <= 2) return path; return parts.slice(-2).join("/"); } + +function truncateMessage(msg: string): string { + const firstLine = msg.split("\n")[0]; + return firstLine.length > 50 ? firstLine.slice(0, 47) + "..." : firstLine; +} diff --git a/apps/web/src/components/review/ReviewTab.tsx b/apps/web/src/components/review/ReviewTab.tsx index 852e81d..c40b129 100644 --- a/apps/web/src/components/review/ReviewTab.tsx +++ b/apps/web/src/components/review/ReviewTab.tsx @@ -6,7 +6,6 @@ import { parseUnifiedDiff } from "./parse-diff"; import { DiffViewer } from "./DiffViewer"; import { ReviewSidebar } from "./ReviewSidebar"; import { ReviewHeader } from "./ReviewHeader"; -import { CommitNav } from "./CommitNav"; import type { ReviewComment, ReviewStatus, DiffLine } from "./types"; interface ReviewTabProps { @@ -235,14 +234,6 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) { preview={previewState} /> - {/* Commit navigation strip */} - - {/* Main content area */} {isDiffLoading ? (
@@ -278,6 +269,8 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) { onFileClick={handleFileClick} selectedCommit={selectedCommit} activeFiles={files} + commits={commits} + onSelectCommit={setSelectedCommit} />
diff --git a/docs/frontend.md b/docs/frontend.md index 9d952f7..523bdbf 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -111,10 +111,9 @@ The initiative detail page has three tabs managed via local state (not URL param ### Review Components (`src/components/review/`) | Component | Purpose | |-----------|---------| -| `ReviewTab` | Review tab container — orchestrates header, commit nav, diff, sidebar, and preview | +| `ReviewTab` | Review tab container — orchestrates header, diff, sidebar, and preview | | `ReviewHeader` | Consolidated toolbar: phase selector pills, branch info, stats, preview controls, approve/reject actions | -| `CommitNav` | Horizontal commit navigation strip — "All changes" + individual commit pills with stats | -| `ReviewSidebar` | File list with comment counts, dimmed files when viewing single commit | +| `ReviewSidebar` | VSCode-style icon strip (Files/Commits views) with file list, comment counts, and commit navigation | | `DiffViewer` | Unified diff renderer with inline comments | | `PreviewPanel` | Docker preview status: building/running/failed with start/stop (legacy, now integrated into ReviewHeader) | | `ProposalCard` | Individual proposal display |