diff --git a/.gitignore b/.gitignore
index 3ba7d15..6da28d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,4 +46,4 @@ next-env.d.ts
# uploaded assets (runtime-generated, not needed in repo)
/public/uploads/
-/assets/review-images/
\ No newline at end of file
+/assets/review-images/
diff --git a/ROADMAP.md b/ROADMAP.md
index 74b88c6..1b02892 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -11,7 +11,7 @@
1. [What's Built](#whats-built)
2. [Remaining Work — Priority Order](#remaining-work--priority-order)
- - [A. Image Comparison & Visual Review](#a-image-comparison--visual-review)
+ - [A. Visual Review Tool](#a-visual-review-tool)
- [B. Collaboration Enhancements](#b-collaboration-enhancements)
- [C. Reporting Completions](#c-reporting-completions)
- [D. Automation Completions](#d-automation-completions)
@@ -114,70 +114,98 @@
---
-### A. Image Comparison & Visual Review
+### A. Visual Review Tool
-The highest-impact remaining feature area. CG production review is fundamentally visual — everything else supports this core workflow.
+The highest-impact remaining feature. CG production review is fundamentally visual — this is a single, unified review tool built in stages, each independently useful. No throwaway prototypes.
+
+The review tool lives at its own dedicated page (`/projects/[projectId]/deliverables/[deliverableId]/review`) and is also accessible from the stage detail sheet via a "Review" button. It's the primary interface for inspecting renders, comparing revisions, annotating feedback, and making approve/reject decisions.
+
+**Existing infrastructure to build on:**
+- `Revision` model exists with `attachments Json?` field (currently unused)
+- Revision CRUD API + hooks + service already built
+- Stage detail sheet has a tabbed interface (Revisions + Comments)
+- No image upload, storage, or display infrastructure exists yet
+
+**New dependency for all stages:** `sharp` (server-side PNG alpha compositing + image processing)
---
-#### A1 — Reference Image Comparison (Lightweight, in Stage Detail)
+#### A1 — Review Viewer & Image Upload `[x]`
-**What:** A-B comparison tools embedded in the existing stage detail sheet. Producers and artists compare the reference/approved image against the current WIP render without leaving the deliverable page.
+The foundation. A dedicated review page with a high-fidelity image viewer and the upload infrastructure to get images into the system.
-**Why first:** Lower complexity than the full review viewer (A2). Directly improves the daily review loop for existing users and unblocks meaningful visual feedback on revision rounds.
-
-**Implementation:**
-- New "Compare" tab in the stage detail sheet (alongside Revisions and Comments tabs)
-- Upload zones for reference image + current render per revision round
-- Three comparison modes:
- - **A-B Slider** — draggable vertical divider (CSS clip-path + pointer events, no dependency)
- - **Toggle** — click/Space to crossfade between images
- - **Side-by-side** — synced zoom/pan on wide screens
-- Scroll-to-zoom + click-drag pan, synced across both images in all modes
-- Image gallery: thumbnails of all uploaded images across all revision rounds with round labels
-- **PNG alpha compositing on upload** — flatten transparent PNGs onto white background server-side using `sharp`, so CG renders with transparent drop shadows compare correctly
-- Extend `Revision.attachments` JSON schema: `{ referenceImage?: {...}, currentImage?: {...}, annotations?: [...] }`
-- Lightweight markup overlay (optional): arrow, rectangle, freehand, text pin drawn on top of current image
+**What gets built:**
+- **Review page** at `/projects/[projectId]/deliverables/[deliverableId]/review` — full-width layout with image viewer as the centerpiece, toolbar at top, panels on the sides
+- **Image viewer** — canvas-based with WebGL acceleration for large CG renders (4000×4000px+)
+ - Zoom: scroll wheel, pinch gesture, keyboard (+/-), toolbar presets (Fit, 50%, 100%, 150%, 200%)
+ - Pan: click-drag when zoomed, minimap overlay showing viewport position
+ - Pixel info: coordinate display (x, y) + color readout (RGB/Hex) in status bar
+ - High-DPI (retina) support with proper pixel ratio handling
+- **Image upload** — drag-and-drop zone + click-to-browse for reference and current render per revision round
+ - Supports PNG, TIFF, JPEG, WebP up to 50MB
+ - **PNG alpha compositing on upload** — flatten transparent PNGs onto white (#FFF) background server-side using `sharp`, so CG renders with semi-transparent drop shadows display correctly in all comparison and overlay modes
+ - Store flattened version for viewing + optionally keep original transparent PNG for download
+ - Extend `Revision.attachments` JSON: `{ referenceImage?: { url, filename, size, uploadedAt, originalUrl? }, currentImage?: { ... } }`
+- **Image gallery** — thumbnail strip of all uploaded images across revision rounds, with round number labels. Click any to load it in the viewer.
+- **"Review" button** on stage cards in the deliverable detail page — opens the review page for that stage
**New API endpoints:**
-- `POST /api/stages/[stageId]/revisions/[revisionId]/upload` — multipart, reference or current image
-- `DELETE /api/stages/[stageId]/revisions/[revisionId]/upload` — remove image
+- `POST /api/stages/[stageId]/revisions/[revisionId]/upload` — multipart form upload (type: "reference" | "current")
+- `DELETE /api/stages/[stageId]/revisions/[revisionId]/upload` — remove an uploaded image
**Key files:**
-- `src/components/revisions/image-compare-slider.tsx` — A-B slider with drag handle
-- `src/components/revisions/image-toggle.tsx` — Toggle/crossfade
-- `src/components/revisions/image-side-by-side.tsx` — Synced side-by-side with zoom/pan
-- `src/components/revisions/image-upload-zone.tsx` — Drag-and-drop upload
-- `src/components/revisions/revision-image-gallery.tsx` — Browse images across revision rounds
-- `src/components/revisions/compare-tab.tsx` — Orchestrator: mode selector + comparison view
-- `src/components/revisions/markup-overlay.tsx` — Lightweight annotation layer
-
-**New dependency:** `sharp` (server-side PNG alpha compositing on upload)
+- `src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/review/page.tsx` — Review page
+- `src/components/review/image-viewer.tsx` — Core canvas viewer
+- `src/components/review/zoom-controls.tsx` — Zoom toolbar + keyboard handler
+- `src/components/review/minimap.tsx` — Navigation minimap overlay
+- `src/components/review/image-upload-zone.tsx` — Drag-and-drop upload
+- `src/components/review/image-gallery.tsx` — Thumbnail strip across rounds
+- `src/hooks/use-image-viewer.ts` — Pan/zoom state management
+- `src/lib/services/upload-service.ts` — File storage + PNG alpha compositing
---
-#### A2 — Full-Screen Image Viewer
+#### A2 — Version Comparison `[ ]`
-**What:** Dedicated full-screen viewer with high-fidelity zoom (up to 200%+) for pixel-level inspection of CG renders.
+Compare two revisions of the same deliverable stage. This is the daily workhorse — producers and artists check what changed between rounds.
-**Implementation:**
-- Canvas-based viewer with WebGL acceleration for large images
-- Zoom: scroll wheel, pinch, keyboard (+/-), toolbar buttons (fit, 50%, 100%, 150%, 200%, free)
-- Pan via click-drag when zoomed; minimap overlay showing viewport position
-- Pixel coordinate + color value readout (RGB/Hex) in status bar
-- High-DPI display support (retina)
+**What gets built:**
+- **Comparison modes** (toolbar toggle in the review page):
+ - **Side-by-side** — dual panes with synced zoom/pan, version labels
+ - **A-B Slider (wipe)** — draggable vertical/horizontal divider revealing one image on each side
+ - **Overlay** — second image overlaid with adjustable opacity (0–100%)
+ - **Toggle** — click or press Space to crossfade between images (default on narrow screens)
+- **Revision selectors** — dropdowns for left/right revision (default: previous round vs. current)
+- **Synced navigation** — zoom/pan one pane, both move together across all modes
+- **Keyboard shortcuts** — 1/2/3/4 to switch modes, left/right arrows to cycle revisions
**Key files:**
-- `src/components/review/image-viewer.tsx`
-- `src/components/review/zoom-controls.tsx`
-- `src/components/review/minimap.tsx`
-- `src/hooks/use-image-viewer.ts`
+- `src/components/review/comparison-viewer.tsx` — Dual-pane orchestrator
+- `src/components/review/wipe-divider.tsx` — Draggable split control
+- `src/components/review/overlay-controls.tsx` — Opacity slider + mode toggles
+
+**Dependencies:** Requires A1
---
-#### A3 — Pixel-Accurate Annotations
+#### A3 — Annotations `[ ]`
-**What:** Draw annotations directly on images — circles, rectangles, arrows, freehand, text labels, pins. Anchored to image coordinates so they stay accurate at any zoom level.
+Draw pixel-accurate annotations directly on images. Each annotation is anchored to image coordinates so it stays accurate at any zoom level, and is linked to a comment for context.
+
+**What gets built:**
+- **SVG overlay layer** on top of the canvas viewer (annotations in SVG for crisp scaling, image in canvas for performance)
+- **Annotation tools** — toolbar with: rectangle, ellipse, arrow, freehand path, text label, pin (point marker)
+- **Screenshot paste callouts** — Cmd+V / Ctrl+V pastes a clipboard image directly onto the review image as a floating, repositionable callout. Use case: paste a screenshot of a Maya attribute editor panel, a Photoshop layer setting, or a cropped reference to show the artist exactly what you mean. Callouts can be:
+ - **Moved** — drag to reposition anywhere on the image
+ - **Resized** — corner handles to scale up/down
+ - **Bordered** — automatic contrast border so the callout stands out against the render
+ - **Linked to a comment** — same as other annotations, the pasted screenshot becomes part of the feedback thread
+ - Stored as a small image (uploaded to server, URL in annotation data) anchored to image coordinates like any other annotation
+- **Color picker** for annotation stroke (default: accent red for visibility against CG renders)
+- **Image-coordinate anchoring** — annotations stored in image space, rendered correctly at any zoom
+- **Comment linking** — each annotation creates or attaches to a Comment record; clicking an annotation highlights its comment in the sidebar, and vice versa
+- **Visibility controls** — show/hide all, show/hide per revision round, show resolved vs. open
+- **Undo/redo** for drawing sessions
**New data model:**
```prisma
@@ -188,63 +216,82 @@ model Annotation {
revisionId String
revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade)
type AnnotationType
- data Json // { x, y, width, height, points[], text, color, strokeWidth }
- imageX Float
+ data Json // { x, y, width, height, points[], text, color, strokeWidth, imageUrl? }
+ imageX Float // anchor point in image coordinates
imageY Float
createdById String
createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now())
}
-enum AnnotationType { RECTANGLE ELLIPSE ARROW FREEHAND TEXT PIN }
+enum AnnotationType { RECTANGLE ELLIPSE ARROW FREEHAND TEXT PIN SCREENSHOT }
```
**Key files:**
-- `src/components/review/annotation-layer.tsx`
-- `src/components/review/annotation-tools.tsx`
-- `src/components/review/annotation-renderer.tsx`
-- `src/lib/services/annotation-service.ts`
-- `src/lib/validators/annotation.ts`
-- `src/hooks/use-annotations.ts`
+- `src/components/review/annotation-layer.tsx` — SVG overlay with tool switching
+- `src/components/review/annotation-tools.tsx` — Toolbar with tool selection
+- `src/components/review/annotation-renderer.tsx` — Renders individual shapes
+- `src/components/review/screenshot-callout.tsx` — Pasted screenshot with drag/resize handles
+- `src/lib/services/annotation-service.ts` — CRUD
+- `src/lib/validators/annotation.ts` — Zod schemas
+- `src/hooks/use-annotations.ts` — TanStack Query hook
-**Dependencies:** Requires A2 (Image Viewer)
+**Dependencies:** Requires A1
---
-#### A4 — Side-by-Side Version Comparison (Full Viewer)
+#### A4 — Revision History Timeline `[ ]`
-**What:** Compare two revisions in the full-screen viewer using side-by-side, overlay (opacity slider), wipe (draggable divider), or onion skin modes.
+A collapsible sidebar panel in the review page showing the full version history for a deliverable stage. The connective tissue between annotations, comparison, and feedback — provides the longitudinal view across all rounds.
+
+**What gets built:**
+- **Vertical timeline** in a collapsible right-side panel, each revision round as a node:
+ - Thumbnail preview of that round's submitted image
+ - Round number + status badge (Submitted, Changes Requested, Approved)
+ - Submitted by (name + avatar) + timestamp
+ - Annotation count (clickable to filter annotation layer to that round)
+ - Comment thread summary — first line + total count
+ - Decision record — who approved/rejected, when, with what note
+- **Click any round** to load that version in the viewer with its annotations
+- **Keyboard navigation** — up/down arrows to move through rounds
+- **Comparison integration** — select two rounds from the timeline to open them in comparison mode (A2)
+- **Filtering** — show all rounds, show only rounds with feedback, show only decision points
+
+**No new data model** — read-only aggregation over existing `Revision`, `Comment`, and `Annotation` records.
**Key files:**
-- `src/components/review/comparison-viewer.tsx`
-- `src/components/review/wipe-divider.tsx`
-- `src/components/review/overlay-controls.tsx`
-- `src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/review/page.tsx`
+- `src/components/review/revision-timeline.tsx` — Main timeline panel
+- `src/components/review/revision-node.tsx` — Individual round node
+- `src/components/review/revision-comments.tsx` — Per-round comment display
+- `src/components/review/revision-annotations-summary.tsx` — Annotation count + filters
+- `src/hooks/use-revision-history.ts` — TanStack Query hook aggregating revisions + comments + annotations
-**Dependencies:** Requires A2
+**Dependencies:** Requires A1. Enhanced by A3 (annotation counts and filtering).
---
-#### A5 — Revision History Timeline
+#### A5 — Feedback Checklist (Artist Action Items) `[ ]`
-**What:** Collapsible panel in the review viewer showing all revision rounds as a vertical timeline. Each node shows thumbnail, status badge, submitter, annotation count, comment summary, and decision record.
+Every annotation and actionable comment becomes a structured to-do item on a checklist for the assigned artist. Closes the feedback-to-fix loop.
-**No new data model** — aggregation over existing `Revision`, `Comment`, and `Annotation` records.
+**The feedback loop:**
+1. Reviewer draws annotation or posts actionable comment
+2. System auto-creates a FeedbackItem linked to the annotation/comment
+3. Artist sees checklist — organized by severity, with direct links to annotation on the image
+4. Artist works through items — checks each off with optional resolution note
+5. Artist submits new revision — unchecked items carry forward with a warning
+6. Reviewer verifies — can confirm resolution or reopen
-**Key files:**
-- `src/components/review/revision-timeline.tsx`
-- `src/components/review/revision-node.tsx`
-- `src/components/review/revision-comments.tsx`
-- `src/components/review/revision-annotations-summary.tsx`
-- `src/hooks/use-revision-history.ts`
+**Where the checklist appears (3 locations):**
+1. **Review page — Feedback Panel** (primary): full checklist with severity indicators, thumbnail crops of annotated regions, resolve/reopen actions, progress bar
+2. **My Work page** — feedback badge per assignment ("5 open items"), expandable inline checklist, deep-link to review page
+3. **Stage card on deliverable page** — compact badge ("4/7 resolved"), color-coded by severity
-**Dependencies:** Requires A2 + A3
-
----
-
-#### A6 — Feedback Action Items (Artist Checklist)
-
-**What:** Every annotation and actionable comment auto-creates a FeedbackItem on a structured checklist. Artists work through items (Critical / Major / Minor / Suggestion), check them off with resolution notes. Unresolved critical items block resubmission. Checklist appears in three places: review viewer panel, My Work page, and stage card badge.
+**Severity levels:**
+- **Critical** — must fix, blocks approval
+- **Major** — should fix, significant quality issue
+- **Minor** — nice to fix, small quality issue
+- **Suggestion** — optional improvement
**New data model:**
```prisma
@@ -280,21 +327,28 @@ enum FeedbackStatus { OPEN IN_PROGRESS RESOLVED VERIFIED REOPENED }
```
**Key files:**
-- `src/components/review/feedback-checklist.tsx`
-- `src/components/review/feedback-item.tsx`
-- `src/components/review/feedback-progress-bar.tsx`
-- `src/components/my-work/feedback-summary.tsx`
-- `src/components/stages/feedback-indicator.tsx`
-- `src/lib/services/feedback-service.ts`
+- `src/components/review/feedback-checklist.tsx` — Main checklist panel
+- `src/components/review/feedback-item.tsx` — Individual item with resolve action
+- `src/components/review/feedback-progress-bar.tsx` — Progress bar
+- `src/components/my-work/feedback-summary.tsx` — Inline checklist on My Work
+- `src/components/stages/feedback-indicator.tsx` — Compact badge for stage cards
+- `src/lib/services/feedback-service.ts` — CRUD, auto-creation, carry-forward logic
- `src/hooks/use-feedback-items.ts`
-**Dependencies:** Requires A3 + A5
+**Dependencies:** Requires A3 (annotations to generate items from) + A4 (timeline for round context)
---
-#### A7 — Review Sessions & Playlists
+#### A6 — Review Sessions & Playlists `[ ]`
-**What:** Curate an ordered set of deliverables into a review session. Walk through them in presenter mode with per-item approve/request-changes/reject decisions. Shareable via link.
+Curate a batch of deliverables into a structured review session. Walk through them sequentially in presenter mode with per-item decisions. The formal review workflow for HP stakeholder reviews.
+
+**What gets built:**
+- **Session builder** — pick deliverables/stages to include, drag to reorder, auto-generate from filters ("all Catalog Images in Review for Project X")
+- **Presenter mode** — full-screen, navigate with arrow keys, image viewer with annotations, comment sidebar, prominent approve/request-changes/reject buttons per item
+- **Summary view** — thumbnail grid with decision status badges, overall session progress
+- **Session states** — DRAFT → IN_PROGRESS → COMPLETED
+- **Shareable link** with optional expiry and access control
**New data model:**
```prisma
@@ -328,14 +382,14 @@ enum ReviewDecision { APPROVED CHANGES_REQUESTED REJECTED }
```
**Key files:**
-- `src/app/(app)/reviews/page.tsx`
-- `src/app/(app)/reviews/[sessionId]/page.tsx`
-- `src/components/review/session-builder.tsx`
-- `src/components/review/session-presenter.tsx`
-- `src/components/review/session-summary.tsx`
+- `src/app/(app)/reviews/page.tsx` — Session list
+- `src/app/(app)/reviews/[sessionId]/page.tsx` — Session presenter view
+- `src/components/review/session-builder.tsx` — Create/edit session
+- `src/components/review/session-presenter.tsx` — Full-screen walkthrough
+- `src/components/review/session-summary.tsx` — Thumbnail grid with decisions
- `src/lib/services/review-session-service.ts`
-**Dependencies:** Requires A2 + A3
+**Dependencies:** Requires A1 + A3
---
@@ -794,8 +848,9 @@ Note: `Dockerfile` and `docker-compose.yml` already exist in the repo root — r
| Model | Feature |
|---|---|
-| Annotation, FeedbackItem | A3, A6 |
-| ReviewSession, ReviewSessionItem | A7 |
+| Annotation | A3 |
+| FeedbackItem | A5 |
+| ReviewSession, ReviewSessionItem | A6 |
| ApprovalChain, ApprovalStep, ApprovalRecord | D2 |
| ProjectTemplate, ProjectTemplateDeliverable | D3 |
| AssetSpec, AssetValidationResult | E1 |
diff --git a/package-lock.json b/package-lock.json
index bfb8bcf..35fb915 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,6 +40,7 @@
"react-markdown": "^10.1.0",
"recharts": "^3.7.0",
"remark-gfm": "^4.0.1",
+ "sharp": "^0.34.5",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
@@ -1225,7 +1226,6 @@
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=18"
}
@@ -13675,7 +13675,6 @@
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
- "optional": true,
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
@@ -13719,7 +13718,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
- "optional": true,
"bin": {
"semver": "bin/semver.js"
},
diff --git a/package.json b/package.json
index a4c9075..42abbe7 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"react-markdown": "^10.1.0",
"recharts": "^3.7.0",
"remark-gfm": "^4.0.1",
+ "sharp": "^0.34.5",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
diff --git a/src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/page.tsx b/src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/page.tsx
index 9287240..f600890 100644
--- a/src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/page.tsx
+++ b/src/app/(app)/projects/[projectId]/deliverables/[deliverableId]/page.tsx
@@ -7,6 +7,7 @@ import { format } from "date-fns";
import {
ArrowLeft,
ChevronRight,
+ Eye,
FileText,
Lock,
RotateCcw,
@@ -376,17 +377,31 @@ export default function DeliverableDetailPage() {
)}
- {/* Details button */}
+ {/* Action buttons */}
{stage.status !== "BLOCKED" && (
-
+