7.3 KiB
7.3 KiB
Frontend-Backend Contract for Cloud Editor Sync
Audience
- Backend engineers who are not familiar with this frontend codebase.
- Frontend engineers integrating cloud sync with
EditorCore.
Context: How the Frontend Editor Works
- Frontend editor state is managed by a singleton
EditorCore. - React components use
useEditor()and read/write through editor managers. - Local save is offline-first and remains the immediate source of truth.
- Cloud sync runs after local save and mirrors the same state to backend.
What Backend Must Treat as Source of Truth
statepayload sent by frontend is the canonical editor snapshot.- Backend stores it as opaque JSON (
state_json) with light validation only. - Backend must not rewrite timeline internals (track layout, element ordering, etc).
API Base Paths
- Editor state API:
/api/editor - File binary API:
/api/files(already exists)
TypeScript Contract (Shared Shapes)
export type ISODateString = string;
export type UUID = string;
export interface EditorProjectState {
schemaVersion: number;
currentSceneId: string;
metadata: {
id: string;
name: string;
duration: number;
updatedAt: ISODateString;
};
settings: Record<string, unknown>;
scenes: Array<Record<string, unknown>>;
}
export interface EditorProjectListItem {
id: UUID;
workspaceId: string;
ownerId: number;
name: string;
version: number;
updatedAt: ISODateString;
createdAt: ISODateString;
}
export interface ListEditorProjectsResponse {
items: EditorProjectListItem[];
total: number;
}
export interface GetEditorProjectResponse {
id: UUID;
workspaceId: string;
ownerId: number;
name: string;
version: number;
state: EditorProjectState;
updatedAt: ISODateString;
createdAt: ISODateString;
}
export interface PutEditorProjectRequest {
name: string;
baseVersion: number;
state: EditorProjectState;
assetFileIds: UUID[];
clientRequestId?: string;
}
export interface PutEditorProjectResponse {
id: UUID;
version: number;
updatedAt: ISODateString;
}
export interface VersionConflictResponse {
error: "VERSION_CONFLICT";
message: string;
serverVersion: number;
serverUpdatedAt: ISODateString;
}
Endpoint Details
GET /api/editor/projects
Purpose:
- Load project picker and recent projects list.
Query:
offsetdefault0limitdefault20, max100searchoptional
Returns:
200withListEditorProjectsResponse
GET /api/editor/projects/:id
Purpose:
- Open project on app load or after conflict.
Returns:
200withGetEditorProjectResponse404if not in workspace
PUT /api/editor/projects/:id
Purpose:
- Autosave cloud snapshot with optimistic concurrency.
Request:
PutEditorProjectRequest
Returns:
200withPutEditorProjectResponse409withVersionConflictResponsewhenbaseVersionmismatches current400invalid body401invalid auth404project/workspace not found413payload too large
Required Backend Semantics
Optimistic concurrency
- Compare
baseVersionwith current serverversion. - If equal: write state and increment version atomically.
- If different: reject with
409 VERSION_CONFLICT.
Idempotency (recommended)
- Respect
clientRequestIdfor repeated retries of the same autosave. - If same
(projectId, clientRequestId)is seen again, return prior success response.
Transaction behavior
PUTshould run in one transaction:
- Read current row with lock.
- Validate
baseVersion. - Update
name,state_json,version,updated_at. - Upsert
editor_project_assetsfromassetFileIds(if implemented).
File Service Integration (/api/files)
Frontend upload sequence
- User adds media file in editor.
- Frontend calls existing
POST /api/fileswith filename/contentType/size. - Frontend uploads binary to returned
uploadUrl. - Frontend stores returned
file.idin timeline element (fileId). - Next autosave sends
assetFileIdsincluding thatfileId.
Backend expectations
- Do not add editor-specific binary upload endpoint.
- Validate
assetFileIdsbelong to same workspace (strongly recommended). - Keep editor API and file API responsibilities separate.
Frontend Sync Lifecycle (Backend Should Support)
A) Open project
- Frontend requests
GET /api/editor/projects/:id. - Frontend hydrates
EditorCorefromstate. - Frontend stores returned
versionasremoteVersion.
B) Autosave success path
- Local save completes.
- Frontend builds snapshot from
EditorCore. - Frontend calls
PUTwithbaseVersion = remoteVersion. - Backend returns new
version. - Frontend updates
remoteVersion.
C) Autosave conflict path
- Frontend sends
PUT. - Backend returns
409 VERSION_CONFLICT. - Frontend fetches latest
GET /api/editor/projects/:id. - Frontend prompts user: keep local or use server.
- Frontend resolves by re-saving chosen state with latest
baseVersion.
Non-Functional Requirements
- P95
PUTlatency target: under 300ms for normal payloads. - Support bursty autosave traffic (same project every few seconds).
- Enforce per-user/workspace rate limiting without breaking normal autosave.
Validation Rules (Minimum)
name: required, string, max 255baseVersion: required, integer,>= 0state.schemaVersion: required integerstate.scenes: required arrayassetFileIds: required array (can be empty), each UUID format
Error Format (Consistent Across Endpoints)
{
"error": "SOME_ERROR_CODE",
"message": "Human-readable message"
}
Conflict is the only error that must include extra fields:
{
"error": "VERSION_CONFLICT",
"message": "Project has a newer version on server",
"serverVersion": 10,
"serverUpdatedAt": "2026-02-09T16:02:00.000Z"
}
Backend Test Cases (Must Implement)
- Auth required: all
/api/editor/*return401without valid JWT. - Workspace isolation: project from another workspace returns
404. - Create/update path:
PUTcreates new and increments version on subsequent saves. - Conflict path: stale
baseVersionreturns deterministic409. - Payload validation: malformed
statereturns400. - Payload size limit: oversized request returns
413. - File reference check: invalid cross-workspace
assetFileIdrejected. - Idempotent retry (if enabled): duplicate
clientRequestIdreturns same result.
Copy/Paste Prompt for Backend Agent
Use these two specs as the source of truth:
- project-management/04a-editor-backend-spec.md
- project-management/04b-editor-frontend-backend-contract.md
Implement `/api/editor` for cloud project sync with optimistic concurrency.
Important: frontend editor state is an opaque JSON snapshot from EditorCore; do not rewrite timeline internals.
Build:
1) Migrations:
- editor_projects
- editor_project_assets (recommended)
2) Endpoints:
- GET /api/editor/projects
- GET /api/editor/projects/:id
- PUT /api/editor/projects/:id
3) Validation and error shapes exactly as documented.
4) Workspace isolation + JWT auth on every endpoint.
5) Integration with existing /api/files via file references only.
6) Integration tests for auth, tenancy, concurrency conflict, validation, and size limits.
Return:
- Migration files
- Route handlers
- Validation schemas
- Tests
- Short implementation notes for frontend consumers