Skip to content

feat(studio): timeline multi-select (marquee) + relative group time editing#2111

Merged
miguel-heygen merged 15 commits into
mainfrom
feat/timeline-multiselect
Jul 9, 2026
Merged

feat(studio): timeline multi-select (marquee) + relative group time editing#2111
miguel-heygen merged 15 commits into
mainfrom
feat/timeline-multiselect

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Adds rubber-band (marquee) multi-selection to the Studio timeline plus relative group time editing.

What

  • Marquee select: drag a rectangle over the tracks to select every clip it intersects, reflected in both the timeline and the preview, drawn in the theme accent color.
  • Group move: dragging one selected clip's body moves the whole selection by the same delta, clamped as a rigid body so relative offsets are preserved.
  • Group resize: dragging one selected clip's start/end handle shifts every selected clip's matching edge by the same clamped delta.
  • Batch persistence: N per-clip timing writes serialize and coalesce into one write per source file with a single undo entry, preserving the z-index-before-timing ordering.

Single source of truth

  • Selection has one owner: selectedElementIds in the player store, with selectedElementId as the anchor. Re-selecting a current member (a DOM-to-selection sync echo during a group drag) updates the anchor without collapsing the set.
  • Shared helpers: resolveTimelineIdForSelection owns DOM-to-timeline id mapping (one sourceFile / activeCompPath / index.html fallback); shared start-trim delta helpers back both single-clip and group resize.

Verification

Studio typecheck and 1509 unit tests green; lint and format clean. Marquee, group move, and group resize verified live in a headless Studio preview.

Stacked on #2068; retarget to main after that merges.

miguel-heygen commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

@miguel-heygen miguel-heygen changed the title feat(studio): activate selected element set state feat(studio): timeline multi-select (marquee) + relative group time editing Jul 9, 2026
@miguel-heygen miguel-heygen marked this pull request as ready for review July 9, 2026 19:07
Use selectedElementIds as the live selection set with selectedElementId as anchor.

Keep single-click selection as a one-element set and clear both fields together.
Render selected styling from selectedElementIds in the timeline.

Sync the set into preview group selection boxes without collapsing the anchor.
Persist group timing edits through one coalesced write per source file.

Keep batch timing queued behind any z-index commit for the same gesture.
Fan selected body drags through a group move session that snapshots all selected members.

The grabbed clip snap result is applied first, then one shared delta is clamped.

Live preview patches every moved member and release persists once through batch timing.
Apply handle deltas through a group resize session that snapshots selected members.

Clamp one shared delta against zero start and min duration before preview and commit.

Playback-start changes are calculated per member with the existing trim formula.
setSelectedElementId no longer resets the selection set when re-selecting an element
that is already a member: DOM-to-selection sync echoes fire on every pointer move during
a group drag and were collapsing the set to the grabbed clip.

Also drops the duplicate clearSelectedElementIds action in favor of clearSelection,
which the marquee now uses to clear on an empty drag.
The marquee and range overlays used a hardcoded blue; they now use the timeline
theme accent for the border with a translucent accent fill.
…mp math

Extract resolveTimelineIdForSelection so DOM-to-timeline id mapping lives in one place
with a single sourceFile / activeCompPath / index.html fallback, fixing a sub-composition
selection that previously diverged between callers.

Extract shared start-trim delta helpers used by both single-clip and group resize, and
remove the never-called refreshDomEditGroupSelectionsFromPreview.
…pping, marquee)

Group move/resize rejects the gesture when any selected member forbids the op (e.g. a
locked clip), so a group never edits a clip that individually cannot move, and a
persist failure now propagates so the optimistic preview rolls back.

Snapping excludes every moving member, not just the grabbed clip. The marquee hit-test
uses the real pixels-per-second (was floored at 1, wrong below 1x zoom), and a
sub-threshold marquee click scrubs the playhead like a plain lane click.
The DOM-selection to timeline sync routes through the canonical resolveTimelineIdForSelection
(source-file, ancestor, active-comp fallback) instead of a narrow domId/id match that
mismatched sub-composition clips.

The preview-sync equality check compares selection as sets both ways and includes the
anchor, so duplicate resolutions no longer mask an unsynced member.
The store-to-preview sync no longer applies a partial selection: if a resolvable member's
DOM node is not ready yet it bails and retries on the next effect run, so the write-back can
never shrink the store's selection by dropping an unresolved member.

Marquee row hit-testing reuses shouldShowTimelineLayerGroupHeader instead of re-deriving
the group-header placement rule, keeping one owner for that predicate.
…ection

setSelectedElementId now always collapses to one element (genuine user intent); a new
setSelectionAnchor moves the anchor within a multi-selection without collapsing it, used
only by the DOM-to-store sync echoes so a group survives a gesture.

applyDomSelection mirrors the whole DOM group into the store via setSelection instead of
writing only the anchor, so the store stays authoritative and a preview click collapses
while a preserved-group echo keeps every member.
@miguel-heygen miguel-heygen force-pushed the feat/timeline-multiselect branch from 9183a04 to 9cf575c Compare July 9, 2026 20:56
A timeline move/resize recorded the timing patch, then a server GSAP rewrite mutated the
same file afterward, leaving the recorded after stale so an undo hit a hash conflict. The
GSAP mutation now snapshots the touched files and records a follow-up edit under the same
coalesceKey, with a per-entry coalesceMs override large enough to survive the GSAP round
trip, so undo restores the original in one step. Applies to single-clip and group edits.
…cker

handleTimelineElementSelect tags each call with a monotonic token and ignores its result
if a newer selection started while it was resolving, so a rapid A-to-B clip click can no
longer let A's slower async lookup land after B and restore the wrong selection.
Base automatically changed from worktree-fix-timeline-zindex-reorder to main July 9, 2026 21:37
@miguel-heygen miguel-heygen merged commit dff27cb into main Jul 9, 2026
35 checks passed
@miguel-heygen miguel-heygen deleted the feat/timeline-multiselect branch July 9, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant