@hyperframes/studio 0.8.132 → 0.8.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/assets/{hyperframes-player-Cp8e_Zst.js → hyperframes-player-Bro1gj0Z.js} +1 -1
  2. package/dist/assets/{index-CASEBNy2.js → index-BwNDTmAD.js} +1 -1
  3. package/dist/assets/{index-DMTt9nTf.js → index-DnVGIbEC.js} +1 -1
  4. package/dist/assets/{index-oE1pOdAy.js → index-wnXyQjW-.js} +143 -143
  5. package/dist/index.html +1 -1
  6. package/dist/index.js +717 -702
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -8
  9. package/src/components/editor/manualEditsDom.ts +5 -1
  10. package/src/components/editor/plainTranslate.test.ts +21 -2
  11. package/src/components/editor/plainTranslate.ts +7 -0
  12. package/src/hooks/draggedGsapPosition.test.ts +34 -0
  13. package/src/hooks/draggedGsapPosition.ts +40 -15
  14. package/src/hooks/gsapDragCommit.ts +17 -10
  15. package/src/hooks/gsapDragPositionCommit.ts +8 -9
  16. package/src/hooks/gsapResizeIntercept.test.ts +72 -1
  17. package/src/hooks/gsapResizeIntercept.ts +84 -17
  18. package/src/hooks/gsapRuntimeBridge.ts +4 -1
  19. package/src/hooks/gsapTrailingKeyframes.realGsap.test.ts +44 -0
  20. package/src/hooks/useEditHistoryActions.test.tsx +20 -0
  21. package/src/hooks/useEditHistoryActions.ts +3 -0
  22. package/src/hooks/useEnableKeyframes.ts +32 -37
  23. package/src/hooks/useGsapAwareEditing.test.tsx +1 -0
  24. package/src/hooks/useGsapAwareEditing.ts +10 -2
  25. package/src/player/components/timelineClipDragCommit.test.ts +22 -0
  26. package/src/player/components/timelineClipDragCommit.ts +7 -10
  27. package/src/player/components/timelineStackingSync.test.ts +101 -142
  28. package/src/player/components/timelineStackingSync.ts +51 -235
  29. package/src/player/components/useTimelineStackingSync.ts +1 -1
  30. package/src/timelineStackingSyncExport.test.tsx +3 -2
  31. package/src/utils/elementGsap.ts +6 -5
  32. package/src/utils/gsapUndoRestore.test.ts +22 -0
  33. package/src/utils/gsapUndoRestore.ts +4 -2
  34. package/src/utils/studioPendingEdits.ts +12 -2
package/dist/index.js CHANGED
@@ -9099,100 +9099,37 @@ function paintsAbove(a, b) {
9099
9099
  if (a.domIndex != null && b.domIndex != null) return a.domIndex > b.domIndex;
9100
9100
  return false;
9101
9101
  }
