Documentation

Engine and state migration

Migrate to atomic edits, composed engine services, and isolated React subscriptions.

Browse documentation

This release changes public engine and hook APIs. Update consumers together; the removed methods and aliases have no compatibility exports.

Editing

Use commitEdit for moves, trims, slips, slides, splits, and deletion. Direct engine methods such as moveClip, trimClip, and deleteClip have been removed. The existing React useTimelineEditCommands hook continues to provide named commands.

const command = {
type: 'move' as const,
clipId: 'intro',
startTime: fromSeconds(4),
overwrite: true,
};
engine.previewEdit(command);
// On pointer release:
const result = engine.commitEdit(command);
// On cancellation instead:
engine.cancelEdit();

Previews leave committed state unchanged. getRenderState() includes preview geometry; getState() describes the document. Commits validate against current content, locks, and policy, even if a preview exists. Preview-generated split IDs stay stable without caching an old document. Set overwrite: true on move or trim commands to cut overlapping clips. commitEdits(commands) applies an ordered batch atomically with one undo step.

Engine services

The engine composes three services with explicit dependencies:

Area Access
Clip lookup, rectangles, hit testing engine.geometry.getClip(), getClipRect(), getClipAtPoint()
Active layers and source timing engine.media.getActiveLayers(), timelineTimeToSourceTime()
Keyframe queries and edits engine.keyframes.getKeyframeRects(), setClipKeyframe()

Move former direct calls to the corresponding service. Playback, document operations, history, and edit transactions remain on the engine. getActiveLayers() no longer includes firstContentTime; call engine.media.getFirstContentTime({ layers }) when choosing a playback start. Custom predicates are evaluated when queried, so application-owned predicate state is not hidden behind a revision cache.

Read state and history

getState(), getRenderState(), and the engine’s track/group/marker collections are readonly snapshots. Initial times and metadata are owned by the engine. Change content through commands instead of assigning to returned state. Unchanged collections and time values retain their references across viewport changes.

Breaking: Clip lookup, hit tests, geometry entries, active-media queries, and keyframe queries now return readonly document records too. Helpers that consume these records should accept TimelineReadonly<Clip> or TimelineReadonly<Track>. Use updateClipProperties, commitEdit, or the keyframe commands to change them. Geometry includes current edit previews; geometry.getClip() and media queries inspect committed content. Repeated queries reuse cached document snapshots.

Breaking: Clip, snap-target, and edit-guide metadata use TimelineMetadata. Values may be plain objects, arrays, strings, finite numbers, booleans, null, or undefined. Cycles, accessors, symbol keys, Maps, Sets, Dates, buffers, and class instances are rejected on input. Store domain objects in an application store keyed by clip.id or sourceId; keep only lightweight values or identifiers in metadata. Metadata is copied on input and deeply frozen in read snapshots.

The constructor accepts inPoint and outPoint alongside playheadTime and duration. Pass all four when restoring a saved project; the engine owns copies of each time. setTrackHeights validates the complete batch, including its optional scrollTop, before changing any track or publishing events.

History defaults to 100 snapshots and a conservative 16 MiB serialized-byte budget. Configure history: { maxEntries, maxBytes } when constructing an engine. The current document is always retained, even if it exceeds the budget; older undo entries are evicted first. Unchanged clip records are shared, and unchanged checkpoints are skipped.

React and controls

Breaking: React state and document-derived hook values now expose the same readonly snapshots as Core. Remove assignments to returned tracks, clips, groups, and state. Read-only application helpers should accept TimelineReadonly<Track>, TimelineReadonly<Clip>, or TimelineStateSnapshot; create an explicit application-owned copy only when editing a separate draft.

Core track commands (addTrack, removeTrack, mute/visibility/lock/target toggles, selectTrack, setTrackHeight, and setTrackGroup), clip selection commands, and updateClipProperties now return TimelineCommandResult. React selection commands use the same result. Replace boolean/void assumptions with a check of result.ok. Missing selection IDs fail with not-found and preserve the current selection. Invalid track input fails with invalid-input; duplicate track or clip IDs fail with duplicate-id.

const added = engine.addTrack(newTrack);
if (added.ok) {
const muted = engine.toggleMuteTrack(newTrack.id, true);
if (!muted.ok) console.error(muted.reason);
}

React track commands follow the same current-state validation, so commands may run consecutively in one event handler without waiting for a React render. There are no compatibility aliases for the old return contracts.

TimelineProvider now supplies the stable engine. Use useTimelineEngine() for imperative commands, narrow domain hooks for reactive controls, and useTimelineState() only when broad state is needed. A playback toolbar no longer rerenders for scrolling or zooming. Custom contexts must pass the engine directly, or use TimelineProvider.

Hook and geometry APIs return the engine’s actual string track kinds. Remove caller-supplied kind generics and narrow with a predicate that checks your application’s allowed kinds. Model types such as Track<MyKind> still describe data that your application has validated.

TimelineCommandResult<Value> is discriminated by ok: failures always have a reason, and successful non-void results have a required value. Narrow on ok before reading the payload.

RangeScrollbar now returns thumbOffsetPercent and thumbSizePercent; remove uses of thumbLeftPercent and thumbWidthPercent.

React hook composition

The canonical scrollbar adapters are useTimelineViewportRangeControl() and useTimelineVerticalRangeControl(). Each includes formatted accessibility values; the separate *Scrollbar hooks and their types have been removed. Use TimelineViewportRangeControlOptions, UseTimelineViewportRangeControlResult, UseTimelineVerticalRangeControlResult, and the corresponding TimelineViewportRangeControlRootProps / TimelineVerticalRangeControlRootProps.

Keep tool selection in application state (useState<TimelineEditMode>('select')) in place of the removed useTimelineEditMode hook. Read selected clips, tracks, and groups through useTimelineSelection(). useTimelineClips() now owns clip collection state, lookups, capabilities, and presentation updates; useTimelineClipGroups() owns group collection state and group commands. For imperative hit testing and source mapping use useTimelineEngine().geometry and .media; use geometry hooks when the UI needs reactive rectangles.

Track toggles no longer accept explicit boolean values. Use setMuted(value), setVisible(value), setLocked(value), and setTrackTarget(value) on a row hook, or pass trackId first on the collection hook. Calling a toggle changes the current engine value. These exports have no compatibility aliases.