Reshapes the Revision model around how producers + clients actually
work: one upload per round (image OR video, no references), per-stage
version chain V0.1 → V0.2 → V1 → V1.1 → V1.2 → V2 …, holding pen for
inbound files received while a previous version is in client approval,
and per-pipeline filename matching for inbound ingest.
Schema:
- Revision: drop roundNumber + multi-key attachments. Add major/minor/
sentToClient/sentAt/asset (single image-or-video object) + unique
(deliverableStageId, major, minor).
- PipelineTemplate: add inboundFilenameRegex (per-pipeline matcher).
- NEW HoldingPenFile model + HoldingPenSource enum (MANUAL/API/BOX).
- Empty prod DB → clean ALTER TABLE migration, no backfill.
Send-to-client semantics:
- The latest internal revision IS the V{n} — promote in place
(major+=1, minor=0, sentToClient=true). Chain reads V0.1, V0.2, V1,
V1.1, V1.2, V2, ...
- sendToClient is the ONLY Box-push trigger. Auto-on-APPROVED removed
from deliverable-status-service. APPROVED is now an internal "done
iterating" state, separate from "shipped to client".
Three input channels, one matcher:
- NEW src/lib/services/inbound-ingest-service.ts — consolidates Box
webhook, /api/v1/upload, and manual upload-by-filename. One regex
resolver, one project/deliverable matcher, one routing + notification
fan-out.
- box-inbound-service is now a thin wrapper that fetches Box metadata
and delegates.
- external-delivery-service.parseInboundFileName takes an optional
regex override. Default: ^(\d+)_([a-z0-9-]+)(?:_v(\d+))?(?:\.[a-z0-9]+)?$
captures (1) OMG #, (2) slug, (3) optional version.
- buildDeliveryNaming now uses {omg}_{slug}_V{major} for Box folders.
Holding pen:
- When a deliverable is IN_REVIEW (a V{n} is awaiting client decision)
and a new file arrives via any channel, it lands in HoldingPenFile —
NOT in the active chain. Producer manually promotes (creates the
next minor on the chosen stage) or discards.
- Held files render in a new HoldingPenPanel on the deliverable detail
page when present. Source pill (MANUAL/API/BOX), parsed identifiers,
target-stage picker, Promote + Discard buttons.
Per-pipeline regex UI:
- NEW InboundMatchingRules section in the pipeline editor. Live regex
compile, sample-filename match test with echoed captures, save with
server-side regex-compile validation.
Upload simplification:
- storeRevisionAsset replaces the old processAndStoreImage +
processAndStoreVideo + multi-key attachment merge. MIME-detects kind,
preserves PNG alpha flatten + TIFF→PNG + thumbnail for images, and
keeps the async HLS transcode pipeline for videos.
- The single revision upload route drops the `type=` parameter.
- Three legacy components deleted: image-gallery, image-upload-zone,
video-upload-zone. NEW asset-upload-zone (unified drop zone).
New API:
- POST /api/stages/:stageId/revisions/:revisionId/send-to-client
- GET /api/deliverables/:id/holding-pen
- POST /api/deliverables/:id/holding-pen/:fileId (promote)
- DELETE /api/deliverables/:id/holding-pen/:fileId (discard)
- POST /api/v1/upload (multipart; same matcher as Box webhook)
UI label rollup:
- src/lib/format-revision-label.ts is the single source of truth.
Sent revisions render `V{major}`; internal `V{major}.{minor}`.
- Revision node/timeline, session presenter/builder/summary, revision
list, stage review panel — all read the helper.
- Comparison toolbar simplified to cross-revision picker (no more
reference-vs-current within a single revision).
The deliverable annotation review page is a temporary stub (links back
to the deliverable detail page where the in-row controls live). The
full annotation overlay + comparison surface will be rebuilt against
the single-asset model in a follow-up.
Run on next deploy:
docker compose -p loreal-prod-tracker exec app npx prisma migrate deploy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>