MediaRouter / docs /content-studio-foundation.md
basyx's picture
Upload 794 files
1b2323a verified
|
Raw
History Blame Contribute Delete
13.4 kB

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<string, any>.

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 <video>, <audio>, and <img> elements with the existing authenticated BFF media path:

/api/backend/v1/media/{request_id}/{filename}

No raw storage credential or provider secret is exposed. Browser loadedmetadata supplies real media duration and dimensions when backend asset metadata is absent. Video/audio insertion remains unavailable until a positive real duration is known. Still-image insertion remains unavailable because no authoritative default image duration exists.

Seeking converts timeline time to source time. While playing, animation-frame synchronization converts the media element's source time back to the timeline playhead. Playback stops at the selected clip boundary or when no playable clip exists. Video transform and opacity plus track/clip volume state are applied to the real preview element.

Phase 1 previews one active source-backed clip. It does not composite multiple video/overlay tracks or mix multiple audio tracks; that requires the future render/preview engine contract.

Inspector

With no selection, the inspector shows real timeline properties. With a clip, it exposes only implemented values: timeline start/duration, volume, opacity, video transform, audio fades, caption text, and removal from the timeline. Removal never deletes the canonical asset. Source-in/out values are visible but read-only until richer source metadata/validation is available. Locked and read-only project states disable mutation controls.

Effect parameters are not editable. The inspector describes their unavailable capability instead of presenting a working-looking control.

Persistence contract

PERSISTENCE = UNAVAILABLE in the current backend.

Runtime OpenAPI discovery only activates persistence when one exact project-scoped path exists with GET and PUT or PATCH:

  • GET /v1/projects/{project_id}/editor-state
  • PUT /v1/projects/{project_id}/editor-state or PATCH /v1/projects/{project_id}/editor-state

The current project metadata endpoint is not used as an editor document store. Its 16 KB limit and lack of editor schema versioning, revisions, and optimistic concurrency do not provide a durable editing contract. The Studio therefore labels the working document Session only; Save is disabled with the exact capability reason. The session is intentionally not stored in localStorage as an authoritative substitute.

A future backend persistence phase must provide:

  • a typed, versioned editor document schema with timeline-sized limits;
  • revision/ETag or equivalent optimistic concurrency semantics;
  • workspace/project authorization for user and API-key authentication;
  • database ownership/RLS isolation;
  • validation of all asset references against project/workspace ownership;
  • safe audit events and structured errors;
  • GET plus idempotent PUT or revision-aware PATCH in runtime OpenAPI.

Optional local crash recovery may be added later, but it must remain explicitly non-authoritative and must never replace the backend document.

Render contract

RENDER_CONTRACT = UNAVAILABLE in the current backend.

Runtime discovery only activates Render when OpenAPI advertises:

  • POST /v1/projects/{project_id}/renders

The Render control is disabled with a truthful reason today. No request is sent, no progress is generated, and no success toast can occur while the contract is absent.

A future render phase must accept a validated editor revision/reference and render settings, then return a durable job identity using the existing job infrastructure. The backend must validate project and asset ownership, execute the FFmpeg/render pipeline, attach the output to the canonical project asset system, and define progress, failure, cancellation, retry, and audit semantics. The runtime OpenAPI contract must include typed request, job, and error schemas.

Capabilities and permissions

The Studio route requires the real project API and projects:read. Timeline mutations require the existing projects:update permission and an active project. Save additionally requires the editor-state write contract. Render requires the render contract plus projects:update and jobs:create.

The UI treats frontend permission/capability checks as presentation gates only. The backend remains authoritative for project ID, workspace, asset ownership, editor metadata, and render permission.

Commands and keyboard shortcuts

Commands are registered dynamically through the shared command registry and removed when the Studio unmounts. Unsupported commands are not registered.

  • Open Content Studio: project read capability.
  • Play/Pause (Space): an active playable clip.
  • Undo (Ctrl/Cmd+Z): editable project and non-empty undo history.
  • Redo (Ctrl/Cmd+Shift+Z): editable project and non-empty redo history.
  • Split Clip (S): editable unlocked selection and valid playhead split.
  • Delete Clip (Delete/Backspace): editable unlocked selection.
  • Zoom In, Zoom Out, Fit Timeline: open Studio.

Shortcuts ignore input, textarea, select, and content-editable focus targets. Browser/system shortcuts are otherwise left intact.

Responsive and accessible design

Desktop uses a dense project-media, preview, inspector, and full-width timeline workspace. Tablet prioritizes preview and timeline while moving the inspector into a lower panel. Mobile exposes dedicated Preview, Timeline, and Inspector modes with 44-pixel minimum touch targets instead of shrinking every desktop panel simultaneously.

Timeline clips are focusable options with selected semantics and spoken time-range labels. The ruler is a keyboard slider. Track controls, trim handles, asset actions, toolbar actions, and mobile tabs have accessible names. Focus uses the design-system focus ring, and the global reduced-motion rule applies. Drag actions have keyboard/touch alternatives.

Performance strategy

  • The route is isolated below the project feature and does not load on Home or Projects.
  • Components subscribe to narrow Zustand selectors rather than the complete store.
  • Clip views are memoized and callbacks/store actions are stable.
  • The timeline calculates the visible time range and culls off-screen clips with an overscan window.
  • Browser media uses metadata preloading instead of eagerly downloading every project asset.
  • TanStack Query owns deduplication and caching for project resources.
  • Timeline data remains time-based, allowing later lane virtualization without changing the document schema.

Track virtualization is intentionally deferred because Phase 1 has no evidence of track counts requiring it. Clip viewport culling is already in place.

Error and empty states

The Studio reuses project loading, not-found, permission, API error, and retry states. Project media has unavailable, loading, API error, empty, no-search- results, and normal states. Preview handles no selection, missing/deleted asset, unsupported format, loading, network/codec failure, and image failure without crashing the editor. Invalid authoritative documents are rejected rather than hydrated.

Future extension points

The typed clip union, track types, transitions, markers, schema version, render settings, API adapters, command registration, and project provider allow future phases to add captions, transitions, effects, AI editing, audio processing, templates, aspect-ratio presets, publishing, and collaboration without replacing this boundary. Those features remain outside Phase 1.