# HP CG Production Tracker — Roadmap > Single source of truth for project status and remaining work. > Previous planning documents (IMPLEMENTATION_PLAN.md, UPGRADE_PLAN.md) are archived in `docs/archive/`. *Last updated: 2026-03-16* --- ## Table of Contents 1. [What's Built](#whats-built) 2. [Remaining Work — Priority Order](#remaining-work--priority-order) - [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) - [E. Asset Intelligence](#e-asset-intelligence) - [F. Quality of Life](#f-quality-of-life) - [G. Docker Deployment](#g-docker-deployment) 3. [Data Model Status](#data-model-status) 4. [Architecture Reference](#architecture-reference) --- ## What's Built ### Core Foundation (Phases 1–4) | Feature | Status | |---|---| | Project scaffold (Next.js, TS, Tailwind, Prisma, Auth.js) | ✅ Complete | | Design system (Oliver Agency palette, Montserrat + Inter, light/dark) | ✅ Complete | | Auth — SSO (Google + Microsoft Entra ID) + dev bypass | ✅ Complete | | Project CRUD + deliverable CRUD with auto-pipeline | ✅ Complete | | User management + role assignment | ✅ Complete | | Dependency engine + stage state machine | ✅ Complete | | Artist assignments + My Work page | ✅ Complete | | Table view (TanStack Table, filters, sort, column visibility) | ✅ Complete | | Board view (Kanban drag-and-drop) | ✅ Complete | | Timeline view (Gantt with dependencies) | ✅ Complete | | Dashboard (KPIs, charts, overdue alerts) | ✅ Complete | | Revision tracking (rounds, feedback notes, history) | ✅ Complete | | Threaded comment system | ✅ Complete | | Notification system (in-app bell + full page) | ✅ Complete | | Excel import (bulk upload from Master CG Tracker) | ✅ Complete | | Excel export | ✅ Complete | | Deadline tracking (approaching/overdue + notifications) | ✅ Complete | | Command palette (Cmd+K) | ✅ Complete | | Bulk operations (multi-select → batch status/assignment/priority) | ✅ Complete | | Loading skeletons, error boundaries, empty states | ✅ Complete | | Responsive design + mobile sidebar | ✅ Complete | | Accessibility (skip-to-content, ARIA, focus-visible) | ✅ Complete | | Stage date override + scheduling features | ✅ Complete | ### Beyond Phase 4 (implemented opportunistically) | Feature | Notes | |---|---| | **Workload capacity grid** | Artist × Week grid with status bars, overallocation warnings, 4/8/12-week projections | | **Workload utilization heatmap** | Color-coded heatmap on workload page (green → yellow → red by load). Added in `c8f88c6`. | | **Calendar view** | Monthly calendar with event pills, filters, day detail. *Not in original plan.* | | **Calendar heatmap** | Heatmap overlay on calendar grid with toggle and bar visualization. Added in `bac6d4c`. *Not in original plan.* | | **Automation rule engine** | Full trigger/action engine with event bus, rule evaluator, action executor, execution log | | **Semantic search (pgvector + Ollama)** | Embedding service, hybrid search, pgvector columns on Project + Deliverable | | **AI Chat panel** | SSE streaming chat with tool calls (search_entities), entity cards, project/deliverable context. *Not in original plan.* | | **Weekly executive report** | Full report page at `/reports/weekly/[date]` with KPI strip, completed, deadlines, at-risk, upcoming sections | | **Skill data model** | `Skill`, `UserSkill`, `StageSkillRequirement` in schema with seed data — **no assignment UI yet** | ### Phase 2: Multi-Brand Ready (Dynamic Pipelines) | Feature | Status | |---|---| | **WP1 — RBAC Foundation** | ✅ Complete | | `Permission` enum (20 perms) + `OrgRolePermission` model | ✅ | | `requireAuth(permission?)` replaces `getAuthSession()` across all 28 API routes | ✅ | | Default permissions per role (ADMIN/PRODUCER/ARTIST) with org-level overrides | ✅ | | Permission matrix settings page (`/settings/permissions`) | ✅ | | **WP2 — Org Hardening** | ✅ Complete | | Denormalized `organizationId` on Deliverable + DeliverableStage | ✅ | | `assertOrgAccess()` on all project/deliverable/stage API routes | ✅ | | Backfill script (`scripts/backfill-org-ids.ts`) | ✅ | | **WP3 — Dynamic Pipeline Schema** | ✅ Complete | | `PipelineTemplate`, `PipelineStageDefinition`, `PipelineStageDependencyV2` models | ✅ | | `pipelineTemplateId` on Project, `stageDefinitionId` on DeliverableStage | ✅ | | Stage resolver normalizes old/new definitions | ✅ | | `deliverable-service.ts` creates stages from project's dynamic pipeline | ✅ | | Migration script (`scripts/migrate-to-dynamic-pipelines.ts`) | ✅ | | **WP4 — Pipeline Template CRUD** | ✅ Complete | | Full service: list, get, create, update, archive, duplicate | ✅ | | Stage CRUD: add, update, remove, reorder | ✅ | | Dependency management with cycle detection | ✅ | | Pipeline validation (cycles, orphans, root stages) | ✅ | | 8 API routes under `/api/pipelines/` | ✅ | | **WP5 — Pipeline Builder UI** | ✅ Complete | | `@xyflow/react` dependency graph visualization | ✅ | | Pipeline list page (`/settings/pipelines`) with create, duplicate, archive | ✅ | | Pipeline editor page with two-panel layout (stage list + dependency graph) | ✅ | | Stage edit sheet, validation banner, custom edge with delete | ✅ | | **WP6 — Invite System & Org Onboarding** | ✅ Complete | | `Invitation` model with token-based accept flow | ✅ | | Invitation service: create, list, revoke, accept | ✅ | | Team settings page (`/settings/team`) with member list + invite form | ✅ | | **WP7 — Custom Fields & Notification Rules** | ✅ Complete | | `CustomFieldDefinition` model + `customFields` JSON on Project/Deliverable | ✅ | | `NotificationRule` model (org-scoped event + conditions + channels) | ✅ | | CRUD services, validators, hooks, API routes for both | ✅ | | Custom fields settings page (`/settings/fields`) | ✅ | | Notification rules settings page (`/settings/notifications`) | ✅ | --- ## Remaining Work — Priority Order --- ### A. Visual Review Tool 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. **Infrastructure built so far:** - Review page at `/projects/[projectId]/deliverables/[deliverableId]/review` with image viewer, upload, gallery - Canvas-based image viewer with zoom/pan/minimap, retina support - Image upload API with PNG alpha compositing + TIFF conversion (sharp) - `Revision.attachments` JSON stores `{ referenceImage, currentImage }` with metadata - Comparison viewer with 4 modes: side-by-side, wipe, overlay, toggle - SVG annotation layer with 7 tools (rect, ellipse, arrow, freehand, text, pin, screenshot paste) - Annotation model in Prisma schema (requires `db push` to sync) - Annotation API: GET/POST `/api/revisions/[id]/annotations`, PATCH/DELETE `/api/revisions/[id]/annotations/[id]` - Annotations linked to comments (transactional create), undo/redo stack - Screenshot paste: Cmd+V pastes clipboard image as draggable/resizable callout - "Review" button on stage cards in deliverable detail page - Review sessions: session builder, presenter mode, summary grid, decision recording - `ReviewSession` + `ReviewSessionItem` models in schema **New dependency for all stages:** `sharp` (server-side PNG alpha compositing + image processing) --- #### A1 — Review Viewer & Image Upload `[x]` The foundation. A dedicated review page with a high-fidelity image viewer and the upload infrastructure to get images into the system. **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 form upload (type: "reference" | "current") - `DELETE /api/stages/[stageId]/revisions/[revisionId]/upload` — remove an uploaded image **Key files:** - `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 — Version Comparison `[x]` Compare two revisions of the same deliverable stage. This is the daily workhorse — producers and artists check what changed between rounds. **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/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 — Annotations `[x]` 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 model Annotation { id String @id @default(cuid()) commentId String comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) revisionId String revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) type AnnotationType 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 SCREENSHOT } ``` **Key files:** - `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 A1 --- #### A4 — Revision History Timeline `[x]` 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/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 A1. Enhanced by A3 (annotation counts and filtering). --- #### A5 — Feedback Checklist (Artist Action Items) `[x]` Every annotation and actionable comment becomes a structured to-do item on a checklist for the assigned artist. Closes the feedback-to-fix loop. **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 — action items with direct links to annotations on the image 4. Artist works through items — checks each off with optional resolution note 5. Artist submits new revision — unchecked action items carry forward with a warning 6. Reviewer verifies — can confirm resolution or reopen **Two item types (simplified from 4-level severity):** - **Action Item** (default) — something the artist needs to fix. Has checkbox, can be resolved/verified. - **Info Callout** — context or reference that doesn't require action (e.g., "FYI the client prefers warmer tones"). No checkbox. Can be toggled from action item and vice versa. **Where the checklist appears (3 locations):** 1. **Review page — Feedback Panel** (primary): full checklist with action items first, then info callouts. Progress bar counts only action items. Filter by type and status. 2. **My Work page** — feedback badge per assignment ("5 open items") 3. **Stage card on deliverable page** — compact badge ("4/7 resolved") for action items **New data model:** ```prisma model FeedbackItem { id String @id @default(cuid()) deliverableStageId String deliverableStage DeliverableStage @relation(...) revisionId String revision Revision @relation(...) annotationId String? annotation Annotation? @relation(...) commentId String? comment Comment? @relation(...) summary String isActionItem Boolean @default(true) status FeedbackStatus @default(OPEN) sortOrder Int @default(0) assignedToId String? createdById String resolvedById String? resolvedAt DateTime? resolutionNote String? verifiedById String? verifiedAt DateTime? carriedFromId String? // carried forward from prior round createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("feedback_items") } enum FeedbackStatus { OPEN IN_PROGRESS RESOLVED VERIFIED REOPENED } ``` **Key files:** - `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 (annotations to generate items from) + A4 (timeline for round context) --- #### A6 — Review Sessions & Playlists `[x]` 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 model ReviewSession { id String @id @default(cuid()) name String status ReviewSessionStatus @default(DRAFT) createdById String organizationId String shareToken String? @unique expiresAt DateTime? items ReviewSessionItem[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ReviewSessionItem { id String @id @default(cuid()) sessionId String deliverableStageId String revisionId String? sortOrder Int decision ReviewDecision? decisionNote String? decidedById String? decidedAt DateTime? } enum ReviewSessionStatus { DRAFT IN_PROGRESS COMPLETED } enum ReviewDecision { APPROVED CHANGES_REQUESTED REJECTED } ``` **Infrastructure built so far:** - Review sessions list page at `/reviews` with create/delete, status filtering - Session detail page at `/reviews/[sessionId]` with builder (list) and summary (grid) views - Session builder with auto-fill from project (filter by stage status), reorder, remove items - Presenter mode with full-screen walkthrough, image viewer, annotation overlay (read-only), keyboard shortcuts (A=approve, C=changes, arrows=navigate, Esc=exit), fullscreen toggle - Session summary with thumbnail grid, decision badges, stats strip - `ReviewSession` + `ReviewSessionItem` models in Prisma schema (decision stored as string, no enum coupling) - Full service layer, API routes (CRUD + add-items/remove/reorder/decide/generate), TanStack Query hooks - "Reviews" added to sidebar navigation - `AnnotationLayer` extended with `readOnly` prop for presenter mode - No shareable links (deferred to B3) - No pipeline advancement on approve (manual "client approved" comes later) **Key files:** - `src/app/(app)/reviews/page.tsx` — Session list - `src/app/(app)/reviews/[sessionId]/page.tsx` — Session detail (builder/summary/presenter) - `src/components/review/create-session-dialog.tsx` — Create session dialog - `src/components/review/session-builder.tsx` — Item list with auto-fill - `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` — All business logic - `src/lib/validators/review-session.ts` — Zod schemas - `src/hooks/use-review-sessions.ts` — TanStack Query hooks - `src/app/api/reviews/route.ts` — List/create API - `src/app/api/reviews/[sessionId]/route.ts` — CRUD + actions API **Dependencies:** Requires A1 + A3 --- #### A7 — Video Review with Timestamped Annotations Extend the review tool to support video. Artists submit MP4 renders; reviewers scrub to a frame, pause, and draw spatial annotations (reusing the existing annotation tools) tied to that timestamp. Optional reference video for side-by-side comparison. Self-hosted transcoding via FFmpeg — no external services. **Reference implementation:** [lawn-video-reviewer](https://github.com/) — React video player with HLS streaming, timestamped comments, and timeline markers. Key patterns to adapt: HLS-first streaming for instant playback and smooth seeking, custom video player with frame-accurate seeking, comment markers on the scrub timeline, and keyboard-driven playback controls. **New dependency:** `ffmpeg` (added to Docker image for HLS transcoding, frame extraction, and metadata parsing) The video feature is broken into sub-stages, each independently useful: --- ##### A7.1 — FFmpeg in Docker + Video Upload with HLS Streaming `[x]` Add FFmpeg to the Docker environment, extend the upload system to accept video files, and transcode to HLS for fast, seekable streaming from day 1. Inspired by the lawn-video-reviewer reference: prioritize playback speed and responsiveness so artists and producers actually want to use the platform. **Architecture decision:** Videos are stored outside `/public` in a mounted volume (`/data/uploads`) and served via a custom API route with proper `Range`, `Cache-Control`, and MIME headers. This avoids baking large files into the container image and enables proper streaming. HLS transcoding (MP4 → `.m3u8` + `.ts` segments) runs async after upload so the user isn't blocked. **What gets built:** - **Dockerfile update** — install `ffmpeg` in both builder and runner stages (`apk add --no-cache ffmpeg` on Alpine) - **docker-compose.yml** — add `/data/uploads` volume mount for persistent media storage - **Upload service extension** — accept MP4 files up to 500MB alongside existing image types - New `type` values: `"video"` and `"referenceVideo"` (in addition to "reference", "current", "screenshot") - Validate MIME type (`video/mp4`) and file size (500MB for video, 50MB for images) - Streaming write to disk (no full file buffering in memory) - Store raw MP4 in `/data/uploads/revisions/[revisionId]/video_[timestamp].mp4` - **HLS transcoding (async)** — after raw MP4 is stored: 1. Extract metadata via `ffprobe` (duration, resolution, fps, codec) — fast, reads headers only 2. Extract thumbnail JPEG (poster frame at 1s) 3. Transcode to HLS: `ffmpeg` → `/data/uploads/revisions/[id]/hls/index.m3u8` + `segment_NNN.ts` 4. HLS settings: 6s segments, single quality tier matching source resolution, fast preset 5. Update `revision.attachments` with `status: "ready"` and `hlsUrl` when complete 6. On failure: set `status: "failed"`, keep raw MP4 as fallback - **Streaming API route** — `GET /api/uploads/[...path]` - Serves files from `/data/uploads/` (not `/public`) - Proper MIME types: `.m3u8` → `application/vnd.apple.mpegurl`, `.ts` → `video/mp2t`, `.mp4` → `video/mp4` - `Range` header support for MP4 fallback seeking - `Cache-Control` headers for segment caching - **Thumbnail extraction** — `ffmpeg` extracts poster frame (at 1s or first keyframe) as JPEG - **Video metadata extraction** — `ffprobe` reads duration, resolution, codec, frame rate - **Extend `Revision.attachments` JSON:** ```json { "referenceImage": { ... }, "currentImage": { ... }, "video": { "url": "/api/uploads/revisions/.../video_xxx.mp4", "hlsUrl": "/api/uploads/revisions/.../hls/index.m3u8", "status": "processing | ready | failed", "thumbnailUrl": "/api/uploads/revisions/.../video_xxx_thumb.jpg", "filename": "video_xxx.mp4", "size": 52428800, "width": 1920, "height": 1080, "duration": 12.5, "fps": 24, "codec": "h264", "uploadedAt": "2026-03-16T..." }, "referenceVideo": { ... } } ``` **Upload flow:** ``` Artist uploads MP4 → Stream-write raw file to /data/uploads/revisions/[id]/video_[ts].mp4 → Extract metadata via ffprobe (instant — reads headers only) → Extract thumbnail JPEG (poster frame at 1s) → Save to revision.attachments with status: "processing" → Return 201 immediately (artist sees thumbnail + "Processing..." indicator) → Async: FFmpeg transcodes to HLS segments → On complete: update revision.attachments with hlsUrl + status: "ready" ``` **Why HLS over raw MP4 serving (lawn learning):** - Instant playback — browser starts streaming the first segment immediately, no full download - Smooth seeking — jumps to nearest segment boundary without buffering the whole file - Works in all browsers — hls.js for Chrome/Firefox, native HLS for Safari - Future-proof — adaptive bitrate (multiple quality tiers) can be added later without changing the player **New API endpoints:** - `POST /api/stages/[stageId]/revisions/[revisionId]/upload` — extend existing endpoint to handle `type: "video" | "referenceVideo"` - `DELETE /api/stages/[stageId]/revisions/[revisionId]/upload?type=video` — remove uploaded video + HLS segments - `GET /api/uploads/[...path]` — streaming file server for all uploaded media **Key files (new/modified):** - `Dockerfile` — add `ffmpeg` installation - `docker-compose.yml` — add `/data/uploads` volume mount - `src/lib/services/upload-service.ts` — extend with video handling, streaming writes - `src/lib/services/video-service.ts` — FFmpeg/ffprobe wrapper (HLS transcoding, thumbnail extraction, metadata parsing) - `src/app/api/uploads/[...path]/route.ts` — streaming file server with Range support - `src/components/review/video-upload-zone.tsx` — drag-and-drop for video files (reuse `image-upload-zone.tsx` pattern) **Dependencies:** Requires A1 (upload infrastructure) --- ##### A7.2 — Video Player Component `[x]` A custom video player built for frame-accurate review, embedded in the existing review page alongside the image viewer. **What gets built:** - **Video player component** — HTML5 `