twotalesanimation 28e8c47907 feat(client-portal): let a reviewer edit and delete their own notes
Notes left through a review link were write-once. A client who mistyped a
timecode or changed their mind had no way to correct it and had to add a second
note contradicting the first, which the studio then had to reconcile by hand.

PATCH and DELETE on comment/[commentId] cover both. Only the reviewer's own
notes are touchable: the studio's comments and those of every other review
session are read-only through a link, whatever the holder sends. Ownership is
re-checked server-side on every write, so the canEdit flag now returned by the
versions route only decides whether to offer the controls and grants nothing.

Refusals are 404 rather than 403 throughout, since a link holder has no business
learning which comment ids exist outside their own notes.

Only the text is editable. The frame a note is pinned to is what the studio
works from, so moving it would silently repoint feedback someone may already
have actioned; a note about a different frame is a new note. Deleting a drawing
note also removes its strokes, mirroring deleteComment in actions/comments.ts,
and the page reloads the player afterwards so the frame does not keep showing
marks that no longer exist. Delete goes through a confirmation that shows the
note and says plainly that it disappears for the studio too.

Worth knowing before this reaches a show: everyone holding the same review link
is the same author. A ReviewSession with no email resolves to a token-derived
guest user, so one link sent to three people gives all three one identity and
any of them can now edit or delete the others' notes. That model is not new,
which is why their comments already showed as one name, but edit and delete is
the first feature where it grants real power rather than muddling attribution.
Per-reviewer links with email set are the fix if that is not wanted, and that is
a change to how links are issued rather than to this code. The scope is
documented on reviewClientEmail so the next reader meets it before it surprises
them.

getOrCreateClientUser was copy-pasted into the comment and approve routes with
the fallback-email expression spelled out in three places. It is one set of
helpers in review-auth.ts now, including findReviewUser, a read-only variant so
that merely opening a review link does not conjure a User row just to answer
"is this theirs?", and ownsComment, the single rule shared by the route that
offers the controls and the route that enforces them.

Nine tests cover that rule directly: internal users' notes stay read-only, a
session that has never posted owns nothing, and an authorless comment — one
whose User was deleted, since Comment.authorId is SetNull — cannot be claimed by
anyone. The routes themselves are not covered, so exercise edit and delete
against a real review link before handing one to a client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 18:17:40 +02:00
2026-07-08 14:17:03 +02:00
2026-06-11 12:39:01 +02:00
2026-07-08 16:28:18 +02:00
2026-05-19 22:20:29 +02:00
2026-05-19 22:20:29 +02:00
2026-05-19 22:20:29 +02:00
2026-05-19 22:20:29 +02:00
2026-05-20 13:06:04 +02:00
2026-06-11 12:59:39 +02:00
2026-06-11 13:07:59 +02:00
2026-05-29 10:40:28 +02:00
2026-07-08 19:54:24 +02:00
2026-05-19 22:20:29 +02:00
2026-06-11 12:30:28 +02:00
2026-06-11 12:30:28 +02:00
2026-05-19 22:20:29 +02:00
2026-06-11 15:19:12 +02:00
2026-05-19 22:20:29 +02:00
2026-05-19 22:20:29 +02:00
2026-05-19 22:20:29 +02:00
2026-07-22 10:37:13 +02:00
2026-07-11 15:06:12 +02:00
2026-06-11 12:30:28 +02:00

FeedBack — VFX Review & Approval Platform

Frame-accurate video review, annotation drawing, and approval tracking for boutique VFX and animation studios.


Features

  • Dead-simple video upload — drag & drop MP4/MOV, up to 2 GB, version-tracked
  • Frame-accurate scrubbing — canvas-based timeline ruler with click-and-drag seek
  • Timestamp comments — comments anchored to exact frame numbers, threaded replies, resolve/unresolve
  • Annotation drawing — freehand, arrow, rectangle, circle tools with color + stroke width picker
  • Approval workflow — Approve / Reject / Needs Changes with notes, full history
  • Client review links — shareable token-based review URLs for external clients (no login required)
  • Role-based access — Admin / Producer / Supervisor / Artist / Client roles
  • Slack notifications — webhook alerts for uploads and approvals
  • Self-hostable — Docker Compose with PostgreSQL + MinIO (S3-compatible) included

Tech Stack

