mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): make vertical lane moves persist correctly and harden the z/lane pipeline
Vertical clip moves committed in the store but never survived: two persist
bugs plus a runtime renumber all fought the stable-track-lanes model.
- timelineMoveAdapter deliberately stripped the track from lane-reorder
persists ('z-only reorder path' — the old z-driven lane model). Lane =
authored data-track-index now: lane-reorder and track-insert both persist
the track; plain timing moves omit it to stay SDK-fast-path eligible.
- Display lanes and file tracks are different coordinate spaces:
normalizeToZones packs sparse authored tracks (1,2,... or gaps, or DOM-index
fallbacks) onto contiguous display lanes, and lane edits persisted the LANE
number — silently re-targeting the wrong row in any non-0-contiguous file.
Elements now record their authoredTrack when remapped; a lane change
persists the target lane's authored track (store stays in lane space).
- The runtime split same-track clips of different kinds (video vs caption
div) onto separate renumbered tracks at discovery, so authored indices
never round-tripped ('drop onto an existing track' bounced back). Removed:
data-track-index is honored verbatim (render never reads it); kind-based
row presentation belongs in the display layer if ever wanted.
Adversarial review fixes on the same pipeline:
- runtime: parseInt(attr) || fallback dropped authored track 0 for GSAP and
overlay clips (parseAuthoredTrack helper honors 0)
- single-clip move fallback persisted only data-start — lane changes snapped
back on reload (now passes the track to the patch builder)
- lane-change z-sync candidate ignored a multi-selection's time shift, so
patches were computed against stale overlap sets
- track insert around a locked clip persisted a colliding renumber (the next
normalize merged lanes); the insert is now refused with a warning
- computeStackingPatches compared leaf z across CSS stacking contexts, where
ancestor z decides paint order; the sync now partitions by
stackingContextId and never patches across contexts
Timeline geometry (user-reported):
- fit zoom leaves 20% trailing headroom (FIT_ZOOM_HEADROOM in
timelineLayout.ts; single fit-pps source, so ruler/lanes/playhead/drag all
inherit it)
- playhead line center now sits exactly on GUTTER + t*pps at every zoom
(wrapper had shrink-wrapped to the 9px diamond, off-centering the line);
ruler ticks center on their timestamp
- ruler: frame-mode steps snap to whole frames (no duplicate labels), hour
steps added for far zoom-out, tick positions computed as exact multiples
(no float drift)
This commit is contained in:
@@ -77,6 +77,15 @@ function resizeCoalesceKey(changes: readonly TimelineGroupResizeChange[]): strin
|
||||
return `timeline-group-resize:${changes.map((change) => change.element.hfId ?? change.element.id).join(",")}`;
|
||||
}
|
||||
|
||||
function toSdkTimingChanges<T extends { element: TimelineElement }>(
|
||||
changes: readonly T[],
|
||||
timingUpdate: (change: T) => { start: number; duration?: number },
|
||||
): Array<{ hfId: string; timingUpdate: { start: number; duration?: number } } | null> {
|
||||
return changes.map((change) =>
|
||||
change.element.hfId ? { hfId: change.element.hfId, timingUpdate: timingUpdate(change) } : null,
|
||||
);
|
||||
}
|
||||
|
||||
function resizeHasPlaybackStartAdjustment(change: TimelineGroupResizeChange): boolean {
|
||||
return (
|
||||
change.playbackStart != null ||
|
||||
@@ -225,11 +234,7 @@ export function useTimelineGroupEditing({
|
||||
await options?.beforeTiming;
|
||||
const handledBySdk = await trySdkBatchPersist({
|
||||
changes,
|
||||
sdkChanges: changes.map((change) =>
|
||||
change.element.hfId
|
||||
? { hfId: change.element.hfId, timingUpdate: { start: change.start } }
|
||||
: null,
|
||||
),
|
||||
sdkChanges: toSdkTimingChanges(changes, (change) => ({ start: change.start })),
|
||||
eligible: changes.every((change) => change.track == null),
|
||||
needsExtension,
|
||||
label: "Move timeline clips",
|
||||
@@ -314,14 +319,10 @@ export function useTimelineGroupEditing({
|
||||
await options?.beforeTiming;
|
||||
const handledBySdk = await trySdkBatchPersist({
|
||||
changes,
|
||||
sdkChanges: changes.map((change) =>
|
||||
change.element.hfId
|
||||
? {
|
||||
hfId: change.element.hfId,
|
||||
timingUpdate: { start: change.start, duration: change.duration },
|
||||
}
|
||||
: null,
|
||||
),
|
||||
sdkChanges: toSdkTimingChanges(changes, (change) => ({
|
||||
start: change.start,
|
||||
duration: change.duration,
|
||||
})),
|
||||
eligible: changes.every((change) => !resizeHasPlaybackStartAdjustment(change)),
|
||||
needsExtension,
|
||||
label: "Resize timeline clips",
|
||||
|
||||
Reference in New Issue
Block a user