dow-prod-tracker/src/lib/validators/annotation.ts
Leivur Djurhuus 95dbaef318 Add timestamped video annotations with timeline markers (A7.3)
- Add timestampSeconds and frameThumbnailUrl fields to Annotation model
- New VideoAnnotationLayer component: auto-pause on draw tool activation,
  SVG annotation overlay on paused video, time-filtered visibility,
  All/Timed toggle, timecode display in toolbar
- New VideoTimelineMarkers: orange=unresolved, green=resolved, clustered
  markers on scrub bar with click-to-seek and hover scale
- Thread timestampSeconds through validator, service, and API layers
- Feedback item cards show timestamp badges for video annotations
- VideoPlayer gains renderOverlay, timelineMarkers, pause/seek in state
- Fix "Processing" overlay shown when MP4 is available (FFmpeg fallback)
- Add revision polling when video status is "processing"
- Configure proxyClientMaxBodySize: 500mb for large video uploads
- Fix pre-existing Prisma JSON type error in upload-service.ts
- Update ROADMAP with lawn reference learnings and A7.3 progress

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:00:23 -05:00

52 lines
1.5 KiB
TypeScript

import { z } from "zod/v4";
const annotationTypeEnum = z.enum([
"RECTANGLE",
"ELLIPSE",
"ARROW",
"FREEHAND",
"TEXT",
"PIN",
"SCREENSHOT",
]);
export type AnnotationTypeValue = z.infer<typeof annotationTypeEnum>;
/**
* Shape data varies by annotation type. We use a loose Json schema
* here and validate more specifically in the service layer if needed.
*/
const annotationDataSchema = z.object({
x: z.number().optional(),
y: z.number().optional(),
width: z.number().optional(),
height: z.number().optional(),
endX: z.number().optional(),
endY: z.number().optional(),
points: z.array(z.object({ x: z.number(), y: z.number() })).optional(),
text: z.string().optional(),
color: z.string().optional(),
strokeWidth: z.number().optional(),
imageUrl: z.string().optional(),
});
export const createAnnotationSchema = z.object({
type: annotationTypeEnum,
data: annotationDataSchema,
imageX: z.number(),
imageY: z.number(),
timestampSeconds: z.number().nullable().optional(), // null/undefined for image, number for video
commentContent: z.string().min(1, "Comment text is required"),
stageId: z.string().min(1, "Stage ID is required"),
});
export type CreateAnnotationInput = z.infer<typeof createAnnotationSchema>;
export const updateAnnotationSchema = z.object({
data: annotationDataSchema.optional(),
imageX: z.number().optional(),
imageY: z.number().optional(),
timestampSeconds: z.number().nullable().optional(),
});
export type UpdateAnnotationInput = z.infer<typeof updateAnnotationSchema>;