# Content Studio Foundation ## Scope and status Content Studio Phase 1 adds a real, project-scoped editing foundation at `/projects/[projectId]/studio`. It uses the authenticated Workspace Shell, existing project provider, canonical project assets, centralized BFF client, runtime OpenAPI discovery, TanStack Query, command registry, permissions, and MediaRouter design system. This phase does not add a backend editor document, rendering pipeline, captions/effects authoring workflow, AI editing, publishing, analytics, or collaboration. Nothing in the Studio fabricates assets, persistence, render jobs, progress, or successful exports. ## Feature architecture The feature boundary is `frontend/features/editor/`: - `types/` defines the persisted editor document and temporary UI state types. - `stores/` contains a per-project Zustand store factory and React provider. - `selectors/` derives narrow UI views without subscribing components to the complete store. - `utils/` contains deterministic timeline and source-time math. - `api/` discovers exact OpenAPI contracts and routes supported calls through the centralized API client. - `services/` separates editor-state and canonical project-asset adapters. - `commands/` and `hooks/` integrate commands and keyboard shortcuts. - `components/` contains the Studio workspace, media panel, preview, timeline, inspector, toolbar, and playback controls. - `tests/` verifies math, state, capability, command, shortcut, UI, preview, asset, and architecture behavior. The Studio route is nested below the existing authenticated project route. It does not replace `/projects` or `/projects/[projectId]`. ## Domain model `ProjectEditorState` is a versioned, typed document containing a project ID, timeline, and render settings. The timeline uses one extensible track model for `video`, `audio`, `caption`, and `overlay` tracks. Clips are a discriminated union of media, audio, caption, and effect clips; core editor state does not use `any[]` or `Record`. Every clip has an ID, owning track ID, timeline start, duration, label, visibility, opacity, and safe primitive metadata. Source-backed media and audio clips also carry the canonical asset ID, source start, and source duration. Media clips include transform and volume; audio clips include volume and fade values. Transitions and markers have typed extension points but no authoring UI in this phase. Incoming authoritative documents are validated at runtime before hydration. Validation covers schema version, project identity, render settings, unique tracks/clips, track ordering, clip discriminators, finite time/property values, source timing, transitions, and markers. ## Timeline coordinate system All timeline and source coordinates are integer milliseconds. Pixels are only a viewport projection: `pixels = milliseconds / 1000 * pixelsPerSecond` Moving a clip changes its timeline `startMs`. Left trimming changes timeline start, duration, source start, and source duration while enforcing source-in and minimum-duration bounds. Right trimming changes duration/source-out and can expand only to browser/backend-discovered source duration. Splitting creates two clips with contiguous timeline and source ranges. The source asset is never modified. The current minimum clip duration is 100 milliseconds. Negative time, non-finite values, duplicate IDs, mismatched track ownership, and invalid source ranges are rejected by timeline validation. ## State ownership TanStack Query remains authoritative for backend resources: - project metadata and permissions; - project assets; - project jobs and activity; - runtime OpenAPI discovery; - future editor-state and render mutations when contracts exist. A per-project Zustand store owns only client editing state: - the in-memory working editor document; - playhead, playback, rate, and duration; - selection and project-asset preview selection; - timeline zoom and scroll viewport; - active tool and mobile editing mode; - media metadata discovered by the browser; - temporary pointer interactions; - bounded undo/redo history and dirty state. The store is created inside `EditorStoreProvider`; it is not a second global project store. Project changes cause a new provider/store instance. Components use narrow selectors and stable store actions. ## Undo, redo, and editing invariants History records meaningful immutable timeline mutations: clip insertion, removal, move, trim, split and properties; track addition, removal, update and reorder. Pointer previews do not create history entries. A completed pointer interaction creates at most one entry. Playback frames, seeking, hover, media metadata discovery, selection, zoom, and temporary mobile UI modes are not recorded. History is bounded to 100 entries. Undo/redo recalculates timeline duration, stops playback, reconciles selection when entities disappear, and compares the result to the last hydrated/saved timeline for accurate dirty state. Locked tracks are protected inside the store as well as in the UI. ## Timeline interaction The timeline provides a time ruler, keyboard-seekable playhead, horizontally scrollable multi-track canvas, track headers, clip selection, clip duration visualization, viewport culling, zoom, fit-to-width, and accessible empty state. Clip movement and edge trimming use controlled pointer capture, with keyboard left/right movement in 100 millisecond steps. Track reordering and canonical asset insertion support controlled drag/drop, while up/down track controls and the Add to timeline button provide keyboard and touch alternatives. Dropping an asset validates compatible track type and lock state before updating the local editor session. Drag payloads are never authorization evidence. Any future persistence or render backend must independently validate workspace, project, asset ownership, document contents, and permissions. ## Preview and playback The preview uses real `