Adds PipelineBranchKind (NONE/APPROVED/DECLINED) on stage definitions
so producers can tag the two routes downstream of a FORMAL-approval
stage. The engine then picks exactly one branch per decision:
• Approve → APPROVED-branch children auto-open, DECLINED-branch
siblings auto-SKIPPED (grayed out, unreachable)
• Request Changes → DECLINED-branch children auto-stamped APPROVED
(passive record of the decline), APPROVED-branch siblings auto-
SKIPPED, then the existing rework edge fires as before
Also fixes a quiet bug in pipeline-template-service.addStage where
approvalType was being dropped from new stages (whitelist didn't
include it).
UI: dropdown on the stage edit sheet + branch-kind badges on the
deliverable detail page. SKIPPED rendering already grays things out.
Smoke test extended: 65/65 passing including the user's split-on-
decision case, N-way split, regression assertion that untagged
pipelines still open all children, and an idempotency check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
diamond, long chain) + rework reset for any pipeline graph
Expands the smoke test from one canonical shape to every pattern a
producer might build:
- linear A → B → C → D (4 stages, full cascade)
- fan-out A → {B, C, D} (all branches open simultaneously)
- fan-in {A, B, C} → D (D stays BLOCKED until *all* parents finish)
- diamond A → {B, C} → D (both legs must finish before convergence)
- long chain (10 stages, full cascade walk)
- rework D → A (full reset of intermediate stages, head spared)
- rework D → A with B SKIPPED (SKIPPED stays SKIPPED across rework)
- rework D → C single-step (only D resets)
- user's pipeline: rework Declined → Inputfile resets
Internal-Approval + Approved + Declined; Inputfile untouched
- state-machine: all 7 new shortcut edges + 2 rejection guards
Plus a pure `planReworkReset` helper that mirrors applyRework's
where-clause math, so any pipeline + rework target combination can
be asserted offline without a DB.
Run: npx tsx scripts/test-workflow-logic.ts
57/57 passing at HEAD.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained tsx script that exercises the pure state-machine +
dependency-engine logic against the user's exact 5-stage pipeline
shape (Inputfile → Internal Approval ⇉ Approved/Declined → Delivery)
— no DB needed.
Catches the regression from earlier today where `Cannot transition
from IN_PROGRESS to APPROVED` blocked the NONE-stage Mark Complete
shortcut, plus verifies:
- Order-based dependency fallback (when no V2 edges are drawn)
- Multi-branch unblock (approving Internal Approval opens BOTH
Approved and Declined simultaneously)
- Downstream-of-downstream stays correctly BLOCKED until its
specific parent completes (Delivery waits for Approved, not for
Internal Approval)
Run: npx tsx scripts/test-workflow-logic.ts
Non-zero exit on any failure. 19/19 assertions pass against current
HEAD.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Wipes all other users and upserts a single admin with the given email +
password. Use after the rename when you want a clean slate.
docker compose -p loreal-prod-tracker exec app \
npx tsx scripts/create-admin.ts admin@loreal.com 'YourPasswordHere'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four phases shipped together. Each is a logical deploy unit on its own;
keeping the diff atomic so the rename runbook + migrations stay aligned.
Phase 1 — restore HP's formal review workflow
- Prisma: FeedbackItem, ReviewSession, ReviewSessionItem + enums
- New ApprovalType (NONE | SIMPLE | FORMAL) on PipelineStageDefinition
and PipelineStageTemplate. Stage row UI branches per type.
- feedback-service + review-session-service ported from HP (no ColorProbe)
- annotation-service auto-creates a FeedbackItem; revision-service
carries forward unresolved action items into the new revision.
- API: /api/reviews/*, /api/stages/[id]/feedback, /api/feedback/[id]
- Hooks: use-feedback, use-review-sessions
- UI: feedback-checklist, feedback-item-card, feedback-progress-bar,
create-session-dialog, session-builder, session-presenter,
session-summary, plus a new stage-review-panel
- Pages: /reviews list + detail, deliverable annotation review page
- Pipeline editor gets the approvalType select; sidebar gets Reviews
Phase 2 — full Dow Jones → L'Oréal rebrand + slug rename
- URL slug /dow-prod-tracker → /loreal-prod-tracker (next.config,
base path, redirects)
- docker-compose name + DB → loreal_prod_tracker; server path
/opt/loreal-prod-tracker; apache template renamed
- All visible strings → L'Oréal; sidebar bg #002B5C → black
- docs/RENAME_RUNBOOK.md describes the one-shot server migration
- Internal modules dow-excel-service/dow-import + OMG webhook domain
dowjones.com deliberately preserved (orthogonal to the rebrand)
Phase 3 — external /api/v1 for projects + deliverables
- API-key auth already in middleware; finished idempotency support
via new IdempotencyRecord model + src/lib/api/idempotency.ts
- Default-pipeline fallback in createProject when no template id given
- POST/GET /api/v1/projects + POST /api/v1/projects/[id]/deliverables
- docs/EXTERNAL_API.md with curl examples
Phase 4 — Box bidirectional integration
- JWT app-auth via jose (no extra deps). Config mounted as a docker
compose secret; deploy.sh stubs an empty {} so compose can start
before the operator drops the real JSON.
- Outbound: pushDeliverableToBox auto-fires on !APPROVED → APPROVED
in deliverable-status-service; "Send to client (Box)" manual button
on the approval stage row. Folder naming
{omgJobNumber}_{slug}_v{round}. 3-attempt exp backoff. BoxPushLog
audit.
- Inbound: /api/webhooks/box receives Box's signed events, matches by
OMG # + slug, creates a new Revision, routes to assignee or notifies
project owner. BoxInboundLog audit + two new NotificationType
values (BOX_UNMATCHED_FILE, NEW_FILE_AWAITING_REVIEWER).
- Naming-convention logic isolated in external-delivery-service so an
OMG-API transport can swap in later without touching matchers.
- Admin /settings/box page surfaces config status + recent activity.
Three Prisma migrations to apply on next deploy:
20260512000000_restore_review_workflow
20260512100000_idempotency_records
20260512200000_box_integration
URL rename is a one-shot — see docs/RENAME_RUNBOOK.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the mismatch: Deliverable.status is a denormalised
column that was only written at create-time (default NOT_STARTED)
and never refreshed when stages moved. The Projects board read it
live and showed "Not Started" while the pipeline ring + dominant-
stage view correctly showed "at Client Feedback (6/11 stages
complete)".
Fix in two parts:
1. New deliverable-status-service with:
- computeDeliverableStatus(stageStatuses[]) — pure function with
the summary rule:
all stages terminal → APPROVED
any IN_REVIEW → IN_REVIEW
any IN_PROGRESS/CHANGES_REQUESTED → IN_PROGRESS
else → NOT_STARTED
ON_HOLD is producer-managed and never overwritten.
- recomputeDeliverableStatus(deliverableId, txClient?) —
executes the rule + writes if different. Accepts an optional
Prisma tx client so callers can run inside their own
transaction.
2. Wired into every stage-write path:
- stage-service.updateStageStatus (single-stage transitions)
- stage-service bulk transaction (bulkUpdateStages) — dedups
touched deliverable IDs so we don't recompute twice.
- stage-transition-service forward + rework (board drag) —
inline inside the same $transaction so the board bucket is
correct on the next refetch.
3. Backfill script scripts/recompute-deliverable-statuses.ts —
one-off sweep to fix existing stale rows:
npx tsx scripts/recompute-deliverable-statuses.ts
Run once after deploy.
Two complementary safety nets for the business-critical DB:
1. Host-side nightly backup
- scripts/backup-db.sh drives pg_dump through docker compose exec,
gzips to /srv/backups/dow-prod-tracker/, auto-prunes >30 days.
Env-overridable (BACKUP_DIR / RETAIN_DAYS / COMPOSE_DIR / PGUSER
/ PGDATABASE) for anyone running a different layout.
- Runs from host cron at midnight; crontab snippet + restore
procedure + optional off-site (S3 / rsync) pattern documented
in DEPLOY.md.
2. On-demand admin XLSX export
- New GET /api/projects/export?format=xlsx — ADMIN-only; builds a
two-sheet workbook via new buildFullExportWorkbook():
- "Job Tracker": one row per project, header strings chosen to
round-trip through the Dow bulk-import endpoint so a dump can
be re-ingested in a worst case. Owner / Risk / OMG Number /
Team / etc., mirroring the importer's fuzzy HEADER_MATCHERS.
- "Deliverables": one row per deliverable with project OMG #,
status, priority, dates, CMF/SKU, current stage, assignees,
notes — enough to reconstitute pipeline state.
Respects visibility scoping (ADMIN sees everything).
- Dashboard shows an "Export Full XLSX" button in the header for
admins; streams the workbook with a date-stamped filename using
the standard blob-download pattern from ExportButton.
Both are additive — no schema, no migration, no deploy breakage.
- basePath /dow-prod-tracker, DB name dow_prod_tracker
- docker-compose: name: dow-prod-tracker (volume isolation on shared server), ports 3002/5492
- OMG webhook env vars (secret + insecure toggle)
- NEXT_PUBLIC_AUTH_ENTRA_ENABLED feature flag (MVP uses local auth)
- Dow logo at public/navbar-logo.png
- apache/hp-prod-tracker.conf → apache/dow-prod-tracker.conf
- Text rebrand across README, SETUP, CLAUDE.md, docs, UI labels
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implemented `stage-resolver.ts` to unify old and new pipeline stage definitions.
- Created `org-scope.ts` for organization access verification and scoping queries.
- Added role-based permissions management in `permissions.ts` and `rbac-service.ts`.
- Introduced invitation management in `invitation-service.ts` with validation schemas.
- Developed custom field and notification rule services with respective validators.
- Established pipeline template CRUD operations in `pipeline-template-service.ts`.
- Added Zustand store for managing pipeline builder state in `pipeline-builder-store.ts`.
- Added CalendarDayDetail component for displaying detailed event information for a selected day.
- Created CalendarEventPill component to represent individual events in a compact format.
- Introduced CalendarFilters component to filter events by project, stage type, and status.
- Developed CalendarGrid component to render the calendar layout and manage event interactions.
- Implemented CalendarView component to manage the overall calendar state and navigation.
- Added useCalendar hook to fetch calendar events based on specified filters.
- Created calendar-service to handle fetching events from the database with filtering capabilities.
- Updated data model to include necessary fields for calendar events and filters.
- Added system prompt and tools for AI assistant to manage calendar-related tasks.
- Implemented Smart Search Panel component for enhanced project and deliverable search functionality.
- Introduced useSemanticSearch and useOllamaHealth hooks for managing search queries and AI availability.
- Developed embedding-service to generate and store vector embeddings for projects and deliverables.
- Created semantic-search-service to handle vector search, structural query detection, and LLM summarization.
- Added support for hybrid search combining structural filters and semantic queries.
- Integrated UI components for displaying search results and user interactions.