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.