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.