Replace 2 stale migration files with a single baseline migration capturing the full 40+ model schema. The database was freshly reset via clean-slate, making this the ideal time to establish migration history. Dockerfile now runs prisma migrate deploy before app start. Updated SETUP.md and ROADMAP.md to reference prisma migrate dev instead of db push. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
61 KiB
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-18
Table of Contents
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]/reviewwith 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.attachmentsJSON 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 (use
prisma migrate devto create migration) - 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+ReviewSessionItemmodels in schema- Video upload with HLS streaming (A7.1) + custom video player (A7.2)
- Video annotation layer with auto-pause, timestamped annotations, timeline markers (A7.3 in progress)
Annotation.timestampSeconds+Annotation.frameThumbnailUrlfields in schema- Feedback item cards show timestamp badges for video annotations
- Revisions auto-poll when video status is "processing"
next.config.tsconfigured withproxyClientMaxBodySize: "500mb"for video uploads
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.attachmentsJSON:{ 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 pagesrc/components/review/image-viewer.tsx— Core canvas viewersrc/components/review/zoom-controls.tsx— Zoom toolbar + keyboard handlersrc/components/review/minimap.tsx— Navigation minimap overlaysrc/components/review/image-upload-zone.tsx— Drag-and-drop uploadsrc/components/review/image-gallery.tsx— Thumbnail strip across roundssrc/hooks/use-image-viewer.ts— Pan/zoom state managementsrc/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 orchestratorsrc/components/review/wipe-divider.tsx— Draggable split controlsrc/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:
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 switchingsrc/components/review/annotation-tools.tsx— Toolbar with tool selectionsrc/components/review/annotation-renderer.tsx— Renders individual shapessrc/components/review/screenshot-callout.tsx— Pasted screenshot with drag/resize handlessrc/lib/services/annotation-service.ts— CRUDsrc/lib/validators/annotation.ts— Zod schemassrc/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 panelsrc/components/review/revision-node.tsx— Individual round nodesrc/components/review/revision-comments.tsx— Per-round comment displaysrc/components/review/revision-annotations-summary.tsx— Annotation count + filterssrc/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:
- Reviewer draws annotation or posts actionable comment
- System auto-creates a FeedbackItem linked to the annotation/comment
- Artist sees checklist — action items with direct links to annotations on the image
- Artist works through items — checks each off with optional resolution note
- Artist submits new revision — unchecked action items carry forward with a warning
- 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):
- 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.
- My Work page — feedback badge per assignment ("5 open items")
- Stage card on deliverable page — compact badge ("4/7 resolved") for action items
New data model:
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 panelsrc/components/review/feedback-item.tsx— Individual item with resolve actionsrc/components/review/feedback-progress-bar.tsx— Progress barsrc/components/my-work/feedback-summary.tsx— Inline checklist on My Worksrc/components/stages/feedback-indicator.tsx— Compact badge for stage cardssrc/lib/services/feedback-service.ts— CRUD, auto-creation, carry-forward logicsrc/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:
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
/reviewswith 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+ReviewSessionItemmodels 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
AnnotationLayerextended withreadOnlyprop 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 listsrc/app/(app)/reviews/[sessionId]/page.tsx— Session detail (builder/summary/presenter)src/components/review/create-session-dialog.tsx— Create session dialogsrc/components/review/session-builder.tsx— Item list with auto-fillsrc/components/review/session-presenter.tsx— Full-screen walkthroughsrc/components/review/session-summary.tsx— Thumbnail grid with decisionssrc/lib/services/review-session-service.ts— All business logicsrc/lib/validators/review-session.ts— Zod schemassrc/hooks/use-review-sessions.ts— TanStack Query hookssrc/app/api/reviews/route.ts— List/create APIsrc/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 (pingdotgg/lawn) — React + Convex + Mux video reviewer with HLS streaming, timestamped text comments, and timeline markers.
Learnings from Lawn (reviewed 2026-03-18):
- Lawn is a time-only comment system — no spatial annotations or drawing on video frames. Our drawing-on-frame annotation feature (SVG overlay + annotation tools) goes beyond Lawn's scope.
- Timeline marker grouping: Lawn deduplicates markers within 1% of the timeline to prevent visual clutter. Markers are green (resolved) / orange (unresolved) with an active ring when playback is within 1.5s.
- Player architecture: Custom player on raw
<video>+ HLS.js (no third-party player SDK). UsesforwardRef+useImperativeHandleto exposeseekTo(time)for sidebar→player communication. All playback state is localuseState. - Dual playback sources: Shows raw original immediately while transcoded video processes, with a quality switcher. User is never blocked waiting for encoding.
- Controls auto-hide: Controls fade after 2.5s during playback, re-shown on mouse move.
- Key patterns adopted: HLS-first streaming, timeline markers with dedup + color coding, comment-to-player seek via timestamp badges, graceful fallback when transcoding unavailable.
- Key patterns NOT adopted: Mux (we self-host with FFmpeg), Convex real-time DB (we use Prisma + TanStack Query), Clerk auth (we use Auth.js).
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
ffmpegin both builder and runner stages (apk add --no-cache ffmpegon Alpine) - docker-compose.yml — add
/data/uploadsvolume mount for persistent media storage - Upload service extension — accept MP4 files up to 500MB alongside existing image types
- New
typevalues:"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
- New
- HLS transcoding (async) — after raw MP4 is stored:
- Extract metadata via
ffprobe(duration, resolution, fps, codec) — fast, reads headers only - Extract thumbnail JPEG (poster frame at 1s)
- Transcode to HLS:
ffmpeg→/data/uploads/revisions/[id]/hls/index.m3u8+segment_NNN.ts - HLS settings: 6s segments, single quality tier matching source resolution, fast preset
- Update
revision.attachmentswithstatus: "ready"andhlsUrlwhen complete - On failure: set
status: "failed", keep raw MP4 as fallback
- Extract metadata via
- 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 Rangeheader support for MP4 fallback seekingCache-Controlheaders for segment caching
- Serves files from
- Thumbnail extraction —
ffmpegextracts poster frame (at 1s or first keyframe) as JPEG - Video metadata extraction —
ffprobereads duration, resolution, codec, frame rate - Extend
Revision.attachmentsJSON:{ "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 handletype: "video" | "referenceVideo"DELETE /api/stages/[stageId]/revisions/[revisionId]/upload?type=video— remove uploaded video + HLS segmentsGET /api/uploads/[...path]— streaming file server for all uploaded media
Key files (new/modified):
Dockerfile— addffmpeginstallationdocker-compose.yml— add/data/uploadsvolume mountsrc/lib/services/upload-service.ts— extend with video handling, streaming writessrc/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 supportsrc/components/review/video-upload-zone.tsx— drag-and-drop for video files (reuseimage-upload-zone.tsxpattern)
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
<video>element with custom controls (no native browser chrome)- Play/pause (Space or K)
- Seek bar / scrub timeline with hover preview
- Frame-by-frame stepping (← / → arrow keys, step by 1/fps seconds)
- Playback speed control (0.25x, 0.5x, 1x, 1.5x, 2x)
- Volume control + mute (M)
- Fullscreen toggle (F)
- Current time / duration display (timecode format:
HH:MM:SS:FF) - Loop toggle
- Review page integration — detect whether the revision has a video or image attachment and render the appropriate viewer
- Tab or toggle to switch between image viewer and video player when both exist
- Gallery strip shows video thumbnails alongside image thumbnails
- Keyboard shortcuts — consistent with lawn's patterns:
- Space/K = play/pause
- J/L = skip back/forward 5s
- ← / → = frame step (when paused)
- , / . = frame step alternative (matching common NLEs)
- F = fullscreen
- M = mute
- [ / ] = playback speed down/up
Key files:
src/components/review/video-player.tsx— Core player component with custom controlssrc/components/review/video-controls.tsx— Play, seek, speed, volume, fullscreen controlssrc/components/review/video-timeline.tsx— Scrub bar with hover time previewsrc/components/review/video-frame-display.tsx— Timecode display (HH:MM:SS:FF)src/hooks/use-video-player.ts— Player state management (current time, playing, speed, volume)
Dependencies: Requires A7.1 (video upload)
A7.3 — Timestamped Video Annotations [~]
The core differentiator: pause a video at any frame, draw spatial annotations on that frame using the existing annotation tools, and have those annotations tied to both a timestamp and image coordinates.
What gets built:
- Frame capture — when the user activates an annotation tool while the video is playing:
- Video auto-pauses
- Current frame is captured to a canvas (via
drawImagefrom<video>) - SVG annotation layer overlays the paused frame (reuse existing
annotation-layer.tsx) - User draws annotation using existing tools (rectangle, ellipse, arrow, freehand, text, pin)
- On submit, annotation is saved with
timestampSeconds+ spatial coordinates
- Server-side frame extraction fallback — API endpoint to extract a specific frame via FFmpeg for cases where client-side capture fails (e.g., CORS, codec issues):
GET /api/stages/[stageId]/revisions/[revisionId]/frame?t=5.25→ returns JPEG of frame at 5.25s
- Timeline annotation markers — colored dots/ticks on the video scrub bar showing where annotations exist
- Orange = unresolved, green = resolved (matches lawn's pattern)
- Click a marker to seek to that timestamp and show the annotation
- Cluster nearby markers when zoomed out
- Annotation visibility by time — annotations fade in/out based on current playback position
- When playing: briefly flash annotations as playhead crosses their timestamp
- When paused: show annotations for the current frame (±0.5s window configurable)
- Toggle: "show all annotations" mode shows all regardless of timestamp
- Comment linking — each video annotation creates a Comment (same as image annotations) with the timestamp embedded
- Comment sidebar shows timestamp badge (clickable to seek)
- Comments sorted by timestamp (not creation time)
Data model extension:
// Extend existing Annotation model — add optional timestamp field
model Annotation {
// ... existing fields ...
timestampSeconds Float? // null for image annotations, set for video annotations
frameThumbnailUrl String? // cached frame thumbnail for the annotation's moment
}
New API endpoints:
GET /api/stages/[stageId]/revisions/[revisionId]/frame?t=5.25— extract frame at timestamp via FFmpeg- Existing annotation CRUD endpoints gain
timestampSecondsfield
Key files (new/modified):
src/components/review/video-annotation-layer.tsx— Orchestrates frame capture + annotation overlay on paused videosrc/components/review/video-timeline-markers.tsx— Annotation markers on scrub barsrc/components/review/annotation-layer.tsx— extend to accept optional timestamp contextsrc/components/review/comment-list.tsx— extend to show timestamp badges, sort by timesrc/lib/services/video-service.ts— add frame extraction endpointsrc/lib/services/annotation-service.ts— extend to handletimestampSecondsprisma/schema.prisma— addtimestampSecondsandframeThumbnailUrlto Annotation
Dependencies: Requires A7.2 (video player) + A3 (annotation system)
A7.4 — Video Comparison (Reference vs. Current) [ ]
Side-by-side video comparison for reviewing changes between a reference render and the current submission.
What gets built:
- Dual video player — two synced
<video>elements playing in lockstep- Synced playback: play/pause/seek one, both follow
- Synced frame stepping
- Independent volume controls
- Labels: "Reference" / "Current"
- Comparison modes (reuse concepts from image comparison):
- Side-by-side — two players next to each other (default)
- Toggle — press Space to swap which video is visible (single player area)
- Wipe mode (stretch goal) — CSS clip-path or canvas compositing to do A/B wipe on video
- More complex due to dual video decoding — defer if performance is an issue
- Revision selectors — dropdowns to pick which revision's video goes on each side
- Annotations shared across comparison — annotations from either video visible, with source label
Key files:
src/components/review/video-comparison.tsx— Dual synced player orchestratorsrc/components/review/synced-video-player.tsx— Player that accepts external time/play statesrc/components/review/video-comparison-toolbar.tsx— Mode switchersrc/hooks/use-synced-video.ts— Shared playback state management
Dependencies: Requires A7.2 (video player). Reference video upload from A7.1.
A7.5 — Video in Review Sessions [ ]
Extend the existing review session presenter (A6) to handle video deliverables alongside images.
What gets built:
- Presenter mode video support — when a ReviewSessionItem's revision has a video attachment, render the video player instead of the image viewer
- All video controls available (play, pause, seek, frame-step, speed)
- Annotations visible on the timeline and as overlays when paused
- Decision buttons (Approve / Changes Requested) work the same as for images
- Mixed sessions — sessions can contain both image and video items; presenter mode switches viewer type per item
- Session summary — video items show poster frame thumbnail (extracted in A7.1) in the summary grid
Key files (modified):
src/components/review/session-presenter.tsx— add video player branchsrc/components/review/session-summary.tsx— video thumbnail supportsrc/components/review/session-builder.tsx— show video icon for video items
Dependencies: Requires A6 (review sessions) + A7.2 (video player)
A7.6 — Feedback Checklist for Video Annotations [ ]
Extend the feedback checklist (A5) to work with timestamped video annotations.
What gets built:
- Timestamp in feedback items — each feedback item created from a video annotation shows the timestamp
- Click-to-seek — clicking a video feedback item in the checklist seeks the player to that timestamp and highlights the annotation
- Frame thumbnail in checklist — small thumbnail of the annotated frame next to each feedback item for quick visual context
- Carry-forward for video — when a new revision is submitted, unresolved video feedback items carry forward with their timestamps (timestamps may shift if the video changes length — flag these for manual review)
Key files (modified):
src/components/review/feedback-checklist.tsx— add timestamp display + seek actionsrc/components/review/feedback-item-card.tsx— frame thumbnail + timestamp badgesrc/lib/services/feedback-service.ts— carry-forward logic for video annotations
Dependencies: Requires A5 (feedback checklist) + A7.3 (timestamped annotations)
Implementation Order & Dependency Graph
A7.1 FFmpeg + Video Upload
│
▼
A7.2 Video Player Component
│
├──────────────┐
▼ ▼
A7.3 Timestamped A7.4 Video
Annotations Comparison
│ │
▼ ▼
A7.6 Feedback A7.5 Review
Checklist Sessions
Estimated build order:
- A7.1 — Foundation. Small scope, mostly backend. Do this first.
- A7.2 — Core player. Can start immediately after A7.1.
- A7.3 — The main feature. Requires the player + existing annotation system.
- A7.4 — Comparison. Can be built in parallel with A7.3 if desired.
- A7.5 + A7.6 — Integration with existing features. Do last.
B. Collaboration Enhancements
B1 — Rich @Mentions with Deep Linking
What: @ trigger in comment input opens user autocomplete. Mentioned users get a notification with a direct link to the comment.
Key files:
src/components/comments/mention-input.tsxsrc/components/comments/mention-renderer.tsx- Update
src/lib/services/comment-service.ts— extract mentions + create notifications
B2 — Project Activity Feed
What: Unified chronological stream of all activity on a project — status changes, comments, uploads, assignments, approvals.
New data model:
model ActivityEntry {
id String @id @default(cuid())
projectId String
deliverableId String?
stageId String?
userId String
type ActivityType
summary String
metadata Json?
createdAt DateTime @default(now())
}
enum ActivityType {
STATUS_CHANGE COMMENT_ADDED REVISION_SUBMITTED ASSIGNMENT_CHANGED
APPROVAL_DECISION FILE_UPLOADED DEADLINE_CHANGED PRIORITY_CHANGED
PROJECT_CREATED DELIVERABLE_CREATED
}
Key files:
src/components/activity/activity-feed.tsxsrc/components/activity/activity-entry.tsxsrc/lib/services/activity-service.tssrc/app/api/projects/[projectId]/activity/route.ts
B3 — External Review Links
What: Token-based URLs for external HP stakeholders to view specific deliverables without an account. Optional watermark (reviewer name, date, CONFIDENTIAL), configurable expiry, access logging, revoke capability.
Key files:
src/app/(external)/review/[token]/page.tsxsrc/components/review/watermark-overlay.tsxsrc/lib/services/external-link-service.ts
C. Reporting Completions
C1 — Velocity & Throughput Metrics
What: Deliverables completed per week, average time per stage, bottleneck detection. Trend charts over 4/8/12/26-week windows.
Key files:
src/components/dashboard/velocity-chart.tsxsrc/components/dashboard/cycle-time-chart.tsxsrc/components/dashboard/bottleneck-chart.tsxsrc/lib/services/analytics-service.ts
C2 — Burndown Charts
What: Per-project burndown with ideal line, actual line, and velocity-based projection with confidence interval. Warning when projection exceeds deadline.
Key files:
src/components/dashboard/burndown-chart.tsxsrc/components/dashboard/projection-engine.ts
C3 — Client-Facing Dashboard (Read-Only Portal)
What: Simplified read-only view for HP stakeholders via secure share link. No account required. Configurable visibility, optional password + expiry.
New data model:
model PortalLink {
id String @id @default(cuid())
token String @unique
organizationId String
projectIds String[]
createdById String
password String?
expiresAt DateTime?
lastAccessedAt DateTime?
accessCount Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
Key files:
src/app/(portal)/[token]/page.tsxsrc/app/(portal)/[token]/projects/[projectId]/page.tsxsrc/components/portal/portal-header.tsxsrc/lib/services/portal-service.ts
New dependency: bcryptjs (password hashing for portal links)
C4 — SLA Tracking
What: Target turnaround times per stage type. Real-time on-track / at-risk / breached status. Business-hours-aware calculation. Compliance dashboard widget.
New data model:
model SLATarget {
id String @id @default(cuid())
organizationId String
stageType String
targetHours Int
warningThreshold Float @default(0.75)
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
Key files:
src/app/(app)/settings/sla/page.tsxsrc/components/dashboard/sla-compliance-chart.tsxsrc/components/stages/sla-indicator.tsxsrc/lib/services/sla-service.ts
D. Automation Completions
The automation rule engine (Phase 7.1) is fully built. These features extend it.
D1 — Automation Engine Activation
The automation engine backend is fully built (event bus, rule engine, action executor, execution logging, REST API). What's missing: event emission isn't wired into the service layer, deadline events have no scheduler, and there's no admin UI. This feature activates the engine end-to-end.
Three sub-stages, each independently useful:
D1.1 — Wire Event Emission into Service Layer [x]
Connect the existing emit helpers to actual service mutations so automation rules fire in production.
What gets wired:
stage-service.ts—emitStageStatusChanged()afterupdateStageStatusandbulkUpdateStageStatusestransactions commit. Extend Prisma queries to includedeliverable.projectfor event payload context.revision-service.ts—emitRevisionSubmitted()aftercreateRevision. Add stage context query (deliverable + project + org) since the function only receivesstageId.assignment-service.ts—emitAssignmentCreated()afterassignUserToStageandbulkAssignArtists. Extend stage query to include deliverable/project context.- Handler registration — ensure
automation-service.ts(which self-registers on import) is imported before events fire.
Critical constraint: Events must fire AFTER transaction commit, not inside it. If emitted inside $transaction and it rolls back, the event would have already fired.
Key files (modified):
src/lib/services/stage-service.tssrc/lib/services/revision-service.tssrc/lib/services/assignment-service.tssrc/lib/automation/event-bus.ts(no changes, but used by all above)
No new dependencies. No new data models.
D1.2 — Deadline Scheduler [x]
Implement the trigger mechanism for deadline.approaching and deadline.passed events. The existing deadline-service.ts already has checkDeadlines() — this adds event emission and a scheduler.
What gets built:
emitDeadlineEvents()indeadline-service.ts— callscheckDeadlines(), emits automation events per approaching/overdue item- 24-hour deduplication — before executing a matched deadline rule, check
AutomationExecutionfor recent successful execution of the same rule (prevents repeated firing on every scheduler run) - Cron API route at
/api/cron/deadlines— protected byCRON_SECRETenv var, callable by external scheduler or manual testing - Next.js instrumentation file —
setIntervalhourly in the Node.js server process as primary trigger mechanism
Key files (new):
src/app/api/cron/deadlines/route.tsinstrumentation.ts
Key files (modified):
src/lib/services/deadline-service.tssrc/lib/services/automation-service.ts(dedup logic)
Dependencies: Requires D1.1
D1.3 — Automation Rules Admin UI [x]
Settings page at /settings/automations for producers and admins to create, edit, and monitor automation rules.
What gets built:
- Rule list — card per rule with name, enabled
<Switch>, trigger event badge, action type badges, condition count, execution count, edit/delete actions - Rule builder dialog — form with:
- Trigger event picker (5 event types)
- Condition builder (dynamic rows, contextual field dropdowns per event type, operator select, value input; AND logic)
- Action configurator (dynamic rows, type selector with contextual params per action type)
send_webhookincludes Teams Adaptive Card auto-formatting when URL containswebhook.office.com
- Execution log — dense table of recent executions across all rules with status badges (SUCCESS/PARTIAL_FAILURE/FAILURE), expandable detail rows
- TanStack Query hooks for all CRUD + execution history
- New API endpoint
GET /api/automations/executionsfor org-wide execution log
Key files (new):
src/app/(app)/settings/automations/page.tsxsrc/hooks/use-automations.tssrc/lib/automation/field-options.ts(event field definitions + status enums for condition builder)src/app/api/automations/executions/route.ts
Key files (modified):
src/app/(app)/settings/page.tsx(add entry to settings index)src/lib/automation/action-executor.ts(Teams Adaptive Card formatting in webhook action)src/lib/services/automation-service.ts(addlistAllExecutions())
Dependencies: Requires D1.1. Enhanced by D1.2 (deadline rules only useful once scheduler exists).
D2 — Multi-Level Approval Chains
What: Approval workflows requiring sign-off from multiple stakeholders in sequence. Each step: approver role/user, required/optional, auto-advance on approve. Visual progress indicator.
New data model:
model ApprovalChain {
id String @id @default(cuid())
name String
organizationId String
stageType String?
steps ApprovalStep[]
createdAt DateTime @default(now())
}
model ApprovalStep {
id String @id @default(cuid())
chainId String
stepOrder Int
approverRole Role?
approverUserId String?
isRequired Boolean @default(true)
autoAdvance Boolean @default(true)
approvalRecords ApprovalRecord[]
}
model ApprovalRecord {
id String @id @default(cuid())
deliverableStageId String
stepId String
decision ReviewDecision
note String?
decidedById String
decidedAt DateTime @default(now())
}
Key files:
src/app/(app)/settings/approvals/page.tsxsrc/components/approvals/chain-builder.tsxsrc/components/approvals/approval-progress.tsxsrc/lib/services/approval-service.ts
D3 — Project Templates
What: Save a full project configuration (deliverables, stage settings, default assignments) as a reusable template. One-click project creation for repeat HP SKU types.
New data model:
model ProjectTemplate {
id String @id @default(cuid())
name String
description String?
organizationId String
createdById String
isShared Boolean @default(true)
configuration Json
deliverables ProjectTemplateDeliverable[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ProjectTemplateDeliverable {
id String @id @default(cuid())
templateId String
name String
type String?
priority Priority @default(MEDIUM)
defaultAssignments Json?
sortOrder Int
}
Key files:
src/app/(app)/templates/page.tsxsrc/components/templates/template-card.tsxsrc/components/templates/template-builder.tsxsrc/lib/services/template-service.ts
E. Asset Intelligence
E1 — File Validation on Upload
What: Automatically validate uploaded files against spec requirements (resolution, color space, format, file size, aspect ratio). Reject non-conforming files with clear error messages. Override option for producers.
New data model:
model AssetSpec {
id String @id @default(cuid())
organizationId String
stageType String
name String
rules Json // { minWidth, minHeight, maxFileSize, allowedFormats[], colorSpace, dpi }
isActive Boolean @default(true)
validationResults AssetValidationResult[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AssetValidationResult {
id String @id @default(cuid())
revisionId String
specId String
passed Boolean
results Json // [{ check, expected, actual, passed }]
overrideById String?
overrideReason String?
validatedAt DateTime @default(now())
}
Key files:
src/app/(app)/settings/asset-specs/page.tsxsrc/components/upload/file-validator.tsxsrc/lib/services/asset-validation-service.ts
New dependency: sharp (already needed for A1 PNG compositing)
E2 — Thumbnail & Preview Generation
What: Auto-generate web-optimized thumbnails (200px), previews (1200px), and full copies from uploaded high-res assets. Background processing queue.
Key files:
src/lib/services/preview-service.tssrc/lib/jobs/generate-previews.tssrc/components/common/optimized-image.tsx
E3 — AI Review Engine Integration Point
What: Webhook endpoint for an external AI Review Engine to post structured quality analysis results. AI-flagged regions rendered as dashed-border annotations in the viewer. Configuration per stage type.
New data model:
model AIReviewResult {
id String @id @default(cuid())
revisionId String
overallScore Float
passed Boolean
checks Json
flaggedRegions Json
engineVersion String
processingTimeMs Int
createdAt DateTime @default(now())
}
Key files:
src/app/api/webhooks/ai-review/route.tssrc/components/review/ai-review-overlay.tsxsrc/components/review/ai-review-summary.tsxsrc/lib/services/ai-review-service.ts
F. Quality of Life
F1 — Saved Filters & Custom Views
What: Save current filter/sort/column config as a named view. Personal and shared views. URL-encodable for link sharing.
New data model:
model SavedView {
id String @id @default(cuid())
name String
userId String
organizationId String
projectId String?
viewType ViewType
configuration Json
isShared Boolean @default(false)
isPinned Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum ViewType { TABLE BOARD TIMELINE }
Key files:
src/components/views/saved-view-picker.tsxsrc/components/views/save-view-dialog.tsxsrc/lib/services/saved-view-service.ts
F2 — Skill-Based Assignment UI
What: The Skill / UserSkill / StageSkillRequirement data models already exist. This adds the UI: skill tags on user profiles, skill requirements on stage templates, and best-fit artist suggestions in the assignment dialog (sorted by skill match + lowest current load).
Key files:
src/app/(app)/settings/skills/page.tsxsrc/components/assignments/skill-match-suggestions.tsxsrc/lib/services/skill-service.ts— matching algorithm
F3 — Batch Upload with Auto-Matching
What: Drop multiple files onto a project; auto-match to deliverables by file naming convention (configurable regex). Preview table shows match confidence, manual override available.
Key files:
src/components/upload/batch-upload-zone.tsxsrc/components/upload/file-matcher.tsxsrc/lib/services/file-matching-service.ts
F4 — Extended Command Palette Actions
What: Add quick-action commands to the existing Cmd+K palette: "approve and advance," "assign to me," "mark blocked," "start review session." Context-aware based on current page.
Key files:
- Update
src/components/layout/command-palette.tsx src/lib/commands/action-registry.ts
G. Docker Deployment
G1 — Docker Compose Stack
What: docker-compose.yml with three services — Next.js app, PostgreSQL + pgvector, Ollama with pre-configured models. One docker compose up starts everything.
Note: Dockerfile and docker-compose.yml already exist in the repo root — review and complete as needed. The Dockerfile must include ffmpeg (required by A7 — Video Review).
Services:
| Service | Image | Purpose |
|---|---|---|
app |
Custom Dockerfile | Next.js production build |
db |
pgvector/pgvector:pg17 |
PostgreSQL with pgvector |
ollama |
ollama/ollama:latest |
Local AI (nomic-embed-text + qwen3.5:9b) |
Key files:
Dockerfile— multi-stage Next.js build (review existing)docker-compose.yml— full stack (review existing)docker/ollama-entrypoint.sh— model bootstrap scriptdocker/db-init.sql—CREATE EXTENSION IF NOT EXISTS vector;.env.example— environment template
G2 — Health Checks & Startup Orchestration
What: pg_isready health check on db, Ollama API health check, prisma migrate deploy runs on app startup before next start. depends_on: condition: service_healthy ensures correct start order.
G3 — Dev Environment Override
What: docker-compose.dev.yml that mounts source code and runs next dev for hot reloading, while keeping database and Ollama in containers.
Key files:
docker-compose.dev.yml
Data Model Status
Models Currently in Schema
| Model | Status |
|---|---|
| Organization, User, Account, Session | ✅ |
| PipelineStageTemplate, PipelineStageDependency | ✅ (legacy global, kept for backward compat) |
| PipelineTemplate, PipelineStageDefinition, PipelineStageDependencyV2 | ✅ (Phase 2 — org-scoped dynamic pipelines) |
| OrgRolePermission | ✅ (Phase 2 — RBAC) |
| Invitation | ✅ (Phase 2 — invite system) |
| CustomFieldDefinition | ✅ (Phase 2 — custom fields) |
| NotificationRule | ✅ (Phase 2 — notification rules) |
| Project, Deliverable, DeliverableStage | ✅ (with pgvector, customFields JSON, org denormalization) |
| StageAssignment, Revision, Comment | ✅ |
| Notification | ✅ |
| AutomationRule, AutomationExecution | ✅ |
| Skill, UserSkill, StageSkillRequirement | ✅ |
| SearchLog | ✅ |
| ChatMessage | ✅ |
Models Needed (not yet in schema)
| Model | Feature |
|---|---|
timestampSeconds, frameThumbnailUrl fields) |
|
| FeedbackItem | A5 |
| ApprovalChain, ApprovalStep, ApprovalRecord | D2 |
| ProjectTemplate, ProjectTemplateDeliverable | D3 |
| AssetSpec, AssetValidationResult | E1 |
| AIReviewResult | E3 |
| PortalLink | C3 |
| SLATarget | C4 |
| ActivityEntry | B2 |
| SavedView | F1 |
Revision.attachments extended with video + referenceVideo |
A7.1 |
Architecture Reference
src/
├── app/
│ ├── (auth)/login/ # SSO login
│ ├── (app)/ # Authenticated routes
│ │ ├── dashboard/
│ │ ├── projects/[projectId]/
│ │ │ ├── table/ board/ timeline/
│ │ │ └── deliverables/[deliverableId]/
│ │ ├── my-work/
│ │ ├── workload/ # ✅ Capacity grid + heatmap
│ │ ├── calendar/ # ✅ Calendar view
│ │ ├── reports/weekly/ # ✅ Weekly executive report
│ │ ├── reviews/ # A7 Review sessions
│ │ ├── templates/ # D3 Project templates
│ │ └── settings/
│ │ ├── permissions/ # ✅ Phase 2 RBAC matrix
│ │ ├── pipelines/ # ✅ Phase 2 Pipeline builder
│ │ ├── team/ # ✅ Phase 2 Team + invitations
│ │ ├── fields/ # ✅ Phase 2 Custom fields
│ │ ├── notifications/ # ✅ Phase 2 Notification rules
│ │ └── skills/ # ✅ Skills management
│ ├── (portal)/[token]/ # C3 Client portal (external)
│ ├── (external)/review/ # B3 External review links
│ └── api/
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── layout/ # Sidebar, topbar, command palette
│ ├── views/ # Table, Board, Timeline, Dashboard
│ ├── workload/ # ✅ Capacity grid + heatmap components
│ ├── calendar/ # ✅ Calendar components
│ ├── reports/weekly/ # ✅ Weekly report components
│ ├── pipeline-builder/ # ✅ Phase 2 Pipeline graph + stage list
│ ├── revisions/ # A1 Image comparison (to build)
│ ├── review/ # A2-A7 Full review viewer (image + video)
│ ├── automations/ # D1 Automation UI (to build)
│ └── search/ # ✅ Smart search panel
├── lib/
│ ├── pipeline/ # ✅ Dependency engine + stage machine + stage resolver
│ ├── rbac/ # ✅ Phase 2 permissions + org scope + requireAuth
│ ├── automation/ # ✅ Event bus + rule engine + executor
│ ├── services/ # Business logic per entity
│ └── validators/ # Zod schemas
├── hooks/ # TanStack Query hooks
└── stores/ # Zustand (UI state, filters)
Key Patterns
- Thin API routes → delegate to
lib/services/for all business logic - Service layer → Prisma queries + business rules (testable, reusable)
- URL-synced filters via
nuqs— views are shareable/bookmarkable - Server Components for initial fetch, TanStack Query for mutations/cache
npx prisma migrate devfor schema changes (versioned migrations inprisma/migrations/)
Local Dev
# Start DB
brew services start postgresql@17
# Start app (must source nvm)
export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && npm run dev
Third-Party Libraries Needed for Remaining Work
| Library | Purpose | Feature |
|---|---|---|
@xyflow/react |
Pipeline dependency graph visualization | ✅ Phase 2 WP5 (installed) |
sharp |
PNG alpha compositing + image validation + thumbnail generation | A1, E1, E2 |
bcryptjs |
Password hashing for portal links | C3 |
@react-pdf/renderer |
PDF export for weekly report download button | C — weekly report enhancement |
ffmpeg (system) |
Video frame extraction, metadata parsing, thumbnail generation | A7 |
Document version: 1.1 — Updated 2026-03-14 (Phase 2: Dynamic Pipelines complete) To be updated as features are built and priorities shift.