9102
- function boundaryZ(neighbours2, reduce) {
9103
- return neighbours2.length > 0 ? reduce(neighbours2.map((o) => o.zIndex)) : null;
9104
- }
9105
- function resolveEditedZ(edited, overlapping, overlappersOf, patchZ) {
9106
- const visualOverlap = overlapping.filter((o) => !o.isAudio);
9107
- if (visualOverlap.length === 0) return false;
9108
- const below = visualOverlap.filter((o) => laneIsAbove(edited, o));
9109
- const above = visualOverlap.filter((o) => laneIsAbove(o, edited));
9110
- const correct = below.every((o) => paintsAbove(edited, o)) && above.every((o) => paintsAbove(o, edited));
9111
- if (correct) return false;
9112
- const maxBelow = boundaryZ(below, (zs) => Math.max(...zs));
9113
- const single = trySingleZ(edited, below, above);
9114
- if (single != null) {
9115
- if (single !== edited.zIndex) patchZ(edited, single);
9116
- return single !== edited.zIndex;
9117
- }
9118
- const target = maxBelow != null ? maxBelow + 1 : 0;
9119
- const clamped = Math.max(0, target);
9120
- if (clamped !== edited.zIndex) patchZ(edited, clamped);
9121
- liftAbove(edited, overlappersOf, patchZ);
9122
- return true;
9123
- }
9124
- function zBetweenNeighbours(maxBelow, minAbove, correctAt) {
9125
- if (minAbove - maxBelow >= 2) {
9126
- const mid = Math.floor((maxBelow + minAbove) / 2);
9127
- return mid > maxBelow && mid < minAbove ? mid : null;
9128
- }
9129
- return correctAt(minAbove) ? minAbove : null;
9130
- }
9131
- function zBelowOnly(minAbove, correctAt) {
9132
- const candidate = minAbove - 1;
9133
- if (candidate >= 0) return candidate;
9134
- return correctAt(minAbove) ? minAbove : null;
9135
- }
9136
- function trySingleZ(edited, below, above) {
9137
- const maxBelow = boundaryZ(below, (zs) => Math.max(...zs));
9138
- const minAbove = boundaryZ(above, (zs) => Math.min(...zs));
9139
- const correctAt = (z) => {
9140
- const probe2 = { ...edited, zIndex: z };
9141
- return below.every((b) => paintsAbove(probe2, b)) && above.every((a) => paintsAbove(a, probe2));
9142
- };
9143
- if (maxBelow != null && minAbove != null)
9144
- return zBetweenNeighbours(maxBelow, minAbove, correctAt);
9145
- if (maxBelow != null) return maxBelow + 1;
9146
- if (minAbove != null) return zBelowOnly(minAbove, correctAt);
9147
- return null;
9148
- }
9149
- function liftAbove(edited, overlappersOf, patchZ) {
9150
- const queue2 = [edited];
9151
- const raiseAbove = (clip, floor) => {
9152
- if (paintsAbove(clip, floor)) return;
9153
- patchZ(clip, floor.zIndex + 1);
9154
- queue2.push(clip);
9155
- };
9156
- while (queue2.length > 0) {
9157
- const floor = queue2.shift();
9158
- for (const other of overlappersOf(floor)) {
9159
- if (laneIsAbove(other, floor) && !paintsAbove(other, floor)) raiseAbove(other, floor);
9160
- }
9161
- }
9102
+ function raisedZ(clip, overlapping) {
9103
+ const below = overlapping.filter((o) => laneIsAbove(clip, o));
9104
+ if (below.every((o) => paintsAbove(clip, o))) return null;
9105
+ return Math.max(...below.map((o) => o.zIndex)) + 1;
9106
+ }
9107
+ function loweredZ(clip, overlapping) {
9108
+ const above = overlapping.filter((o) => laneIsAbove(o, clip));
9109
+ if (above.every((o) => paintsAbove(o, clip))) return null;
9110
+ const keepOver = overlapping.filter((o) => laneIsAbove(clip, o) && paintsAbove(clip, o));
9111
+ const floor = Math.max(
9112
+ 0,
9113
+ ...keepOver.map(
9114
+ (o) => paintsAbove({ ...clip, zIndex: o.zIndex }, o) ? o.zIndex : o.zIndex + 1
9115
+ )
9116
+ );
9117
+ const z = Math.max(floor, Math.min(...above.map((o) => o.zIndex)) - 1);
9118
+ return z < clip.zIndex ? z : null;
9162
9119
  }
9163
- function computeStackingPatches(elements, editedKeys) {
9120
+ function computeStackingPatches(elements, editedKeys, direction) {
9164
9121
  const editedSet = new Set(editedKeys);
9165
- if (editedSet.size === 0) return [];
9166
- const allResolved = elements.filter((e) => Number.isFinite(e.zIndex));
9167
- const editedScopes = new Set(allResolved.filter((e) => editedSet.has(e.key)).map(paintScopeKey));
9168
- const resolved2 = allResolved.filter((e) => editedScopes.has(paintScopeKey(e)));
9169
- const byKey = new Map(resolved2.map((e) => [e.key, { ...e }]));
9170
- const edited = resolved2.filter((e) => editedSet.has(e.key) && !e.isAudio).map((e) => byKey.get(e.key)).sort((a, b) => b.track - a.track);
9171
- const changed = /* @__PURE__ */ new Map();
9172
- const patchZ = (clip, z) => {
9173
- clip.zIndex = z;
9174
- changed.set(clip.key, z);
9175
- };
9176
- const all = [...byKey.values()];
9177
- const overlappersOf = (clip) => all.filter(
9178
- (o) => o.key !== clip.key && !o.isAudio && samePaintScope(clip, o) && overlapsInTime(clip, o)
9179
- );
9180
- for (const clip of edited) {
9181
- resolveEditedZ(clip, overlappersOf(clip), overlappersOf, patchZ);
9182
- }
9183
- const emitted = /* @__PURE__ */ new Set();
9122
+ const live = elements.filter((e) => Number.isFinite(e.zIndex) && !e.isAudio).map((e) => ({ ...e }));
9123
+ const edited = live.filter((e) => editedSet.has(e.key)).sort((a, b) => direction === "up" ? b.track - a.track : a.track - b.track);
9184
9124
  const patches = [];
9185
9125
  for (const clip of edited) {
9186
- if (changed.has(clip.key) && !emitted.has(clip.key)) {
9187
- patches.push({ key: clip.key, zIndex: changed.get(clip.key) });
9188
- emitted.add(clip.key);
9189
- }
9190
- }
9191
- for (const [key2, zIndex] of changed) {
9192
- if (!emitted.has(key2)) {
9193
- patches.push({ key: key2, zIndex });
9194
- emitted.add(key2);
9195
- }
9126
+ const overlapping = live.filter(
9127
+ (o) => o.key !== clip.key && overlapsInTime(clip, o) && samePaintScope(clip, o)
9128
+ );
9129
+ const zIndex = direction === "up" ? raisedZ(clip, overlapping) : loweredZ(clip, overlapping);
9130
+ if (zIndex === null) continue;
9131
+ clip.zIndex = zIndex;
9132
+ patches.push({ key: clip.key, zIndex });
9196
9133
  }
9197
9134
  return patches;
9198
9135
  }
@@ -9528,15 +9465,24 @@ function focusedField() {
9528
9465
  function hasStudioPendingEdits() {
9529
9466
  return pendingEdits.size > 0 || focusedField() !== null;
9530
9467
  }
9468
+ var shownRestores = /* @__PURE__ */ new Set();
9469
+ function beginStudioShownRestore() {
9470
+ let end = () => {
9471
+ };
9472
+ const landed = new Promise((resolve) => end = resolve);
9473
+ shownRestores.add(landed);
9474
+ void landed.then(() => shownRestores.delete(landed));
9475
+ return end;
9476
+ }
9531
9477
  function isStudioEditSaving() {
9532
- return pendingEdits.size > 0;
9478
+ return pendingEdits.size > 0 || shownRestores.size > 0;
9533
9479
  }
9534
9480
  function afterStudioPendingEdits(run) {
9535
9481
  let waiting = true;
9536
9482
  const check = () => {
9537
9483
  if (!waiting) return;
9538
9484
  if (isStudioEditSaving()) {
9539
- void Promise.allSettled([...pendingEdits.keys()]).then(check);
9485
+ void Promise.allSettled([...pendingEdits.keys(), ...shownRestores]).then(check);
9540
9486
  return;
9541
9487
  }
9542
9488
  waiting = false;
@@ -9841,7 +9787,6 @@ function commitDraggedClipMove(drag, deps) {
9841
9787
  if (multi?.keys.has(keyOf6(e))) return { ...e, start: multi.movedStart(e) };
9842
9788
  return e;
9843
9789
  });
9844
- const multiKeys = multi ? multi.keys : null;
9845
9790
  if (!isVertical || !deps.readZIndex || !deps.onStackingPatches) {
9846
9791
  void refreshAfterDurableLaneMove(
9847
9792
  persistMoveEdits(edits, deps, coalesceKey, "lane-reorder"),
@@ -9857,7 +9802,6 @@ function commitDraggedClipMove(drag, deps) {
9857
9802
  dragKey,
9858
9803
  drag.element.track,
9859
9804
  drag.previewTrack,
9860
- multiKeys,
9861
9805
  deps,
9862
9806
  coalesceKey
9863
9807
  )
@@ -9947,7 +9891,6 @@ function commitTrackInsert(drag, deps, multi) {
9947
9891
  dragKey,
9948
9892
  drag.element.track,
9949
9893
  drag.insertRow,
9950
- multi ? multi.keys : null,
9951
9894
  deps,
9952
9895
  coalesceKey
9953
9896
  )
@@ -9975,7 +9918,7 @@ function commitZMirrorLaneMove(element, move, deps, coalesceKey) {
9975
9918
  deps
9976
9919
  );
9977
9920
  }
9978
- function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, multiKeys, deps, coalesceKey) {
9921
+ function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, deps, coalesceKey) {
9979
9922
  const { readZIndex, onStackingPatches } = deps;
9980
9923
  if (!readZIndex || !onStackingPatches) return Promise.resolve();
9981
9924
  if (aimedLane === currentLane) return Promise.resolve();
@@ -9990,11 +9933,11 @@ function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, multiKe
9990
9933
  domIndex,
9991
9934
  stackingContextId: el.stackingContextId ?? null
9992
9935
  }));
9993
- const editedKeys = [dragKey];
9994
- if (multiKeys) {
9995
- for (const k of multiKeys) if (k !== dragKey) editedKeys.push(k);
9996
- }
9997
- const patches = computeStackingPatches(stackingEls, editedKeys);
9936
+ const patches = computeStackingPatches(
9937
+ stackingEls,
9938
+ [dragKey],
9939
+ aimedLane < currentLane ? "up" : "down"
9940
+ );
9998
9941
  if (patches.length === 0) return Promise.resolve();
9999
9942
  return Promise.resolve(onStackingPatches(patches, coalesceKey)).then(() => void 0);
10000
9943
  }
@@ -17188,6 +17131,12 @@ function setElementGsapScale(element, x, y) {
17188
17131
  gsap3.set(element, { scaleX: x, scaleY: y });
17189
17132
  return true;
17190
17133
  }
17134
+ function setElementGsapSize(element, width, height) {
17135
+ const gsap3 = gsapOf(element);
17136
+ if (!gsap3?.set) return false;
17137
+ gsap3.set(element, { width, height });
17138
+ return true;
17139
+ }
17191
17140
  function elementTargets(tween) {
17192
17141
  return (tween.targets?.() ?? []).filter(
17193
17142
  (t2) => Boolean(t2?.style && t2.nodeType)
@@ -30408,9 +30357,12 @@ var gestureSaves = /* @__PURE__ */ new WeakMap();
30408
30357
  function studioManualEditSavesIn(doc) {
30409
30358
  return gestureSaves.get(doc) ?? 0;
30410
30359
  }
30360
+ function countStudioPreviewChange(doc) {
30361
+ gestureSaves.set(doc, studioManualEditSavesIn(doc) + 1);
30362
+ }
30411
30363
  function countStudioManualEditSave(element, save) {
30412
30364
  const doc = element.ownerDocument;
30413
- const count = () => void gestureSaves.set(doc, studioManualEditSavesIn(doc) + 1);
30365
+ const count = () => countStudioPreviewChange(doc);
30414
30366
  count();
30415
30367
  const result = save();
30416
30368
  void Promise.resolve(result).then(count, count);
@@ -37478,22 +37430,31 @@ function readCssRotation(element, withRotate = true) {
37478
37430
  return angle(transform);
37479
37431
  }
37480
37432
  }
37481
- function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
37433
+ function readDragStamp(element) {
37434
+ const read2 = (name) => Number.parseFloat(element.getAttribute(name) ?? "");
37435
+ return {
37436
+ origX: read2("data-hf-drag-initial-offset-x") || 0,
37437
+ origY: read2("data-hf-drag-initial-offset-y") || 0,
37438
+ baseX: read2("data-hf-drag-gsap-base-x"),
37439
+ baseY: read2("data-hf-drag-gsap-base-y")
37440
+ };
37441
+ }
37442
+ var freezeDragStamp = (element) => ({
37443
+ ...readDragStamp(element),
37444
+ frozen: true
37445
+ });
37446
+ function computeDraggedGsapPosition(element, studioOffset, fallbackBase, stamp = readDragStamp(element)) {
37482
37447
  const rotStyle = element.style.getPropertyValue("--hf-studio-rotation");
37483
37448
  const rotDeg = Number.parseFloat(rotStyle) || 0;
37484
37449
  const rad = -rotDeg * Math.PI / 180;
37485
37450
  const cos = Math.cos(rad);
37486
37451
  const sin = Math.sin(rad);
37487
- const origX = Number.parseFloat(element.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
37488
- const origY = Number.parseFloat(element.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
37489
- const deltaX = studioOffset.x - origX;
37490
- const deltaY = studioOffset.y - origY;
37452
+ const deltaX = studioOffset.x - stamp.origX;
37453
+ const deltaY = studioOffset.y - stamp.origY;
37491
37454
  const adjX = deltaX * cos - deltaY * sin;
37492
37455
  const adjY = deltaX * sin + deltaY * cos;
37493
- const parsedBaseX = Number.parseFloat(element.getAttribute("data-hf-drag-gsap-base-x") ?? "");
37494
- const parsedBaseY = Number.parseFloat(element.getAttribute("data-hf-drag-gsap-base-y") ?? "");
37495
- const baseGsapX = Number.isFinite(parsedBaseX) ? parsedBaseX : fallbackBase.x;
37496
- const baseGsapY = Number.isFinite(parsedBaseY) ? parsedBaseY : fallbackBase.y;
37456
+ const baseGsapX = Number.isFinite(stamp.baseX) ? stamp.baseX : fallbackBase.x;
37457
+ const baseGsapY = Number.isFinite(stamp.baseY) ? stamp.baseY : fallbackBase.y;
37497
37458
  return {
37498
37459
  newX: roundTo3(baseGsapX + adjX),
37499
37460
  newY: roundTo3(baseGsapY + adjY),
@@ -37501,10 +37462,10 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
37501
37462
  baseGsapY
37502
37463
  };
37503
37464
  }
37504
- function restoreDragOffset(element) {
37505
- const origin2 = (axis) => Number.parseFloat(element.getAttribute(`data-hf-drag-initial-offset-${axis}`) ?? "") || 0;
37506
- element.style.setProperty("--hf-studio-offset-x", `${origin2("x")}px`);
37507
- element.style.setProperty("--hf-studio-offset-y", `${origin2("y")}px`);
37465
+ function restoreDragOffset(element, stamp = readDragStamp(element)) {
37466
+ if (stamp.frozen) return;
37467
+ element.style.setProperty("--hf-studio-offset-x", `${stamp.origX}px`);
37468
+ element.style.setProperty("--hf-studio-offset-y", `${stamp.origY}px`);
37508
37469
  element.removeAttribute("data-hf-drag-initial-offset-x");
37509
37470
  element.removeAttribute("data-hf-drag-initial-offset-y");
37510
37471
  }
@@ -37578,6 +37539,12 @@ function readTranslatePx(el) {
37578
37539
  y: evaluateLength(y, side("height", "top", "bottom"))
37579
37540
  };
37580
37541
  }
37542
+ function readTranslatePxLeavingPercent(el) {
37543
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).translate || "none";
37544
+ const [x = "0px", y = "0px"] = value === "none" ? [] : splitTopLevelWhitespace(value);
37545
+ const px = (part) => roundTo3(evaluateLength(part, 0)) || 0;
37546
+ return { x: px(x), y: px(y) };
37547
+ }
37581
37548
  var UNREADABLE_TRANSLATE = "Studio can't read this layer's translate. Move it in the Code tab.";
37582
37549
  function formatTranslatePx(p) {
37583
37550
  return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
@@ -45806,7 +45773,12 @@ function refuseStepListRewrite(anim) {
45806
45773
  if (step) throw new GsapEditBlockedError("keyframes-uneditable", step);
45807
45774
  }
45808
45775
  async function commitStaticGsapPosition(selection, studioOffset, gsapPos, selector, existingSet, callbacks) {
45809
- const { newX, newY } = computeDraggedGsapPosition(selection.element, studioOffset, gsapPos);
45776
+ const { newX, newY } = computeDraggedGsapPosition(
45777
+ selection.element,
45778
+ studioOffset,
45779
+ gsapPos,
45780
+ callbacks.stamp
45781
+ );
45810
45782
  if (existingSet) {
45811
45783
  if (existingSet.keyframes) {
45812
45784
  await replaceKeyframedPositionHold(
@@ -45925,21 +45897,16 @@ async function commitStaticGsapSize(selection, size, selector, existingSet, call
45925
45897
  }
45926
45898
  async function commitWholePathOffset(selection, anim, studioOffset, gsapPos, iframe, selector, callbacks) {
45927
45899
  const el = selection.element;
45900
+ const stamp = callbacks.stamp ?? readDragStamp(el);
45928
45901
  const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
45929
45902
  el,
45930
45903
  studioOffset,
45931
- gsapPos
45904
+ gsapPos,
45905
+ stamp
45932
45906
  );
45933
45907
  const deltaX = newX - baseGsapX;
45934
45908
  const deltaY = newY - baseGsapY;
45935
- const origX = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
45936
- const origY = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
45937
- const restoreOffset = () => {
45938
- el.style.setProperty("--hf-studio-offset-x", `${origX}px`);
45939
- el.style.setProperty("--hf-studio-offset-y", `${origY}px`);
45940
- el.removeAttribute("data-hf-drag-initial-offset-x");
45941
- el.removeAttribute("data-hf-drag-initial-offset-y");
45942
- };
45909
+ const restoreOffset = () => restoreDragOffset(el, stamp);
45943
45910
  let effectiveAnim = anim;
45944
45911
  if (anim.keyframes) {
45945
45912
  refuseStepListRewrite(anim);
@@ -47877,19 +47844,14 @@ function gsapPositionFromDragOutcome(selection, anim, studioOffset, gsapPos, ifr
47877
47844
  }
47878
47845
  async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos, iframe, callbacks) {
47879
47846
  const el = selection.element;
47847
+ const stamp = callbacks.stamp ?? readDragStamp(el);
47880
47848
  const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
47881
47849
  el,
47882
47850
  studioOffset,
47883
- gsapPos
47851
+ gsapPos,
47852
+ stamp
47884
47853
  );
47885
- const origX = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
47886
- const origY = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
47887
- const restoreOffset = () => {
47888
- el.style.setProperty("--hf-studio-offset-x", `${origX}px`);
47889
- el.style.setProperty("--hf-studio-offset-y", `${origY}px`);
47890
- el.removeAttribute("data-hf-drag-initial-offset-x");
47891
- el.removeAttribute("data-hf-drag-initial-offset-y");
47892
- };
47854
+ const restoreOffset = () => restoreDragOffset(el, stamp);
47893
47855
  if (anim.arcPath?.enabled) {
47894
47856
  const { activeKeyframePct, currentTime, setActiveKeyframePct } = usePlayerStore.getState();
47895
47857
  const tweenStart = resolveTweenStart(anim);
@@ -48152,7 +48114,8 @@ async function tryGsapDragIntercept(selection, offset, allAnimations, iframe, ge
48152
48114
  const existingSet = posAnim && isInstantHold(posAnim) && posAnim.targetSelector === selector ? posAnim : findExistingPositionWrite(resolvedAnimations, selector, selection.element);
48153
48115
  await commitStaticGsapPosition(selection, offset, gsapPos, selector, existingSet, {
48154
48116
  commitMutation,
48155
- fetchAnimations: fetchFallbackAnimations
48117
+ fetchAnimations: fetchFallbackAnimations,
48118
+ stamp: options?.stamp
48156
48119
  });
48157
48120
  return { status: "persisted" };
48158
48121
  }
@@ -48170,7 +48133,7 @@ async function tryGsapDragIntercept(selection, offset, allAnimations, iframe, ge
48170
48133
  posAnim = freshMatch;
48171
48134
  }
48172
48135
  }
48173
- const cbs = { commitMutation, fetchAnimations: fetchFallbackAnimations };
48136
+ const cbs = { commitMutation, fetchAnimations: fetchFallbackAnimations, stamp: options?.stamp };
48174
48137
  if (route === "whole-path") {
48175
48138
  await commitWholePathOffset(selection, posAnim, offset, gsapPos, iframe, selector, cbs);
48176
48139
  } else {
@@ -74853,7 +74816,8 @@ function showRestoreInPlace(iframe, activeCompPath, files, currentTime) {
74853
74816
  const doc = iframe?.contentDocument;
74854
74817
  const win = iframe?.contentWindow;
74855
74818
  const plan = doc && win ? planRestoreTargets(doc, activeCompPath ?? "index.html", files) : null;
74856
- if (!iframe || !win || !plan || plan.scripted) return null;
74819
+ if (!iframe || !doc || !win || !plan || plan.scripted) return null;
74820
+ countStudioPreviewChange(doc);
74857
74821
  const before = plan.targets.map(
74858
74822
  ({ live, restored }) => [live, live.cloneNode(false), restored]
74859
74823
  );
@@ -74887,6 +74851,7 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
74887
74851
  reloadPreview();
74888
74852
  return "full";
74889
74853
  }
74854
+ countStudioPreviewChange(doc);
74890
74855
  for (const target of plan.targets)
74891
74856
  syncElementAttributes(target.live, target.restored, target.previous);
74892
74857
  const active = files[activeDocPath];
@@ -78246,7 +78211,7 @@ function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
78246
78211
  }
78247
78212
 
78248
78213
  // src/hooks/useDomEditSession.ts
78249
- import { useCallback as useCallback148 } from "react";
78214
+ import { useCallback as useCallback149 } from "react";
78250
78215
 
78251
78216
  // src/hooks/useAskAgentModal.ts
78252
78217
  import { useState as useState117, useCallback as useCallback125, useRef as useRef140, useEffect as useEffect115 } from "react";
@@ -83230,7 +83195,7 @@ function useGsapAwareGroupMove({
83230
83195
  }
83231
83196
 
83232
83197
  // src/hooks/useGsapAwareEditing.ts
83233
- import { useCallback as useCallback146 } from "react";
83198
+ import { useCallback as useCallback147 } from "react";
83234
83199
 
83235
83200
  // src/hooks/gsapResizePreflight.ts
83236
83201
  function resizeRoute(animations, fetchedAnimations) {
@@ -83262,9 +83227,339 @@ function preflightGsapResizeIntercept(selection, animations, iframe, fetchedAnim
83262
83227
  return { status: "persisted" };
83263
83228
  }
83264
83229
 
83230
+ // src/hooks/useEnableKeyframes.ts
83231
+ import { useCallback as useCallback145 } from "react";
83232
+ var enableKeyframesTransactionCounter = 0;
83233
+ function animatedProps(anim) {
83234
+ if (!anim) return ["x", "y"];
83235
+ const own = Object.keys(anim.properties ?? {});
83236
+ if (own.length > 0) return own;
83237
+ const stops = anim.keyframes?.keyframes;
83238
+ if (stops?.length) {
83239
+ const keys = /* @__PURE__ */ new Set();
83240
+ for (const stop2 of stops) for (const k of Object.keys(stop2.properties ?? {})) keys.add(k);
83241
+ if (keys.size > 0) return [...keys];
83242
+ }
83243
+ return ["x", "y"];
83244
+ }
83245
+ function isPlayheadWithinTween(anim, currentTime) {
83246
+ const start = resolveTweenStart(anim);
83247
+ if (start === null) return true;
83248
+ return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
83249
+ }
83250
+ async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
83251
+ const position = readElementPosition(iframe, sel, setAnim);
83252
+ if (Object.keys(position).length === 0) {
83253
+ for (const [key2, held] of Object.entries(setAnim.properties ?? {})) {
83254
+ if (typeof held === "number") position[key2] = held;
83255
+ }
83256
+ }
83257
+ if (Object.keys(position).length === 0 || !session.commitMutation) return;
83258
+ const range = resolveNewTweenRange(sel.dataAttributes?.start, sel.dataAttributes?.duration, t2);
83259
+ await session.commitMutation(
83260
+ {
83261
+ type: "replace-with-keyframes",
83262
+ animationId: setAnim.id,
83263
+ targetSelector: selector,
83264
+ position: roundTo3(range.start),
83265
+ duration: roundTo3(range.duration),
83266
+ keyframes: [{ percentage: 0, properties: position }],
83267
+ ease: setAnim.ease
83268
+ },
83269
+ { label: "Enable keyframes", keyframeAction: "add", softReload: true }
83270
+ );
83271
+ }
83272
+ function readElementPosition(iframe, sel, anim) {
83273
+ const result = {};
83274
+ if (!iframe?.contentWindow) return result;
83275
+ let gsap3;
83276
+ try {
83277
+ gsap3 = iframe.contentWindow.gsap;
83278
+ } catch {
83279
+ return result;
83280
+ }
83281
+ const element = sel.element;
83282
+ if (!element?.isConnected || !gsap3?.getProperty) return result;
83283
+ const props = animatedProps(anim);
83284
+ for (const prop of props) {
83285
+ const val = Number(gsap3.getProperty(element, prop));
83286
+ if (!Number.isFinite(val)) continue;
83287
+ result[prop] = POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
83288
+ }
83289
+ return result;
83290
+ }
83291
+ function resolveNewTweenRange(authoredStart, authoredDuration, currentTime) {
83292
+ const t2 = Math.max(0, roundTo3(currentTime));
83293
+ const start = authoredStart != null ? Number.parseFloat(authoredStart) : Number.NaN;
83294
+ const duration = authoredDuration != null ? Number.parseFloat(authoredDuration) : Number.NaN;
83295
+ if (!Number.isFinite(start) || !Number.isFinite(duration) || duration <= 0) {
83296
+ return { start: t2, duration: 1 };
83297
+ }
83298
+ const end = start + duration;
83299
+ const clampedStart = Math.min(Math.max(t2, start), end);
83300
+ return { start: clampedStart, duration: Math.max(0.5, roundTo3(end - clampedStart)) };
83301
+ }
83302
+ function singleKeyTweenMutation(sel, properties, currentTime) {
83303
+ const targetSelector = writeTargetSelector(sel);
83304
+ if (!targetSelector) return null;
83305
+ const { start, duration } = resolveNewTweenRange(
83306
+ sel.dataAttributes?.start,
83307
+ sel.dataAttributes?.duration,
83308
+ currentTime
83309
+ );
83310
+ return {
83311
+ type: "add-with-keyframes",
83312
+ targetSelector,
83313
+ position: roundTo3(start),
83314
+ duration: roundTo3(duration),
83315
+ keyframes: [{ percentage: 0, properties }]
83316
+ };
83317
+ }
83318
+ async function tryFetchAnimationsForElement(sel) {
83319
+ const projectId = window.location.hash.match(/project\/([^?/]+)/)?.[1];
83320
+ if (!projectId) return null;
83321
+ const sourceFile = sel.sourceFile || "index.html";
83322
+ const parsed = await fetchParsedAnimations(projectId, sourceFile);
83323
+ if (!parsed) return null;
83324
+ return getAnimationsForElement(parsed.animations, {
83325
+ id: sel.id,
83326
+ selector: sel.selector
83327
+ });
83328
+ }
83329
+ async function fetchAnimationsForElement(sel) {
83330
+ return await tryFetchAnimationsForElement(sel) ?? [];
83331
+ }
83332
+ async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, duration, iframe, commitOverrides) {
83333
+ const selector = existingTweenTargetSelector(anim, sel);
83334
+ const position = readElementPosition(iframe, sel, anim);
83335
+ if (!selector || Object.keys(position).length === 0 || !session.commitMutation) return;
83336
+ const extended = buildExtendedKeyframes(anim, currentTime, position, duration);
83337
+ await session.commitMutation(
83338
+ {
83339
+ type: "replace-with-keyframes",
83340
+ animationId: anim.id,
83341
+ targetSelector: selector,
83342
+ position: extended.position,
83343
+ duration: extended.duration,
83344
+ keyframes: extended.keyframes,
83345
+ ...keyframeEases(anim)
83346
+ },
83347
+ {
83348
+ label: "Add keyframe",
83349
+ keyframeAction: "add",
83350
+ softReload: true,
83351
+ ...commitOverrides
83352
+ }
83353
+ );
83354
+ }
83355
+ async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
83356
+ const keepAuthoredDuration = kfAnim.durationUnresolved === true;
83357
+ if (keepAuthoredDuration) {
83358
+ kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
83359
+ }
83360
+ if (keepAuthoredDuration && kfAnim.duration == null) return;
83361
+ const duration = resolveTweenDuration(kfAnim);
83362
+ const start = resolveTweenStart(kfAnim);
83363
+ if (start !== null && !isTimeWithinTween(t2, start, duration)) {
83364
+ if (keepAuthoredDuration) return;
83365
+ await extendKeyframedTweenToPlayhead(
83366
+ session,
83367
+ sel,
83368
+ kfAnim,
83369
+ t2,
83370
+ duration,
83371
+ iframe,
83372
+ commitOverrides
83373
+ );
83374
+ return;
83375
+ }
83376
+ const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
83377
+ const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
83378
+ if (existing) {
83379
+ session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
83380
+ return;
83381
+ }
83382
+ if (session.handleGsapAddKeyframeBatch) {
83383
+ const position = readElementPosition(iframe, sel, kfAnim);
83384
+ if (Object.keys(position).length > 0) {
83385
+ await session.handleGsapAddKeyframeBatch(kfAnim.id, pct, position, commitOverrides);
83386
+ }
83387
+ }
83388
+ }
83389
+ async function promoteSetToKeyframes(session, sel, setAnim, t2, iframe) {
83390
+ const selector = existingTweenTargetSelector(setAnim, sel);
83391
+ const setStart = resolveTweenStart(setAnim) ?? 0;
83392
+ if (!selector || !session.commitMutation) return;
83393
+ if (t2 <= setStart) {
83394
+ await replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector);
83395
+ return;
83396
+ }
83397
+ const endPosition = readElementPosition(iframe, sel, setAnim);
83398
+ if (Object.keys(endPosition).length === 0) return;
83399
+ const startPosition = {};
83400
+ for (const key2 of Object.keys(endPosition)) {
83401
+ const held = setAnim.properties?.[key2];
83402
+ if (typeof held === "number") startPosition[key2] = held;
83403
+ }
83404
+ await session.commitMutation(
83405
+ {
83406
+ type: "replace-with-keyframes",
83407
+ animationId: setAnim.id,
83408
+ targetSelector: selector,
83409
+ position: roundTo3(setStart),
83410
+ duration: roundTo3(t2 - setStart),
83411
+ keyframes: [
83412
+ {
83413
+ percentage: 0,
83414
+ properties: Object.keys(startPosition).length > 0 ? startPosition : endPosition,
83415
+ auto: true
83416
+ },
83417
+ { percentage: 100, properties: endPosition }
83418
+ ],
83419
+ ease: setAnim.ease
83420
+ },
83421
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83422
+ );
83423
+ }
83424
+ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
83425
+ if (!session.commitMutation) return;
83426
+ const targetSelector = existingTweenTargetSelector(arcAnim, sel);
83427
+ if (!targetSelector) return;
83428
+ const start = resolveTweenStart(arcAnim) ?? 0;
83429
+ const duration = resolveTweenDuration(arcAnim);
83430
+ if (!isTimeWithinTween(t2, start, duration)) {
83431
+ const held = readElementPosition(iframe, sel, arcAnim);
83432
+ if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
83433
+ const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
83434
+ await session.commitMutation(
83435
+ {
83436
+ type: "replace-with-keyframes",
83437
+ animationId: arcAnim.id,
83438
+ targetSelector,
83439
+ position: extended.position,
83440
+ duration: extended.duration,
83441
+ keyframes: extended.keyframes,
83442
+ ease: "none"
83443
+ },
83444
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83445
+ );
83446
+ return;
83447
+ }
83448
+ const nodes = linearArcKeyframes(arcAnim);
83449
+ const playheadPercentage = absoluteToPercentage(t2, start, duration);
83450
+ const timedNodeIndex = nodes.findIndex(
83451
+ (node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
83452
+ );
83453
+ if (timedNodeIndex !== -1) {
83454
+ if (timedNodeIndex > 0 && timedNodeIndex < nodes.length - 1) {
83455
+ await session.commitMutation(
83456
+ {
83457
+ type: "replace-with-keyframes",
83458
+ animationId: arcAnim.id,
83459
+ targetSelector,
83460
+ position: roundTo3(start),
83461
+ duration: roundTo3(duration),
83462
+ keyframes: nodes.filter((_, index) => index !== timedNodeIndex),
83463
+ ease: "none"
83464
+ },
83465
+ { label: "Remove keyframe", softReload: true }
83466
+ );
83467
+ }
83468
+ return;
83469
+ }
83470
+ const live = readElementPosition(iframe, sel, arcAnim);
83471
+ if (typeof live.x !== "number" || typeof live.y !== "number") return;
83472
+ await session.commitMutation(
83473
+ {
83474
+ type: "replace-with-keyframes",
83475
+ animationId: arcAnim.id,
83476
+ targetSelector,
83477
+ position: roundTo3(start),
83478
+ duration: roundTo3(duration),
83479
+ keyframes: buildTemporalArcKeyframes(arcAnim, playheadPercentage, {
83480
+ x: live.x,
83481
+ y: live.y
83482
+ }),
83483
+ ease: "none"
83484
+ },
83485
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83486
+ );
83487
+ }
83488
+ function useEnableKeyframes(sessionRef) {
83489
+ return useCallback145(async () => {
83490
+ const session = sessionRef.current;
83491
+ if (!session) return;
83492
+ const sel = session.domEditSelection;
83493
+ if (!sel) return;
83494
+ const t2 = usePlayerStore.getState().currentTime;
83495
+ const iframe = session.previewIframeRef?.current ?? null;
83496
+ const fetched = await tryFetchAnimationsForElement(sel);
83497
+ const anims = fetched ?? session.selectedGsapAnimations;
83498
+ const arcAnim = anims.find((a) => a.arcPath);
83499
+ const kfAnim = anims.find((a) => a.keyframes && !a.arcPath);
83500
+ const setAnim = anims.find((a) => isInstantHold(a) && !a.keyframes && !a.arcPath);
83501
+ const flatAnim = anims.find((a) => !a.keyframes && !a.arcPath && !isInstantHold(a));
83502
+ if (arcAnim) {
83503
+ await applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe);
83504
+ } else if (kfAnim) {
83505
+ await applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe);
83506
+ } else if (setAnim) {
83507
+ await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
83508
+ } else if (flatAnim) {
83509
+ const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
83510
+ enableKeyframesTransactionCounter += 1;
83511
+ const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
83512
+ const convertCommitOverrides = {
83513
+ keyframeTelemetry: false,
83514
+ skipReload: true,
83515
+ coalesceKey,
83516
+ coalesceMs: Number.POSITIVE_INFINITY
83517
+ };
83518
+ await session.handleGsapConvertToKeyframes(
83519
+ flatAnim.id,
83520
+ void 0,
83521
+ void 0,
83522
+ convertCommitOverrides
83523
+ );
83524
+ const converted = (await fetchAnimationsForElement(sel)).find((a) => a.keyframes);
83525
+ if (converted) {
83526
+ const applyCommitOverrides = {
83527
+ softReload: true,
83528
+ coalesceKey,
83529
+ // Must match the convert phase's window: editHistory keys coalescing
83530
+ // off the incoming entry, so without Infinity here the apply (landing
83531
+ // after two POSTs + a fetch) falls back to the 300ms default and the
83532
+ // conversion splits into two undo entries under real latency.
83533
+ coalesceMs: Number.POSITIVE_INFINITY
83534
+ };
83535
+ await applyKeyframeAtPlayhead(
83536
+ session,
83537
+ sel,
83538
+ converted,
83539
+ t2,
83540
+ iframe,
83541
+ applyCommitOverrides,
83542
+ flatLive
83543
+ );
83544
+ }
83545
+ } else {
83546
+ const position = readElementPosition(iframe, sel, null);
83547
+ if (Object.keys(position).length === 0)
83548
+ Object.assign(position, readTranslatePxLeavingPercent(sel.element));
83549
+ const mutation = singleKeyTweenMutation(sel, position, t2);
83550
+ if (mutation && session.commitMutation) {
83551
+ await session.commitMutation(mutation, { label: "Enable keyframes", softReload: true });
83552
+ } else {
83553
+ session.handleGsapAddAnimation("to");
83554
+ }
83555
+ }
83556
+ }, [sessionRef]);
83557
+ }
83558
+
83265
83559
  // src/hooks/gsapResizeIntercept.ts
83266
83560
  var SIZE_PROPS = /* @__PURE__ */ new Set(["width", "height"]);
83267
83561
  var POSITION_XY = /* @__PURE__ */ new Set(["x", "y"]);
83562
+ var MOVED_OR_SIZED = /* @__PURE__ */ new Set(["x", "y", "xPercent", "yPercent", "width", "height"]);
83268
83563
  function originalBoxSize(el, measuredAttr, inlineProperty) {
83269
83564
  const measured = Number.parseFloat(el?.getAttribute(measuredAttr) ?? "");
83270
83565
  if (Number.isFinite(measured) && measured > 0) return measured;
@@ -83284,7 +83579,8 @@ async function commitSizeAtPlayhead(selection, anim, size, iframe, dragOffset, c
83284
83579
  const anchor2 = moves && animationWritesAnyProperty(anim, POSITION_XY) ? computeDraggedGsapPosition(
83285
83580
  selection.element,
83286
83581
  dragOffset,
83287
- readGsapPositionFromIframe(iframe, selector) ?? { x: 0, y: 0 }
83582
+ readGsapPositionFromIframe(iframe, selector) ?? { x: 0, y: 0 },
83583
+ callbacks.stamp
83288
83584
  ) : null;
83289
83585
  const written = await commitValueAtPlayhead(
83290
83586
  selection,
@@ -83298,11 +83594,28 @@ async function commitSizeAtPlayhead(selection, anim, size, iframe, dragOffset, c
83298
83594
  ...preGestureBoxSize(selection.element),
83299
83595
  ...anchor2 && { x: anchor2.baseGsapX, y: anchor2.baseGsapY }
83300
83596
  },
83301
- ...anchor2 && { beforeReload: () => restoreDragOffset(selection.element) }
83597
+ ...anchor2 && { beforeReload: () => restoreDragOffset(selection.element, callbacks.stamp) }
83302
83598
  }
83303
83599
  );
83304
83600
  return written.status === "persisted" && anchor2 ? { ...written, ownsDragOffset: true } : written;
83305
83601
  }
83602
+ function handOverDraftSize(selection, written, size, draw) {
83603
+ const { width, height } = size;
83604
+ if (written.status !== "persisted" || width == null || height == null) return written;
83605
+ draw(() => {
83606
+ clearStudioBoxSize(selection.element);
83607
+ setElementGsapSize(selection.element, width, height);
83608
+ });
83609
+ return written;
83610
+ }
83611
+ function firstSizeKey(selection, size, resizeGroup, animations) {
83612
+ const { autoKeyframeEnabled, currentTime } = usePlayerStore.getState();
83613
+ const keyframed = animations.some(
83614
+ (a) => a.keyframes && animationWritesAnyProperty(a, MOVED_OR_SIZED)
83615
+ );
83616
+ if (resizeGroup !== "size" || !autoKeyframeEnabled || !keyframed) return null;
83617
+ return singleKeyTweenMutation(selection, size, currentTime);
83618
+ }
83306
83619
  function tweenUsesScaleLonghands(anim) {
83307
83620
  const isLonghand = (name) => name === "scaleX" || name === "scaleY";
83308
83621
  const inKeyframes = (anim?.keyframes?.keyframes ?? []).some(
@@ -83310,7 +83623,7 @@ function tweenUsesScaleLonghands(anim) {
83310
83623
  );
83311
83624
  return inKeyframes || Object.keys(anim?.properties ?? {}).some(isLonghand);
83312
83625
  }
83313
- async function tryGsapResizeIntercept(selection, size, animations, iframe, commitMutation, fetchFallbackAnimations, dragOffset, draw = (run) => run()) {
83626
+ async function tryGsapResizeIntercept(selection, size, animations, iframe, commitMutation, fetchFallbackAnimations, dragOffset, draw = (run) => run(), stamp) {
83314
83627
  const fetchedAnimations = fetchFallbackAnimations ? await fetchFallbackAnimations() : [];
83315
83628
  const outcome = preflightGsapResizeIntercept(selection, animations, iframe, fetchedAnimations);
83316
83629
  if (outcome.status === "blocked") return outcome;
@@ -83335,10 +83648,16 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83335
83648
  size
83336
83649
  });
83337
83650
  if (!anim || isInstantHold(anim)) {
83651
+ const sized = { width: roundToLayoutPx(size.width), height: roundToLayoutPx(size.height) };
83338
83652
  const scriptWritesSize = allKnownAnimations.some(
83339
83653
  (a) => animationWritesAnyProperty(a, SIZE_PROPS)
83340
83654
  );
83341
- if (!scriptWritesSize) return { status: "element-size" };
83655
+ if (!scriptWritesSize) {
83656
+ const firstKey = firstSizeKey(selection, sized, resizeGroup, workingAnimations);
83657
+ if (!firstKey) return { status: "element-size" };
83658
+ await commitMutation(selection, firstKey, { label: "Resize", softReload: true });
83659
+ return handOverDraftSize(selection, { status: "persisted" }, sized, draw);
83660
+ }
83342
83661
  const sel = selectorFromSelection(selection) ?? writeTargetSelector(selection);
83343
83662
  if (!sel) return { status: "blocked", reason: "no-selector" };
83344
83663
  const sizeSet = resizeGroup === "size" ? anim ?? findSizeSetAnimation(workingAnimations, sel, selection.element) : findSizeSetAnimation(workingAnimations, sel, selection.element);
@@ -83350,11 +83669,19 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83350
83669
  );
83351
83670
  if (animatedTween) {
83352
83671
  logResize("intercept-route", { route: "keyframed-size", tweenId: animatedTween.id });
83353
- const sized = { width: roundToLayoutPx(size.width), height: roundToLayoutPx(size.height) };
83354
- return commitSizeAtPlayhead(selection, animatedTween, sized, iframe, dragOffset, {
83355
- commitMutation,
83356
- fetchAnimations: fetchFallbackAnimations
83357
- });
83672
+ const written2 = await commitSizeAtPlayhead(
83673
+ selection,
83674
+ animatedTween,
83675
+ sized,
83676
+ iframe,
83677
+ dragOffset,
83678
+ {
83679
+ commitMutation,
83680
+ fetchAnimations: fetchFallbackAnimations,
83681
+ stamp
83682
+ }
83683
+ );
83684
+ return handOverDraftSize(selection, written2, sized, draw);
83358
83685
  }
83359
83686
  }
83360
83687
  logResize("intercept-route", { route: "static-size-set", hadSizeSet: !!sizeSet, resizeGroup });
@@ -83433,7 +83760,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83433
83760
  const { baseGsapX, baseGsapY } = computeDraggedGsapPosition(
83434
83761
  selection.element,
83435
83762
  { x: 0, y: 0 },
83436
- gsapPos
83763
+ gsapPos,
83764
+ stamp
83437
83765
  );
83438
83766
  const base3 = { x: baseGsapX, y: baseGsapY };
83439
83767
  setElementGsapPosition(draftEl, base3.x, base3.y);
@@ -83474,7 +83802,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83474
83802
  assertGsapEditPersisted(
83475
83803
  await commitGsapPositionFromDrag(selection, positionTween, delta, base2, iframe, {
83476
83804
  commitMutation,
83477
- fetchAnimations: fetchFallbackAnimations
83805
+ fetchAnimations: fetchFallbackAnimations,
83806
+ stamp
83478
83807
  })
83479
83808
  );
83480
83809
  return true;
@@ -83482,7 +83811,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83482
83811
  const existingSet = findExistingPositionWrite(currentAnimations, selector, selection.element);
83483
83812
  await commitStaticGsapPosition(selection, delta, base2, selector, existingSet, {
83484
83813
  commitMutation,
83485
- fetchAnimations: fetchFallbackAnimations
83814
+ fetchAnimations: fetchFallbackAnimations,
83815
+ stamp
83486
83816
  });
83487
83817
  return true;
83488
83818
  };
@@ -83499,9 +83829,18 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83499
83829
  );
83500
83830
  return { status: "persisted", ownsDragOffset: await finalizeScaleResizeCommit() };
83501
83831
  }
83502
- const callbacks = { commitMutation, fetchAnimations: fetchFallbackAnimations };
83503
- if (resizeGroup === "size")
83504
- return commitSizeAtPlayhead(selection, anim, resizeProps, iframe, dragOffset, callbacks);
83832
+ const callbacks = { commitMutation, fetchAnimations: fetchFallbackAnimations, stamp };
83833
+ if (resizeGroup === "size") {
83834
+ const written2 = await commitSizeAtPlayhead(
83835
+ selection,
83836
+ anim,
83837
+ resizeProps,
83838
+ iframe,
83839
+ dragOffset,
83840
+ callbacks
83841
+ );
83842
+ return handOverDraftSize(selection, written2, resizeProps, draw);
83843
+ }
83505
83844
  const written = await commitValueAtPlayhead(selection, anim, resizeProps, iframe, callbacks, {
83506
83845
  label: "Resize",
83507
83846
  backfill: resizeBackfill
@@ -83511,7 +83850,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83511
83850
  }
83512
83851
 
83513
83852
  // src/hooks/useAnimatedPropertyCommit.ts
83514
- import { useCallback as useCallback145 } from "react";
83853
+ import { useCallback as useCallback146 } from "react";
83515
83854
  import { classifyPropertyGroup as classifyPropertyGroup4 } from "@hyperframes/core/gsap-parser";
83516
83855
  function pickBestAnimation(animations, selector, property) {
83517
83856
  const targetGroup = property ? classifyPropertyGroup4(property) : void 0;
@@ -83749,7 +84088,7 @@ async function commitKeyframeProps(selection, anim, props, propEntries, primaryP
83749
84088
  }
83750
84089
  function useAnimatedPropertyCommit(deps) {
83751
84090
  const { selectedGsapAnimations, gsapCommitMutation, previewIframeRef, bumpGsapCache } = deps;
83752
- const commitAnimatedProperties = useCallback145(
84091
+ const commitAnimatedProperties = useCallback146(
83753
84092
  // This is the single routing boundary for set, keyframe, whole-tween, and first-group writes.
83754
84093
  // fallow-ignore-next-line complexity
83755
84094
  async (selection, props, commit2 = gsapCommitMutation) => {
@@ -83855,7 +84194,7 @@ function useAnimatedPropertyCommit(deps) {
83855
84194
  },
83856
84195
  [selectedGsapAnimations, gsapCommitMutation, previewIframeRef, bumpGsapCache]
83857
84196
  );
83858
- const commitAnimatedProperty = useCallback145(
84197
+ const commitAnimatedProperty = useCallback146(
83859
84198
  (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
83860
84199
  [commitAnimatedProperties]
83861
84200
  );
@@ -83882,14 +84221,14 @@ function useGsapAwareEditing({
83882
84221
  setArcPath,
83883
84222
  updateArcSegment
83884
84223
  }) {
83885
- const getGsapAnimationsForSelection = useCallback146(
84224
+ const getGsapAnimationsForSelection = useCallback147(
83886
84225
  (selection) => {
83887
84226
  if (domEditSelection?.element === selection.element) return selectedGsapAnimations;
83888
84227
  return makeFetchFallback(selection, { failOnFetchError: true })();
83889
84228
  },
83890
84229
  [domEditSelection, selectedGsapAnimations, makeFetchFallback]
83891
84230
  );
83892
- const handleGsapAwarePathOffsetCommit = useCallback146(
84231
+ const handleGsapAwarePathOffsetCommit = useCallback147(
83893
84232
  async (selection, next, modifiers) => {
83894
84233
  const writes = observeGsapGesture(gsapCommitMutation);
83895
84234
  if (modifiers?.plainTranslate ?? editsPlainCss(selection.element, "move")) {
@@ -83940,8 +84279,9 @@ function useGsapAwareEditing({
83940
84279
  stageElementPositionOffset,
83941
84280
  showToast
83942
84281
  });
83943
- const handleGsapAwareBoxSizeCommit = useCallback146(
84282
+ const handleGsapAwareBoxSizeCommit = useCallback147(
83944
84283
  async (selection, next, offset, restore2 = () => void 0, route) => {
84284
+ const stamp = freezeDragStamp(selection.element);
83945
84285
  const writes = observeGsapGesture(gsapCommitMutation);
83946
84286
  if (route?.plainTranslate ?? editsPlainCss(selection.element, "resize")) {
83947
84287
  const result = await handleDomBoxSizeCommit(selection, next, offset, restore2);
@@ -83982,7 +84322,8 @@ function useGsapAwareEditing({
83982
84322
  targetAnimations,
83983
84323
  previewIframeRef.current,
83984
84324
  commitMutation2,
83985
- makeFetchFallback(selection)
84325
+ makeFetchFallback(selection),
84326
+ { stamp }
83986
84327
  );
83987
84328
  await saveMove(dragOutcome, async () => {
83988
84329
  const plainAfterSettle = editsPlainCss(selection.element, "move");
@@ -84003,7 +84344,12 @@ function useGsapAwareEditing({
84003
84344
  x: 0,
84004
84345
  y: 0
84005
84346
  };
84006
- const { newX, newY } = computeDraggedGsapPosition(selection.element, offset, gsapPos);
84347
+ const { newX, newY } = computeDraggedGsapPosition(
84348
+ selection.element,
84349
+ offset,
84350
+ gsapPos,
84351
+ stamp
84352
+ );
84007
84353
  logResize("sync-settle", { gsapPos, offset, newX, newY });
84008
84354
  setElementGsapPosition(selection.element, newX, newY);
84009
84355
  });
@@ -84020,7 +84366,8 @@ function useGsapAwareEditing({
84020
84366
  commitMutation2,
84021
84367
  makeFetchFallback(selection),
84022
84368
  offset,
84023
- writes.drawKeepingUndone
84369
+ writes.drawKeepingUndone,
84370
+ stamp
84024
84371
  );
84025
84372
  assertGsapEditPersisted(outcome);
84026
84373
  if (outcome.status === "element-size") {
@@ -84074,7 +84421,7 @@ function useGsapAwareEditing({
84074
84421
  getGsapAnimationsForSelection
84075
84422
  ]
84076
84423
  );
84077
- const handleGsapAwareRotationCommit = useCallback146(
84424
+ const handleGsapAwareRotationCommit = useCallback147(
84078
84425
  async (selection, next) => {
84079
84426
  const writes = observeGsapGesture(gsapCommitMutation);
84080
84427
  if (next.plain === void 0 ? editsPlainCss(selection.element, "rotate") : next.plain) {
@@ -84117,7 +84464,7 @@ function useGsapAwareEditing({
84117
84464
  previewIframeRef,
84118
84465
  bumpGsapCache
84119
84466
  });
84120
- const commitAnimatedProperties = useCallback146(
84467
+ const commitAnimatedProperties = useCallback147(
84121
84468
  async (selection, properties) => {
84122
84469
  try {
84123
84470
  await writeSizeWithCrop(
@@ -84139,25 +84486,25 @@ function useGsapAwareEditing({
84139
84486
  trackGsapInteractionFailure
84140
84487
  ]
84141
84488
  );
84142
- const commitAnimatedProperty = useCallback146(
84489
+ const commitAnimatedProperty = useCallback147(
84143
84490
  (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
84144
84491
  [commitAnimatedProperties]
84145
84492
  );
84146
- const handleSetArcPath = useCallback146(
84493
+ const handleSetArcPath = useCallback147(
84147
84494
  (animId, config) => {
84148
84495
  if (!domEditSelection) return;
84149
84496
  setArcPath(domEditSelection, animId, config);
84150
84497
  },
84151
84498
  [domEditSelection, setArcPath]
84152
84499
  );
84153
- const handleUpdateArcSegment = useCallback146(
84500
+ const handleUpdateArcSegment = useCallback147(
84154
84501
  (animId, segmentIndex, update) => {
84155
84502
  if (!domEditSelection) return;
84156
84503
  updateArcSegment(domEditSelection, animId, segmentIndex, update);
84157
84504
  },
84158
84505
  [domEditSelection, updateArcSegment]
84159
84506
  );
84160
- const noopCommit = useCallback146(async () => {
84507
+ const noopCommit = useCallback147(async () => {
84161
84508
  }, []);
84162
84509
  const trackGsapSaveFailure = useGsapSaveFailureTelemetry(null);
84163
84510
  const safeGsapCommit = useSafeGsapCommitMutation(
@@ -84165,14 +84512,14 @@ function useGsapAwareEditing({
84165
84512
  trackGsapSaveFailure,
84166
84513
  showToast
84167
84514
  );
84168
- const commitMutation = useCallback146(
84515
+ const commitMutation = useCallback147(
84169
84516
  async (mutation, options, selection = domEditSelection) => {
84170
84517
  if (!selection) return;
84171
84518
  await safeGsapCommit(selection, mutation, options);
84172
84519
  },
84173
84520
  [domEditSelection, safeGsapCommit]
84174
84521
  );
84175
- const handleUnroll = useCallback146(() => {
84522
+ const handleUnroll = useCallback147(() => {
84176
84523
  void commitMutation(
84177
84524
  { type: "unroll-timeline" },
84178
84525
  { label: "Unroll to literal tweens", softReload: true }
@@ -84323,12 +84670,12 @@ function useStudioSelectionPublisher({
84323
84670
  }
84324
84671
 
84325
84672
  // src/hooks/useKeyframeEaseCommits.ts
84326
- import { useCallback as useCallback147 } from "react";
84673
+ import { useCallback as useCallback148 } from "react";
84327
84674
  function useKeyframeEaseCommits({
84328
84675
  gsapCommitMutation,
84329
84676
  domEditSelectionRef
84330
84677
  }) {
84331
- const handleUpdateSegmentEase = useCallback147(
84678
+ const handleUpdateSegmentEase = useCallback148(
84332
84679
  (targets, ease) => {
84333
84680
  const selection = domEditSelectionRef.current;
84334
84681
  if (!selection || targets.length === 0) return;
@@ -84362,11 +84709,11 @@ function useKeyframeEaseCommits({
84362
84709
  },
84363
84710
  [gsapCommitMutation, domEditSelectionRef]
84364
84711
  );
84365
- const handleUpdateKeyframeEase = useCallback147(
84712
+ const handleUpdateKeyframeEase = useCallback148(
84366
84713
  (animationId, percentage, ease) => handleUpdateSegmentEase([{ animationId, tweenPercentage: percentage }], ease),
84367
84714
  [handleUpdateSegmentEase]
84368
84715
  );
84369
- const handleSetAllKeyframeEases = useCallback147(
84716
+ const handleSetAllKeyframeEases = useCallback148(
84370
84717
  (animationId, ease) => {
84371
84718
  const sel = domEditSelectionRef.current;
84372
84719
  if (!sel) return;
@@ -84802,7 +85149,7 @@ function useDomEditSession({
84802
85149
  forceReloadSdkSession,
84803
85150
  timelineElements
84804
85151
  });
84805
- const handleDomEditElementDelete = useCallback148(
85152
+ const handleDomEditElementDelete = useCallback149(
84806
85153
  async (selection, options) => {
84807
85154
  if (isRecordingRef?.current) {
84808
85155
  showToast("Cannot edit timeline while recording", "error");
@@ -84825,7 +85172,7 @@ function useDomEditSession({
84825
85172
  timelineElements
84826
85173
  ]
84827
85174
  );
84828
- const handleGroupSelection = useCallback148(() => {
85175
+ const handleGroupSelection = useCallback149(() => {
84829
85176
  const group = domEditGroupSelectionsRef.current;
84830
85177
  const single = domEditSelectionRef.current;
84831
85178
  const members = group.length > 0 ? group : single ? [single] : [];
@@ -84843,7 +85190,7 @@ function useDomEditSession({
84843
85190
  trackStudioEvent("group", { action: "create", count: members.length });
84844
85191
  void groupSelection(members);
84845
85192
  }, [domEditGroupSelectionsRef, domEditSelectionRef, groupSelection, showToast]);
84846
- const handleUngroupSelection = useCallback148(() => {
85193
+ const handleUngroupSelection = useCallback149(() => {
84847
85194
  const sel = domEditSelectionRef.current;
84848
85195
  if (!sel?.element.hasAttribute("data-hf-group")) {
84849
85196
  showToast("Select a group to ungroup", "info");
@@ -84951,7 +85298,7 @@ function useDomEditSession({
84951
85298
  setArcPath,
84952
85299
  updateArcSegment
84953
85300
  });
84954
- const handleDomGroupMoveBy = useCallback148(
85301
+ const handleDomGroupMoveBy = useCallback149(
84955
85302
  (moves) => moveDomGroupBy(
84956
85303
  moves,
84957
85304
  (updates) => handleGsapAwareGroupPathOffsetCommit(updates, { refusalToast: false })
@@ -85077,7 +85424,7 @@ function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections)
85077
85424
  import { useEffect as useEffect126 } from "react";
85078
85425
 
85079
85426
  // src/hooks/useSdkSession.ts
85080
- import { useState as useState121, useEffect as useEffect125, useCallback as useCallback149, useRef as useRef154 } from "react";
85427
+ import { useState as useState121, useEffect as useEffect125, useCallback as useCallback150, useRef as useRef154 } from "react";
85081
85428
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
85082
85429
 
85083
85430
  // src/hooks/externalFileReloadBus.ts
@@ -85291,8 +85638,8 @@ function useSdkSession(projectId, activeCompPath, fileTree = [], fileTreeLoaded
85291
85638
  }
85292
85639
  };
85293
85640
  }, [projectId, activeCompPath, reloadToken]);
85294
- const forceReload = useCallback149(() => setReloadToken((t2) => t2 + 1), []);
85295
- const publish = useCallback149(({ candidate, expectedSession, targetPath }) => {
85641
+ const forceReload = useCallback150(() => setReloadToken((t2) => t2 + 1), []);
85642
+ const publish = useCallback150(({ candidate, expectedSession, targetPath }) => {
85296
85643
  const expectedOwner = sessionOwnersRef.current.get(expectedSession);
85297
85644
  const current = ownedSessionRef.current;
85298
85645
  if (!isSessionOwnerActive(
@@ -85335,7 +85682,7 @@ function useStudioSdkSessions(projectId, activeCompPath, masterCompPath, fileTre
85335
85682
  }
85336
85683
 
85337
85684
  // src/hooks/useStudioExternalFileChanges.ts
85338
- import { useCallback as useCallback151 } from "react";
85685
+ import { useCallback as useCallback152 } from "react";
85339
85686
 
85340
85687
  // src/utils/externalConflictStorage.ts
85341
85688
  var DB_NAME = "hyperframes-studio-external-conflicts";
@@ -85436,7 +85783,7 @@ async function deleteExternalConflictSnapshot(projectId, filePath) {
85436
85783
  }
85437
85784
 
85438
85785
  // src/hooks/useExternalFileChangeCoordinator.ts
85439
- import { useCallback as useCallback150, useEffect as useEffect127, useRef as useRef155, useState as useState122 } from "react";
85786
+ import { useCallback as useCallback151, useEffect as useEffect127, useRef as useRef155, useState as useState122 } from "react";
85440
85787
  function testHotAdapter() {
85441
85788
  const value = globalThis.__HF_STUDIO_HOT_TEST_ADAPTER__;
85442
85789
  if (!value || typeof value !== "object") return null;
@@ -85497,7 +85844,7 @@ function useExternalFileChangeCoordinator({
85497
85844
  const drainingRef = useRef155(false);
85498
85845
  const pendingPayloadRef = useRef155(null);
85499
85846
  blockedRef.current = blocked;
85500
- const setBlocked = useCallback150((next) => {
85847
+ const setBlocked = useCallback151((next) => {
85501
85848
  setBlockedState(
85502
85849
  (held) => next && held ? { ...next, payload: mergeFileChangeAffectedCompositions(held.payload, next.payload) } : next
85503
85850
  );
@@ -85552,7 +85899,7 @@ function useExternalFileChangeCoordinator({
85552
85899
  cancelled = true;
85553
85900
  };
85554
85901
  }, [loadConflictSnapshot, projectId, recoveryFilePath, setBlocked]);
85555
- const reloadAcceptedGeneration = useCallback150(
85902
+ const reloadAcceptedGeneration = useCallback151(
85556
85903
  (path, affectsPreview = true) => {
85557
85904
  logReload(affectsPreview ? "reload" : "file-tree only", {
85558
85905
  path,
@@ -85564,7 +85911,7 @@ function useExternalFileChangeCoordinator({
85564
85911
  },
85565
85912
  [reloadPreview, reloadSdkSession, refreshFileTree]
85566
85913
  );
85567
- const persistSnapshotInOrder = useCallback150(async (write) => {
85914
+ const persistSnapshotInOrder = useCallback151(async (write) => {
85568
85915
  const next = snapshotWriteTailRef.current.catch(() => void 0).then(write);
85569
85916
  snapshotWriteTailRef.current = next.then(
85570
85917
  () => void 0,
@@ -85572,7 +85919,7 @@ function useExternalFileChangeCoordinator({
85572
85919
  );
85573
85920
  await next;
85574
85921
  }, []);
85575
- const drainOnePending = useCallback150(
85922
+ const drainOnePending = useCallback151(
85576
85923
  // fallow-ignore-next-line complexity
85577
85924
  async (payload) => {
85578
85925
  const path = readStudioFileChangePath(payload);
@@ -85671,7 +86018,7 @@ function useExternalFileChangeCoordinator({
85671
86018
  onAcceptedPersistedFileChange
85672
86019
  ]
85673
86020
  );
85674
- const startDrainLoop = useCallback150(async () => {
86021
+ const startDrainLoop = useCallback151(async () => {
85675
86022
  if (drainingRef.current) return;
85676
86023
  drainingRef.current = true;
85677
86024
  try {
@@ -85685,7 +86032,7 @@ function useExternalFileChangeCoordinator({
85685
86032
  drainingRef.current = false;
85686
86033
  }
85687
86034
  }, [drainOnePending]);
85688
- const processChange = useCallback150(
86035
+ const processChange = useCallback151(
85689
86036
  // fallow-ignore-next-line complexity
85690
86037
  (payload) => {
85691
86038
  const path = readStudioFileChangePath(payload);
@@ -85744,14 +86091,14 @@ function useExternalFileChangeCoordinator({
85744
86091
  }
85745
86092
  return sseFileChangeChannel(handler);
85746
86093
  }, []);
85747
- const retry = useCallback150(async () => {
86094
+ const retry = useCallback151(async () => {
85748
86095
  const current = blockedRef.current;
85749
86096
  if (!current || current.status === "conflict" || current.recovered) return;
85750
86097
  resetSaveQueues?.();
85751
86098
  lastEventIdentityRef.current = null;
85752
86099
  processChange(current.payload);
85753
86100
  }, [processChange, resetSaveQueues]);
85754
- const useExternalFile = useCallback150(
86101
+ const useExternalFile = useCallback151(
85755
86102
  // fallow-ignore-next-line complexity
85756
86103
  async () => {
85757
86104
  const current = blockedRef.current;
@@ -85779,7 +86126,7 @@ function useExternalFileChangeCoordinator({
85779
86126
  resetSaveQueues
85780
86127
  ]
85781
86128
  );
85782
- const keepStudioFile = useCallback150(async () => {
86129
+ const keepStudioFile = useCallback151(async () => {
85783
86130
  const current = blockedRef.current;
85784
86131
  if (!current || !projectId) return;
85785
86132
  if (current.generation !== generationRef.current) return;
@@ -85850,19 +86197,19 @@ function useStudioExternalFileChanges({
85850
86197
  const { flushPendingSourceSave, discardPendingSourceSave } = fileManager;
85851
86198
  const { drainPendingDomEditSaves, resetDomEditSaveQueueBreaker } = previewPersistence;
85852
86199
  const bumpThumbnailRevisions = usePlayerStore((state) => state.bumpThumbnailRevisions);
85853
- const onAcceptedPersistedFileChange = useCallback151(
86200
+ const onAcceptedPersistedFileChange = useCallback152(
85854
86201
  (_path, affectedCompositions) => {
85855
86202
  onOutsideChange?.();
85856
86203
  bumpThumbnailRevisions(affectedCompositions);
85857
86204
  },
85858
86205
  [bumpThumbnailRevisions, onOutsideChange]
85859
86206
  );
85860
- const drainPendingChanges = useCallback151(async () => {
86207
+ const drainPendingChanges = useCallback152(async () => {
85861
86208
  const source = await flushPendingSourceSave();
85862
86209
  if (source.status !== "clean") return source;
85863
86210
  return drainPendingDomEditSaves();
85864
86211
  }, [drainPendingDomEditSaves, flushPendingSourceSave]);
85865
- const discardPendingChanges = useCallback151(() => {
86212
+ const discardPendingChanges = useCallback152(() => {
85866
86213
  discardPendingSourceSave();
85867
86214
  resetDomEditSaveQueueBreaker();
85868
86215
  }, [discardPendingSourceSave, resetDomEditSaveQueueBreaker]);
@@ -85890,7 +86237,7 @@ function useStudioExternalFileChanges({
85890
86237
  }
85891
86238
 
85892
86239
  // src/hooks/useBlockHandlers.ts
85893
- import { useCallback as useCallback152, useMemo as useMemo75, useRef as useRef156, useState as useState123 } from "react";
86240
+ import { useCallback as useCallback153, useMemo as useMemo75, useRef as useRef156, useState as useState123 } from "react";
85894
86241
 
85895
86242
  // src/utils/blockInstaller.ts
85896
86243
  function getMaxZIndexFromIframe(iframe) {
@@ -86109,7 +86456,7 @@ function useBlockHandlers({
86109
86456
  );
86110
86457
  const installingBlockRef = useRef156(false);
86111
86458
  const { showToast, dismissToast } = blockCtxDeps;
86112
- const runBlockInstall = useCallback152(
86459
+ const runBlockInstall = useCallback153(
86113
86460
  async (blockName, install) => {
86114
86461
  if (installingBlockRef.current) {
86115
86462
  showToast("A block is already installing \u2014 one moment\u2026", "info");
@@ -86126,7 +86473,7 @@ function useBlockHandlers({
86126
86473
  },
86127
86474
  [showToast, dismissToast]
86128
86475
  );
86129
- const handleAddBlock = useCallback152(
86476
+ const handleAddBlock = useCallback153(
86130
86477
  (blockName) => {
86131
86478
  if (!projectId) return;
86132
86479
  void (async () => {
@@ -86156,7 +86503,7 @@ function useBlockHandlers({
86156
86503
  },
86157
86504
  [projectId, blockCtx, previewIframeRef, runBlockInstall, setRightCollapsed, setRightPanelTab]
86158
86505
  );
86159
- const handleTimelineBlockDrop = useCallback152(
86506
+ const handleTimelineBlockDrop = useCallback153(
86160
86507
  (blockName, placement) => {
86161
86508
  if (!projectId) return;
86162
86509
  void runBlockInstall(
@@ -86175,7 +86522,7 @@ function useBlockHandlers({
86175
86522
  },
86176
86523
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
86177
86524
  );
86178
- const handleAddMediaOverlay = useCallback152(
86525
+ const handleAddMediaOverlay = useCallback153(
86179
86526
  async (blockName, placement) => {
86180
86527
  if (!projectId) return;
86181
86528
  const { compositionPath, ...timelinePlacement } = placement;
@@ -86194,7 +86541,7 @@ function useBlockHandlers({
86194
86541
  },
86195
86542
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
86196
86543
  );
86197
- const handlePreviewBlockDrop = useCallback152(
86544
+ const handlePreviewBlockDrop = useCallback153(
86198
86545
  (blockName, position) => {
86199
86546
  if (!projectId) return;
86200
86547
  void runBlockInstall(
@@ -86224,7 +86571,7 @@ function useBlockHandlers({
86224
86571
  }
86225
86572
 
86226
86573
  // src/hooks/useAppHotkeys.ts
86227
- import { useCallback as useCallback154, useEffect as useEffect128, useRef as useRef157 } from "react";
86574
+ import { useCallback as useCallback155, useEffect as useEffect128, useRef as useRef157 } from "react";
86228
86575
 
86229
86576
  // src/hooks/appHotkeysDispatch.ts
86230
86577
  function handleUndoRedoKey(event, onUndo, onRedo) {
@@ -86421,7 +86768,7 @@ function dispatchPlainKey(event, key2, cb) {
86421
86768
  }
86422
86769
 
86423
86770
  // src/hooks/useEditHistoryActions.ts
86424
- import { useCallback as useCallback153, useMemo as useMemo76 } from "react";
86771
+ import { useCallback as useCallback154, useMemo as useMemo76 } from "react";
86425
86772
  function useEditHistoryActions({
86426
86773
  editHistory,
86427
86774
  readOptionalProjectFile,
@@ -86435,20 +86782,21 @@ function useEditHistoryActions({
86435
86782
  activeCompPath,
86436
86783
  forceReloadSdkSession
86437
86784
  }) {
86438
- const readHistoryFile = useCallback153(
86785
+ const readHistoryFile = useCallback154(
86439
86786
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
86440
86787
  [readOptionalProjectFile, readProjectFile]
86441
86788
  );
86442
- const serializeHistoryFiles = useCallback153(
86789
+ const serializeHistoryFiles = useCallback154(
86443
86790
  (paths, task) => serializeStudioFileMutations(writeProjectFile, paths, task),
86444
86791
  [writeProjectFile]
86445
86792
  );
86446
- const apply = useCallback153(
86793
+ const apply = useCallback154(
86447
86794
  async (direction) => {
86448
86795
  const noun = direction === "undo" ? "Undo" : "Redo";
86449
86796
  const pendingEditShown = direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
86450
86797
  const predicted = pendingEditShown ? null : editHistory.predict?.(direction) ?? null;
86451
86798
  const predictedShown = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
86799
+ const shownLanded = predictedShown ? beginStudioShownRestore() : null;
86452
86800
  const putBack = predictedShown ?? pendingEditShown?.showAgain;
86453
86801
  const claimedAfter = pendingEditShown?.claimsAtBegin ?? (direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0);
86454
86802
  let result = { ok: false, reason: "failed" };
@@ -86468,6 +86816,7 @@ function useEditHistoryActions({
86468
86816
  serverSteppedShown = predictedShown ? stepped && result.undoes === predicted?.id : stepped && editClaimed;
86469
86817
  } finally {
86470
86818
  if (putBack && !serverSteppedShown && !revertIsTheUndo) putBack();
86819
+ shownLanded?.();
86471
86820
  }
86472
86821
  if (!result.ok && result.reason === "content-mismatch") {
86473
86822
  showToast(
@@ -86504,8 +86853,8 @@ function useEditHistoryActions({
86504
86853
  forceReloadSdkSession
86505
86854
  ]
86506
86855
  );
86507
- const undo = useCallback153(() => apply("undo"), [apply]);
86508
- const redo = useCallback153(() => apply("redo"), [apply]);
86856
+ const undo = useCallback154(() => apply("undo"), [apply]);
86857
+ const redo = useCallback154(() => apply("redo"), [apply]);
86509
86858
  return useMemo76(() => ({ undo, redo }), [undo, redo]);
86510
86859
  }
86511
86860
  function fromShown(files, shown2) {
@@ -86594,7 +86943,7 @@ function useAppHotkeys({
86594
86943
  activeCompPath,
86595
86944
  forceReloadSdkSession
86596
86945
  });
86597
- const applyHistory = useCallback154(
86946
+ const applyHistory = useCallback155(
86598
86947
  async (direction) => {
86599
86948
  const captionState = useCaptionStore.getState();
86600
86949
  if (captionState.isEditMode && isCaptionPreviewVisible()) {
@@ -86610,8 +86959,8 @@ function useAppHotkeys({
86610
86959
  },
86611
86960
  [editHistory.state, fileHistory, showToast]
86612
86961
  );
86613
- const handleUndo = useCallback154(() => applyHistory("undo"), [applyHistory]);
86614
- const handleRedo = useCallback154(() => applyHistory("redo"), [applyHistory]);
86962
+ const handleUndo = useCallback155(() => applyHistory("undo"), [applyHistory]);
86963
+ const handleRedo = useCallback155(() => applyHistory("redo"), [applyHistory]);
86615
86964
  const cbRef = useRef157(null);
86616
86965
  cbRef.current = {
86617
86966
  handleTimelineElementsDelete,
@@ -86634,7 +86983,7 @@ function useAppHotkeys({
86634
86983
  showToast,
86635
86984
  readOnlyPreview
86636
86985
  };
86637
- const handleAppKeyDown = useCallback154((event) => {
86986
+ const handleAppKeyDown = useCallback155((event) => {
86638
86987
  const cb = cbRef.current;
86639
86988
  const key2 = event.key.toLowerCase();
86640
86989
  if (dispatchLinkShortcut(event, cb)) return;
@@ -86648,7 +86997,7 @@ function useAppHotkeys({
86648
86997
  window.addEventListener("keydown", handleAppKeyDown, true);
86649
86998
  return () => window.removeEventListener("keydown", handleAppKeyDown, true);
86650
86999
  }, [handleAppKeyDown]);
86651
- const syncPreviewHotkeys = useCallback154(
87000
+ const syncPreviewHotkeys = useCallback155(
86652
87001
  (iframe) => {
86653
87002
  previewHistoryCleanupRef.current?.();
86654
87003
  previewHistoryCleanupRef.current = null;
@@ -86678,7 +87027,7 @@ function useAppHotkeys({
86678
87027
  }
86679
87028
 
86680
87029
  // src/hooks/useClipboard.ts
86681
- import { useCallback as useCallback155, useRef as useRef158 } from "react";
87030
+ import { useCallback as useCallback156, useRef as useRef158 } from "react";
86682
87031
  import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants as walkCompositionDescendants2 } from "@hyperframes/parsers/hf-ids";
86683
87032
 
86684
87033
  // src/utils/clipboardPayload.ts
@@ -86958,7 +87307,7 @@ function useClipboard({
86958
87307
  const clipboardRef = useRef158(null);
86959
87308
  const projectIdRef = useRef158(projectId);
86960
87309
  projectIdRef.current = projectId;
86961
- const readSaved = useCallback155(
87310
+ const readSaved = useCallback156(
86962
87311
  async (path) => {
86963
87312
  const pid = projectIdRef.current;
86964
87313
  if (!pid) throw new Error("No project is open.");
@@ -86972,7 +87321,7 @@ function useClipboard({
86972
87321
  },
86973
87322
  [waitForPendingDomEditSaves, writeProjectFile]
86974
87323
  );
86975
- const findSelectedClips = useCallback155(() => {
87324
+ const findSelectedClips = useCallback156(() => {
86976
87325
  const selected = getSelectedElements();
86977
87326
  if (selected.length === 0) return null;
86978
87327
  const lives = [];
@@ -86986,7 +87335,7 @@ function useClipboard({
86986
87335
  }
86987
87336
  return { elements: selected, lives };
86988
87337
  }, [activeCompPath, previewIframeRef, showToast]);
86989
- const readClips = useCallback155(
87338
+ const readClips = useCallback156(
86990
87339
  async (targets) => {
86991
87340
  const paths = targets.elements.map((el) => el.sourceFile || activeCompPath || "index.html");
86992
87341
  const markup = await readSavedMarkup(readSaved, paths, targets.lives);
@@ -87002,7 +87351,7 @@ function useClipboard({
87002
87351
  },
87003
87352
  [activeCompPath, readSaved]
87004
87353
  );
87005
- const copyTimelineSelection = useCallback155(() => {
87354
+ const copyTimelineSelection = useCallback156(() => {
87006
87355
  const targets = findSelectedClips();
87007
87356
  if (!targets) return null;
87008
87357
  const sourceFile = targets.elements[0]?.sourceFile || activeCompPath || "index.html";
@@ -87017,7 +87366,7 @@ function useClipboard({
87017
87366
  };
87018
87367
  });
87019
87368
  }, [activeCompPath, findSelectedClips, readClips, showToast]);
87020
- const copyDomSelection = useCallback155(
87369
+ const copyDomSelection = useCallback156(
87021
87370
  (domSelection) => {
87022
87371
  const live = getSelectedDomElement(previewIframeRef, domSelection, activeCompPath);
87023
87372
  if (!live) {
@@ -87039,7 +87388,7 @@ function useClipboard({
87039
87388
  },
87040
87389
  [activeCompPath, previewIframeRef, readSaved, showToast]
87041
87390
  );
87042
- const copyToClipboard = useCallback155(() => {
87391
+ const copyToClipboard = useCallback156(() => {
87043
87392
  const domSelection = domEditSelectionRef.current;
87044
87393
  let pending2;
87045
87394
  if (usePlayerStore.getState().selectedElementId) pending2 = copyTimelineSelection();
@@ -87061,8 +87410,8 @@ function useClipboard({
87061
87410
  });
87062
87411
  return own;
87063
87412
  }, [copyDomSelection, copyTimelineSelection, domEditSelectionRef, showToast]);
87064
- const handleCopy2 = useCallback155(() => copyToClipboard() !== null, [copyToClipboard]);
87065
- const handlePaste2 = useCallback155(
87413
+ const handleCopy2 = useCallback156(() => copyToClipboard() !== null, [copyToClipboard]);
87414
+ const handlePaste2 = useCallback156(
87066
87415
  async (options) => {
87067
87416
  const payload = await clipboardRef.current;
87068
87417
  if (!payload) {
@@ -87110,7 +87459,7 @@ function useClipboard({
87110
87459
  },
87111
87460
  [activeCompPath, recordEdit, reloadPreview, showToast, writeProjectFile]
87112
87461
  );
87113
- const handleDuplicate = useCallback155(
87462
+ const handleDuplicate = useCallback156(
87114
87463
  async (options) => {
87115
87464
  const targets = findSelectedClips();
87116
87465
  if (!targets) return false;
@@ -87162,7 +87511,7 @@ function useClipboard({
87162
87511
  writeProjectFile
87163
87512
  ]
87164
87513
  );
87165
- const handleCut = useCallback155(async () => {
87514
+ const handleCut = useCallback156(async () => {
87166
87515
  const selected = getSelectedElements();
87167
87516
  const domSelection = domEditSelectionRef.current;
87168
87517
  const copied = copyToClipboard();
@@ -87182,7 +87531,7 @@ function useClipboard({
87182
87531
  handleTimelineElementsDelete,
87183
87532
  handleDomEditElementDelete
87184
87533
  ]);
87185
- const canPaste = useCallback155(() => clipboardRef.current !== null, []);
87534
+ const canPaste = useCallback156(() => clipboardRef.current !== null, []);
87186
87535
  return { handleCopy: handleCopy2, handlePaste: handlePaste2, handleCut, handleDuplicate, canPaste };
87187
87536
  }
87188
87537
 
@@ -87541,11 +87890,11 @@ function useCaptionDetection({
87541
87890
  }
87542
87891
 
87543
87892
  // src/hooks/useConsoleErrorCapture.ts
87544
- import { useCallback as useCallback156, useEffect as useEffect130, useRef as useRef160, useState as useState124 } from "react";
87893
+ import { useCallback as useCallback157, useEffect as useEffect130, useRef as useRef160, useState as useState124 } from "react";
87545
87894
  function useConsoleErrorCapture(previewIframe) {
87546
87895
  const [consoleErrors, setConsoleErrors] = useState124(null);
87547
87896
  const consoleErrorsRef = useRef160([]);
87548
- const resetErrors = useCallback156(() => {
87897
+ const resetErrors = useCallback157(() => {
87549
87898
  consoleErrorsRef.current = [];
87550
87899
  setConsoleErrors(null);
87551
87900
  }, []);
@@ -87614,7 +87963,7 @@ function useConsoleErrorCapture(previewIframe) {
87614
87963
  }
87615
87964
 
87616
87965
  // src/hooks/useFrameCapture.ts
87617
- import { useMemo as useMemo77, useState as useState125, useCallback as useCallback157, useRef as useRef161 } from "react";
87966
+ import { useMemo as useMemo77, useState as useState125, useCallback as useCallback158, useRef as useRef161 } from "react";
87618
87967
  function useFrameCapture({
87619
87968
  projectId,
87620
87969
  activeCompPath,
@@ -87632,16 +87981,16 @@ function useFrameCapture({
87632
87981
  livePlayheadRef.current = time;
87633
87982
  })
87634
87983
  );
87635
- const playheadTime = useCallback157(() => {
87984
+ const playheadTime = useCallback158(() => {
87636
87985
  const { isPlaying, currentTime } = usePlayerStore.getState();
87637
87986
  return isPlaying ? livePlayheadRef.current : currentTime;
87638
87987
  }, []);
87639
87988
  const [captureStamp, setCaptureStamp] = useState125(Date.now);
87640
- const refreshCaptureFrameTime = useCallback157(() => {
87989
+ const refreshCaptureFrameTime = useCallback158(() => {
87641
87990
  setCaptureFrameTime(playheadTime());
87642
87991
  setCaptureStamp(Date.now());
87643
87992
  }, [playheadTime]);
87644
- const handleCaptureFrameClick = useCallback157(
87993
+ const handleCaptureFrameClick = useCallback158(
87645
87994
  async (event) => {
87646
87995
  if (!projectId) return;
87647
87996
  event.preventDefault();
@@ -87725,7 +88074,7 @@ function useFrameCapture({
87725
88074
  }
87726
88075
 
87727
88076
  // src/hooks/useLintModal.ts
87728
- import { useState as useState126, useCallback as useCallback158, useEffect as useEffect131, useRef as useRef162, useMemo as useMemo78 } from "react";
88077
+ import { useState as useState126, useCallback as useCallback159, useEffect as useEffect131, useRef as useRef162, useMemo as useMemo78 } from "react";
87729
88078
  function parseFinding(f) {
87730
88079
  return {
87731
88080
  severity: f.severity === "error" ? "error" : "warning",
@@ -87745,7 +88094,7 @@ function useLintModal(projectId, refreshKey) {
87745
88094
  const [linting, setLinting] = useState126(false);
87746
88095
  const [backgroundFindings, setBackgroundFindings] = useState126([]);
87747
88096
  const autoLintRanRef = useRef162(false);
87748
- const runBackgroundLint = useCallback158(async () => {
88097
+ const runBackgroundLint = useCallback159(async () => {
87749
88098
  if (!projectId) return;
87750
88099
  if (!isPreviewBooted(projectId) && !await whenPreviewBooted(projectId)) return;
87751
88100
  try {
@@ -87753,7 +88102,7 @@ function useLintModal(projectId, refreshKey) {
87753
88102
  } catch {
87754
88103
  }
87755
88104
  }, [projectId]);
87756
- const handleLint = useCallback158(async () => {
88105
+ const handleLint = useCallback159(async () => {
87757
88106
  if (!projectId) return;
87758
88107
  setLinting(true);
87759
88108
  try {
@@ -87782,8 +88131,8 @@ function useLintModal(projectId, refreshKey) {
87782
88131
  const timer = setTimeout(() => void runBackgroundLint(), 1e3);
87783
88132
  return () => clearTimeout(timer);
87784
88133
  }, [projectId, refreshKey, runBackgroundLint]);
87785
- const closeLintModal = useCallback158(() => setLintModal(null), []);
87786
- const groupFindings = useCallback158(
88134
+ const closeLintModal = useCallback159(() => setLintModal(null), []);
88135
+ const groupFindings = useCallback159(
87787
88136
  (keyFn) => {
87788
88137
  const map = /* @__PURE__ */ new Map();
87789
88138
  for (const f of backgroundFindings) {
@@ -87820,7 +88169,7 @@ function useLintModal(projectId, refreshKey) {
87820
88169
  }
87821
88170
 
87822
88171
  // src/hooks/useToast.ts
87823
- import { useState as useState127, useCallback as useCallback159, useRef as useRef163 } from "react";
88172
+ import { useState as useState127, useCallback as useCallback160, useRef as useRef163 } from "react";
87824
88173
  var AUTO_DISMISS_MS2 = 4e3;
87825
88174
  var EXIT_MS2 = 160;
87826
88175
  var MAX_TOASTS = 3;
@@ -87828,21 +88177,21 @@ var nextToastId = 1;
87828
88177
  function useToast() {
87829
88178
  const [toasts, setToasts] = useState127([]);
87830
88179
  const timersRef = useRef163(/* @__PURE__ */ new Map());
87831
- const clearTimer = useCallback159((id) => {
88180
+ const clearTimer = useCallback160((id) => {
87832
88181
  const timer = timersRef.current.get(id);
87833
88182
  if (timer) {
87834
88183
  clearTimeout(timer);
87835
88184
  timersRef.current.delete(id);
87836
88185
  }
87837
88186
  }, []);
87838
- const removeToast = useCallback159(
88187
+ const removeToast = useCallback160(
87839
88188
  (id) => {
87840
88189
  clearTimer(id);
87841
88190
  setToasts((prev) => prev.filter((t2) => t2.id !== id));
87842
88191
  },
87843
88192
  [clearTimer]
87844
88193
  );
87845
- const dismissToast = useCallback159(
88194
+ const dismissToast = useCallback160(
87846
88195
  (id) => {
87847
88196
  clearTimer(id);
87848
88197
  setToasts((prev) => prev.map((t2) => t2.id === id ? { ...t2, leaving: true } : t2));
@@ -87851,7 +88200,7 @@ function useToast() {
87851
88200
  },
87852
88201
  [clearTimer, removeToast]
87853
88202
  );
87854
- const showToast = useCallback159(
88203
+ const showToast = useCallback160(
87855
88204
  (message, tone = "error") => {
87856
88205
  const id = nextToastId++;
87857
88206
  setToasts((prev) => {
@@ -87878,7 +88227,7 @@ function useToast() {
87878
88227
  }
87879
88228
 
87880
88229
  // src/hooks/useStudioUrlState.ts
87881
- import { useCallback as useCallback160, useEffect as useEffect132, useRef as useRef164, useState as useState128 } from "react";
88230
+ import { useCallback as useCallback161, useEffect as useEffect132, useRef as useRef164, useState as useState128 } from "react";
87882
88231
 
87883
88232
  // src/utils/studioUrlState.ts
87884
88233
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
@@ -88117,7 +88466,7 @@ function useStudioUrlState({
88117
88466
  const pendingSelectionRef = useRef164(initialState2.selection);
88118
88467
  const stableTimeRef = useRef164(initialState2.currentTime);
88119
88468
  const selectionApplySeqRef = useRef164(0);
88120
- const buildUrlState = useCallback160(
88469
+ const buildUrlState = useCallback161(
88121
88470
  () => ({
88122
88471
  activeCompPath,
88123
88472
  currentTime: stableTimeRef.current,
@@ -88128,7 +88477,7 @@ function useStudioUrlState({
88128
88477
  }),
88129
88478
  [activeCompPath, domEditGroupSelections, domEditSelection, rightCollapsed, rightPanelTab]
88130
88479
  );
88131
- const applyUrlSelection = useCallback160(
88480
+ const applyUrlSelection = useCallback161(
88132
88481
  (selection) => {
88133
88482
  const applySeq = ++selectionApplySeqRef.current;
88134
88483
  if (!selection) {
@@ -88273,7 +88622,7 @@ function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
88273
88622
  }
88274
88623
 
88275
88624
  // src/hooks/useStudioContextValue.ts
88276
- import { useCallback as useCallback161, useMemo as useMemo80 } from "react";
88625
+ import { useCallback as useCallback162, useMemo as useMemo80 } from "react";
88277
88626
  function buildStudioContextValue(input) {
88278
88627
  return {
88279
88628
  projectId: input.projectId,
@@ -88318,11 +88667,11 @@ function useInspectorState(rightPanel, isPlaying, domEditSelection, isGestureRec
88318
88667
  }, [rightPanel, isPlaying, isGestureRecording, domEditSelection]);
88319
88668
  }
88320
88669
  function useFileDropTarget(onImportFiles) {
88321
- const onDragOver = useCallback161((e) => {
88670
+ const onDragOver = useCallback162((e) => {
88322
88671
  if (!e.dataTransfer.types.includes("Files")) return;
88323
88672
  e.preventDefault();
88324
88673
  }, []);
88325
- const onDrop = useCallback161(
88674
+ const onDrop = useCallback162(
88326
88675
  (e) => {
88327
88676
  if (e.defaultPrevented) return;
88328
88677
  e.preventDefault();
@@ -88333,7 +88682,7 @@ function useFileDropTarget(onImportFiles) {
88333
88682
  return { onDragOver, onDrop };
88334
88683
  }
88335
88684
  function useGlobalFileDrop(handleTimelineFileDrop) {
88336
- const onDrop = useCallback161(
88685
+ const onDrop = useCallback162(
88337
88686
  (files) => {
88338
88687
  const start = usePlayerStore.getState().currentTime;
88339
88688
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -88820,10 +89169,10 @@ var StudioHeader = memo44(function StudioHeader2({
88820
89169
  });
88821
89170
 
88822
89171
  // src/hooks/useGestureCommit.ts
88823
- import { useState as useState131, useCallback as useCallback163, useRef as useRef167, useEffect as useEffect135 } from "react";
89172
+ import { useState as useState131, useCallback as useCallback164, useRef as useRef167, useEffect as useEffect135 } from "react";
88824
89173
 
88825
89174
  // src/hooks/useGestureRecording.ts
88826
- import { useCallback as useCallback162, useEffect as useEffect134, useRef as useRef166, useState as useState130 } from "react";
89175
+ import { useCallback as useCallback163, useEffect as useEffect134, useRef as useRef166, useState as useState130 } from "react";
88827
89176
  function readBasePosition(element, iframeEl) {
88828
89177
  let baseOpacity = 1;
88829
89178
  let baseScale = 1;
@@ -88980,7 +89329,7 @@ function useGestureRecording() {
88980
89329
  r.cleanup = null;
88981
89330
  };
88982
89331
  }, []);
88983
- const startRecording = useCallback162(
89332
+ const startRecording = useCallback163(
88984
89333
  (element, iframeEl, elementEndTime) => {
88985
89334
  if (isRecordingRef.current) return;
88986
89335
  isRecordingRef.current = true;
@@ -89079,7 +89428,7 @@ function useGestureRecording() {
89079
89428
  []
89080
89429
  // No deps — uses refs only for all mutable state
89081
89430
  );
89082
- const stopRecording = useCallback162(() => {
89431
+ const stopRecording = useCallback163(() => {
89083
89432
  if (!isRecordingRef.current) return [];
89084
89433
  isRecordingRef.current = false;
89085
89434
  const r = refs.current;
@@ -89091,7 +89440,7 @@ function useGestureRecording() {
89091
89440
  setIsRecording(false);
89092
89441
  return frozen;
89093
89442
  }, []);
89094
- const clearSamples = useCallback162(() => {
89443
+ const clearSamples = useCallback163(() => {
89095
89444
  const r = refs.current;
89096
89445
  r.samples = [];
89097
89446
  r.trail = [];
@@ -89363,7 +89712,7 @@ function useGestureCommit({
89363
89712
  const capturedSelectionRef = useRef167(null);
89364
89713
  const capturedAnimationsRef = useRef167([]);
89365
89714
  useEffect135(() => () => clearInterval(recordingAutoStopRef.current), []);
89366
- const cancelRecording = useCallback163(() => {
89715
+ const cancelRecording = useCallback164(() => {
89367
89716
  clearInterval(recordingAutoStopRef.current);
89368
89717
  gestureRecording.stopRecording();
89369
89718
  gestureRecording.clearSamples();
@@ -89375,7 +89724,7 @@ function useGestureCommit({
89375
89724
  useEffect135(() => {
89376
89725
  if (readOnlyPreview && gestureStateRef.current === "recording") cancelRecording();
89377
89726
  }, [cancelRecording, readOnlyPreview]);
89378
- const stopAndCommitRecording = useCallback163(async () => {
89727
+ const stopAndCommitRecording = useCallback164(async () => {
89379
89728
  clearInterval(recordingAutoStopRef.current);
89380
89729
  if (commitInFlightRef.current) {
89381
89730
  return;
@@ -89554,7 +89903,7 @@ function useGestureCommit({
89554
89903
  commitInFlightRef.current = false;
89555
89904
  }
89556
89905
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
89557
- const armAutoStop = useCallback163(
89906
+ const armAutoStop = useCallback164(
89558
89907
  (elementEnd) => {
89559
89908
  clearInterval(recordingAutoStopRef.current);
89560
89909
  const autoStopAt = elementEnd ?? Infinity;
@@ -89568,7 +89917,7 @@ function useGestureCommit({
89568
89917
  },
89569
89918
  [stopAndCommitRecording]
89570
89919
  );
89571
- const startRecording = useCallback163(
89920
+ const startRecording = useCallback164(
89572
89921
  (method) => {
89573
89922
  if (readOnlyPreview) return;
89574
89923
  const sel = domEditSessionRef.current.domEditSelection;
@@ -89603,7 +89952,7 @@ function useGestureCommit({
89603
89952
  armAutoStop
89604
89953
  ]
89605
89954
  );
89606
- const handleToggleRecording = useCallback163(
89955
+ const handleToggleRecording = useCallback164(
89607
89956
  (method = "button") => {
89608
89957
  if (commitInFlightRef.current) return;
89609
89958
  if (gestureStateRef.current === "recording") {
@@ -89765,7 +90114,7 @@ function useGestureTrailOverlay({
89765
90114
  import { useMemo as useMemo88 } from "react";
89766
90115
 
89767
90116
  // src/components/StudioLeftPanels.tsx
89768
- import { memo as memo49, useCallback as useCallback171 } from "react";
90117
+ import { memo as memo49, useCallback as useCallback172 } from "react";
89769
90118
 
89770
90119
  // src/components/MediaPreview.tsx
89771
90120
  import { useState as useState132 } from "react";
@@ -89884,7 +90233,7 @@ function MediaPreview({ projectId, filePath }) {
89884
90233
  }
89885
90234
 
89886
90235
  // src/components/sidebar/AssetsTab.tsx
89887
- import { memo as memo46, useState as useState138, useCallback as useCallback167, useRef as useRef172, useMemo as useMemo84, useEffect as useEffect141 } from "react";
90236
+ import { memo as memo46, useState as useState138, useCallback as useCallback168, useRef as useRef172, useMemo as useMemo84, useEffect as useEffect141 } from "react";
89888
90237
 
89889
90238
  // src/components/ui/SearchInput.tsx
89890
90239
  import { jsx as jsx210, jsxs as jsxs167 } from "react/jsx-runtime";
@@ -89995,11 +90344,11 @@ var CATEGORY_LABELS = {
89995
90344
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
89996
90345
 
89997
90346
  // src/components/sidebar/AudioRow.tsx
89998
- import { useState as useState134, useRef as useRef170, useEffect as useEffect137, useCallback as useCallback165 } from "react";
90347
+ import { useState as useState134, useRef as useRef170, useEffect as useEffect137, useCallback as useCallback166 } from "react";
89999
90348
  import { classifyWebAudioMediaRoute } from "@hyperframes/core/runtime/web-audio-route";
90000
90349
 
90001
90350
  // src/components/sidebar/AssetContextMenu.tsx
90002
- import { useCallback as useCallback164, useEffect as useEffect136, useLayoutEffect as useLayoutEffect14, useRef as useRef169, useState as useState133 } from "react";
90351
+ import { useCallback as useCallback165, useEffect as useEffect136, useLayoutEffect as useLayoutEffect14, useRef as useRef169, useState as useState133 } from "react";
90003
90352
  import { Fragment as Fragment59, jsx as jsx211, jsxs as jsxs168 } from "react/jsx-runtime";
90004
90353
  function isValidAssetName(name) {
90005
90354
  return name.length > 0 && !/[/\\]/.test(name) && !name.includes("..");
@@ -90059,7 +90408,7 @@ function ContextMenu3({
90059
90408
  menuRef.current?.querySelector('[role="menuitem"]')?.focus();
90060
90409
  }
90061
90410
  }, [mode]);
90062
- const commitRename = useCallback164(() => {
90411
+ const commitRename = useCallback165(() => {
90063
90412
  const trimmed = renameDraft.trim();
90064
90413
  if (trimmed === filename(asset)) {
90065
90414
  onClose();
@@ -90275,7 +90624,7 @@ function AudioRow({
90275
90624
  const serveUrl = resolveMediaPreviewUrl(asset, projectId);
90276
90625
  const isCopied = copyFeedback?.path === asset && copyFeedback.ok;
90277
90626
  const copyFailed = copyFeedback?.path === asset && !copyFeedback.ok;
90278
- const stopPlayback = useCallback165(() => {
90627
+ const stopPlayback = useCallback166(() => {
90279
90628
  audioRef.current?.pause();
90280
90629
  setPlaying(false);
90281
90630
  cancelAnimationFrame(animRef.current);
@@ -90286,7 +90635,7 @@ function AudioRow({
90286
90635
  const elements = usePlayerStore((s) => s.elements);
90287
90636
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
90288
90637
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
90289
- const activateRow = useCallback165(() => {
90638
+ const activateRow = useCallback166(() => {
90290
90639
  if (used) {
90291
90640
  const clip = findClipForAsset(elements, asset);
90292
90641
  if (clip) {
@@ -90308,10 +90657,10 @@ function AudioRow({
90308
90657
  setPreviewAsset,
90309
90658
  clearPreviewAsset
90310
90659
  ]);
90311
- const handlePointerDown = useCallback165((e) => {
90660
+ const handlePointerDown = useCallback166((e) => {
90312
90661
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
90313
90662
  }, []);
90314
- const handlePointerUp = useCallback165(
90663
+ const handlePointerUp = useCallback166(
90315
90664
  (e) => {
90316
90665
  const origin2 = pointerDownRef.current;
90317
90666
  pointerDownRef.current = null;
@@ -90357,7 +90706,7 @@ function AudioRow({
90357
90706
  }
90358
90707
  return () => cancelAnimationFrame(animRef.current);
90359
90708
  }, [playing]);
90360
- const togglePlay = useCallback165(async () => {
90709
+ const togglePlay = useCallback166(async () => {
90361
90710
  if (playing) {
90362
90711
  stopPlayback();
90363
90712
  if (stopCurrentPreview === stopPlayback) stopCurrentPreview = null;
@@ -90555,7 +90904,7 @@ function GlobalAssetsView({ searchQuery }) {
90555
90904
  }
90556
90905
 
90557
90906
  // src/components/sidebar/AssetCard.tsx
90558
- import { useState as useState137, useEffect as useEffect140, useRef as useRef171, useCallback as useCallback166 } from "react";
90907
+ import { useState as useState137, useEffect as useEffect140, useRef as useRef171, useCallback as useCallback167 } from "react";
90559
90908
 
90560
90909
  // src/components/ui/VideoFrameThumbnail.tsx
90561
90910
  import { useState as useState136, useEffect as useEffect139 } from "react";
@@ -90702,10 +91051,10 @@ function AssetCard({
90702
91051
  const elements = usePlayerStore((s) => s.elements);
90703
91052
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
90704
91053
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
90705
- const handlePointerDown = useCallback166((e) => {
91054
+ const handlePointerDown = useCallback167((e) => {
90706
91055
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
90707
91056
  }, []);
90708
- const activateCard = useCallback166(() => {
91057
+ const activateCard = useCallback167(() => {
90709
91058
  if (used) {
90710
91059
  const clip = findClipForAsset(elements, asset);
90711
91060
  if (clip) {
@@ -90727,7 +91076,7 @@ function AssetCard({
90727
91076
  setPreviewAsset,
90728
91077
  clearPreviewAsset
90729
91078
  ]);
90730
- const handlePointerUp = useCallback166(
91079
+ const handlePointerUp = useCallback167(
90731
91080
  (e) => {
90732
91081
  const origin2 = pointerDownRef.current;
90733
91082
  pointerDownRef.current = null;
@@ -91059,7 +91408,7 @@ var AssetsTab = memo46(function AssetsTab2({
91059
91408
  cancelled = true;
91060
91409
  };
91061
91410
  }, [projectId, assetsKey]);
91062
- const handleImport = useCallback167(
91411
+ const handleImport = useCallback168(
91063
91412
  async (files) => {
91064
91413
  if (!onImport) return;
91065
91414
  setImporting(true);
@@ -91071,7 +91420,7 @@ var AssetsTab = memo46(function AssetsTab2({
91071
91420
  },
91072
91421
  [onImport]
91073
91422
  );
91074
- const handleDrop = useCallback167(
91423
+ const handleDrop = useCallback168(
91075
91424
  (e) => {
91076
91425
  e.preventDefault();
91077
91426
  setDragOver(false);
@@ -91079,7 +91428,7 @@ var AssetsTab = memo46(function AssetsTab2({
91079
91428
  },
91080
91429
  [handleImport]
91081
91430
  );
91082
- const handleCopyPath = useCallback167(async (path) => {
91431
+ const handleCopyPath = useCallback168(async (path) => {
91083
91432
  const copied = await copyTextToClipboard(path);
91084
91433
  setCopyFeedback({ path, ok: copied });
91085
91434
  setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
@@ -91289,11 +91638,11 @@ var AssetsTab = memo46(function AssetsTab2({
91289
91638
  });
91290
91639
 
91291
91640
  // src/components/sidebar/BlocksTab.tsx
91292
- import { memo as memo47, useState as useState140, useCallback as useCallback169, useRef as useRef174, useEffect as useEffect143 } from "react";
91641
+ import { memo as memo47, useState as useState140, useCallback as useCallback170, useRef as useRef174, useEffect as useEffect143 } from "react";
91293
91642
  import { createPortal as createPortal13 } from "react-dom";
91294
91643
 
91295
91644
  // src/components/sidebar/PromptPreviewModal.tsx
91296
- import { useState as useState139, useCallback as useCallback168, useEffect as useEffect142, useRef as useRef173 } from "react";
91645
+ import { useState as useState139, useCallback as useCallback169, useEffect as useEffect142, useRef as useRef173 } from "react";
91297
91646
  import { jsx as jsx217, jsxs as jsxs173 } from "react/jsx-runtime";
91298
91647
  function PromptPreviewModal({
91299
91648
  title,
@@ -91319,7 +91668,7 @@ function PromptPreviewModal({
91319
91668
  textareaRef.current?.focus();
91320
91669
  });
91321
91670
  }, []);
91322
- const handleCopy2 = useCallback168(async () => {
91671
+ const handleCopy2 = useCallback169(async () => {
91323
91672
  try {
91324
91673
  await navigator.clipboard.writeText(valueRef.current);
91325
91674
  setCopyState("copied");
@@ -91605,13 +91954,13 @@ function BlockCard({
91605
91954
  const hoverTimer = useRef174(null);
91606
91955
  const colors = getCategoryColors(category);
91607
91956
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
91608
- const handleEnter = useCallback169(() => {
91957
+ const handleEnter = useCallback170(() => {
91609
91958
  hoverTimer.current = setTimeout(() => {
91610
91959
  setHovered(true);
91611
91960
  onPreview?.({ videoUrl, posterUrl, title });
91612
91961
  }, 300);
91613
91962
  }, [onPreview, videoUrl, posterUrl, title]);
91614
- const handleLeave = useCallback169(() => {
91963
+ const handleLeave = useCallback170(() => {
91615
91964
  if (hoverTimer.current) {
91616
91965
  clearTimeout(hoverTimer.current);
91617
91966
  hoverTimer.current = null;
@@ -91624,7 +91973,7 @@ function BlockCard({
91624
91973
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
91625
91974
  };
91626
91975
  }, []);
91627
- const handleAdd = useCallback169(
91976
+ const handleAdd = useCallback170(
91628
91977
  async (e) => {
91629
91978
  e.stopPropagation();
91630
91979
  if (addState !== "idle" || !onAdd) return;
@@ -91640,7 +91989,7 @@ function BlockCard({
91640
91989
  [onAdd, addState]
91641
91990
  );
91642
91991
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
91643
- const handleShowPrompt = useCallback169(
91992
+ const handleShowPrompt = useCallback170(
91644
91993
  (e) => {
91645
91994
  e.stopPropagation();
91646
91995
  const state = usePlayerStore.getState();
@@ -91780,7 +92129,7 @@ function BlockCard({
91780
92129
  }
91781
92130
 
91782
92131
  // src/components/sidebar/CompositionsTab.tsx
91783
- import { memo as memo48, useCallback as useCallback170, useEffect as useEffect144, useLayoutEffect as useLayoutEffect15, useMemo as useMemo85, useRef as useRef175, useState as useState141 } from "react";
92132
+ import { memo as memo48, useCallback as useCallback171, useEffect as useEffect144, useLayoutEffect as useLayoutEffect15, useMemo as useMemo85, useRef as useRef175, useState as useState141 } from "react";
91784
92133
  import { jsx as jsx219, jsxs as jsxs175 } from "react/jsx-runtime";
91785
92134
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
91786
92135
  var CARD_W = 80;
@@ -91891,7 +92240,7 @@ function CompCard({
91891
92240
  const hoverTimer = useRef175(null);
91892
92241
  const syncTimer = useRef175(null);
91893
92242
  const draggedRef = useRef175(false);
91894
- const requestIframePlaybackSync = useCallback170((shouldPlay) => {
92243
+ const requestIframePlaybackSync = useCallback171((shouldPlay) => {
91895
92244
  if (syncTimer.current) {
91896
92245
  clearTimeout(syncTimer.current);
91897
92246
  syncTimer.current = null;
@@ -92271,7 +92620,7 @@ var StudioLeftPanels = memo49(function StudioLeftPanels2({
92271
92620
  handleImportFiles,
92272
92621
  handleContentChange
92273
92622
  } = useFileManagerContext();
92274
- const handleRenderComposition = useCallback171(
92623
+ const handleRenderComposition = useCallback172(
92275
92624
  async (comp) => {
92276
92625
  if (renderQueue.ffmpegMissing) {
92277
92626
  useDockLayoutStore.getState().activatePanel("renders");
@@ -92381,13 +92730,13 @@ function CodeBody({
92381
92730
  }
92382
92731
 
92383
92732
  // src/components/StudioRightPanels.tsx
92384
- import { memo as memo59, useCallback as useCallback188 } from "react";
92733
+ import { memo as memo59, useCallback as useCallback189 } from "react";
92385
92734
 
92386
92735
  // src/components/editor/LayersPanel.tsx
92387
- import { memo as memo50, useState as useState143, useCallback as useCallback173, useEffect as useEffect145, useRef as useRef177 } from "react";
92736
+ import { memo as memo50, useState as useState143, useCallback as useCallback174, useEffect as useEffect145, useRef as useRef177 } from "react";
92388
92737
 
92389
92738
  // src/components/editor/useLayerDrag.ts
92390
- import { useRef as useRef176, useState as useState142, useCallback as useCallback172 } from "react";
92739
+ import { useRef as useRef176, useState as useState142, useCallback as useCallback173 } from "react";
92391
92740
  var DRAG_THRESHOLD_PX4 = 4;
92392
92741
  function isLayerDraggable(layer) {
92393
92742
  if (!(layer.selector || layer.id)) return false;
@@ -92457,7 +92806,7 @@ function useLayerDrag({
92457
92806
  const dragRef = useRef176(null);
92458
92807
  const [dragKey, setDragKey] = useState142(null);
92459
92808
  const [insertionLineY, setInsertionLineY] = useState142(null);
92460
- const handleRowPointerDown = useCallback172(
92809
+ const handleRowPointerDown = useCallback173(
92461
92810
  (layerIndex, e) => {
92462
92811
  if (e.button !== 0) return;
92463
92812
  const layer = visibleLayers[layerIndex];
@@ -92484,7 +92833,7 @@ function useLayerDrag({
92484
92833
  },
92485
92834
  [visibleLayers, scrollContainerRef, onSingleSibling]
92486
92835
  );
92487
- const handleContainerPointerMove = useCallback172(
92836
+ const handleContainerPointerMove = useCallback173(
92488
92837
  (e) => {
92489
92838
  const drag = dragRef.current;
92490
92839
  if (!drag) return;
@@ -92510,7 +92859,7 @@ function useLayerDrag({
92510
92859
  },
92511
92860
  [visibleLayers, scrollContainerRef]
92512
92861
  );
92513
- const handleContainerPointerUp = useCallback172(() => {
92862
+ const handleContainerPointerUp = useCallback173(() => {
92514
92863
  const drag = dragRef.current;
92515
92864
  dragRef.current = null;
92516
92865
  setDragKey(null);
@@ -92685,7 +93034,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92685
93034
  });
92686
93035
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
92687
93036
  const livePreviewIframe = useLivePreviewIframe();
92688
- const collectLayers = useCallback173(() => {
93037
+ const collectLayers = useCallback174(() => {
92689
93038
  const iframe = previewIframeRef.current;
92690
93039
  if (!iframe) return;
92691
93040
  let doc = null;
@@ -92733,7 +93082,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92733
93082
  throttle.cancel();
92734
93083
  };
92735
93084
  }, [collectLayers]);
92736
- const resolveSelection = useCallback173(
93085
+ const resolveSelection = useCallback174(
92737
93086
  (layer) => {
92738
93087
  const doc = previewIframeRef.current?.contentDocument;
92739
93088
  const el = liveLayerElement(layer, doc, activeCompPath);
@@ -92746,7 +93095,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92746
93095
  },
92747
93096
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
92748
93097
  );
92749
- const seekToLayer = useCallback173(
93098
+ const seekToLayer = useCallback174(
92750
93099
  async (layer) => {
92751
93100
  const selection = await resolveSelection(layer);
92752
93101
  if (!selection) return;
@@ -92775,7 +93124,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92775
93124
  },
92776
93125
  [currentTime, resolveSelection, timelineElements]
92777
93126
  );
92778
- const handleSelectLayer = useCallback173(
93127
+ const handleSelectLayer = useCallback174(
92779
93128
  async (layer) => {
92780
93129
  const selection = await resolveSelection(layer);
92781
93130
  if (!selection) return;
@@ -92785,7 +93134,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92785
93134
  },
92786
93135
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
92787
93136
  );
92788
- const handleLayerDoubleClick = useCallback173(
93137
+ const handleLayerDoubleClick = useCallback174(
92789
93138
  async (layer) => {
92790
93139
  const selection = await resolveSelection(layer);
92791
93140
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -92796,7 +93145,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92796
93145
  },
92797
93146
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
92798
93147
  );
92799
- const handleLayerHover = useCallback173(
93148
+ const handleLayerHover = useCallback174(
92800
93149
  async (layer) => {
92801
93150
  if (!layer) {
92802
93151
  updateDomEditHoverSelection(null);
@@ -92807,11 +93156,11 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92807
93156
  },
92808
93157
  [resolveSelection, updateDomEditHoverSelection]
92809
93158
  );
92810
- const toggleCollapse = useCallback173((key2, e) => {
93159
+ const toggleCollapse = useCallback174((key2, e) => {
92811
93160
  e.stopPropagation();
92812
93161
  setCollapsed((prev) => ({ ...prev, [key2]: !prev[key2] }));
92813
93162
  }, []);
92814
- const handleReorder = useCallback173(
93163
+ const handleReorder = useCallback174(
92815
93164
  (event) => {
92816
93165
  const { siblingLayers, fromIndex, toIndex } = event;
92817
93166
  const reordered = [...siblingLayers];
@@ -92874,7 +93223,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92874
93223
  );
92875
93224
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
92876
93225
  const visibleLayers = getVisibleLayers(layers, collapsed);
92877
- const handleSingleSibling = useCallback173(() => {
93226
+ const handleSingleSibling = useCallback174(() => {
92878
93227
  showToast("Only one layer at this level", "info");
92879
93228
  }, [showToast]);
92880
93229
  const {
@@ -93006,10 +93355,10 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
93006
93355
  });
93007
93356
 
93008
93357
  // src/captions/components/CaptionPropertyPanel.tsx
93009
- import { memo as memo52, useCallback as useCallback175, useState as useState145 } from "react";
93358
+ import { memo as memo52, useCallback as useCallback176, useState as useState145 } from "react";
93010
93359
 
93011
93360
  // src/captions/components/CaptionAnimationPanel.tsx
93012
- import { memo as memo51, useCallback as useCallback174 } from "react";
93361
+ import { memo as memo51, useCallback as useCallback175 } from "react";
93013
93362
 
93014
93363
  // src/captions/components/shared.tsx
93015
93364
  import { useEffect as useEffect146, useRef as useRef178, useState as useState144 } from "react";
@@ -93234,25 +93583,25 @@ var CaptionAnimationPanel = memo51(function CaptionAnimationPanel2() {
93234
93583
  }
93235
93584
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
93236
93585
  const animation = group?.animation;
93237
- const handleEntranceChange = useCallback174(
93586
+ const handleEntranceChange = useCallback175(
93238
93587
  (update) => {
93239
93588
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
93240
93589
  },
93241
93590
  [resolvedGroupId, updateGroupAnimation]
93242
93591
  );
93243
- const handleHighlightChange = useCallback174(
93592
+ const handleHighlightChange = useCallback175(
93244
93593
  (update) => {
93245
93594
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
93246
93595
  },
93247
93596
  [resolvedGroupId, updateGroupAnimation]
93248
93597
  );
93249
- const handleExitChange = useCallback174(
93598
+ const handleExitChange = useCallback175(
93250
93599
  (update) => {
93251
93600
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
93252
93601
  },
93253
93602
  [resolvedGroupId, updateGroupAnimation]
93254
93603
  );
93255
- const handleApplyToAll = useCallback174(() => {
93604
+ const handleApplyToAll = useCallback175(() => {
93256
93605
  if (animation) applyAnimationToAll(animation);
93257
93606
  }, [animation, applyAnimationToAll]);
93258
93607
  if (!group || !resolvedGroupId || !animation) {
@@ -93352,7 +93701,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93352
93701
  ...groupStyle,
93353
93702
  ...segmentOverrides
93354
93703
  };
93355
- const applyToIframeDom = useCallback175(
93704
+ const applyToIframeDom = useCallback176(
93356
93705
  (updates) => {
93357
93706
  const iframe = iframeRef.current;
93358
93707
  if (!iframe || !model) return;
@@ -93426,7 +93775,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93426
93775
  },
93427
93776
  [iframeRef, model, selectedSegmentIds]
93428
93777
  );
93429
- const handleStyleChange = useCallback175(
93778
+ const handleStyleChange = useCallback176(
93430
93779
  (updates) => {
93431
93780
  if (selectedGroupId) {
93432
93781
  updateGroupStyle(selectedGroupId, updates);
@@ -93531,7 +93880,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93531
93880
  });
93532
93881
 
93533
93882
  // src/components/editor/BlockParamsPanel.tsx
93534
- import { memo as memo53, useCallback as useCallback176, useEffect as useEffect147, useRef as useRef179, useState as useState146 } from "react";
93883
+ import { memo as memo53, useCallback as useCallback177, useEffect as useEffect147, useRef as useRef179, useState as useState146 } from "react";
93535
93884
  import { jsx as jsx227, jsxs as jsxs182 } from "react/jsx-runtime";
93536
93885
  var BlockParamsPanel = memo53(function BlockParamsPanel2({
93537
93886
  blockName,
@@ -93554,7 +93903,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93554
93903
  );
93555
93904
  const [commitState, setCommitState] = useState146({ tone: "idle" });
93556
93905
  const commitTimersRef = useRef179(/* @__PURE__ */ new Map());
93557
- const commitParamNow = useCallback176(
93906
+ const commitParamNow = useCallback177(
93558
93907
  async (key2, nextValue) => {
93559
93908
  if (!fileManager) return;
93560
93909
  const previous = appliedRef.current[key2];
@@ -93599,7 +93948,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93599
93948
  },
93600
93949
  [fileManager, compositionPath, setRefreshKey, blockName]
93601
93950
  );
93602
- const commitParam = useCallback176(
93951
+ const commitParam = useCallback177(
93603
93952
  (key2, nextValue) => fileManager ? serializeStudioFileMutation(
93604
93953
  fileManager.writeProjectFile,
93605
93954
  compositionPath,
@@ -93607,7 +93956,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93607
93956
  ) : Promise.resolve(),
93608
93957
  [commitParamNow, compositionPath, fileManager]
93609
93958
  );
93610
- const handleChange = useCallback176(
93959
+ const handleChange = useCallback177(
93611
93960
  (key2, value) => {
93612
93961
  setValues((prev) => ({ ...prev, [key2]: value }));
93613
93962
  const timers = commitTimersRef.current;
@@ -93770,7 +94119,7 @@ import { createPortal as createPortal14 } from "react-dom";
93770
94119
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
93771
94120
 
93772
94121
  // src/components/renders/RenderQueueItem.tsx
93773
- import { memo as memo54, useCallback as useCallback177, useState as useState147 } from "react";
94122
+ import { memo as memo54, useCallback as useCallback178, useState as useState147 } from "react";
93774
94123
  import { Fragment as Fragment64, jsx as jsx228, jsxs as jsxs183 } from "react/jsx-runtime";
93775
94124
  function formatDuration2(ms) {
93776
94125
  if (ms < 1e3) return `${ms}ms`;
@@ -93793,10 +94142,10 @@ var RenderQueueItem = memo54(function RenderQueueItem2({
93793
94142
  const [videoReady, setVideoReady] = useState147(false);
93794
94143
  const [confirmingDelete, setConfirmingDelete] = useState147(false);
93795
94144
  const fileSrc = buildProjectApiPath(projectId, `/renders/file/${job.filename}`);
93796
- const handleOpen = useCallback177(() => {
94145
+ const handleOpen = useCallback178(() => {
93797
94146
  window.open(fileSrc, "_blank");
93798
94147
  }, [fileSrc]);
93799
- const handleDownload = useCallback177(
94148
+ const handleDownload = useCallback178(
93800
94149
  (e) => {
93801
94150
  e.stopPropagation();
93802
94151
  const a = document.createElement("a");
@@ -94544,11 +94893,11 @@ var RenderQueuePanel = memo57(function RenderQueuePanel2() {
94544
94893
  });
94545
94894
 
94546
94895
  // src/components/panels/SlideshowPanel.tsx
94547
- import { useState as useState151, useEffect as useEffect150, useCallback as useCallback179, useRef as useRef182 } from "react";
94896
+ import { useState as useState151, useEffect as useEffect150, useCallback as useCallback180, useRef as useRef182 } from "react";
94548
94897
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
94549
94898
 
94550
94899
  // src/components/panels/SlideshowSubPanels.tsx
94551
- import { useState as useState150, useCallback as useCallback178, useId as useId6 } from "react";
94900
+ import { useState as useState150, useCallback as useCallback179, useId as useId6 } from "react";
94552
94901
  import { jsx as jsx232, jsxs as jsxs186 } from "react/jsx-runtime";
94553
94902
  function SectionHeader({
94554
94903
  children,
@@ -94736,7 +95085,7 @@ function BranchTree({
94736
95085
  }) {
94737
95086
  const [newLabel, setNewLabel] = useState150("");
94738
95087
  const inputId = useId6();
94739
- const handleCreate = useCallback178(() => {
95088
+ const handleCreate = useCallback179(() => {
94740
95089
  const label = newLabel.trim();
94741
95090
  if (!label) return;
94742
95091
  onCreateSequence(label);
@@ -94798,7 +95147,7 @@ function BranchItem({
94798
95147
  const [editing, setEditing] = useState150(false);
94799
95148
  const [draft, setDraft] = useState150(seq.label);
94800
95149
  const [confirmingDelete, setConfirmingDelete] = useState150(false);
94801
- const commitRename = useCallback178(() => {
95150
+ const commitRename = useCallback179(() => {
94802
95151
  const label = draft.trim();
94803
95152
  if (label && label !== seq.label) onRename(seq.id, label);
94804
95153
  setEditing(false);
@@ -94931,7 +95280,7 @@ function HotspotTool({
94931
95280
  const selectedElementId = domEditSelection?.element?.id ?? null;
94932
95281
  const selectedHfId = domEditSelection?.hfId ?? null;
94933
95282
  const elementKey3 = selectedElementId || selectedHfId;
94934
- const handleMakeHotspot = useCallback178(() => {
95283
+ const handleMakeHotspot = useCallback179(() => {
94935
95284
  if (!selectedSceneId || !targetSequenceId || !elementKey3) return;
94936
95285
  const id = `hotspot-${elementKey3}-${generateId()}`;
94937
95286
  const label = hotspotLabel.trim() || elementKey3;
@@ -95256,7 +95605,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95256
95605
  setUndoDepth(0);
95257
95606
  setPersistError(false);
95258
95607
  }, [compHtml]);
95259
- const applyManifest = useCallback179(
95608
+ const applyManifest = useCallback180(
95260
95609
  async (next, opts) => {
95261
95610
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
95262
95611
  if (!opts?.skipUndo) {
@@ -95276,14 +95625,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95276
95625
  },
95277
95626
  [onPersist]
95278
95627
  );
95279
- const handleUndo = useCallback179(() => {
95628
+ const handleUndo = useCallback180(() => {
95280
95629
  const prev = undoStackRef.current.pop();
95281
95630
  if (!prev) return;
95282
95631
  setUndoDepth(undoStackRef.current.length);
95283
95632
  applyManifest(prev, { skipUndo: true }).catch(() => {
95284
95633
  });
95285
95634
  }, [applyManifest]);
95286
- const handleRetryPersist = useCallback179(async () => {
95635
+ const handleRetryPersist = useCallback180(async () => {
95287
95636
  setRetrying(true);
95288
95637
  try {
95289
95638
  await onPersist(manifestRef.current);
@@ -95294,7 +95643,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95294
95643
  setRetrying(false);
95295
95644
  }
95296
95645
  }, [onPersist]);
95297
- const applyNotesManifest = useCallback179(
95646
+ const applyNotesManifest = useCallback180(
95298
95647
  (next) => {
95299
95648
  setManifest(next);
95300
95649
  manifestRef.current = next;
@@ -95308,7 +95657,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95308
95657
  ctrl.flush();
95309
95658
  };
95310
95659
  }, []);
95311
- const toggleSection = useCallback179((key2) => {
95660
+ const toggleSection = useCallback180((key2) => {
95312
95661
  setExpandedSections((prev) => {
95313
95662
  const next = new Set(prev);
95314
95663
  if (next.has(key2)) {
@@ -95322,25 +95671,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95322
95671
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
95323
95672
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
95324
95673
  const sequences = manifest.slideSequences ?? [];
95325
- const handleSelectBranchSlide = useCallback179((sequenceId, sceneId) => {
95674
+ const handleSelectBranchSlide = useCallback180((sequenceId, sceneId) => {
95326
95675
  setSelectedSceneId(sceneId);
95327
95676
  setSelectedSequenceId(sequenceId);
95328
95677
  }, []);
95329
- const handleToggleSlide = useCallback179(
95678
+ const handleToggleSlide = useCallback180(
95330
95679
  (sceneId) => {
95331
95680
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
95332
95681
  });
95333
95682
  },
95334
95683
  [applyManifest]
95335
95684
  );
95336
- const handleReorder = useCallback179(
95685
+ const handleReorder = useCallback180(
95337
95686
  (sceneId, dir) => {
95338
95687
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
95339
95688
  });
95340
95689
  },
95341
95690
  [applyManifest]
95342
95691
  );
95343
- const handleSetNotes = useCallback179(
95692
+ const handleSetNotes = useCallback180(
95344
95693
  (notes) => {
95345
95694
  if (!selectedSceneId) return;
95346
95695
  applyNotesManifest(
@@ -95349,7 +95698,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95349
95698
  },
95350
95699
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
95351
95700
  );
95352
- const handleMarkFragment = useCallback179(() => {
95701
+ const handleMarkFragment = useCallback180(() => {
95353
95702
  if (!selectedSceneId) return;
95354
95703
  applyManifest(
95355
95704
  addFragment(
@@ -95361,7 +95710,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95361
95710
  ).catch(() => {
95362
95711
  });
95363
95712
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
95364
- const handleRemoveFragment = useCallback179(
95713
+ const handleRemoveFragment = useCallback180(
95365
95714
  (time) => {
95366
95715
  if (!selectedSceneId) return;
95367
95716
  applyManifest(
@@ -95371,7 +95720,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95371
95720
  },
95372
95721
  [selectedSceneId, selectedSequenceId, applyManifest]
95373
95722
  );
95374
- const handleCreateSequence = useCallback179(
95723
+ const handleCreateSequence = useCallback180(
95375
95724
  (label) => {
95376
95725
  const id = `seq-${generateId()}`;
95377
95726
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -95379,21 +95728,21 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95379
95728
  },
95380
95729
  [applyManifest]
95381
95730
  );
95382
- const handleRenameSequence = useCallback179(
95731
+ const handleRenameSequence = useCallback180(
95383
95732
  (id, label) => {
95384
95733
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
95385
95734
  });
95386
95735
  },
95387
95736
  [applyManifest]
95388
95737
  );
95389
- const handleDeleteSequence = useCallback179(
95738
+ const handleDeleteSequence = useCallback180(
95390
95739
  (id) => {
95391
95740
  applyManifest(deleteSequence(manifestRef.current, id)).catch(() => {
95392
95741
  });
95393
95742
  },
95394
95743
  [applyManifest]
95395
95744
  );
95396
- const handleAssign = useCallback179(
95745
+ const handleAssign = useCallback180(
95397
95746
  (sequenceId, sceneId, assign) => {
95398
95747
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
95399
95748
  () => {
@@ -95402,7 +95751,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95402
95751
  },
95403
95752
  [applyManifest]
95404
95753
  );
95405
- const handleAddHotspot = useCallback179(
95754
+ const handleAddHotspot = useCallback180(
95406
95755
  (sceneId, hotspot) => {
95407
95756
  applyManifest(
95408
95757
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -95411,7 +95760,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95411
95760
  },
95412
95761
  [selectedSequenceId, applyManifest]
95413
95762
  );
95414
- const handleRemoveHotspot = useCallback179(
95763
+ const handleRemoveHotspot = useCallback180(
95415
95764
  (sceneId, hotspotId) => {
95416
95765
  applyManifest(
95417
95766
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -95561,10 +95910,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95561
95910
  }
95562
95911
 
95563
95912
  // src/components/panels/VariablesPanel.tsx
95564
- import { memo as memo58, useCallback as useCallback183, useEffect as useEffect152, useMemo as useMemo86, useState as useState155 } from "react";
95913
+ import { memo as memo58, useCallback as useCallback184, useEffect as useEffect152, useMemo as useMemo86, useState as useState155 } from "react";
95565
95914
 
95566
95915
  // src/hooks/useVariablesPersist.ts
95567
- import { useCallback as useCallback180 } from "react";
95916
+ import { useCallback as useCallback181 } from "react";
95568
95917
  function useVariablesPersist({
95569
95918
  sdkSession,
95570
95919
  activeCompPath,
@@ -95574,7 +95923,7 @@ function useVariablesPersist({
95574
95923
  reloadPreview,
95575
95924
  publishSdkSession
95576
95925
  }) {
95577
- return useCallback180(
95926
+ return useCallback181(
95578
95927
  async (label, mutate) => {
95579
95928
  if (!sdkSession) return false;
95580
95929
  const path = activeCompPath ?? "index.html";
@@ -95609,10 +95958,10 @@ function useVariablesPersist({
95609
95958
  }
95610
95959
 
95611
95960
  // src/components/panels/VariablesOtherCompositions.tsx
95612
- import { useCallback as useCallback182, useState as useState154 } from "react";
95961
+ import { useCallback as useCallback183, useState as useState154 } from "react";
95613
95962
 
95614
95963
  // src/hooks/useProjectCompositionVariables.ts
95615
- import { useCallback as useCallback181, useEffect as useEffect151, useState as useState152 } from "react";
95964
+ import { useCallback as useCallback182, useEffect as useEffect151, useState as useState152 } from "react";
95616
95965
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
95617
95966
  async function readGroup(path, readProjectFile) {
95618
95967
  let content;
@@ -95655,7 +96004,7 @@ function useProjectCompositionVariables(compositionPaths, excludePath, readProje
95655
96004
  }
95656
96005
  function useEditVariablesInFile(deps) {
95657
96006
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview } = deps;
95658
- return useCallback181(
96007
+ return useCallback182(
95659
96008
  async (path, label, mutate) => {
95660
96009
  const originalContent = await readProjectFile(path);
95661
96010
  await persistSdkSerialize(
@@ -96032,7 +96381,7 @@ function VariablesOtherCompositions({
96032
96381
  reloadPreview
96033
96382
  });
96034
96383
  const [editingKey, setEditingKey] = useState154(null);
96035
- const onSave = useCallback182(
96384
+ const onSave = useCallback183(
96036
96385
  (path, decl) => {
96037
96386
  setEditingKey(null);
96038
96387
  void editInFile(
@@ -96043,7 +96392,7 @@ function VariablesOtherCompositions({
96043
96392
  },
96044
96393
  [editInFile]
96045
96394
  );
96046
- const onRemove = useCallback182(
96395
+ const onRemove = useCallback183(
96047
96396
  (path, id) => {
96048
96397
  void editInFile(
96049
96398
  path,
@@ -96277,7 +96626,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96277
96626
  // eslint-disable-next-line react-hooks/exhaustive-deps
96278
96627
  [sdkSession, previewValues, refreshKey, revision]
96279
96628
  );
96280
- const copyToClipboard = useCallback183(
96629
+ const copyToClipboard = useCallback184(
96281
96630
  (text, what) => {
96282
96631
  void copyTextToClipboard(text).then(
96283
96632
  (ok) => showToast(
@@ -96288,7 +96637,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96288
96637
  },
96289
96638
  [showToast]
96290
96639
  );
96291
- const dropPreviewOverride = useCallback183(
96640
+ const dropPreviewOverride = useCallback184(
96292
96641
  (id) => {
96293
96642
  if (previewValues && id in previewValues) {
96294
96643
  const next = { ...previewValues };
@@ -96298,7 +96647,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96298
96647
  },
96299
96648
  [previewValues, setPreviewValues]
96300
96649
  );
96301
- const commitPreviewValue = useCallback183(
96650
+ const commitPreviewValue = useCallback184(
96302
96651
  (id, value, declDefault) => {
96303
96652
  const next = { ...previewValues ?? {} };
96304
96653
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -96311,7 +96660,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96311
96660
  },
96312
96661
  [previewValues, setPreviewValues, reloadPreview]
96313
96662
  );
96314
- const runSchemaEdit = useCallback183(
96663
+ const runSchemaEdit = useCallback184(
96315
96664
  async (label, mutate) => {
96316
96665
  try {
96317
96666
  const changed = await persistVariables(label, mutate);
@@ -96325,7 +96674,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96325
96674
  },
96326
96675
  [persistVariables, showToast]
96327
96676
  );
96328
- const handleAdd = useCallback183(
96677
+ const handleAdd = useCallback184(
96329
96678
  (decl) => {
96330
96679
  if (!sdkSession) return;
96331
96680
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -96338,7 +96687,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96338
96687
  },
96339
96688
  [sdkSession, runSchemaEdit, showToast]
96340
96689
  );
96341
- const handleUpdate = useCallback183(
96690
+ const handleUpdate = useCallback184(
96342
96691
  (decl) => {
96343
96692
  if (!sdkSession) return;
96344
96693
  const check = sdkSession.can({
@@ -96358,7 +96707,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96358
96707
  },
96359
96708
  [sdkSession, runSchemaEdit, showToast]
96360
96709
  );
96361
- const handleRemove = useCallback183(
96710
+ const handleRemove = useCallback184(
96362
96711
  (id) => {
96363
96712
  if (!sdkSession) return;
96364
96713
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -96374,18 +96723,18 @@ var VariablesPanel = memo58(function VariablesPanel2({
96374
96723
  },
96375
96724
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
96376
96725
  );
96377
- const handleSetDefault = useCallback183(
96726
+ const handleSetDefault = useCallback184(
96378
96727
  (id, value) => {
96379
96728
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
96380
96729
  dropPreviewOverride(id);
96381
96730
  },
96382
96731
  [runSchemaEdit, dropPreviewOverride]
96383
96732
  );
96384
- const resetPreview = useCallback183(() => {
96733
+ const resetPreview = useCallback184(() => {
96385
96734
  setPreviewValues(null);
96386
96735
  reloadPreview();
96387
96736
  }, [setPreviewValues, reloadPreview]);
96388
- const handleBind = useCallback183(
96737
+ const handleBind = useCallback184(
96389
96738
  // Guard chain (session, selection, type-compat) — one branch per guard.
96390
96739
  // fallow-ignore-next-line complexity
96391
96740
  (action, id) => {
@@ -96497,7 +96846,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96497
96846
  });
96498
96847
 
96499
96848
  // src/hooks/useSlideshowPersist.ts
96500
- import { useCallback as useCallback184 } from "react";
96849
+ import { useCallback as useCallback185 } from "react";
96501
96850
 
96502
96851
  // src/utils/setSlideshowManifest.ts
96503
96852
  import {
@@ -96554,7 +96903,7 @@ function useSlideshowPersist({
96554
96903
  publishSdkSession,
96555
96904
  coalesceKey
96556
96905
  }) {
96557
- return useCallback184(
96906
+ return useCallback185(
96558
96907
  async (manifest) => {
96559
96908
  if (!sdkSession) return;
96560
96909
  const path = activeCompPath ?? "index.html";
@@ -96612,7 +96961,7 @@ function useSlideshowTabState(params) {
96612
96961
  }
96613
96962
 
96614
96963
  // src/components/DesignPanelPromoteProvider.tsx
96615
- import { useCallback as useCallback185 } from "react";
96964
+ import { useCallback as useCallback186 } from "react";
96616
96965
  import { jsx as jsx238 } from "react/jsx-runtime";
96617
96966
  function DesignPanelPromoteProvider({
96618
96967
  selection,
@@ -96631,7 +96980,7 @@ function DesignPanelPromoteProvider({
96631
96980
  publishSdkSession: handle.publish,
96632
96981
  activeCompPath: targetPath
96633
96982
  });
96634
- const persist2 = useCallback185(
96983
+ const persist2 = useCallback186(
96635
96984
  async (label, mutate) => {
96636
96985
  const committed = await rawPersist(label, mutate);
96637
96986
  if (committed) forceReloadSharedSdkSession?.();
@@ -96639,7 +96988,7 @@ function DesignPanelPromoteProvider({
96639
96988
  },
96640
96989
  [rawPersist, forceReloadSharedSdkSession]
96641
96990
  );
96642
- const handlePersistError = useCallback185(
96991
+ const handlePersistError = useCallback186(
96643
96992
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
96644
96993
  [showToast]
96645
96994
  );
@@ -96656,7 +97005,7 @@ function DesignPanelPromoteProvider({
96656
97005
  }
96657
97006
 
96658
97007
  // src/hooks/useRemoveBackground.ts
96659
- import { useCallback as useCallback186, useEffect as useEffect153, useRef as useRef183 } from "react";
97008
+ import { useCallback as useCallback187, useEffect as useEffect153, useRef as useRef183 } from "react";
96660
97009
 
96661
97010
  // src/components/studioMediaJobs.ts
96662
97011
  var MEDIA_JOB_RECONNECT_TIMEOUT_MS = 15e3;
@@ -96769,7 +97118,7 @@ function useRemoveBackground(projectId, refreshFileTree, showToast) {
96769
97118
  },
96770
97119
  []
96771
97120
  );
96772
- return useCallback186(
97121
+ return useCallback187(
96773
97122
  // fallow-ignore-next-line complexity
96774
97123
  async (inputPath, options) => {
96775
97124
  const response = await studioApiFetch(
@@ -96808,7 +97157,7 @@ function useRemoveBackground(projectId, refreshFileTree, showToast) {
96808
97157
  }
96809
97158
 
96810
97159
  // src/hooks/useApplyColorGradingScope.ts
96811
- import { useCallback as useCallback187 } from "react";
97160
+ import { useCallback as useCallback188 } from "react";
96812
97161
 
96813
97162
  // src/components/editor/colorGradingScopePatch.ts
96814
97163
  var MEDIA_TAG_RE = /<\s*(video|img)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
@@ -96912,7 +97261,7 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
96912
97261
  const { projectId, activeCompPath, showToast, waitForPendingDomEditSaves } = useStudioShellContext();
96913
97262
  const { domEditSelection } = useDomEditContext();
96914
97263
  const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
96915
- return useCallback187(
97264
+ return useCallback188(
96916
97265
  async (scope, value) => applyColorGradingScopeUpdate({
96917
97266
  scope,
96918
97267
  value,
@@ -97060,11 +97409,11 @@ var StudioRightPanels = memo59(function StudioRightPanels2({
97060
97409
  useCaptionDesignFocus(captionEditMode);
97061
97410
  const handleApplyColorGradingScope = useApplyColorGradingScope(recordEdit, reloadPreview);
97062
97411
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
97063
- const setAttributeWhileDragging = useCallback188(
97412
+ const setAttributeWhileDragging = useCallback189(
97064
97413
  (attr, value) => handleDomAttributeLiveCommit(attr, value, void 0, { previewOnly: true }),
97065
97414
  [handleDomAttributeLiveCommit]
97066
97415
  );
97067
- const handleHideAllSelected = useCallback188(() => {
97416
+ const handleHideAllSelected = useCallback189(() => {
97068
97417
  if (!canHideSelections(domEditGroupSelections)) {
97069
97418
  showToast("Audio can't be hidden \u2014 use the group's own controls", "info");
97070
97419
  return;
@@ -97073,7 +97422,7 @@ var StudioRightPanels = memo59(function StudioRightPanels2({
97073
97422
  const keys = timelineKeysForSelections(domEditGroupSelections, elements, activeCompPath);
97074
97423
  if (keys.length > 0) void onToggleElementHidden?.(keys, true);
97075
97424
  }, [domEditGroupSelections, showToast, activeCompPath, onToggleElementHidden]);
97076
- const convertToKeyframes = useCallback188(
97425
+ const convertToKeyframes = useCallback189(
97077
97426
  (animId, duration) => handleGsapConvertToKeyframes(animId, void 0, duration),
97078
97427
  [handleGsapConvertToKeyframes]
97079
97428
  );
@@ -97229,340 +97578,6 @@ import {
97229
97578
  MagnifyingGlassPlus
97230
97579
  } from "@phosphor-icons/react";
97231
97580
 
97232
- // src/hooks/useEnableKeyframes.ts
97233
- import { useCallback as useCallback189 } from "react";
97234
- var enableKeyframesTransactionCounter = 0;
97235
- function animatedProps(anim) {
97236
- if (!anim) return ["x", "y"];
97237
- const own = Object.keys(anim.properties ?? {});
97238
- if (own.length > 0) return own;
97239
- const stops = anim.keyframes?.keyframes;
97240
- if (stops?.length) {
97241
- const keys = /* @__PURE__ */ new Set();
97242
- for (const stop2 of stops) for (const k of Object.keys(stop2.properties ?? {})) keys.add(k);
97243
- if (keys.size > 0) return [...keys];
97244
- }
97245
- return ["x", "y"];
97246
- }
97247
- function isPlayheadWithinTween(anim, currentTime) {
97248
- const start = resolveTweenStart(anim);
97249
- if (start === null) return true;
97250
- return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
97251
- }
97252
- async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
97253
- const position = readElementPosition(iframe, sel, setAnim);
97254
- if (Object.keys(position).length === 0) {
97255
- for (const [key2, held] of Object.entries(setAnim.properties ?? {})) {
97256
- if (typeof held === "number") position[key2] = held;
97257
- }
97258
- }
97259
- if (Object.keys(position).length === 0 || !session.commitMutation) return;
97260
- const range = resolveNewTweenRange(sel.dataAttributes?.start, sel.dataAttributes?.duration, t2);
97261
- await session.commitMutation(
97262
- {
97263
- type: "replace-with-keyframes",
97264
- animationId: setAnim.id,
97265
- targetSelector: selector,
97266
- position: roundTo3(range.start),
97267
- duration: roundTo3(range.duration),
97268
- keyframes: [{ percentage: 0, properties: position }],
97269
- ease: setAnim.ease
97270
- },
97271
- { label: "Enable keyframes", keyframeAction: "add", softReload: true }
97272
- );
97273
- }
97274
- function readElementPosition(iframe, sel, anim) {
97275
- const result = {};
97276
- if (!iframe?.contentWindow) return result;
97277
- let gsap3;
97278
- try {
97279
- gsap3 = iframe.contentWindow.gsap;
97280
- } catch {
97281
- return result;
97282
- }
97283
- const element = sel.element;
97284
- if (!element?.isConnected || !gsap3?.getProperty) return result;
97285
- const props = animatedProps(anim);
97286
- for (const prop of props) {
97287
- const val = Number(gsap3.getProperty(element, prop));
97288
- if (!Number.isFinite(val)) continue;
97289
- result[prop] = POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
97290
- }
97291
- return result;
97292
- }
97293
- function resolveNewTweenRange(authoredStart, authoredDuration, currentTime) {
97294
- const t2 = Math.max(0, roundTo3(currentTime));
97295
- const start = authoredStart != null ? Number.parseFloat(authoredStart) : Number.NaN;
97296
- const duration = authoredDuration != null ? Number.parseFloat(authoredDuration) : Number.NaN;
97297
- if (!Number.isFinite(start) || !Number.isFinite(duration) || duration <= 0) {
97298
- return { start: t2, duration: 1 };
97299
- }
97300
- const end = start + duration;
97301
- const clampedStart = Math.min(Math.max(t2, start), end);
97302
- return { start: clampedStart, duration: Math.max(0.5, roundTo3(end - clampedStart)) };
97303
- }
97304
- async function tryFetchAnimationsForElement(sel) {
97305
- const projectId = window.location.hash.match(/project\/([^?/]+)/)?.[1];
97306
- if (!projectId) return null;
97307
- const sourceFile = sel.sourceFile || "index.html";
97308
- const parsed = await fetchParsedAnimations(projectId, sourceFile);
97309
- if (!parsed) return null;
97310
- return getAnimationsForElement(parsed.animations, {
97311
- id: sel.id,
97312
- selector: sel.selector
97313
- });
97314
- }
97315
- async function fetchAnimationsForElement(sel) {
97316
- return await tryFetchAnimationsForElement(sel) ?? [];
97317
- }
97318
- async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, duration, iframe, commitOverrides) {
97319
- const selector = existingTweenTargetSelector(anim, sel);
97320
- const position = readElementPosition(iframe, sel, anim);
97321
- if (!selector || Object.keys(position).length === 0 || !session.commitMutation) return;
97322
- const extended = buildExtendedKeyframes(anim, currentTime, position, duration);
97323
- await session.commitMutation(
97324
- {
97325
- type: "replace-with-keyframes",
97326
- animationId: anim.id,
97327
- targetSelector: selector,
97328
- position: extended.position,
97329
- duration: extended.duration,
97330
- keyframes: extended.keyframes,
97331
- ...keyframeEases(anim)
97332
- },
97333
- {
97334
- label: "Add keyframe",
97335
- keyframeAction: "add",
97336
- softReload: true,
97337
- ...commitOverrides
97338
- }
97339
- );
97340
- }
97341
- async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
97342
- const keepAuthoredDuration = kfAnim.durationUnresolved === true;
97343
- if (keepAuthoredDuration) {
97344
- kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
97345
- }
97346
- if (keepAuthoredDuration && kfAnim.duration == null) return;
97347
- const duration = resolveTweenDuration(kfAnim);
97348
- const start = resolveTweenStart(kfAnim);
97349
- if (start !== null && !isTimeWithinTween(t2, start, duration)) {
97350
- if (keepAuthoredDuration) return;
97351
- await extendKeyframedTweenToPlayhead(
97352
- session,
97353
- sel,
97354
- kfAnim,
97355
- t2,
97356
- duration,
97357
- iframe,
97358
- commitOverrides
97359
- );
97360
- return;
97361
- }
97362
- const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
97363
- const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
97364
- if (existing) {
97365
- session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
97366
- return;
97367
- }
97368
- if (session.handleGsapAddKeyframeBatch) {
97369
- const position = readElementPosition(iframe, sel, kfAnim);
97370
- if (Object.keys(position).length > 0) {
97371
- await session.handleGsapAddKeyframeBatch(kfAnim.id, pct, position, commitOverrides);
97372
- }
97373
- }
97374
- }
97375
- async function promoteSetToKeyframes(session, sel, setAnim, t2, iframe) {
97376
- const selector = existingTweenTargetSelector(setAnim, sel);
97377
- const setStart = resolveTweenStart(setAnim) ?? 0;
97378
- if (!selector || !session.commitMutation) return;
97379
- if (t2 <= setStart) {
97380
- await replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector);
97381
- return;
97382
- }
97383
- const endPosition = readElementPosition(iframe, sel, setAnim);
97384
- if (Object.keys(endPosition).length === 0) return;
97385
- const startPosition = {};
97386
- for (const key2 of Object.keys(endPosition)) {
97387
- const held = setAnim.properties?.[key2];
97388
- if (typeof held === "number") startPosition[key2] = held;
97389
- }
97390
- await session.commitMutation(
97391
- {
97392
- type: "replace-with-keyframes",
97393
- animationId: setAnim.id,
97394
- targetSelector: selector,
97395
- position: roundTo3(setStart),
97396
- duration: roundTo3(t2 - setStart),
97397
- keyframes: [
97398
- {
97399
- percentage: 0,
97400
- properties: Object.keys(startPosition).length > 0 ? startPosition : endPosition,
97401
- auto: true
97402
- },
97403
- { percentage: 100, properties: endPosition }
97404
- ],
97405
- ease: setAnim.ease
97406
- },
97407
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97408
- );
97409
- }
97410
- async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
97411
- if (!session.commitMutation) return;
97412
- const targetSelector = existingTweenTargetSelector(arcAnim, sel);
97413
- if (!targetSelector) return;
97414
- const start = resolveTweenStart(arcAnim) ?? 0;
97415
- const duration = resolveTweenDuration(arcAnim);
97416
- if (!isTimeWithinTween(t2, start, duration)) {
97417
- const held = readElementPosition(iframe, sel, arcAnim);
97418
- if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
97419
- const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
97420
- await session.commitMutation(
97421
- {
97422
- type: "replace-with-keyframes",
97423
- animationId: arcAnim.id,
97424
- targetSelector,
97425
- position: extended.position,
97426
- duration: extended.duration,
97427
- keyframes: extended.keyframes,
97428
- ease: "none"
97429
- },
97430
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97431
- );
97432
- return;
97433
- }
97434
- const nodes = linearArcKeyframes(arcAnim);
97435
- const playheadPercentage = absoluteToPercentage(t2, start, duration);
97436
- const timedNodeIndex = nodes.findIndex(
97437
- (node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
97438
- );
97439
- if (timedNodeIndex !== -1) {
97440
- if (timedNodeIndex > 0 && timedNodeIndex < nodes.length - 1) {
97441
- await session.commitMutation(
97442
- {
97443
- type: "replace-with-keyframes",
97444
- animationId: arcAnim.id,
97445
- targetSelector,
97446
- position: roundTo3(start),
97447
- duration: roundTo3(duration),
97448
- keyframes: nodes.filter((_, index) => index !== timedNodeIndex),
97449
- ease: "none"
97450
- },
97451
- { label: "Remove keyframe", softReload: true }
97452
- );
97453
- }
97454
- return;
97455
- }
97456
- const live = readElementPosition(iframe, sel, arcAnim);
97457
- if (typeof live.x !== "number" || typeof live.y !== "number") return;
97458
- await session.commitMutation(
97459
- {
97460
- type: "replace-with-keyframes",
97461
- animationId: arcAnim.id,
97462
- targetSelector,
97463
- position: roundTo3(start),
97464
- duration: roundTo3(duration),
97465
- keyframes: buildTemporalArcKeyframes(arcAnim, playheadPercentage, {
97466
- x: live.x,
97467
- y: live.y
97468
- }),
97469
- ease: "none"
97470
- },
97471
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97472
- );
97473
- }
97474
- function useEnableKeyframes(sessionRef) {
97475
- return useCallback189(async () => {
97476
- const session = sessionRef.current;
97477
- if (!session) return;
97478
- const sel = session.domEditSelection;
97479
- if (!sel) return;
97480
- const t2 = usePlayerStore.getState().currentTime;
97481
- const iframe = session.previewIframeRef?.current ?? null;
97482
- const fetched = await tryFetchAnimationsForElement(sel);
97483
- const anims = fetched ?? session.selectedGsapAnimations;
97484
- const arcAnim = anims.find((a) => a.arcPath);
97485
- const kfAnim = anims.find((a) => a.keyframes && !a.arcPath);
97486
- const setAnim = anims.find((a) => isInstantHold(a) && !a.keyframes && !a.arcPath);
97487
- const flatAnim = anims.find((a) => !a.keyframes && !a.arcPath && !isInstantHold(a));
97488
- if (arcAnim) {
97489
- await applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe);
97490
- } else if (kfAnim) {
97491
- await applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe);
97492
- } else if (setAnim) {
97493
- await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
97494
- } else if (flatAnim) {
97495
- const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
97496
- enableKeyframesTransactionCounter += 1;
97497
- const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
97498
- const convertCommitOverrides = {
97499
- keyframeTelemetry: false,
97500
- skipReload: true,
97501
- coalesceKey,
97502
- coalesceMs: Number.POSITIVE_INFINITY
97503
- };
97504
- await session.handleGsapConvertToKeyframes(
97505
- flatAnim.id,
97506
- void 0,
97507
- void 0,
97508
- convertCommitOverrides
97509
- );
97510
- const converted = (await fetchAnimationsForElement(sel)).find((a) => a.keyframes);
97511
- if (converted) {
97512
- const applyCommitOverrides = {
97513
- softReload: true,
97514
- coalesceKey,
97515
- // Must match the convert phase's window: editHistory keys coalescing
97516
- // off the incoming entry, so without Infinity here the apply (landing
97517
- // after two POSTs + a fetch) falls back to the 300ms default and the
97518
- // conversion splits into two undo entries under real latency.
97519
- coalesceMs: Number.POSITIVE_INFINITY
97520
- };
97521
- await applyKeyframeAtPlayhead(
97522
- session,
97523
- sel,
97524
- converted,
97525
- t2,
97526
- iframe,
97527
- applyCommitOverrides,
97528
- flatLive
97529
- );
97530
- }
97531
- } else {
97532
- const position = readElementPosition(iframe, sel, null);
97533
- const { start: elStart, duration: elDuration } = resolveNewTweenRange(
97534
- sel.dataAttributes?.start,
97535
- sel.dataAttributes?.duration,
97536
- t2
97537
- );
97538
- const selector = writeTargetSelector(sel);
97539
- if (!selector) {
97540
- session.handleGsapAddAnimation("to");
97541
- return;
97542
- }
97543
- if (Object.keys(position).length === 0) {
97544
- position.x = 0;
97545
- position.y = 0;
97546
- }
97547
- const keyframes = [{ percentage: 0, properties: { ...position } }];
97548
- if (session.commitMutation) {
97549
- await session.commitMutation(
97550
- {
97551
- type: "add-with-keyframes",
97552
- targetSelector: selector,
97553
- position: roundTo3(elStart),
97554
- duration: roundTo3(elDuration),
97555
- keyframes
97556
- },
97557
- { label: "Enable keyframes", softReload: true }
97558
- );
97559
- } else {
97560
- session.handleGsapAddAnimation("to");
97561
- }
97562
- }
97563
- }, [sessionRef]);
97564
- }
97565
-
97566
97581
  // src/hooks/useKeyframeKeyboard.ts
97567
97582
  import { useEffect as useEffect154, useCallback as useCallback190 } from "react";
97568
97583
  function useKeyframeKeyboard({ enabled, onAddKeyframe }) {