Layer Technology
Framework Next.js 15 App Router (Turbopack)
Language TypeScript 5 strict
Styling TailwindCSS 3 + shadcn/ui (zinc, dark-only)
Auth NextAuth.js v5 (Credentials + JWT)
Database PostgreSQL 16 via Prisma 6
File Storage UploadThing / S3 / R2 / B2 / MinIO / local
State Zustand 5 (player) + TanStack Query 5 (server)
Annotations HTML5 Canvas API (normalized coordinates)
Video Playback HTML5 <video> with custom controls
Container Docker multi-stage build

Quick Start (Docker)

# 1. Clone and configure
cp .env.example .env
# Edit .env — set AUTH_SECRET, DATABASE_URL, storage provider

# 2. Start services (PostgreSQL + MinIO + App)
docker compose up -d

# 3. Run migrations and seed
docker compose exec app npx prisma migrate deploy
docker compose exec app npx tsx prisma/seed.ts

# 4. Open http://localhost:3000

Demo accounts (after seed):

Email Password Role
admin@vfxreview.local admin123 Admin
producer@vfxreview.local producer123 Producer
supervisor@vfxreview.local supervisor123 Supervisor
artist@vfxreview.local artist123 Artist
client@studio.com client123 Client

Local Development

# Install dependencies
npm install

# Set up database (Postgres must be running)
npx prisma migrate dev
npx tsx prisma/seed.ts

# Start dev server
npm run dev

Open http://localhost:3000.


Environment Variables

See .env.example for full reference. Key variables:

# Required
DATABASE_URL="postgresql://..."
AUTH_SECRET="your-32-char-secret"
NEXTAUTH_URL="http://localhost:3000"

# Storage (choose one)
STORAGE_PROVIDER="local"          # local | uploadthing | s3 | r2 | b2 | minio

# UploadThing (if STORAGE_PROVIDER=uploadthing)
UPLOADTHING_SECRET="sk_live_..."
UPLOADTHING_APP_ID="..."

# AWS S3 / R2 / B2 compatible
S3_ACCESS_KEY_ID=""
S3_SECRET_ACCESS_KEY=""
S3_BUCKET_NAME=""
S3_REGION=""
S3_ENDPOINT=""            # for R2/B2/MinIO

# Slack (optional)
SLACK_BOT_TOKEN=""

# Email (optional, nodemailer)
SMTP_HOST=""
SMTP_PORT="587"
SMTP_USER=""
SMTP_PASS=""

Project Structure

app/
  (auth)/login/          — Sign-in page
  (dashboard)/           — Protected dashboard layout
    dashboard/           — Home stats + shot queue
    projects/            — Project list + detail pages
    settings/            — User profile
  review/[versionId]/    — Full-screen review player
  client/[token]/        — Unauthenticated client review link
  api/
    auth/[...nextauth]/  — NextAuth route handler
    uploadthing/         — UploadThing route handler
    notifications/       — Notification CRUD
    projects/            — Projects REST endpoint

components/
  player/                — ReviewPlayer, FrameTimeline, PlaybackControls
  annotations/           — AnnotationCanvas, AnnotationTools
  comments/              — CommentPanel (threads + replies)
  versions/              — VersionUpload, VersionList
  shots/                 — ShotCard, NewShotDialog
  projects/              — ProjectCard, NewProjectDialog
  dashboard/             — StatsCards, ShotQueue, RecentActivity
  layout/                — Sidebar, Header, NotificationBell, Providers
  ui/                    — shadcn/ui base components

actions/                 — Server Actions (projects, shots, versions, comments, annotations, approvals)
lib/                     — db, auth, utils, frame-utils, storage, slack, notifications, uploadthing
hooks/                   — use-review-player (Zustand), use-annotations, use-frame-comments
prisma/                  — schema.prisma, seed.ts

Review Player Keyboard Shortcuts

Key Action
Space / K Play / Pause
J Reverse playback
L Forward playback
← / → Step one frame
F Toggle fullscreen

Documentation

Doc Covers
RenderPipeline2.md Render pipeline specification — normative design, state machine, API contracts
DELIVERY_WORKFLOW.md Building and shipping delivery packages (how-to)
VFXReviewConnector.md After Effects panel — setup, Shot Builder, QC, diagnostics
RenderWorker/README.md Render worker service — packaging, install, validation, delivery builds
BlenderPanel/README.md Blender add-on — queueing a .blend on the farm
EXT_API_REFERENCE.md External API (/api/ext/*) used by the panel and workers
Shot task workflow.md Shot and task lifecycle in the web app

License

MIT

S
Description
No description provided
Readme 490 MiB
Languages
TypeScript 78.7%
C# 10.5%
JavaScript 7.5%
Python 2.2%
PowerShell 0.5%
Other 0.5%