@hyperframes/studio 0.8.46 → 0.8.48

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 (112) hide show
  1. package/dist/assets/hyperframes-player-CtVX6jL_.js +464 -0
  2. package/dist/assets/{index-CbchRscN.js → index-B3-orIjW.js} +1 -1
  3. package/dist/assets/index-B9LfWNyM.css +1 -0
  4. package/dist/assets/index-C9gJKpqQ.js +441 -0
  5. package/dist/assets/{index-BywY2OM3.js → index-D_j-0PD3.js} +1 -1
  6. package/dist/index.d.ts +83 -26
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +1118 -661
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/src/App.tsx +17 -26
  12. package/src/components/AskAgentModal.tsx +9 -7
  13. package/src/components/TimelineToolbar.tsx +26 -1
  14. package/src/components/editor/PropertyPanelFlat.tsx +8 -8
  15. package/src/components/editor/domEditing.ts +7 -1
  16. package/src/components/editor/domEditingAgentPrompt.test.ts +199 -0
  17. package/src/components/editor/domEditingAgentPrompt.ts +136 -34
  18. package/src/components/editor/manualEditingAvailability.test.ts +19 -0
  19. package/src/components/editor/manualEditingAvailability.ts +9 -5
  20. package/src/components/editor/propertyPanelAutomation.test.ts +82 -1
  21. package/src/components/editor/propertyPanelAutomation.ts +15 -0
  22. package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +35 -0
  23. package/src/components/editor/propertyPanelFlatMediaSection.tsx +23 -9
  24. package/src/components/editor/useVolumeAutomation.test.tsx +81 -10
  25. package/src/components/editor/useVolumeAutomation.ts +23 -2
  26. package/src/components/panels/VariablesPanel.tsx +5 -8
  27. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +59 -0
  28. package/src/components/sidebar/CompositionsTab.tsx +15 -0
  29. package/src/components/sidebar/LeftSidebar.masterComposition.test.tsx +47 -0
  30. package/src/components/sidebar/LeftSidebar.tsx +7 -0
  31. package/src/contexts/FileManagerContext.tsx +0 -3
  32. package/src/hooks/appHotkeysDispatch.ts +256 -0
  33. package/src/hooks/domEditDeleteMembers.test.ts +30 -0
  34. package/src/hooks/domEditDeleteMembers.ts +19 -0
  35. package/src/hooks/useActiveComposition.ts +69 -0
  36. package/src/hooks/useAppHotkeys.test.ts +23 -1
  37. package/src/hooks/useAppHotkeys.ts +6 -239
  38. package/src/hooks/useAutoOpenRootComposition.test.tsx +151 -0
  39. package/src/hooks/useAutoOpenRootComposition.ts +33 -0
  40. package/src/hooks/useDomEditCommits.ts +13 -15
  41. package/src/hooks/useDomEditSession.test.tsx +90 -1
  42. package/src/hooks/useDomEditSession.ts +31 -34
  43. package/src/hooks/useDomSelectionTypes.ts +7 -2
  44. package/src/hooks/useElementLifecycleOps.ts +0 -8
  45. package/src/hooks/useFileManager.projectOwnership.test.tsx +0 -1
  46. package/src/hooks/useFileManager.ts +0 -2
  47. package/src/hooks/useFileTree.test.tsx +214 -0
  48. package/src/hooks/useFileTree.ts +66 -24
  49. package/src/hooks/useGroupCommits.test.ts +98 -0
  50. package/src/hooks/useGroupCommits.ts +66 -9
  51. package/src/hooks/useResetSelectionOnProjectSwitch.test.tsx +234 -0
  52. package/src/hooks/useResetSelectionOnProjectSwitch.ts +34 -0
  53. package/src/hooks/useSdkSession.lifecycle.test.tsx +76 -0
  54. package/src/hooks/useSdkSession.ts +21 -3
  55. package/src/hooks/useTimelineAssetDropOps.test.tsx +72 -0
  56. package/src/hooks/useTimelineAssetDropOps.ts +20 -16
  57. package/src/hooks/useTimelineDeleteOps.test.tsx +119 -0
  58. package/src/hooks/useTimelineDeleteOps.ts +103 -9
  59. package/src/hooks/useTimelineEditing.ts +1 -0
  60. package/src/hooks/useTimelineGroupEditing.test.tsx +74 -0
  61. package/src/hooks/useTimelineGroupEditing.ts +19 -5
  62. package/src/index.ts +10 -0
  63. package/src/player/components/Timeline.test.ts +0 -4
  64. package/src/player/components/Timeline.tsx +0 -1
  65. package/src/player/components/TimelineAutomationLaneSlot.tsx +4 -2
  66. package/src/player/components/TimelineClipDiamonds.test.tsx +13 -1
  67. package/src/player/components/TimelineDiamondConnectors.test.ts +16 -0
  68. package/src/player/components/TimelineDiamondConnectors.tsx +17 -1
  69. package/src/player/components/TimelinePropertyLanes.test.tsx +1 -1
  70. package/src/player/components/automationLaneData.test.ts +11 -3
  71. package/src/player/components/automationLaneData.ts +5 -4
  72. package/src/player/components/automationLaneGeometry.ts +15 -0
  73. package/src/player/components/timelineDragDrop.test.tsx +21 -4
  74. package/src/player/components/timelineDragDrop.ts +9 -25
  75. package/src/player/components/timelineGapCommit.test.ts +96 -0
  76. package/src/player/components/timelineGapCommit.ts +56 -11
  77. package/src/player/components/timelineGaps.ts +2 -2
  78. package/src/player/components/timelineLayout.test.ts +0 -1
  79. package/src/player/components/timelineLayout.ts +1 -6
  80. package/src/player/components/timelineZones.test.ts +20 -1
  81. package/src/player/components/timelineZones.ts +10 -3
  82. package/src/player/components/useAutomationLaneGestures.ts +2 -10
  83. package/src/player/components/useTimelineTrackLayout.ts +7 -6
  84. package/src/player/hooks/usePlaybackKeyboard.test.ts +34 -0
  85. package/src/player/hooks/usePlaybackKeyboard.ts +13 -0
  86. package/src/player/hooks/useTimelinePlayer.ts +9 -4
  87. package/src/player/hooks/useTimelineSyncCallbacks.ts +7 -4
  88. package/src/player/lib/timelineIframeHelpers.test.ts +22 -0
  89. package/src/player/lib/timelineIframeHelpers.ts +1 -0
  90. package/src/player/store/playerStore.test.ts +42 -0
  91. package/src/player/store/playerStore.ts +25 -28
  92. package/src/player/store/readinessSlice.ts +42 -0
  93. package/src/utils/razorSplitTransaction.test.ts +10 -0
  94. package/src/utils/razorSplitTransaction.ts +5 -0
  95. package/src/utils/sdkCutover.gate.test.ts +5 -4
  96. package/src/utils/sdkCutover.test.ts +93 -0
  97. package/src/utils/sdkCutover.ts +46 -25
  98. package/src/utils/sdkCutoverEligibility.ts +21 -0
  99. package/src/utils/sdkCutoverPolicy.test.ts +11 -2
  100. package/src/utils/sdkCutoverPolicy.ts +5 -3
  101. package/src/utils/sdkEditTransaction.decline.test.ts +46 -0
  102. package/src/utils/sdkEditTransaction.ts +22 -1
  103. package/src/utils/sdkResolverShadow.ts +10 -1
  104. package/src/utils/studioHelpers.test.ts +18 -0
  105. package/src/utils/studioHelpers.ts +41 -5
  106. package/src/utils/studioUiPreferences.test.ts +14 -0
  107. package/src/utils/studioUiPreferences.ts +6 -0
  108. package/src/utils/studioUrlState.ts +15 -7
  109. package/src/utils/timelineInspector.ts +10 -0
  110. package/dist/assets/hyperframes-player-CsV2rywT.js +0 -464
  111. package/dist/assets/index-DCblmTX3.js +0 -441
  112. package/dist/assets/index-DnRfAiK2.css +0 -1
package/dist/index.js CHANGED
@@ -1338,6 +1338,9 @@ function readStorage(storage) {
1338
1338
  if (typeof parsed.timelineSnapEnabled === "boolean") {
1339
1339
  preferences.timelineSnapEnabled = parsed.timelineSnapEnabled;
1340
1340
  }
1341
+ if (typeof parsed.rippleEditEnabled === "boolean") {
1342
+ preferences.rippleEditEnabled = parsed.rippleEditEnabled;
1343
+ }
1341
1344
  if (parsed.timeDisplayMode === "time" || parsed.timeDisplayMode === "frame") {
1342
1345
  preferences.timeDisplayMode = parsed.timeDisplayMode;
1343
1346
  }
@@ -1594,6 +1597,30 @@ function createThumbnailSlice(set) {
1594
1597
  };
1595
1598
  }
1596
1599
 
1600
+ // src/player/store/readinessSlice.ts
1601
+ import { settleCompositionReadiness } from "@hyperframes/core/composition-readiness";
1602
+ var timelineReadyGeneration = 0;
1603
+ function resetPlaybackReadinessState() {
1604
+ timelineReadyGeneration++;
1605
+ return { timelineReady: false };
1606
+ }
1607
+ function createPlaybackReadinessSlice(set) {
1608
+ return {
1609
+ timelineReady: false,
1610
+ setTimelineReady: (ready) => {
1611
+ timelineReadyGeneration++;
1612
+ set({ timelineReady: ready });
1613
+ },
1614
+ requestTimelineReady: (doc) => {
1615
+ const generation = ++timelineReadyGeneration;
1616
+ if (!doc) return set({ timelineReady: true });
1617
+ settleCompositionReadiness(doc, () => {
1618
+ if (generation === timelineReadyGeneration) set({ timelineReady: true });
1619
+ });
1620
+ }
1621
+ };
1622
+ }
1623
+
1597
1624
  // src/player/store/liveTime.ts
1598
1625
  var timeListeners = /* @__PURE__ */ new Set();
1599
1626
  var liveTime = {
@@ -1623,7 +1650,7 @@ function createTimelineResetState() {
1623
1650
  isPlaying: false,
1624
1651
  currentTime: 0,
1625
1652
  duration: 0,
1626
- timelineReady: false,
1653
+ ...resetPlaybackReadinessState(),
1627
1654
  beatDragging: false,
1628
1655
  elements: [],
1629
1656
  selectedElementId: null,
@@ -1666,7 +1693,6 @@ var usePlayerStore = create2((set, get) => ({
1666
1693
  isPlaying: false,
1667
1694
  currentTime: 0,
1668
1695
  duration: 0,
1669
- timelineReady: false,
1670
1696
  timelineSessionEpoch: 0,
1671
1697
  timelineProjectId: null,
1672
1698
  beatDragging: false,
@@ -1692,6 +1718,7 @@ var usePlayerStore = create2((set, get) => ({
1692
1718
  ...createThumbnailSlice(set),
1693
1719
  ...createAutomationSelectionSlice(set),
1694
1720
  ...createEditingModeSlice(set),
1721
+ ...createPlaybackReadinessSlice(set),
1695
1722
  activeKeyframePct: null,
1696
1723
  setActiveKeyframePct: (pct) => set({ activeKeyframePct: pct }),
1697
1724
  selectedElementIds: /* @__PURE__ */ new Set(),
@@ -1813,6 +1840,12 @@ var usePlayerStore = create2((set, get) => ({
1813
1840
  writeStudioUiPreferences({ timelineSnapEnabled: enabled });
1814
1841
  set({ timelineSnapEnabled: enabled });
1815
1842
  },
1843
+ rippleEditEnabled: readStudioUiPreferences().rippleEditEnabled ?? true,
1844
+ // default on
1845
+ setRippleEditEnabled: (enabled) => {
1846
+ writeStudioUiPreferences({ rippleEditEnabled: enabled });
1847
+ set({ rippleEditEnabled: enabled });
1848
+ },
1816
1849
  timeDisplayMode: readStudioUiPreferences().timeDisplayMode ?? "time",
1817
1850
  setTimeDisplayMode: (mode) => {
1818
1851
  writeStudioUiPreferences({ timeDisplayMode: mode });
@@ -1856,7 +1889,6 @@ var usePlayerStore = create2((set, get) => ({
1856
1889
  bumpZEditVersion: () => set((state) => ({ zEditVersion: state.zEditVersion + 1 })),
1857
1890
  setCurrentTime: (time) => set({ currentTime: Number.isFinite(time) ? time : 0 }),
1858
1891
  setDuration: (duration) => set({ duration: Number.isFinite(duration) ? duration : 0 }),
1859
- setTimelineReady: (ready) => set({ timelineReady: ready }),
1860
1892
  setBeatDragging: (dragging) => set({ beatDragging: dragging }),
1861
1893
  setElements: (elements) => set({ elements }),
1862
1894
  // A genuine single selection: always collapse the set to just this element. User
@@ -3029,6 +3061,10 @@ function isAudioTimelineElement(element) {
3029
3061
  if (AUDIO_TIMELINE_TAGS.has(tag)) return true;
3030
3062
  return Boolean(element.src && AUDIO_SOURCE_EXT_RE.test(element.src));
3031
3063
  }
3064
+ function isAudioOrVideoTimelineElement(element) {
3065
+ if (!element) return false;
3066
+ return isAudioTimelineElement(element) || element.tag.trim().toLowerCase() === "video";
3067
+ }
3032
3068
  function isMusicTrack(element) {
3033
3069
  if (!element) return false;
3034
3070
  if (!isAudioTimelineElement(element)) return false;
@@ -3104,7 +3140,6 @@ function FileManagerProvider({
3104
3140
  projectDir,
3105
3141
  fileTree,
3106
3142
  fileTreeLoaded,
3107
- setFileTree,
3108
3143
  editingPathRef,
3109
3144
  projectIdRef,
3110
3145
  saveRafRef,
@@ -3145,7 +3180,6 @@ function FileManagerProvider({
3145
3180
  projectDir,
3146
3181
  fileTree,
3147
3182
  fileTreeLoaded,
3148
- setFileTree,
3149
3183
  editingPathRef,
3150
3184
  projectIdRef,
3151
3185
  saveRafRef,
@@ -3183,7 +3217,6 @@ function FileManagerProvider({
3183
3217
  projectDir,
3184
3218
  fileTree,
3185
3219
  fileTreeLoaded,
3186
- setFileTree,
3187
3220
  editingPathRef,
3188
3221
  projectIdRef,
3189
3222
  saveRafRef,
@@ -3939,6 +3972,7 @@ function buildMissingCompositionEntry(params) {
3939
3972
  start: window2.start,
3940
3973
  duration: window2.duration,
3941
3974
  track: timing.trackIndex,
3975
+ authoredTrack: timing.trackIndex,
3942
3976
  domId: optionalNonEmpty(element.id),
3943
3977
  hfId: optionalNonEmpty(element.getAttribute("data-hf-id")),
3944
3978
  selector,
@@ -5214,10 +5248,7 @@ function resolveTimelineAssetDrop(input, clientX, clientY) {
5214
5248
  });
5215
5249
  const contentY = clientY - input.rectTop + input.scrollTop;
5216
5250
  const pointerStart = Math.round(x / Math.max(input.pixelsPerSecond, 1) * 100) / 100;
5217
- const start = Math.max(
5218
- 0,
5219
- input.clampStartToDuration === false ? pointerStart : Math.min(input.duration, pointerStart)
5220
- );
5251
+ const start = Math.max(0, pointerStart);
5221
5252
  const rowIndex = Math.floor(getTimelineRowFromY(contentY, input.rowHeights));
5222
5253
  return {
5223
5254
  start,
@@ -6579,10 +6610,6 @@ function invokeDropCallback(callback) {
6579
6610
  } catch {
6580
6611
  }
6581
6612
  }
6582
- function resolveDropStart(usePointerStart, pointerStart) {
6583
- if (usePointerStart) return pointerStart;
6584
- return Math.max(0, usePlayerStore.getState().currentTime);
6585
- }
6586
6613
  function applyFileDrop(transfer, onFileDrop, placement) {
6587
6614
  if (!onFileDrop || transfer.files.length === 0) return false;
6588
6615
  invokeDropCallback(() => onFileDrop(Array.from(transfer.files), placement));
@@ -6602,7 +6629,6 @@ function applyTypedJsonDrop(transfer, mime, field, apply, placement) {
6602
6629
  function useTimelineAssetDrop({
6603
6630
  scrollRef,
6604
6631
  ppsRef,
6605
- durationRef,
6606
6632
  trackOrderRef,
6607
6633
  rowGeometryRef,
6608
6634
  contentOrigin,
@@ -6683,10 +6709,10 @@ function useTimelineAssetDrop({
6683
6709
  [clearDropPreview]
6684
6710
  );
6685
6711
  const resolveDropPlacement2 = useCallback7(
6686
- (clientX, clientY, usePointerStart = false) => {
6712
+ (clientX, clientY) => {
6687
6713
  const scroll = scrollRef.current;
6688
6714
  const rect = scroll?.getBoundingClientRect();
6689
- const pointer = resolveTimelineAssetDrop(
6715
+ return resolveTimelineAssetDrop(
6690
6716
  {
6691
6717
  rectLeft: rect?.left ?? 0,
6692
6718
  rectTop: rect?.top ?? 0,
@@ -6694,20 +6720,14 @@ function useTimelineAssetDrop({
6694
6720
  scrollTop: scroll?.scrollTop ?? 0,
6695
6721
  contentOrigin,
6696
6722
  pixelsPerSecond: ppsRef.current,
6697
- duration: durationRef.current,
6698
- clampStartToDuration: !usePointerStart,
6699
6723
  rowHeights: rowGeometryRef.current.rowHeights,
6700
6724
  trackOrder: trackOrderRef.current
6701
6725
  },
6702
6726
  clientX,
6703
6727
  clientY
6704
6728
  );
6705
- return {
6706
- start: resolveDropStart(usePointerStart, pointer.start),
6707
- track: pointer.track
6708
- };
6709
6729
  },
6710
- [scrollRef, ppsRef, durationRef, trackOrderRef, rowGeometryRef, contentOrigin]
6730
+ [scrollRef, ppsRef, trackOrderRef, rowGeometryRef, contentOrigin]
6711
6731
  );
6712
6732
  const handleAssetDrop = useCallback7(
6713
6733
  (e) => {
@@ -6715,15 +6735,14 @@ function useTimelineAssetDrop({
6715
6735
  const canCommit = activeDropEpochRef.current === sessionEpoch;
6716
6736
  clearDropPreview();
6717
6737
  if (!canCommit) return;
6738
+ const placement = resolveDropPlacement2(e.clientX, e.clientY);
6718
6739
  const compositionPayload = parseTimelineCompositionPayload(
6719
6740
  e.dataTransfer.getData(TIMELINE_COMPOSITION_MIME)
6720
6741
  );
6721
6742
  if (compositionPayload && onCompositionDrop) {
6722
- const placement2 = resolveDropPlacement2(e.clientX, e.clientY, true);
6723
- invokeDropCallback(() => onCompositionDrop(compositionPayload.sourcePath, placement2));
6743
+ invokeDropCallback(() => onCompositionDrop(compositionPayload.sourcePath, placement));
6724
6744
  return;
6725
6745
  }
6726
- const placement = resolveDropPlacement2(e.clientX, e.clientY);
6727
6746
  if (applyFileDrop(e.dataTransfer, onFileDrop, placement)) return;
6728
6747
  if (applyTypedJsonDrop(e.dataTransfer, TIMELINE_ASSET_MIME, "path", onAssetDrop, placement)) {
6729
6748
  return;
@@ -9042,6 +9061,11 @@ function keyframeTarget(keyframe) {
9042
9061
 
9043
9062
  // src/player/components/TimelineDiamondConnectors.tsx
9044
9063
  import { Fragment as Fragment10, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
9064
+ var EASE_BUTTON_PX = 16;
9065
+ var EASE_BUTTON_REST_GAP_PX = 4;
9066
+ function hasRoomForRestingEase(connectorWidth) {
9067
+ return connectorWidth >= EASE_BUTTON_PX + EASE_BUTTON_REST_GAP_PX * 2;
9068
+ }
9045
9069
  function TimelineDiamondConnectors({
9046
9070
  markers,
9047
9071
  centerY,
@@ -9100,6 +9124,7 @@ function TimelineDiamondConnectors({
9100
9124
  previous.keyframe.percentage
9101
9125
  ),
9102
9126
  roomForFullTarget: connectorWidth >= 24,
9127
+ restingVisible: hasRoomForRestingEase(connectorWidth),
9103
9128
  onSelectSegment
9104
9129
  }
9105
9130
  )
@@ -9119,6 +9144,7 @@ function SegmentEaseControl({
9119
9144
  rovingTargetId,
9120
9145
  afterLabel,
9121
9146
  roomForFullTarget,
9147
+ restingVisible,
9122
9148
  onSelectSegment
9123
9149
  }) {
9124
9150
  const pressXRef = useRef12(null);
@@ -9151,7 +9177,7 @@ function SegmentEaseControl({
9151
9177
  tabIndex: focusId === rovingTargetId ? 0 : -1,
9152
9178
  "aria-label": `Edit ${ease} easing after ${afterLabel}`,
9153
9179
  title: `Edit ${ease} easing`,
9154
- className: `absolute flex items-center justify-center rounded opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 ${roomForFullTarget ? "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']" : ""}`,
9180
+ className: `absolute flex items-center justify-center rounded ${restingVisible ? "opacity-40" : "opacity-0"} transition-opacity hover:opacity-100 group-hover:opacity-100 focus-visible:opacity-100 ${roomForFullTarget ? "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']" : ""}`,
9155
9181
  style: {
9156
9182
  left: "50%",
9157
9183
  top: "50%",
@@ -10896,6 +10922,11 @@ import {
10896
10922
  } from "@hyperframes/core/audio-automation";
10897
10923
  import { getAudioFxDef } from "@hyperframes/core/audio-fx";
10898
10924
  var POINT_MERGE_SEC = 0.02;
10925
+ function mergeInsertPoint(points, t, v) {
10926
+ const kept = points.filter((p) => Math.abs(p.t - t) > POINT_MERGE_SEC);
10927
+ const seeded = points.length === 0 && t > POINT_MERGE_SEC ? [{ t: 0, v }] : [];
10928
+ return [...seeded, ...kept, { t, v }].sort((a, b) => a.t - b.t);
10929
+ }
10899
10930
  var MIN_POINT_GAP_SEC = 1e-3;
10900
10931
  var GRAB_PX = 7;
10901
10932
  var SEGMENT_GRAB_PX = 5;
@@ -11794,12 +11825,7 @@ function useAutomationLaneGestures({
11794
11825
  return;
11795
11826
  }
11796
11827
  const { t, v } = pointAt(e.clientX, e.clientY);
11797
- const kept = lane.points.filter((p) => Math.abs(p.t - t) > POINT_MERGE_SEC);
11798
- const seeded = lane.points.length === 0 && t > POINT_MERGE_SEC ? [{ t: 0, v }] : [];
11799
- commitPoints(
11800
- [...seeded, ...kept, { t, v }].sort((a, b) => a.t - b.t),
11801
- true
11802
- );
11828
+ commitPoints(mergeInsertPoint(lane.points, t, v), true);
11803
11829
  },
11804
11830
  [lane, pointAt, commitPoints, readOnly, hitIndex, segmentIndex]
11805
11831
  );
@@ -12522,7 +12548,7 @@ function laneGroupKey(target, chain) {
12522
12548
  function groupAutomationLanes(elements) {
12523
12549
  const groups = /* @__PURE__ */ new Map();
12524
12550
  for (const element of elements) {
12525
- if (!isAudioTimelineElement(element)) continue;
12551
+ if (!isAudioOrVideoTimelineElement(element)) continue;
12526
12552
  const chain = elementFxChain(element);
12527
12553
  for (const lane of elementAutomationLanes(element)) {
12528
12554
  const key2 = laneGroupKey(lane.target, chain);
@@ -12619,7 +12645,7 @@ function TimelineAutomationLaneSlot({
12619
12645
  currentTime,
12620
12646
  beatTimes
12621
12647
  }) {
12622
- const clips = elements.filter(isAudioTimelineElement);
12648
+ const clips = elements.filter(isAudioOrVideoTimelineElement);
12623
12649
  const rowsByClip = /* @__PURE__ */ new Map();
12624
12650
  groupAutomationLanes(clips).forEach((group, rowIndex) => {
12625
12651
  for (const entry2 of group.entries) {
@@ -13051,6 +13077,10 @@ function matchesByDomId(selection, element, selectionSourceFile) {
13051
13077
  if (!selection.id) return false;
13052
13078
  return element.domId === selection.id && (element.sourceFile || "index.html") === selectionSourceFile;
13053
13079
  }
13080
+ function matchesByHfId(selection, element, selectionSourceFile) {
13081
+ if (!selection.hfId) return false;
13082
+ return element.hfId === selection.hfId && (element.sourceFile || "index.html") === selectionSourceFile;
13083
+ }
13054
13084
  function matchesByCompositionHost(selection, element) {
13055
13085
  if (!selection.isCompositionHost || !selection.compositionSrc) return false;
13056
13086
  return element.compositionSrc === selection.compositionSrc;
@@ -13063,6 +13093,8 @@ function findMatchingTimelineElementId(selection, elements) {
13063
13093
  const selectionSourceFile = selection.sourceFile || "index.html";
13064
13094
  const byId = selection.id ? elements.find((el) => matchesByDomId(selection, el, selectionSourceFile)) : void 0;
13065
13095
  if (byId) return byId.key ?? byId.id;
13096
+ const byHfId = selection.hfId ? elements.find((el) => matchesByHfId(selection, el, selectionSourceFile)) : void 0;
13097
+ if (byHfId) return byHfId.key ?? byHfId.id;
13066
13098
  const bySelector = selection.selector ? elements.find((el) => matchesBySelector(selection, el)) : void 0;
13067
13099
  if (bySelector) return bySelector.key ?? bySelector.id;
13068
13100
  const byHost = elements.find((el) => matchesByCompositionHost(selection, el));
@@ -13072,6 +13104,11 @@ function findMatchingTimelineElementId(selection, elements) {
13072
13104
  }
13073
13105
  return null;
13074
13106
  }
13107
+ function resolveElementTrack(element) {
13108
+ return Math.round(
13109
+ Number.isFinite(element.authoredTrack) ? element.authoredTrack : element.track
13110
+ );
13111
+ }
13075
13112
  function findTimelineIdByAncestor(element, elements, sourceFile) {
13076
13113
  let ancestor = element?.parentElement ?? null;
13077
13114
  while (ancestor) {
@@ -15647,7 +15684,7 @@ function trackShowsBeatStrip(els, beatTimes, ctx3) {
15647
15684
  return ctx3.selectedElementId ? els.some((e) => (e.key ?? e.id) === ctx3.selectedElementId) : els.some((e) => ctx3.isMusicTrack(e));
15648
15685
  }
15649
15686
  function automationLaneCountOf(element) {
15650
- return isAudioTimelineElement(element) ? elementAutomationLanes(element).length : 0;
15687
+ return isAudioOrVideoTimelineElement(element) ? elementAutomationLanes(element).length : 0;
15651
15688
  }
15652
15689
  function trackAutomationLaneCount(elements) {
15653
15690
  return groupAutomationLanes(elements).length;
@@ -19146,6 +19183,9 @@ import { useRef as useRef29, useState as useState17, useCallback as useCallback1
19146
19183
  function classifyZone(el) {
19147
19184
  return isAudioTimelineElement(el) ? "audio" : "visual";
19148
19185
  }
19186
+ function isMainTrackElement(el) {
19187
+ return el.track === 0 && classifyZone(el) === "visual" && el.expandedParentStart == null;
19188
+ }
19149
19189
  var keyOf = (el) => el.key ?? el.id;
19150
19190
  var EPS = 1e-6;
19151
19191
  function overlaps2(a, b) {
@@ -21939,20 +21979,23 @@ function resolveCloseGapShifts(elements, gap) {
21939
21979
  // src/player/components/timelineGapCommit.ts
21940
21980
  var keyOf5 = (e) => e.key ?? e.id;
21941
21981
  var gapCloseGestureSeq = 0;
21942
- function canShiftTrackGapClips(laneElements, shifts) {
21943
- const byKey = new Map(laneElements.map((e) => [keyOf5(e), e]));
21944
- return shifts.every((s) => {
21982
+ function resolveShiftedElements(elements, shifts) {
21983
+ const byKey = new Map(elements.map((e) => [keyOf5(e), e]));
21984
+ return shifts.flatMap((s) => {
21945
21985
  const element = byKey.get(s.key);
21946
- return element != null && getTimelineEditCapabilities(element).canMove;
21986
+ return element ? [{ element, start: s.newStart }] : [];
21947
21987
  });
21948
21988
  }
21989
+ function canShiftTrackGapClips(laneElements, shifts) {
21990
+ const resolved2 = resolveShiftedElements(laneElements, shifts);
21991
+ return resolved2.length === shifts.length && resolved2.every((r) => getTimelineEditCapabilities(r.element).canMove);
21992
+ }
21949
21993
  function buildShiftEdits(laneElements, shifts) {
21950
21994
  if (shifts.length === 0 || !canShiftTrackGapClips(laneElements, shifts)) return null;
21951
- const byKey = new Map(laneElements.map((e) => [keyOf5(e), e]));
21952
- return shifts.map((s) => {
21953
- const element = byKey.get(s.key);
21954
- return { element, updates: { start: s.newStart, track: element.track } };
21955
- });
21995
+ return resolveShiftedElements(laneElements, shifts).map(({ element, start }) => ({
21996
+ element,
21997
+ updates: { start, track: element.track }
21998
+ }));
21956
21999
  }
21957
22000
  function commitShifts(laneElements, shifts, deps) {
21958
22001
  const edits = buildShiftEdits(laneElements, shifts);
@@ -21972,6 +22015,22 @@ function commitCloseAllTrackGaps(laneElements, deps) {
21972
22015
  deps
21973
22016
  );
21974
22017
  }
22018
+ function resolveMainTrackDeleteRippleShifts(survivingElements, deletedElements, rippleEnabled) {
22019
+ const deletedMainTrack = deletedElements.filter(isMainTrackElement);
22020
+ if (!rippleEnabled || deletedMainTrack.length === 0) return null;
22021
+ const survivors = survivingElements.filter(isMainTrackElement);
22022
+ const shifts = [];
22023
+ for (const survivor of survivors) {
22024
+ const width = deletedMainTrack.filter((d) => d.start < survivor.start).reduce((sum, d) => sum + d.duration, 0);
22025
+ if (width <= 0) continue;
22026
+ const newStart = round32(Math.max(laneGapFloor(survivors), survivor.start - width));
22027
+ if (Math.abs(newStart - survivor.start) > TRACK_GAP_EPSILON_S) {
22028
+ shifts.push({ key: keyOf5(survivor), newStart });
22029
+ }
22030
+ }
22031
+ if (shifts.length === 0 || !canShiftTrackGapClips(survivors, shifts)) return null;
22032
+ return shifts;
22033
+ }
21975
22034
 
21976
22035
  // src/player/components/useTrackGapMenu.ts
21977
22036
  function useTrackGapMenu({
@@ -23179,7 +23238,6 @@ var Timeline = memo16(function Timeline2({
23179
23238
  const assetDrop = useTimelineAssetDrop({
23180
23239
  scrollRef,
23181
23240
  ppsRef,
23182
- durationRef,
23183
23241
  trackOrderRef,
23184
23242
  rowGeometryRef,
23185
23243
  contentOrigin,
@@ -24661,6 +24719,9 @@ function canvasNudgeKeysClaimed() {
24661
24719
  }
24662
24720
 
24663
24721
  // src/player/hooks/usePlaybackKeyboard.ts
24722
+ function shouldRazorClaimPlainA(e) {
24723
+ return !e.shiftKey && !e.altKey && usePlayerStore.getState().activeTool === "razor";
24724
+ }
24664
24725
  function usePlaybackKeyboard({
24665
24726
  iframeRef,
24666
24727
  shuttleDirectionRef,
@@ -24790,6 +24851,7 @@ function usePlaybackKeyboard({
24790
24851
  return;
24791
24852
  }
24792
24853
  if (key2 === "a") {
24854
+ if (shouldRazorClaimPlainA(e)) return;
24793
24855
  e.preventDefault();
24794
24856
  seek(usePlayerStore.getState().inPoint ?? 0, { keepPlaying: true });
24795
24857
  return;
@@ -25080,7 +25142,7 @@ function useTimelineSyncCallbacks({
25080
25142
  syncTimelineElements,
25081
25143
  setDuration,
25082
25144
  setCurrentTime,
25083
- setTimelineReady,
25145
+ requestTimelineReady,
25084
25146
  setIsPlaying,
25085
25147
  attachIframeShortcutListeners,
25086
25148
  applyPreviewAudioState
@@ -25152,7 +25214,7 @@ function useTimelineSyncCallbacks({
25152
25214
  syncAdapterDuration(adapter, setDuration);
25153
25215
  setCurrentTime(startTime);
25154
25216
  if (!isRefreshingRef.current) {
25155
- setTimelineReady(true);
25217
+ requestTimelineReady(safeContentDocument(iframeRef.current));
25156
25218
  }
25157
25219
  isRefreshingRef.current = false;
25158
25220
  setIsPlaying(false);
@@ -25170,7 +25232,7 @@ function useTimelineSyncCallbacks({
25170
25232
  getAdapter,
25171
25233
  setDuration,
25172
25234
  setCurrentTime,
25173
- setTimelineReady,
25235
+ requestTimelineReady,
25174
25236
  setIsPlaying,
25175
25237
  processTimelineMessage,
25176
25238
  enrichMissingCompositions,
@@ -25641,7 +25703,7 @@ function useTimelinePlayer() {
25641
25703
  const lastTimelineMessageRef = useRef44(0);
25642
25704
  const staticSeekAdapterRef = useRef44(null);
25643
25705
  const staticSeekWarnedRef = useRef44(false);
25644
- const { setIsPlaying, setCurrentTime, setDuration, setTimelineReady, setElements } = usePlayerStore.getState();
25706
+ const { setIsPlaying, setCurrentTime, setDuration, requestTimelineReady, setElements } = usePlayerStore.getState();
25645
25707
  const syncTimelineElements = useCallback36(
25646
25708
  // The lease guard adds one deliberate branch at the shared synchronization boundary.
25647
25709
  // fallow-ignore-next-line complexity
@@ -25667,7 +25729,7 @@ function useTimelinePlayer() {
25667
25729
  setDuration(nextDuration ?? 0);
25668
25730
  }
25669
25731
  if (!state.timelineReady) {
25670
- setTimelineReady(true);
25732
+ requestTimelineReady(safeContentDocument(iframeRef.current));
25671
25733
  }
25672
25734
  void probeMissingSourceDurations(
25673
25735
  mergedElements,
@@ -25683,7 +25745,7 @@ function useTimelinePlayer() {
25683
25745
  }
25684
25746
  );
25685
25747
  },
25686
- [setElements, setTimelineReady, setDuration]
25748
+ [setElements, requestTimelineReady, setDuration]
25687
25749
  );
25688
25750
  const getAdapter = useCallback36(() => {
25689
25751
  try {
@@ -25953,7 +26015,7 @@ function useTimelinePlayer() {
25953
26015
  syncTimelineElements,
25954
26016
  setDuration,
25955
26017
  setCurrentTime,
25956
- setTimelineReady,
26018
+ requestTimelineReady,
25957
26019
  setIsPlaying,
25958
26020
  attachIframeShortcutListeners,
25959
26021
  applyPreviewAudioState
@@ -28524,6 +28586,13 @@ function pauseStudioPreviewPlayback(iframe) {
28524
28586
  }
28525
28587
 
28526
28588
  // src/components/editor/domEditingAgentPrompt.ts
28589
+ var GUARDRAIL_LINES = [
28590
+ "Guardrails:",
28591
+ "- Make a targeted change to this element only.",
28592
+ "- Preserve the rest of the composition and its timing.",
28593
+ "- Do not modify other elements' data-* attributes or positioning.",
28594
+ "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors."
28595
+ ];
28527
28596
  function formatBoundingBox(bounds) {
28528
28597
  return `x=${Math.round(bounds.x)}, y=${Math.round(bounds.y)}, width=${Math.round(bounds.width)}, height=${Math.round(bounds.height)}`;
28529
28598
  }
@@ -28535,6 +28604,35 @@ function formatTextFields(fields) {
28535
28604
  (field) => `- key=${field.key}; tag=<${field.tagName}>; source=${field.source}; text=${JSON.stringify(field.value)}`
28536
28605
  ).join("\n");
28537
28606
  }
28607
+ function formatSelectorTagLine(info) {
28608
+ return `Selector: ${info.selector ?? "(none)"} Tag: <${info.tagName}>`;
28609
+ }
28610
+ function formatTextLine(textContent) {
28611
+ return textContent ? `Text: ${textContent}` : "";
28612
+ }
28613
+ function buildElementInfoLines(info) {
28614
+ const lines = [
28615
+ `DOM id: ${info.id ?? "(none)"}`,
28616
+ `Selector: ${info.selector ?? "(none)"}`,
28617
+ `Selector index: ${info.selectorIndex ?? 0}`,
28618
+ `Tag: <${info.tagName}>`
28619
+ ];
28620
+ if (info.label) lines.push(`Label: ${info.label}`);
28621
+ lines.push(`Bounds: ${formatBoundingBox(info.boundingBox)}`);
28622
+ if (info.src) lines.push(`Source (media): ${info.src}`);
28623
+ if (info.textContent) lines.push(`Text: ${info.textContent}`);
28624
+ return lines;
28625
+ }
28626
+ function buildElementDetailLines(info) {
28627
+ const lines = [];
28628
+ const textFieldsBlock = info.textFields ? formatTextFields(info.textFields) : "";
28629
+ if (textFieldsBlock) lines.push("", "Text fields:", textFieldsBlock);
28630
+ const inlineStyleBlock = info.inlineStyles ? formatStyleBlock(info.inlineStyles) : "";
28631
+ if (inlineStyleBlock) lines.push("", "Inline styles:", inlineStyleBlock);
28632
+ const computedStyleBlock = info.computedStyles ? formatStyleBlock(info.computedStyles) : "";
28633
+ if (computedStyleBlock) lines.push("", "Computed styles (browser-resolved):", computedStyleBlock);
28634
+ return lines;
28635
+ }
28538
28636
  function buildElementAgentPrompt({
28539
28637
  selection,
28540
28638
  currentTime,
@@ -28544,6 +28642,17 @@ function buildElementAgentPrompt({
28544
28642
  sourceFilePath
28545
28643
  }) {
28546
28644
  const displayedSourceFile = sourceFilePath?.trim() || selection.sourceFile;
28645
+ const info = {
28646
+ id: selection.id ?? null,
28647
+ selector: selection.selector,
28648
+ selectorIndex: selection.selectorIndex,
28649
+ tagName: selection.tagName,
28650
+ boundingBox: selection.boundingBox,
28651
+ textContent: selection.textContent,
28652
+ textFields: selection.textFields,
28653
+ inlineStyles: selection.inlineStyles,
28654
+ computedStyles: selection.computedStyles
28655
+ };
28547
28656
  const lines = [
28548
28657
  "## HyperFrames element edit request v1",
28549
28658
  "Schema version: 1",
@@ -28553,52 +28662,62 @@ function buildElementAgentPrompt({
28553
28662
  `Composition: ${selection.compositionPath}`,
28554
28663
  `Playback time: ${formatTime(currentTime)}`,
28555
28664
  `Source file: ${displayedSourceFile}`,
28556
- `DOM id: ${selection.id ?? "(none)"}`,
28557
- `Selector: ${selection.selector ?? "(none)"}`,
28558
- `Selector index: ${selection.selectorIndex ?? 0}`,
28559
- `Tag: <${selection.tagName}>`,
28560
- `Bounds: ${formatBoundingBox(selection.boundingBox)}`
28665
+ ...buildElementInfoLines(info)
28561
28666
  ];
28562
- if (selection.textContent) {
28563
- lines.push(`Text: ${selection.textContent}`);
28564
- }
28565
28667
  const trimmedSelectionContext = selectionContext?.trim();
28566
28668
  if (trimmedSelectionContext) {
28567
28669
  lines.push("", "Selection context:", trimmedSelectionContext);
28568
28670
  }
28569
- const textFieldsBlock = formatTextFields(selection.textFields);
28570
- if (textFieldsBlock) {
28571
- lines.push("", "Text fields:", textFieldsBlock);
28572
- }
28573
- const inlineStyleBlock = formatStyleBlock(selection.inlineStyles);
28574
- if (inlineStyleBlock) {
28575
- lines.push("", "Inline styles:", inlineStyleBlock);
28576
- }
28577
- const computedStyleBlock = formatStyleBlock(selection.computedStyles);
28578
- if (computedStyleBlock) {
28579
- lines.push("", "Computed styles (browser-resolved):", computedStyleBlock);
28580
- }
28671
+ lines.push(...buildElementDetailLines(info));
28581
28672
  if (tagSnippet) {
28582
28673
  lines.push("", "Target HTML:", tagSnippet);
28583
28674
  }
28584
- lines.push(
28585
- "",
28586
- "Guardrails:",
28587
- "- Make a targeted change to this element only.",
28588
- "- Preserve the rest of the composition and its timing.",
28589
- "- Do not modify other elements' data-* attributes or positioning.",
28590
- "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors."
28591
- );
28675
+ lines.push("", ...GUARDRAIL_LINES);
28592
28676
  return lines.join("\n");
28593
28677
  }
28678
+ function buildSelectorAndTextLines(info) {
28679
+ return [formatSelectorTagLine(info), formatTextLine(info.textContent)];
28680
+ }
28594
28681
  function buildAgentContextPreview(selection, activeCompPath) {
28595
28682
  return [
28596
28683
  `Composition: ${selection.compositionPath}`,
28597
28684
  `Source: ${selection.sourceFile || activeCompPath || "index.html"}`,
28598
- `Selector: ${selection.selector ?? "(none)"} Tag: <${selection.tagName}>`,
28599
- selection.textContent ? `Text: ${selection.textContent}` : ""
28685
+ ...buildSelectorAndTextLines(selection)
28600
28686
  ].filter(Boolean).join("\n");
28601
28687
  }
28688
+ function buildPickerAgentPrompt({
28689
+ selection,
28690
+ userInstruction
28691
+ }) {
28692
+ const lines = [
28693
+ "## HyperFrames element edit request v1",
28694
+ "Schema version: 1",
28695
+ "",
28696
+ userInstruction?.trim() || "Edit this selected HyperFrames element."
28697
+ ];
28698
+ if (!selection) return lines.join("\n");
28699
+ const info = {
28700
+ id: selection.id,
28701
+ selector: selection.selector,
28702
+ tagName: selection.tagName,
28703
+ label: selection.label,
28704
+ boundingBox: selection.boundingBox,
28705
+ textContent: selection.textContent,
28706
+ src: selection.src
28707
+ };
28708
+ lines.push(
28709
+ "",
28710
+ ...buildElementInfoLines(info),
28711
+ ...buildElementDetailLines(info),
28712
+ "",
28713
+ ...GUARDRAIL_LINES
28714
+ );
28715
+ return lines.join("\n");
28716
+ }
28717
+ function buildPickerAgentContextPreview(selection) {
28718
+ if (!selection) return "";
28719
+ return buildSelectorAndTextLines(selection).filter(Boolean).join("\n");
28720
+ }
28602
28721
 
28603
28722
  // src/components/editor/domEditOverlayBasis.ts
28604
28723
  function readPositiveDimension(value) {
@@ -38233,7 +38352,7 @@ var STUDIO_SDK_OPERATION_FAMILIES = [
38233
38352
  function resolveEnabledSdkFamilies(env2, masterEnabled) {
38234
38353
  if (!masterEnabled) return /* @__PURE__ */ new Set();
38235
38354
  const raw = env2["VITE_STUDIO_SDK_CUTOVER_FAMILIES"];
38236
- if (typeof raw !== "string" || raw.trim() === "") return /* @__PURE__ */ new Set();
38355
+ if (typeof raw !== "string" || raw.trim() === "") return new Set(STUDIO_SDK_OPERATION_FAMILIES);
38237
38356
  const requested = raw.split(",").map((family) => family.trim()).filter(Boolean);
38238
38357
  const known = new Set(STUDIO_SDK_OPERATION_FAMILIES);
38239
38358
  const unknown = requested.filter((family) => !known.has(family));
@@ -38266,7 +38385,7 @@ var env = { ...import.meta.env ?? {}, ...runtimeEnv };
38266
38385
  var STUDIO_SDK_CUTOVER_ENABLED = resolveStudioBooleanEnvFlag(
38267
38386
  env,
38268
38387
  ["VITE_STUDIO_SDK_CUTOVER_ENABLED"],
38269
- false
38388
+ true
38270
38389
  );
38271
38390
  var STUDIO_SDK_CUTOVER_FAMILIES = resolveEnabledSdkFamilies(
38272
38391
  env,
@@ -38759,6 +38878,13 @@ function shouldDeclineTextCutoverForTarget(target, ops) {
38759
38878
  const tag = elementTag(children[0]);
38760
38879
  return tag !== null && NON_HTML_CHILD_TAGS.has(tag);
38761
38880
  }
38881
+ function isResolverDisagreement(session, hfId) {
38882
+ try {
38883
+ return resolveSnapshot(session, hfId) !== null;
38884
+ } catch {
38885
+ return false;
38886
+ }
38887
+ }
38762
38888
  function shouldUseSdkCutover(flagEnabled, hasSession, hfId, ops) {
38763
38889
  return flagEnabled && hasSession && !!hfId && ops.length > 0 && ops.every((o) => CUTOVER_OP_TYPES.has(o.type)) && // SDK edit ops target only the element hfId; child-scoped patch ops need the server path.
38764
38890
  !hasChildScopedOp(ops) && !ops.some(mapsToReservedAttr) && !hasUnsafeHtmlAttributeOp(ops);
@@ -38800,7 +38926,12 @@ function isSelfWriteEcho(path, content, now2 = Date.now()) {
38800
38926
  }
38801
38927
 
38802
38928
  // src/utils/sdkEditTransaction.ts
38803
- function declinedCutover(reason) {
38929
+ function declinedCutover(reason, family, resolverDisagreement) {
38930
+ trackStudioEvent("sdk_cutover_declined", {
38931
+ reason,
38932
+ family: family ?? null,
38933
+ ...resolverDisagreement ? { resolverDisagreement: true } : {}
38934
+ });
38804
38935
  return { status: "declined", reason };
38805
38936
  }
38806
38937
  function asCutoverError(error) {
@@ -38967,15 +39098,17 @@ function gsapReadSource(deps, targetPath) {
38967
39098
  }
38968
39099
  async function sdkCutoverPersist(selection, ops, originalContent, targetPath, sdkSession, deps, options) {
38969
39100
  if (!shouldUseSdkCutover(sdkFamilyEnabled("dom"), !!sdkSession, selection.hfId, ops))
38970
- return declinedCutover("ineligible_operation");
38971
- if (!sdkSession) return declinedCutover("session_unavailable");
39101
+ return declinedCutover("ineligible_operation", "dom");
39102
+ if (!sdkSession) return declinedCutover("session_unavailable", "dom");
38972
39103
  const hfId = selection.hfId;
38973
- if (!hfId) return declinedCutover("target_unaddressable");
39104
+ if (!hfId) return declinedCutover("target_unaddressable", "dom");
38974
39105
  const target = sdkSession.getElement(hfId);
38975
- if (!target) return declinedCutover("target_not_found");
39106
+ if (!target)
39107
+ return declinedCutover("target_not_found", "dom", isResolverDisagreement(sdkSession, hfId));
38976
39108
  if (shouldDeclineTextCutoverForTarget(target, ops))
38977
- return declinedCutover("unsupported_text_target");
38978
- if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
39109
+ return declinedCutover("unsupported_text_target", "dom");
39110
+ if (wrongCompositionFile(deps, targetPath))
39111
+ return declinedCutover("wrong_composition_file", "dom");
38979
39112
  const result = await persistSdkCandidateMutation(
38980
39113
  sdkSession,
38981
39114
  targetPath,
@@ -38998,10 +39131,12 @@ async function sdkTimingPersist(hfId, targetPath, timingUpdate, sdkSession, deps
38998
39131
  timingSrc ? () => timingSrc(targetPath) : void 0,
38999
39132
  { targetPath, compositionPath: deps.compositionPath }
39000
39133
  );
39001
- if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled");
39002
- if (!sdkSession) return declinedCutover("session_unavailable");
39003
- if (!sdkSession.getElement(hfId)) return declinedCutover("target_not_found");
39004
- if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
39134
+ if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled", "timing");
39135
+ if (!sdkSession) return declinedCutover("session_unavailable", "timing");
39136
+ if (!sdkSession.getElement(hfId))
39137
+ return declinedCutover("target_not_found", "timing", isResolverDisagreement(sdkSession, hfId));
39138
+ if (wrongCompositionFile(deps, targetPath))
39139
+ return declinedCutover("wrong_composition_file", "timing");
39005
39140
  try {
39006
39141
  const serializedBefore = sdkSession.serialize();
39007
39142
  const result = await persistSdkCandidateMutation(
@@ -39032,11 +39167,17 @@ async function sdkTimingBatchPersist(changes, targetPath, sdkSession, deps, opti
39032
39167
  { targetPath, compositionPath: deps.compositionPath }
39033
39168
  );
39034
39169
  }
39035
- if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled");
39036
- if (!sdkSession) return declinedCutover("session_unavailable");
39037
- if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
39038
- if (changes.some((change) => !sdkSession.getElement(change.hfId)))
39039
- return declinedCutover("target_not_found");
39170
+ if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled", "timing");
39171
+ if (!sdkSession) return declinedCutover("session_unavailable", "timing");
39172
+ if (wrongCompositionFile(deps, targetPath))
39173
+ return declinedCutover("wrong_composition_file", "timing");
39174
+ const unresolved = changes.find((change) => !sdkSession.getElement(change.hfId));
39175
+ if (unresolved)
39176
+ return declinedCutover(
39177
+ "target_not_found",
39178
+ "timing",
39179
+ isResolverDisagreement(sdkSession, unresolved.hfId)
39180
+ );
39040
39181
  try {
39041
39182
  const serializedBefore = sdkSession.serialize();
39042
39183
  const result = await persistSdkCandidateMutation(
@@ -39091,9 +39232,9 @@ function sdkGsapTweenPersist(targetPath, op, sdkSession, deps, options) {
39091
39232
  );
39092
39233
  }
39093
39234
  if (!sdkFamilyEnabled("gsap-animation"))
39094
- return Promise.resolve(declinedCutover("feature_disabled"));
39235
+ return Promise.resolve(declinedCutover("feature_disabled", "gsap-animation"));
39095
39236
  if (op.kind === "add" && sdkSession && !sdkSession.getElement(op.target))
39096
- return Promise.resolve(declinedCutover("target_not_found"));
39237
+ return Promise.resolve(declinedCutover("target_not_found", "gsap-animation"));
39097
39238
  return dispatchGsapOpAndPersist("gsap-animation", targetPath, sdkSession, deps, options, (s) => {
39098
39239
  s.batch(() => {
39099
39240
  if (op.kind === "add") {
@@ -39116,9 +39257,10 @@ async function dispatchGsapOpAndPersist(family, targetPath, sdkSession, deps, op
39116
39257
  { targetPath, compositionPath: deps.compositionPath }
39117
39258
  );
39118
39259
  }
39119
- if (!sdkFamilyEnabled(family)) return declinedCutover("feature_disabled");
39120
- if (!sdkSession) return declinedCutover("session_unavailable");
39121
- if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
39260
+ if (!sdkFamilyEnabled(family)) return declinedCutover("feature_disabled", family);
39261
+ if (!sdkSession) return declinedCutover("session_unavailable", family);
39262
+ if (wrongCompositionFile(deps, targetPath))
39263
+ return declinedCutover("wrong_composition_file", family);
39122
39264
  const session = sdkSession;
39123
39265
  try {
39124
39266
  const serializedBefore = session.serialize();
@@ -39271,10 +39413,16 @@ async function sdkDeletePersist(hfId, originalContent, targetPath, sdkSession, d
39271
39413
  () => Promise.resolve(originalContent),
39272
39414
  { targetPath, compositionPath: deps.compositionPath }
39273
39415
  );
39274
- if (!sdkFamilyEnabled("lifecycle")) return declinedCutover("feature_disabled");
39275
- if (!sdkSession) return declinedCutover("session_unavailable");
39276
- if (!sdkSession.getElement(hfId)) return declinedCutover("target_not_found");
39277
- if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
39416
+ if (!sdkFamilyEnabled("lifecycle")) return declinedCutover("feature_disabled", "lifecycle");
39417
+ if (!sdkSession) return declinedCutover("session_unavailable", "lifecycle");
39418
+ if (!sdkSession.getElement(hfId))
39419
+ return declinedCutover(
39420
+ "target_not_found",
39421
+ "lifecycle",
39422
+ isResolverDisagreement(sdkSession, hfId)
39423
+ );
39424
+ if (wrongCompositionFile(deps, targetPath))
39425
+ return declinedCutover("wrong_composition_file", "lifecycle");
39278
39426
  const result = await persistSdkCandidateMutation(
39279
39427
  sdkSession,
39280
39428
  targetPath,
@@ -39518,14 +39666,7 @@ function useElementLifecycleOps({
39518
39666
  },
39519
39667
  [commitDomEditPatchBatches, onReorderShadow]
39520
39668
  );
39521
- const handleDomEditElementDelete = useCallback54(
39522
- async (selection) => {
39523
- await handleDomEditElementsDelete([selection]);
39524
- },
39525
- [handleDomEditElementsDelete]
39526
- );
39527
39669
  return {
39528
- handleDomEditElementDelete,
39529
39670
  handleDomEditElementsDelete,
39530
39671
  handleDomZIndexReorderCommit
39531
39672
  };
@@ -54076,6 +54217,10 @@ function withLane2(automation, lane) {
54076
54217
  lanes: [...automation.lanes.filter((l) => l.target !== lane.target), lane]
54077
54218
  };
54078
54219
  }
54220
+ function withPointAt(automation, target, t, v) {
54221
+ const points = automation.lanes.find((lane) => lane.target === target)?.points ?? [];
54222
+ return withLane2(automation, { target, points: mergeInsertPoint(points, t, v) });
54223
+ }
54079
54224
  function automationAttrValue(automation) {
54080
54225
  return automation.lanes.length > 0 ? serializeAutomation2(automation) : "";
54081
54226
  }
@@ -56939,24 +57084,33 @@ function AudioFxGroup({
56939
57084
  }
56940
57085
 
56941
57086
  // src/components/editor/useVolumeAutomation.ts
56942
- import { HF_AUDIO_AUTOMATION_DATA_KEY as HF_AUDIO_AUTOMATION_DATA_KEY2, VOLUME_TARGET as VOLUME_TARGET2 } from "@hyperframes/core/audio-automation";
56943
- function useVolumeAutomation(element, onSetAttributeQuiet) {
57087
+ import {
57088
+ HF_AUDIO_AUTOMATION_DATA_KEY as HF_AUDIO_AUTOMATION_DATA_KEY2,
57089
+ sampleAutomationLane as sampleAutomationLane9,
57090
+ VOLUME_TARGET as VOLUME_TARGET2
57091
+ } from "@hyperframes/core/audio-automation";
57092
+ function useVolumeAutomation(element, currentTime, onSetAttributeQuiet) {
56944
57093
  const automation = readPanelAutomation(
56945
57094
  element.dataAttributes?.[HF_AUDIO_AUTOMATION_DATA_KEY2],
56946
57095
  void 0
56947
57096
  );
57097
+ const { start: elStart, duration: elDuration } = deriveElementTiming(element);
57098
+ const clipTimeSec = clampNumber(currentTime - elStart, 0, elDuration > 0 ? elDuration : Infinity);
56948
57099
  const write = (next) => {
56949
57100
  void onSetAttributeQuiet(HF_AUDIO_AUTOMATION_ATTR3, automationAttrValue(next) || null);
56950
57101
  };
56951
57102
  const raw = element.dataAttributes?.["volume"];
56952
57103
  const parsed = raw ? Number(raw) : 1;
56953
57104
  const current = Number.isFinite(parsed) ? parsed : 1;
57105
+ const volumeLane = automation.lanes.find((lane) => lane.target === VOLUME_TARGET2);
56954
57106
  return {
56955
- volumeAutomated: automation.lanes.some((lane) => lane.target === VOLUME_TARGET2),
57107
+ volumeAutomated: volumeLane !== void 0,
57108
+ automatedVolumeValue: volumeLane ? sampleAutomationLane9(volumeLane, clipTimeSec) : void 0,
56956
57109
  // Seeded at the level the slider already shows, so automating the track does
56957
57110
  // not change how loud it is.
56958
57111
  onAutomateVolume: () => write(withSeededLane(automation, VOLUME_TARGET2, Number.isFinite(current) ? current : 1)),
56959
- onRemoveVolumeAutomation: () => write(withoutLane(automation, VOLUME_TARGET2))
57112
+ onRemoveVolumeAutomation: () => write(withoutLane(automation, VOLUME_TARGET2)),
57113
+ onCommitVolumeAt: (v) => write(withPointAt(automation, VOLUME_TARGET2, clipTimeSec, v))
56960
57114
  };
56961
57115
  }
56962
57116
 
@@ -57049,7 +57203,9 @@ function FlatMediaSection({
57049
57203
  onRemoveBackground,
57050
57204
  volumeAutomated,
57051
57205
  onAutomateVolume,
57052
- onRemoveVolumeAutomation
57206
+ onRemoveVolumeAutomation,
57207
+ onCommitVolumeAt,
57208
+ automatedVolumeValue
57053
57209
  }) {
57054
57210
  const track2 = useTrackDesignInput();
57055
57211
  const isVideo = element.tagName === "video";
@@ -57057,7 +57213,7 @@ function FlatMediaSection({
57057
57213
  const isImage = element.tagName === "img";
57058
57214
  const isVisualMedia = isVideo || isImage;
57059
57215
  const el = element.element;
57060
- const volume = parseNumericValue(element.dataAttributes.volume ?? "") ?? 1;
57216
+ const volume = volumeAutomated && automatedVolumeValue !== void 0 ? automatedVolumeValue : parseNumericValue(element.dataAttributes.volume ?? "") ?? 1;
57061
57217
  const volumeFaderPosition = audioGainToFaderPosition2(volume);
57062
57218
  const mediaStart = Number.parseFloat(
57063
57219
  element.dataAttributes["media-start"] ?? element.dataAttributes["playback-start"] ?? "0"
@@ -57206,9 +57362,15 @@ function FlatMediaSection({
57206
57362
  max: AUDIO_GAIN_FADER_MAX2,
57207
57363
  tier: volume === 1 ? "default" : "explicitCustom",
57208
57364
  displayValue: audioGainToText2(volume),
57209
- disabled: volumeAutomated,
57210
57365
  centerTick: true,
57211
- onCommit: (next) => void onSetAttribute("volume", formatAudioGain2(audioFaderPositionToGain2(next)))
57366
+ onCommit: (next) => {
57367
+ const gain = audioFaderPositionToGain2(next);
57368
+ if (volumeAutomated) {
57369
+ onCommitVolumeAt?.(gain);
57370
+ } else {
57371
+ void onSetAttribute("volume", formatAudioGain2(gain));
57372
+ }
57373
+ }
57212
57374
  }
57213
57375
  ) }),
57214
57376
  /* @__PURE__ */ jsx142(
@@ -60887,7 +61049,11 @@ function PropertyPanelFlat({
60887
61049
  const audioSelection = isAudioDomElement(element.element);
60888
61050
  const showMotionEffects = gsapEffectHandlers !== null && !audioSelection;
60889
61051
  const showMotionGroup = showMotionTiming || showMotionEffects;
60890
- const volumeAutomation = useVolumeAutomation(element, onSetAttributeQuiet ?? onSetAttributeLive);
61052
+ const volumeAutomation = useVolumeAutomation(
61053
+ element,
61054
+ currentTime,
61055
+ onSetAttributeQuiet ?? onSetAttributeLive
61056
+ );
60891
61057
  const audioGroupLabel = useMemo52(() => {
60892
61058
  const doc = element.element?.ownerDocument;
60893
61059
  const id = element.id;
@@ -62730,7 +62896,7 @@ var FileTree = memo34(function FileTree2({
62730
62896
  });
62731
62897
 
62732
62898
  // src/App.tsx
62733
- import { useState as useState156, useCallback as useCallback176, useRef as useRef171, useMemo as useMemo77, useLayoutEffect as useLayoutEffect11 } from "react";
62899
+ import { useState as useState157, useCallback as useCallback176, useRef as useRef175, useMemo as useMemo78, useLayoutEffect as useLayoutEffect11 } from "react";
62734
62900
 
62735
62901
  // src/components/renders/useRenderQueue.ts
62736
62902
  import { useState as useState95, useEffect as useEffect89, useCallback as useCallback80, useRef as useRef104, useMemo as useMemo56 } from "react";
@@ -65147,45 +65313,70 @@ function usePanelLayout(initialState2) {
65147
65313
  }
65148
65314
 
65149
65315
  // src/hooks/useFileManager.ts
65150
- import { useState as useState103, useCallback as useCallback88, useMemo as useMemo59, useRef as useRef114 } from "react";
65316
+ import { useState as useState103, useCallback as useCallback88, useMemo as useMemo59, useRef as useRef115 } from "react";
65151
65317
 
65152
65318
  // src/hooks/useFileTree.ts
65153
- import { useState as useState102, useCallback as useCallback86, useEffect as useEffect94, useMemo as useMemo58 } from "react";
65319
+ import { useState as useState102, useCallback as useCallback86, useEffect as useEffect94, useMemo as useMemo58, useRef as useRef113 } from "react";
65320
+ var EMPTY_FILE_LIST = [];
65154
65321
  function useFileTree({ projectId, projectIdRef }) {
65155
- const [projectDir, setProjectDir] = useState102(null);
65156
- const [fileTree, setFileTree] = useState102([]);
65157
- const [compositionPaths, setCompositionPaths] = useState102([]);
65158
- const [fileTreeLoaded, setFileTreeLoaded] = useState102(false);
65322
+ const [fetched, setFetched] = useState102(null);
65323
+ const current = fetched?.projectId === projectId ? fetched : null;
65324
+ const fileTree = current?.fileTree ?? EMPTY_FILE_LIST;
65325
+ const compositionPaths = current?.compositionPaths ?? EMPTY_FILE_LIST;
65326
+ const projectDir = current?.projectDir ?? null;
65327
+ const fileTreeLoaded = current?.loaded ?? false;
65159
65328
  useEffect94(() => {
65160
- if (!projectId) {
65161
- setFileTreeLoaded(false);
65162
- return;
65163
- }
65329
+ if (!projectId) return;
65164
65330
  let cancelled = false;
65165
- setFileTreeLoaded(false);
65331
+ setFetched({ projectId, loaded: false, fileTree: [], compositionPaths: [], projectDir: null });
65166
65332
  fetch(buildProjectApiPath(projectId)).then((r) => r.json()).then((data) => {
65167
65333
  if (cancelled) return;
65168
- if (data.files) setFileTree(data.files);
65169
- if (data.compositions) setCompositionPaths(data.compositions);
65170
- setProjectDir(typeof data.dir === "string" ? data.dir : null);
65334
+ setFetched({
65335
+ projectId,
65336
+ loaded: true,
65337
+ fileTree: data.files ?? [],
65338
+ compositionPaths: data.compositions ?? [],
65339
+ projectDir: typeof data.dir === "string" ? data.dir : null
65340
+ });
65171
65341
  void captureProjectProvenance(projectId, data.files ?? [], data.compositions ?? []);
65172
65342
  }).catch(() => {
65173
- if (!cancelled) setProjectDir(null);
65174
- }).finally(() => {
65175
- if (!cancelled) setFileTreeLoaded(true);
65343
+ if (!cancelled) {
65344
+ setFetched((prev) => prev?.projectId === projectId ? { ...prev, loaded: true } : prev);
65345
+ }
65176
65346
  });
65177
65347
  return () => {
65178
65348
  cancelled = true;
65179
65349
  };
65180
65350
  }, [projectId]);
65351
+ const refreshRequestRef = useRef113(0);
65352
+ const refreshAbortRef = useRef113(null);
65181
65353
  const refreshFileTree = useCallback86(async () => {
65182
65354
  const pid = projectIdRef.current;
65183
65355
  if (!pid) return;
65184
- const res = await fetch(buildProjectApiPath(pid));
65185
- const data = await res.json();
65186
- if (data.files) setFileTree(data.files);
65356
+ refreshAbortRef.current?.abort();
65357
+ const requestId = ++refreshRequestRef.current;
65358
+ const controller = new AbortController();
65359
+ refreshAbortRef.current = controller;
65360
+ let data;
65361
+ try {
65362
+ const res = await fetch(buildProjectApiPath(pid), { signal: controller.signal });
65363
+ data = await res.json();
65364
+ } catch (err) {
65365
+ if (err instanceof DOMException && err.name === "AbortError") return;
65366
+ throw err;
65367
+ }
65368
+ if (data.files && requestId === refreshRequestRef.current) {
65369
+ setFetched(
65370
+ (prev) => prev?.projectId === pid ? {
65371
+ ...prev,
65372
+ fileTree: data.files ?? prev.fileTree,
65373
+ // A response that omits `compositions` must not wipe out a
65374
+ // known-good list — only replace it when the field is present.
65375
+ compositionPaths: data.compositions ?? prev.compositionPaths
65376
+ } : prev
65377
+ );
65378
+ }
65187
65379
  }, [projectIdRef]);
65188
- const compositions = compositionPaths;
65189
65380
  const assets = useMemo58(
65190
65381
  () => fileTree.filter((f) => !f.endsWith(".html") && !f.endsWith(".md") && !f.endsWith(".json")),
65191
65382
  [fileTree]
@@ -65201,17 +65392,16 @@ function useFileTree({ projectId, projectIdRef }) {
65201
65392
  return {
65202
65393
  projectDir,
65203
65394
  fileTree,
65204
- setFileTree,
65205
65395
  fileTreeLoaded,
65206
65396
  refreshFileTree,
65207
- compositions,
65397
+ compositions: compositionPaths,
65208
65398
  assets,
65209
65399
  fontAssets
65210
65400
  };
65211
65401
  }
65212
65402
 
65213
65403
  // src/hooks/useEditorSave.ts
65214
- import { useCallback as useCallback87, useRef as useRef113 } from "react";
65404
+ import { useCallback as useCallback87, useRef as useRef114 } from "react";
65215
65405
  var FAILURE_BURST_MS = 5e3;
65216
65406
  function useEditorSave({
65217
65407
  editingPathRef,
@@ -65222,13 +65412,13 @@ function useEditorSave({
65222
65412
  setRefreshKey,
65223
65413
  showToast
65224
65414
  }) {
65225
- const saveRafRef = useRef113(null);
65226
- const refreshRafRef = useRef113(null);
65227
- const lastFailureToastAtRef = useRef113(null);
65228
- const lastFailureReportRef = useRef113(null);
65229
- const pendingCandidateRef = useRef113(null);
65230
- const inFlightRef = useRef113(null);
65231
- const inFlightCandidateRef = useRef113(null);
65415
+ const saveRafRef = useRef114(null);
65416
+ const refreshRafRef = useRef114(null);
65417
+ const lastFailureToastAtRef = useRef114(null);
65418
+ const lastFailureReportRef = useRef114(null);
65419
+ const pendingCandidateRef = useRef114(null);
65420
+ const inFlightRef = useRef114(null);
65421
+ const inFlightCandidateRef = useRef114(null);
65232
65422
  const reportFailure = useCallback87(
65233
65423
  (path, error) => {
65234
65424
  const now2 = Date.now();
@@ -65344,11 +65534,11 @@ function useFileManager({
65344
65534
  }) {
65345
65535
  const [editingFile, setEditingFile] = useState103(null);
65346
65536
  const [revealSourceOffset, setRevealSourceOffset] = useState103(null);
65347
- const editingPathRef = useRef114(editingFile?.path);
65537
+ const editingPathRef = useRef115(editingFile?.path);
65348
65538
  editingPathRef.current = editingFile?.path;
65349
- const projectIdRef = useRef114(projectId);
65539
+ const projectIdRef = useRef115(projectId);
65350
65540
  projectIdRef.current = projectId;
65351
- const importedFontAssetsRef = useRef114([]);
65541
+ const importedFontAssetsRef = useRef115([]);
65352
65542
  const fileVersionScope = useMemo59(
65353
65543
  () => ({ projectId, versions: /* @__PURE__ */ new Map() }),
65354
65544
  [projectId]
@@ -65363,7 +65553,6 @@ function useFileManager({
65363
65553
  const {
65364
65554
  projectDir,
65365
65555
  fileTree,
65366
- setFileTree,
65367
65556
  fileTreeLoaded,
65368
65557
  refreshFileTree,
65369
65558
  compositions,
@@ -65498,8 +65687,8 @@ function useFileManager({
65498
65687
  },
65499
65688
  [fileVersions, updateEditingFileContent, writeProjectFile]
65500
65689
  );
65501
- const revealRequestIdRef = useRef114(0);
65502
- const revealAbortRef = useRef114(null);
65690
+ const revealRequestIdRef = useRef115(0);
65691
+ const revealAbortRef = useRef115(null);
65503
65692
  const handleFileSelect = useCallback88(
65504
65693
  (path) => {
65505
65694
  const pid = projectIdRef.current;
@@ -65747,7 +65936,6 @@ function useFileManager({
65747
65936
  projectDir,
65748
65937
  fileTree,
65749
65938
  fileTreeLoaded,
65750
- setFileTree,
65751
65939
  // Refs
65752
65940
  editingPathRef,
65753
65941
  projectIdRef,
@@ -65787,7 +65975,7 @@ function useFileManager({
65787
65975
  }
65788
65976
 
65789
65977
  // src/hooks/usePreviewPersistence.ts
65790
- import { useCallback as useCallback89, useRef as useRef115, useState as useState104 } from "react";
65978
+ import { useCallback as useCallback89, useRef as useRef116, useState as useState104 } from "react";
65791
65979
 
65792
65980
  // src/utils/gsapUndoRestore.ts
65793
65981
  function elementIdentityKey(el) {
@@ -66023,11 +66211,11 @@ function usePreviewPersistence({
66023
66211
  }) {
66024
66212
  void _recordEdit;
66025
66213
  const [domEditSaveQueuePaused, setDomEditSaveQueuePaused] = useState104(null);
66026
- const domTextCommitVersionRef = useRef115(0);
66027
- const showToastRef = useRef115(showToast);
66214
+ const domTextCommitVersionRef = useRef116(0);
66215
+ const showToastRef = useRef116(showToast);
66028
66216
  showToastRef.current = showToast;
66029
- const domEditSaveQueueRef = useRef115(null);
66030
- const applyStudioManualEditsToPreviewRef = useRef115(async () => {
66217
+ const domEditSaveQueueRef = useRef116(null);
66218
+ const applyStudioManualEditsToPreviewRef = useRef116(async () => {
66031
66219
  });
66032
66220
  if (!domEditSaveQueueRef.current) {
66033
66221
  domEditSaveQueueRef.current = createDomEditSaveQueue({
@@ -66120,10 +66308,10 @@ function usePreviewPersistence({
66120
66308
  }
66121
66309
 
66122
66310
  // src/hooks/usePreviewDocumentVersion.ts
66123
- import { useCallback as useCallback90, useEffect as useEffect95, useRef as useRef116, useState as useState105 } from "react";
66311
+ import { useCallback as useCallback90, useEffect as useEffect95, useRef as useRef117, useState as useState105 } from "react";
66124
66312
  function usePreviewDocumentVersion() {
66125
66313
  const [previewDocumentVersion, setPreviewDocumentVersion] = useState105(0);
66126
- const refreshTimersRef = useRef116([]);
66314
+ const refreshTimersRef = useRef117([]);
66127
66315
  const refresh = useCallback90(() => {
66128
66316
  for (const id of refreshTimersRef.current) clearTimeout(id);
66129
66317
  refreshTimersRef.current = [];
@@ -66143,10 +66331,10 @@ function usePreviewDocumentVersion() {
66143
66331
  }
66144
66332
 
66145
66333
  // src/hooks/useTimelineEditing.ts
66146
- import { useCallback as useCallback100, useRef as useRef119 } from "react";
66334
+ import { useCallback as useCallback100, useRef as useRef121 } from "react";
66147
66335
 
66148
66336
  // src/hooks/useRazorSplit.ts
66149
- import { useCallback as useCallback91, useRef as useRef117 } from "react";
66337
+ import { useCallback as useCallback91, useRef as useRef118 } from "react";
66150
66338
 
66151
66339
  // src/utils/razorSplitTransaction.ts
66152
66340
  function targetIdentity(path, target) {
@@ -66164,7 +66352,10 @@ function buildCutTarget(element, target, splitTime) {
66164
66352
  elementDuration: element.duration,
66165
66353
  ...element.playbackStart != null ? { playbackStart: element.playbackStart } : {},
66166
66354
  ...element.playbackRate != null ? { playbackRate: element.playbackRate } : {},
66167
- ...element.kind === "composition" ? { isComposition: true } : {}
66355
+ ...element.kind === "composition" ? { isComposition: true } : {},
66356
+ // Pin both halves to the current track: unstamped, the runtime's positional
66357
+ // fallback (parseAuthoredTrack) renumbers the new sibling onto a new row.
66358
+ track: resolveElementTrack(element)
66168
66359
  };
66169
66360
  }
66170
66361
  function buildAtomicCutIntents(elements, splitTime, activeCompPath) {
@@ -66278,7 +66469,7 @@ function useRazorSplit({
66278
66469
  forceReloadSdkSession,
66279
66470
  isRecordingRef
66280
66471
  }) {
66281
- const projectIdRef = useRef117(projectId);
66472
+ const projectIdRef = useRef118(projectId);
66282
66473
  projectIdRef.current = projectId;
66283
66474
  const synchronize = useCallback91(() => {
66284
66475
  let failure;
@@ -66479,22 +66670,25 @@ function useTimelineAssetDropOps({
66479
66670
  (te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath
66480
66671
  );
66481
66672
  const newElementZIndex = Math.max(1, relevantElements.length + 1);
66482
- const patchedContent = insertTimelineAssetIntoSource(
66483
- originalContent,
66484
- buildTimelineAssetInsertHtml({
66485
- id: newId,
66486
- hfId: `hf-${generateId()}`,
66487
- assetPath: resolvedAssetSrc,
66488
- kind,
66489
- start: normalizedStart,
66490
- duration: normalizedDuration,
66491
- track: placement.track,
66492
- zIndex: newElementZIndex,
66493
- geometry: fitTimelineAssetGeometry(
66494
- null,
66495
- resolveTimelineAssetCompositionSize(originalContent)
66496
- )
66497
- })
66673
+ const patchedContent = extendRootDurationInSource(
66674
+ insertTimelineAssetIntoSource(
66675
+ originalContent,
66676
+ buildTimelineAssetInsertHtml({
66677
+ id: newId,
66678
+ hfId: `hf-${generateId()}`,
66679
+ assetPath: resolvedAssetSrc,
66680
+ kind,
66681
+ start: normalizedStart,
66682
+ duration: normalizedDuration,
66683
+ track: placement.track,
66684
+ zIndex: newElementZIndex,
66685
+ geometry: fitTimelineAssetGeometry(
66686
+ null,
66687
+ resolveTimelineAssetCompositionSize(originalContent)
66688
+ )
66689
+ })
66690
+ ),
66691
+ normalizedStart + normalizedDuration
66498
66692
  );
66499
66693
  await saveProjectFilesWithHistory({
66500
66694
  projectId: pid,
@@ -66835,7 +67029,16 @@ function useSetElementAttribute({
66835
67029
  }
66836
67030
 
66837
67031
  // src/hooks/useTimelineDeleteOps.ts
66838
- import { useCallback as useCallback95 } from "react";
67032
+ import { useCallback as useCallback95, useRef as useRef119 } from "react";
67033
+ function applyRippleShifts(survivors, changes) {
67034
+ if (!changes) return survivors;
67035
+ const newStartByElement = new Map(changes.map((c) => [c.element, c.start]));
67036
+ return survivors.map((te) => {
67037
+ const newStart = newStartByElement.get(te);
67038
+ return newStart != null ? { ...te, start: newStart } : te;
67039
+ });
67040
+ }
67041
+ var deleteGestureSeq = 0;
66839
67042
  function useTimelineDeleteOps({
66840
67043
  projectIdRef,
66841
67044
  activeCompPath,
@@ -66846,8 +67049,10 @@ function useTimelineDeleteOps({
66846
67049
  reloadPreview,
66847
67050
  isRecordingRef,
66848
67051
  forceReloadSdkSession,
66849
- previewIframeRef
67052
+ previewIframeRef,
67053
+ handleTimelineGroupMove
66850
67054
  }) {
67055
+ const rippleNoticeShownRef = useRef119(false);
66851
67056
  const handleTimelineElementsDelete = useCallback95(
66852
67057
  // fallow-ignore-next-line complexity
66853
67058
  async (selection) => {
@@ -66895,11 +67100,14 @@ function useTimelineDeleteOps({
66895
67100
  if (deleteContentEnd > 0 && targetPath === (activeCompPath || "index.html")) {
66896
67101
  usePlayerStore.getState().setDuration(deleteContentEnd);
66897
67102
  }
67103
+ const coalesceKey = `main-track-ripple-delete:${deleteGestureSeq++}`;
67104
+ const deleteHistoryLabel = "Delete timeline clip";
66898
67105
  try {
66899
67106
  await saveProjectFilesWithHistory({
66900
67107
  projectId: pid,
66901
- label: "Delete timeline clip",
67108
+ label: deleteHistoryLabel,
66902
67109
  kind: "timeline",
67110
+ coalesceKey,
66903
67111
  files: { [targetPath]: patchedContent },
66904
67112
  readFile: async () => originalContent,
66905
67113
  // remove-element already wrote the removal, so disk holds THAT — not the
@@ -66913,15 +67121,53 @@ function useTimelineDeleteOps({
66913
67121
  throw error;
66914
67122
  }
66915
67123
  const deletedKeys = new Set(sameFile.map((te) => te.key ?? te.id));
66916
- usePlayerStore.getState().setElements(timelineElements.filter((te) => !deletedKeys.has(te.key ?? te.id)));
67124
+ const survivors = timelineElements.filter((te) => !deletedKeys.has(te.key ?? te.id));
67125
+ const rippleShifts = resolveMainTrackDeleteRippleShifts(
67126
+ survivors,
67127
+ sameFile,
67128
+ usePlayerStore.getState().rippleEditEnabled
67129
+ );
67130
+ let rippleApplied = null;
67131
+ let rippleFailed = false;
67132
+ if (rippleShifts) {
67133
+ const rippleChanges = resolveShiftedElements(survivors, rippleShifts);
67134
+ try {
67135
+ await handleTimelineGroupMove(rippleChanges, {
67136
+ coalesceKey,
67137
+ coalesceMs: Number.POSITIVE_INFINITY,
67138
+ // Coalescing keeps the LAST entry's label; without this the undo
67139
+ // toast reads "Undid Move timeline clips" after a delete, naming
67140
+ // the ripple's mechanics instead of what the user actually did.
67141
+ label: deleteHistoryLabel,
67142
+ // This call gets its own, more specific failure toast below —
67143
+ // the generic one would tell the user about one action twice.
67144
+ suppressFailureToast: true
67145
+ });
67146
+ rippleApplied = rippleChanges;
67147
+ } catch (error) {
67148
+ rippleFailed = true;
67149
+ console.error("[Timeline] ripple-edit failed to persist after delete", error);
67150
+ showToast("Clip deleted, but the gap could not be closed.", "error");
67151
+ }
67152
+ }
67153
+ usePlayerStore.getState().setElements(applyRippleShifts(survivors, rippleApplied));
66917
67154
  usePlayerStore.getState().setSelectedElementId(null);
66918
67155
  usePlayerStore.getState().setSelectedElementIds(/* @__PURE__ */ new Set());
66919
67156
  forceReloadSdkSession?.();
66920
67157
  reloadPreview();
66921
- showToast(
66922
- `Deleted ${label}. Use Undo to restore ${sameFile.length === 1 ? "it" : "them"}.`,
66923
- "info"
66924
- );
67158
+ if (!rippleFailed) {
67159
+ showToast(
67160
+ `Deleted ${label}. Use Undo to restore ${sameFile.length === 1 ? "it" : "them"}.`,
67161
+ "info"
67162
+ );
67163
+ }
67164
+ if (rippleApplied && !rippleNoticeShownRef.current) {
67165
+ rippleNoticeShownRef.current = true;
67166
+ showToast(
67167
+ "Ripple closed the gap on the main track. Undo (\u2318Z) restores it, or turn off Ripple in the timeline toolbar.",
67168
+ "info"
67169
+ );
67170
+ }
66925
67171
  } catch (error) {
66926
67172
  const message = error instanceof Error ? error.message : "Failed to delete timeline clip";
66927
67173
  showToast(message);
@@ -66937,7 +67183,8 @@ function useTimelineDeleteOps({
66937
67183
  reloadPreview,
66938
67184
  isRecordingRef,
66939
67185
  forceReloadSdkSession,
66940
- previewIframeRef
67186
+ previewIframeRef,
67187
+ handleTimelineGroupMove
66941
67188
  ]
66942
67189
  );
66943
67190
  const handleTimelineElementDelete = useCallback95(
@@ -67541,21 +67788,22 @@ function useTimelineGroupEditing({
67541
67788
  syncPreviewContentDuration(previewIframeRef.current);
67542
67789
  const coalesceKey = options?.coalesceKey ?? moveCoalesceKey(changes);
67543
67790
  const coalesceMs = options?.coalesceMs;
67544
- return enqueueGroupOperation("Move timeline clips", async (projectId) => {
67791
+ const label = options?.label ?? "Move timeline clips";
67792
+ return enqueueGroupOperation(label, async (projectId) => {
67545
67793
  await options?.beforeTiming;
67546
67794
  const handledBySdk = await trySdkBatchPersist({
67547
67795
  changes,
67548
67796
  sdkChanges: toSdkTimingChanges(changes, (change) => ({ start: change.start })),
67549
67797
  eligible: changes.every((change) => change.track == null),
67550
67798
  needsExtension,
67551
- label: "Move timeline clips",
67799
+ label,
67552
67800
  coalesceKey,
67553
67801
  coalesceMs
67554
67802
  });
67555
67803
  if (!handledBySdk) {
67556
67804
  await persistServerBatch(
67557
67805
  projectId,
67558
- "Move timeline clips",
67806
+ label,
67559
67807
  changes.map((change) => ({
67560
67808
  element: change.element,
67561
67809
  buildPatches: (original, target) => buildTimelineMoveTimingPatch(
@@ -67576,7 +67824,7 @@ function useTimelineGroupEditing({
67576
67824
  projectId,
67577
67825
  iframe: previewIframeRef.current,
67578
67826
  reloadPreview,
67579
- label: "Move timeline clips",
67827
+ label,
67580
67828
  errorLabel: "Failed to shift GSAP positions",
67581
67829
  coalesceKey,
67582
67830
  recordEdit,
@@ -67595,7 +67843,11 @@ function useTimelineGroupEditing({
67595
67843
  }
67596
67844
  }).catch((error) => {
67597
67845
  rollbackDuration();
67598
- showToast(getStudioSaveErrorMessage(error), "error");
67846
+ if (options?.suppressFailureToast) {
67847
+ console.error("[Timeline] group move failed to persist", error);
67848
+ } else {
67849
+ showToast(getStudioSaveErrorMessage(error), "error");
67850
+ }
67599
67851
  throw error;
67600
67852
  });
67601
67853
  },
@@ -67713,10 +67965,10 @@ function useTimelineGroupEditing({
67713
67965
  }
67714
67966
 
67715
67967
  // src/hooks/useBlockedTimelineEditToast.ts
67716
- import { useCallback as useCallback99, useRef as useRef118 } from "react";
67968
+ import { useCallback as useCallback99, useRef as useRef120 } from "react";
67717
67969
  var BLOCKED_TOAST_INTERVAL_MS = 1500;
67718
67970
  function useBlockedTimelineEditToast(showToast) {
67719
- const lastAtRef = useRef118(0);
67971
+ const lastAtRef = useRef120(0);
67720
67972
  return useCallback99(
67721
67973
  (_element) => {
67722
67974
  const now2 = Date.now();
@@ -67748,9 +68000,9 @@ function useTimelineEditing({
67748
68000
  invalidateGsapCache,
67749
68001
  handleDomZIndexReorderCommitRef
67750
68002
  }) {
67751
- const projectIdRef = useRef119(projectId);
68003
+ const projectIdRef = useRef121(projectId);
67752
68004
  projectIdRef.current = projectId;
67753
- const editQueueRef = useRef119(Promise.resolve());
68005
+ const editQueueRef = useRef121(Promise.resolve());
67754
68006
  const enqueueEdit = useCallback100(
67755
68007
  (element, label, buildPatches, coalesceKey) => {
67756
68008
  if (isRecordingRef?.current) {
@@ -68052,7 +68304,8 @@ function useTimelineEditing({
68052
68304
  reloadPreview,
68053
68305
  isRecordingRef,
68054
68306
  forceReloadSdkSession,
68055
- previewIframeRef
68307
+ previewIframeRef,
68308
+ handleTimelineGroupMove: groupEditing.handleTimelineGroupMove
68056
68309
  });
68057
68310
  const { handleTimelineAssetDrop, handleTimelineFileDrop, handleTimelineCompositionDrop } = useTimelineAssetDropOps({
68058
68311
  projectIdRef,
@@ -68120,7 +68373,7 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
68120
68373
  import { useCallback as useCallback123 } from "react";
68121
68374
 
68122
68375
  // src/hooks/useAskAgentModal.ts
68123
- import { useState as useState106, useCallback as useCallback101, useRef as useRef120, useEffect as useEffect96 } from "react";
68376
+ import { useState as useState106, useCallback as useCallback101, useRef as useRef122, useEffect as useEffect96 } from "react";
68124
68377
  function useAskAgentModal({
68125
68378
  activeCompPath,
68126
68379
  projectDir,
@@ -68136,7 +68389,7 @@ function useAskAgentModal({
68136
68389
  );
68137
68390
  const [copiedAgentPrompt, setCopiedAgentPrompt] = useState106(false);
68138
68391
  const [agentModalOpen, setAgentModalOpen] = useState106(false);
68139
- const copiedAgentTimerRef = useRef120(null);
68392
+ const copiedAgentTimerRef = useRef122(null);
68140
68393
  const preloadAgentPromptSnippet = useCallback101(
68141
68394
  async (selection) => {
68142
68395
  const pid = projectIdRef.current;
@@ -68231,7 +68484,7 @@ function useAskAgentModal({
68231
68484
  }
68232
68485
 
68233
68486
  // src/hooks/useDomSelection.ts
68234
- import { useState as useState107, useCallback as useCallback102, useRef as useRef121, useEffect as useEffect98 } from "react";
68487
+ import { useState as useState107, useCallback as useCallback102, useRef as useRef123, useEffect as useEffect98 } from "react";
68235
68488
 
68236
68489
  // src/utils/domEditHelpers.ts
68237
68490
  function domEditSelectionsTargetSame(a, b) {
@@ -68458,14 +68711,14 @@ function useDomSelection({
68458
68711
  const [domEditGroupSelections, setDomEditGroupSelections] = useState107([]);
68459
68712
  const [domEditHoverSelection, setDomEditHoverSelection] = useState107(null);
68460
68713
  const [activeGroupElement, setActiveGroupElementState] = useState107(null);
68461
- const rightPanelTabRef = useRef121(rightPanelTab);
68714
+ const rightPanelTabRef = useRef123(rightPanelTab);
68462
68715
  rightPanelTabRef.current = rightPanelTab;
68463
- const domEditSelectionRef = useRef121(domEditSelection);
68464
- const domEditGroupSelectionsRef = useRef121(domEditGroupSelections);
68465
- const domEditHoverSelectionRef = useRef121(domEditHoverSelection);
68466
- const activeGroupElementRef = useRef121(activeGroupElement);
68467
- const compositionIdentityRef = useRef121({ activeCompPath, projectId });
68468
- const timelineSelectSeqRef = useRef121(0);
68716
+ const domEditSelectionRef = useRef123(domEditSelection);
68717
+ const domEditGroupSelectionsRef = useRef123(domEditGroupSelections);
68718
+ const domEditHoverSelectionRef = useRef123(domEditHoverSelection);
68719
+ const activeGroupElementRef = useRef123(activeGroupElement);
68720
+ const compositionIdentityRef = useRef123({ activeCompPath, projectId });
68721
+ const timelineSelectSeqRef = useRef123(0);
68469
68722
  domEditSelectionRef.current = domEditSelection;
68470
68723
  domEditGroupSelectionsRef.current = domEditGroupSelections;
68471
68724
  domEditHoverSelectionRef.current = domEditHoverSelection;
@@ -68660,7 +68913,7 @@ function useDomSelection({
68660
68913
  },
68661
68914
  [applyDomSelection, buildDomSelectionForTimelineElement]
68662
68915
  );
68663
- const refreshDomEditGroupSelectionsFromPreviewRef = useRef121(async () => {
68916
+ const refreshDomEditGroupSelectionsFromPreviewRef = useRef123(async () => {
68664
68917
  });
68665
68918
  const refreshDomEditSelectionFromPreview = useCallback102(
68666
68919
  // fallow-ignore-next-line complexity
@@ -68818,7 +69071,7 @@ function useDomSelection({
68818
69071
  }
68819
69072
 
68820
69073
  // src/hooks/usePreviewInteraction.ts
68821
- import { useCallback as useCallback103, useRef as useRef122 } from "react";
69074
+ import { useCallback as useCallback103, useRef as useRef124 } from "react";
68822
69075
  var CYCLE_RADIUS_PX = 6;
68823
69076
  var CYCLE_WINDOW_MS = 600;
68824
69077
  var DOUBLE_CLICK_MS = 400;
@@ -68835,8 +69088,8 @@ function usePreviewInteraction({
68835
69088
  setActiveGroupElement,
68836
69089
  onClickToSource
68837
69090
  }) {
68838
- const cycleRef = useRef122(null);
68839
- const lastDownRef = useRef122(null);
69091
+ const cycleRef = useRef124(null);
69092
+ const lastDownRef = useRef124(null);
68840
69093
  const pausePreviewPlayback = useCallback103(() => {
68841
69094
  const pausedTime = pauseStudioPreviewPlayback(previewIframeRef.current);
68842
69095
  const playerStore = usePlayerStore.getState();
@@ -68994,7 +69247,7 @@ function usePreviewInteraction({
68994
69247
  }
68995
69248
 
68996
69249
  // src/hooks/useDomEditCommits.ts
68997
- import { useCallback as useCallback108, useRef as useRef125 } from "react";
69250
+ import { useCallback as useCallback108, useRef as useRef127 } from "react";
68998
69251
  import { findUnsafeDomPatchValues as findUnsafeDomPatchValues2 } from "@hyperframes/core/studio-api/finite-mutation";
68999
69252
 
69000
69253
  // src/utils/studioFontHelpers.ts
@@ -69155,7 +69408,7 @@ function useDomEditPositionPatchCommit({
69155
69408
  }
69156
69409
 
69157
69410
  // src/hooks/useDomEditTextCommits.ts
69158
- import { useCallback as useCallback106, useRef as useRef124 } from "react";
69411
+ import { useCallback as useCallback106, useRef as useRef126 } from "react";
69159
69412
 
69160
69413
  // src/hooks/domEditTextFieldCommitOps.ts
69161
69414
  function hasSameKeysInSamePositions(originalFields, nextFields) {
@@ -69210,7 +69463,7 @@ function planDomTextCommit(originalTextFields, nextTextFields, plainTextContent)
69210
69463
  }
69211
69464
 
69212
69465
  // src/hooks/useDomEditAttributeCommits.ts
69213
- import { useCallback as useCallback105, useRef as useRef123 } from "react";
69466
+ import { useCallback as useCallback105, useRef as useRef125 } from "react";
69214
69467
  import { HF_AUDIO_GROUP_ATTR as HF_AUDIO_GROUP_ATTR5, HF_AUDIO_GROUP_TAG as HF_AUDIO_GROUP_TAG8 } from "@hyperframes/core/audio-groups";
69215
69468
  function resolveFullAttrName(attr, prefixData) {
69216
69469
  return prefixData && !attr.startsWith("data-") ? `data-${attr}` : attr;
@@ -69250,7 +69503,7 @@ function useDomEditAttributeCommits({
69250
69503
  refreshDomEditSelectionFromPreview,
69251
69504
  persistDomEditOperations
69252
69505
  }) {
69253
- const domAttributeCommitVersionRef = useRef123(/* @__PURE__ */ new Map());
69506
+ const domAttributeCommitVersionRef = useRef125(/* @__PURE__ */ new Map());
69254
69507
  const commitDataAttribute = useCallback105(
69255
69508
  async (attr, value, options) => {
69256
69509
  if (!domEditSelection) return;
@@ -69532,8 +69785,8 @@ function useDomEditTextCommits({
69532
69785
  persistDomEditOperations,
69533
69786
  resolveImportedFontAsset
69534
69787
  }) {
69535
- const domTextCommitVersionRef = useRef124(/* @__PURE__ */ new Map());
69536
- const domStyleCommitVersionRef = useRef124(/* @__PURE__ */ new Map());
69788
+ const domTextCommitVersionRef = useRef126(/* @__PURE__ */ new Map());
69789
+ const domStyleCommitVersionRef = useRef126(/* @__PURE__ */ new Map());
69537
69790
  const {
69538
69791
  handleDomAttributeCommit,
69539
69792
  handleDomAttributeLiveCommit,
@@ -70444,7 +70697,7 @@ function useDomEditCommits({
70444
70697
  },
70445
70698
  [fileTree, projectId, importedFontAssetsRef]
70446
70699
  );
70447
- const reportedUnresolvableRef = useRef125(/* @__PURE__ */ new Set());
70700
+ const reportedUnresolvableRef = useRef127(/* @__PURE__ */ new Set());
70448
70701
  const performPersistDomEditOperations = useCallback108(
70449
70702
  // fallow-ignore-next-line complexity
70450
70703
  async (selection, operations, options, expectedProjectId) => {
@@ -70689,7 +70942,7 @@ function useDomEditCommits({
70689
70942
  showToast,
70690
70943
  commitPositionPatchToHtml
70691
70944
  });
70692
- const { handleDomEditElementDelete, handleDomEditElementsDelete, handleDomZIndexReorderCommit } = useElementLifecycleOps({
70945
+ const { handleDomEditElementsDelete, handleDomZIndexReorderCommit } = useElementLifecycleOps({
70693
70946
  activeCompPath,
70694
70947
  showToast,
70695
70948
  writeProjectFile,
@@ -70722,7 +70975,6 @@ function useDomEditCommits({
70722
70975
  handleDomBoxSizeCommit,
70723
70976
  handleDomRotationCommit,
70724
70977
  handleDomManualEditsReset,
70725
- handleDomEditElementDelete,
70726
70978
  handleDomEditElementsDelete,
70727
70979
  handleDomZIndexReorderCommit
70728
70980
  };
@@ -70730,13 +70982,21 @@ function useDomEditCommits({
70730
70982
 
70731
70983
  // src/hooks/useGroupCommits.ts
70732
70984
  import { useCallback as useCallback109 } from "react";
70733
- function computeGroupGeometry(members) {
70985
+ function resolveAuthoredTrack(selection, timelineElements) {
70986
+ const id = findMatchingTimelineElementId(selection, timelineElements);
70987
+ if (!id) return void 0;
70988
+ const match = timelineElements.find((el) => (el.key ?? el.id) === id);
70989
+ if (!match) return void 0;
70990
+ return resolveElementTrack(match);
70991
+ }
70992
+ function computeGroupGeometry(members, timelineElements) {
70734
70993
  const boxes = members.map((m) => ({
70735
70994
  target: buildDomEditPatchTarget(m),
70736
70995
  left: m.element.offsetLeft,
70737
70996
  top: m.element.offsetTop,
70738
70997
  right: m.element.offsetLeft + m.element.offsetWidth,
70739
- bottom: m.element.offsetTop + m.element.offsetHeight
70998
+ bottom: m.element.offsetTop + m.element.offsetHeight,
70999
+ track: resolveAuthoredTrack(m, timelineElements)
70740
71000
  }));
70741
71001
  const left = Math.min(...boxes.map((b) => b.left));
70742
71002
  const top = Math.min(...boxes.map((b) => b.top));
@@ -70745,9 +71005,29 @@ function computeGroupGeometry(members) {
70745
71005
  return {
70746
71006
  bbox: { left, top, width, height },
70747
71007
  targets: boxes.map((b) => b.target),
70748
- rebases: boxes.map((b) => ({ target: b.target, left: b.left - left, top: b.top - top }))
71008
+ rebases: boxes.map((b) => ({
71009
+ target: b.target,
71010
+ left: b.left - left,
71011
+ top: b.top - top,
71012
+ track: b.track
71013
+ }))
70749
71014
  };
70750
71015
  }
71016
+ function resolveGroupChildTracks(group, timelineElements) {
71017
+ const sourceFile = group.sourceFile || "index.html";
71018
+ const result = [];
71019
+ for (const child of Array.from(group.element.children)) {
71020
+ const id = child.id || void 0;
71021
+ const hfId = readHfId(child);
71022
+ if (!id && !hfId) continue;
71023
+ const track2 = resolveAuthoredTrack(
71024
+ { id, hfId, sourceFile, isCompositionHost: false },
71025
+ timelineElements
71026
+ );
71027
+ result.push({ target: buildDomEditPatchTarget({ id, hfId }), track: track2 });
71028
+ }
71029
+ return result;
71030
+ }
70751
71031
  async function commitStructuralMutation(pid, targetPath, route, body, label, deps) {
70752
71032
  const originalContent = await readProjectFileContent(pid, targetPath);
70753
71033
  const mutateResponse = await fetch(
@@ -70779,7 +71059,7 @@ async function commitStructuralMutation(pid, targetPath, route, body, label, dep
70779
71059
  return mutateData;
70780
71060
  }
70781
71061
  function useGroupCommits(params) {
70782
- const { activeCompPath, showToast, projectIdRef } = params;
71062
+ const { activeCompPath, showToast, projectIdRef, timelineElements } = params;
70783
71063
  const groupSelection = useCallback109(
70784
71064
  async (members) => {
70785
71065
  const pid = projectIdRef.current;
@@ -70791,7 +71071,7 @@ function useGroupCommits(params) {
70791
71071
  }
70792
71072
  const doc = members[0].element.ownerDocument;
70793
71073
  const groupId = `Group ${doc.querySelectorAll("[data-hf-group]").length + 1}`;
70794
- const { bbox, targets, rebases } = computeGroupGeometry(members);
71074
+ const { bbox, targets, rebases } = computeGroupGeometry(members, timelineElements);
70795
71075
  try {
70796
71076
  const data = await commitStructuralMutation(
70797
71077
  pid,
@@ -70807,19 +71087,20 @@ function useGroupCommits(params) {
70807
71087
  return null;
70808
71088
  }
70809
71089
  },
70810
- [activeCompPath, projectIdRef, showToast, params]
71090
+ [activeCompPath, projectIdRef, showToast, params, timelineElements]
70811
71091
  );
70812
71092
  const ungroupSelection = useCallback109(
70813
71093
  async (group) => {
70814
71094
  const pid = projectIdRef.current;
70815
71095
  if (!pid) return;
70816
71096
  const targetPath = group.sourceFile || activeCompPath || "index.html";
71097
+ const childTracks = resolveGroupChildTracks(group, timelineElements);
70817
71098
  try {
70818
71099
  await commitStructuralMutation(
70819
71100
  pid,
70820
71101
  targetPath,
70821
71102
  "unwrap-elements",
70822
- { target: buildDomEditPatchTarget(group) },
71103
+ { target: buildDomEditPatchTarget(group), childTracks },
70823
71104
  "Ungroup elements",
70824
71105
  params
70825
71106
  );
@@ -70827,13 +71108,13 @@ function useGroupCommits(params) {
70827
71108
  showToast(error instanceof Error ? error.message : "Failed to ungroup elements", "error");
70828
71109
  }
70829
71110
  },
70830
- [activeCompPath, projectIdRef, showToast, params]
71111
+ [activeCompPath, projectIdRef, showToast, params, timelineElements]
70831
71112
  );
70832
71113
  return { groupSelection, ungroupSelection };
70833
71114
  }
70834
71115
 
70835
71116
  // src/hooks/useGsapScriptCommits.ts
70836
- import { useCallback as useCallback115, useMemo as useMemo60, useRef as useRef127 } from "react";
71117
+ import { useCallback as useCallback115, useMemo as useMemo60, useRef as useRef129 } from "react";
70837
71118
  import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
70838
71119
 
70839
71120
  // src/hooks/gsapRuntimePatch.ts
@@ -71628,7 +71909,7 @@ function useGsapKeyframeOps({
71628
71909
  }
71629
71910
 
71630
71911
  // src/hooks/useGsapPropertyDebounce.ts
71631
- import { useCallback as useCallback113, useEffect as useEffect99, useRef as useRef126 } from "react";
71912
+ import { useCallback as useCallback113, useEffect as useEffect99, useRef as useRef128 } from "react";
71632
71913
  import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
71633
71914
  var DEBOUNCE_MS = 150;
71634
71915
  function mergeTweenProperties(sdkSession, animationId, edited, kind) {
@@ -71644,9 +71925,9 @@ function mergeTweenProperties(sdkSession, animationId, edited, kind) {
71644
71925
  }
71645
71926
  }
71646
71927
  function useGsapPropertyDebounce(commitMutationSafely, sdk) {
71647
- const pendingPropertyEditRef = useRef126(null);
71648
- const debounceTimerRef = useRef126(null);
71649
- const sdkRef = useRef126(sdk);
71928
+ const pendingPropertyEditRef = useRef128(null);
71929
+ const debounceTimerRef = useRef128(null);
71930
+ const sdkRef = useRef128(sdk);
71650
71931
  sdkRef.current = sdk;
71651
71932
  const flushPendingPropertyEdit = useCallback113(async () => {
71652
71933
  const pending2 = pendingPropertyEditRef.current;
@@ -72077,9 +72358,9 @@ function instantPatchesFor(options) {
72077
72358
  }
72078
72359
  function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef, editHistory, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast, sdkSession, publishSdkSession, writeProjectFile, forceReloadSdkSession }) {
72079
72360
  const activeProjectId = projectIdRef.current;
72080
- const activeCompPathRef = useRef127(activeCompPath);
72361
+ const activeCompPathRef = useRef129(activeCompPath);
72081
72362
  activeCompPathRef.current = activeCompPath;
72082
- const serializerRef = useRef127(createKeyedSerializer());
72363
+ const serializerRef = useRef129(createKeyedSerializer());
72083
72364
  const recordMutationEdit = useCallback115(async (targetPath, result, options) => {
72084
72365
  if (result.before == null || result.after == null) return;
72085
72366
  await editHistory.recordEdit({
@@ -72260,10 +72541,10 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
72260
72541
  }
72261
72542
 
72262
72543
  // src/hooks/useDomEditWiring.ts
72263
- import { useCallback as useCallback119, useEffect as useEffect101, useRef as useRef130 } from "react";
72544
+ import { useCallback as useCallback119, useEffect as useEffect101, useRef as useRef132 } from "react";
72264
72545
 
72265
72546
  // src/hooks/useDomEditPreviewSync.ts
72266
- import { useEffect as useEffect100, useRef as useRef128 } from "react";
72547
+ import { useEffect as useEffect100, useRef as useRef130 } from "react";
72267
72548
  function useDomEditPreviewSync({
72268
72549
  previewIframe,
72269
72550
  activeCompPath,
@@ -72342,7 +72623,7 @@ function useDomEditPreviewSync({
72342
72623
  applyStudioManualEditsToPreviewRef,
72343
72624
  gsapCacheVersion
72344
72625
  ]);
72345
- const openSourceRef = useRef128(openSourceForSelection);
72626
+ const openSourceRef = useRef130(openSourceForSelection);
72346
72627
  openSourceRef.current = openSourceForSelection;
72347
72628
  useEffect100(
72348
72629
  // fallow-ignore-next-line complexity
@@ -72440,7 +72721,7 @@ function useGsapInteractionFailureTelemetry(activeCompPath, showToast) {
72440
72721
  }
72441
72722
 
72442
72723
  // src/hooks/useGsapSelectionHandlers.ts
72443
- import { useCallback as useCallback118, useRef as useRef129 } from "react";
72724
+ import { useCallback as useCallback118, useRef as useRef131 } from "react";
72444
72725
  function useGsapSelectionHandlers({
72445
72726
  domEditSelection,
72446
72727
  updateGsapProperty,
@@ -72464,7 +72745,7 @@ function useGsapSelectionHandlers({
72464
72745
  selectedGsapAnimations,
72465
72746
  showToast
72466
72747
  }) {
72467
- const lastSelectionRef = useRef129(null);
72748
+ const lastSelectionRef = useRef131(null);
72468
72749
  if (domEditSelection) lastSelectionRef.current = domEditSelection;
72469
72750
  const resolveWriteSelection2 = useCallback118(
72470
72751
  (selectionOverride) => selectionOverride === void 0 ? domEditSelection ?? lastSelectionRef.current : selectionOverride,
@@ -72808,7 +73089,7 @@ function useDomEditWiring({
72808
73089
  const key2 = matchKey ? matchKey.key ?? matchKey.id : null;
72809
73090
  if (key2 && key2 !== selectedElementId) setSelectedElementId(key2);
72810
73091
  }, [domEditSelection?.id]);
72811
- const prevRefreshKeyRef = useRef130(refreshKey);
73092
+ const prevRefreshKeyRef = useRef132(refreshKey);
72812
73093
  useEffect101(() => {
72813
73094
  if (refreshKey !== prevRefreshKeyRef.current) {
72814
73095
  prevRefreshKeyRef.current = refreshKey;
@@ -73909,7 +74190,7 @@ function useGsapAwareEditing({
73909
74190
  }
73910
74191
 
73911
74192
  // src/hooks/useStudioSelectionPublisher.ts
73912
- import { useEffect as useEffect102, useRef as useRef131 } from "react";
74193
+ import { useEffect as useEffect102, useRef as useRef133 } from "react";
73913
74194
 
73914
74195
  // src/utils/studioSelectionSnapshot.ts
73915
74196
  function round34(value) {
@@ -74000,8 +74281,8 @@ function useStudioSelectionPublisher({
74000
74281
  previewDocumentVersion,
74001
74282
  refreshDomEditSelectionFromPreview
74002
74283
  }) {
74003
- const lastSelectionRefreshKeyRef = useRef131(refreshKey);
74004
- const pendingSelectionRefreshKeyRef = useRef131(null);
74284
+ const lastSelectionRefreshKeyRef = useRef133(refreshKey);
74285
+ const pendingSelectionRefreshKeyRef = useRef133(null);
74005
74286
  useEffect102(() => {
74006
74287
  if (!projectId) return;
74007
74288
  const selection = domEditSelection?.element.isConnected ? buildStudioSelectionSnapshot({
@@ -74104,6 +74385,16 @@ function useKeyframeEaseCommits({
74104
74385
  function membersForDelete(selection, group, options) {
74105
74386
  return options?.expandGroup && group.length > 0 ? group : [selection];
74106
74387
  }
74388
+ function timelineElementsForDelete(members, elements) {
74389
+ const matches = [];
74390
+ for (const member of members) {
74391
+ const id = findMatchingTimelineElementId(member, elements);
74392
+ const match = id ? elements.find((el) => (el.key ?? el.id) === id) : void 0;
74393
+ if (!match) return null;
74394
+ matches.push(match);
74395
+ }
74396
+ return matches;
74397
+ }
74107
74398
 
74108
74399
  // src/hooks/useDomEditSession.ts
74109
74400
  function useDomEditSession({
@@ -74144,7 +74435,8 @@ function useDomEditSession({
74144
74435
  getSidebarTab,
74145
74436
  sdkSession,
74146
74437
  publishSdkSession,
74147
- forceReloadSdkSession
74438
+ forceReloadSdkSession,
74439
+ handleTimelineElementsDelete
74148
74440
  }) {
74149
74441
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
74150
74442
  void _setRefreshKey;
@@ -74314,9 +74606,8 @@ function useDomEditSession({
74314
74606
  readProjectFile,
74315
74607
  publishSession: publishSdkSession
74316
74608
  }) : void 0,
74317
- // Resolver shadow for the z-index reorder edit: it takes the server path (no
74318
- // SDK persist), but the tripwire is decoupled from cutover — record whether
74319
- // the SDK resolves each reordered element (the reorderElements op's targets).
74609
+ // Z-index reorder takes the server path (no SDK persist); this decoupled
74610
+ // tripwire still records whether the SDK resolves each reordered target.
74320
74611
  onReorderShadow: sdkSession ? (targets) => {
74321
74612
  let reorderSrcPromise;
74322
74613
  const reorderSrc = activeCompPath ? () => reorderSrcPromise ??= readProjectFile(activeCompPath) : void 0;
@@ -74332,7 +74623,8 @@ function useDomEditSession({
74332
74623
  projectIdRef,
74333
74624
  reloadPreview,
74334
74625
  clearDomSelection,
74335
- forceReloadSdkSession
74626
+ forceReloadSdkSession,
74627
+ timelineElements
74336
74628
  });
74337
74629
  const handleDomEditElementDelete = useCallback123(
74338
74630
  async (selection, options) => {
@@ -74341,9 +74633,21 @@ function useDomEditSession({
74341
74633
  return;
74342
74634
  }
74343
74635
  const members = membersForDelete(selection, domEditGroupSelectionsRef.current, options);
74636
+ const timelineTargets = timelineElementsForDelete(members, timelineElements);
74637
+ if (timelineTargets) {
74638
+ await handleTimelineElementsDelete(timelineTargets);
74639
+ return;
74640
+ }
74344
74641
  await handleDomEditElementsDelete(members);
74345
74642
  },
74346
- [domEditGroupSelectionsRef, handleDomEditElementsDelete, isRecordingRef, showToast]
74643
+ [
74644
+ domEditGroupSelectionsRef,
74645
+ handleDomEditElementsDelete,
74646
+ handleTimelineElementsDelete,
74647
+ isRecordingRef,
74648
+ showToast,
74649
+ timelineElements
74650
+ ]
74347
74651
  );
74348
74652
  const handleGroupSelection = useCallback123(() => {
74349
74653
  const group = domEditGroupSelectionsRef.current;
@@ -74594,7 +74898,7 @@ function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections)
74594
74898
  import { useEffect as useEffect105 } from "react";
74595
74899
 
74596
74900
  // src/hooks/useSdkSession.ts
74597
- import { useState as useState108, useEffect as useEffect104, useCallback as useCallback124, useRef as useRef132 } from "react";
74901
+ import { useState as useState108, useEffect as useEffect104, useCallback as useCallback124, useRef as useRef134 } from "react";
74598
74902
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
74599
74903
 
74600
74904
  // src/hooks/externalFileReloadBus.ts
@@ -74644,15 +74948,15 @@ function disposeSdkSession(session) {
74644
74948
  }
74645
74949
  function useSdkSession(projectId, activeCompPath) {
74646
74950
  const [ownedSession, setOwnedSession] = useState108(null);
74647
- const ownedSessionRef = useRef132(null);
74648
- const sessionOwnersRef = useRef132(/* @__PURE__ */ new WeakMap());
74649
- const generationRef = useRef132(0);
74650
- const projectIdRef = useRef132(projectId);
74951
+ const ownedSessionRef = useRef134(null);
74952
+ const sessionOwnersRef = useRef134(/* @__PURE__ */ new WeakMap());
74953
+ const generationRef = useRef134(0);
74954
+ const projectIdRef = useRef134(projectId);
74651
74955
  projectIdRef.current = projectId;
74652
- const activeCompPathRef = useRef132(activeCompPath);
74956
+ const activeCompPathRef = useRef134(activeCompPath);
74653
74957
  activeCompPathRef.current = activeCompPath;
74654
74958
  const [reloadToken, setReloadToken] = useState108(0);
74655
- const reloadTokenRef = useRef132(reloadToken);
74959
+ const reloadTokenRef = useRef134(reloadToken);
74656
74960
  reloadTokenRef.current = reloadToken;
74657
74961
  useEffect104(
74658
74962
  () => addExternalFileReloadListener((changedPath) => {
@@ -74679,7 +74983,11 @@ function useSdkSession(projectId, activeCompPath) {
74679
74983
  generation
74680
74984
  };
74681
74985
  readProjectFileOptional(projectId, activeCompPath).then(async (content) => {
74682
- if (cancelled || typeof content !== "string") return;
74986
+ if (cancelled) return;
74987
+ if (typeof content !== "string") {
74988
+ trackStudioEvent("sdk_session_unavailable", { stage: "read" });
74989
+ return;
74990
+ }
74683
74991
  const comp = await openComposition3(content, { history: false });
74684
74992
  if (cancelled) {
74685
74993
  disposeSdkSession(comp);
@@ -74693,6 +75001,7 @@ function useSdkSession(projectId, activeCompPath) {
74693
75001
  reloadTokenRef.current
74694
75002
  )) {
74695
75003
  disposeSdkSession(comp);
75004
+ trackStudioEvent("sdk_session_unavailable", { stage: "ownership" });
74696
75005
  return;
74697
75006
  }
74698
75007
  const displaced = ownedSessionRef.current;
@@ -74701,8 +75010,14 @@ function useSdkSession(projectId, activeCompPath) {
74701
75010
  ownedSessionRef.current = installed;
74702
75011
  setOwnedSession(installed);
74703
75012
  if (displaced && displaced.session !== comp) disposeSdkSession(displaced.session);
74704
- }).catch(() => {
74705
- if (!cancelled && generationRef.current === generation) setOwnedSession(null);
75013
+ }).catch((error) => {
75014
+ if (!cancelled && generationRef.current === generation) {
75015
+ setOwnedSession(null);
75016
+ trackStudioEvent("sdk_session_unavailable", {
75017
+ stage: "open",
75018
+ error: error instanceof Error ? error.message : String(error)
75019
+ });
75020
+ }
74706
75021
  });
74707
75022
  return () => {
74708
75023
  cancelled = true;
@@ -74852,7 +75167,7 @@ async function deleteExternalConflictSnapshot(projectId, filePath) {
74852
75167
  }
74853
75168
 
74854
75169
  // src/hooks/useExternalFileChangeCoordinator.ts
74855
- import { useCallback as useCallback125, useEffect as useEffect106, useRef as useRef133, useState as useState109 } from "react";
75170
+ import { useCallback as useCallback125, useEffect as useEffect106, useRef as useRef135, useState as useState109 } from "react";
74856
75171
  function testHotAdapter() {
74857
75172
  const value = globalThis.__HF_STUDIO_HOT_TEST_ADAPTER__;
74858
75173
  if (!value || typeof value !== "object") return null;
@@ -74904,13 +75219,13 @@ function useExternalFileChangeCoordinator({
74904
75219
  onAcceptedPersistedFileChange
74905
75220
  }) {
74906
75221
  const [blocked, setBlocked] = useState109(null);
74907
- const generationRef = useRef133(0);
74908
- const mountedRef = useRef133(true);
74909
- const lastEventIdentityRef = useRef133(null);
74910
- const blockedRef = useRef133(blocked);
74911
- const snapshotWriteTailRef = useRef133(Promise.resolve());
74912
- const drainingRef = useRef133(false);
74913
- const pendingPayloadRef = useRef133(null);
75222
+ const generationRef = useRef135(0);
75223
+ const mountedRef = useRef135(true);
75224
+ const lastEventIdentityRef = useRef135(null);
75225
+ const blockedRef = useRef135(blocked);
75226
+ const snapshotWriteTailRef = useRef135(Promise.resolve());
75227
+ const drainingRef = useRef135(false);
75228
+ const pendingPayloadRef = useRef135(null);
74914
75229
  blockedRef.current = blocked;
74915
75230
  useEffect106(() => {
74916
75231
  mountedRef.current = true;
@@ -75267,7 +75582,7 @@ function useStudioExternalFileChanges({
75267
75582
  }
75268
75583
 
75269
75584
  // src/hooks/useBlockHandlers.ts
75270
- import { useCallback as useCallback127, useMemo as useMemo61, useRef as useRef134, useState as useState110 } from "react";
75585
+ import { useCallback as useCallback127, useMemo as useMemo61, useRef as useRef136, useState as useState110 } from "react";
75271
75586
 
75272
75587
  // src/utils/blockInstaller.ts
75273
75588
  function getMaxZIndexFromIframe(iframe) {
@@ -75476,7 +75791,7 @@ function useBlockHandlers({
75476
75791
  blockCtxDeps.showToast
75477
75792
  ]
75478
75793
  );
75479
- const installingBlockRef = useRef134(false);
75794
+ const installingBlockRef = useRef136(false);
75480
75795
  const runBlockInstall = useCallback127(
75481
75796
  async (blockName, install) => {
75482
75797
  if (installingBlockRef.current) {
@@ -75587,26 +75902,9 @@ function useBlockHandlers({
75587
75902
  }
75588
75903
 
75589
75904
  // src/hooks/useAppHotkeys.ts
75590
- import { useCallback as useCallback128, useEffect as useEffect107, useRef as useRef135 } from "react";
75591
- function iframeContentWindow(iframe) {
75592
- try {
75593
- return iframe?.contentWindow ?? null;
75594
- } catch {
75595
- return null;
75596
- }
75597
- }
75598
- function safeAddListener(t, type, h, capture = false) {
75599
- try {
75600
- t?.addEventListener(type, h, capture);
75601
- } catch {
75602
- }
75603
- }
75604
- function safeRemoveListener(t, type, h) {
75605
- try {
75606
- t?.removeEventListener(type, h);
75607
- } catch {
75608
- }
75609
- }
75905
+ import { useCallback as useCallback128, useEffect as useEffect107, useRef as useRef137 } from "react";
75906
+
75907
+ // src/hooks/appHotkeysDispatch.ts
75610
75908
  function handleUndoRedoKey(event, onUndo, onRedo) {
75611
75909
  const key2 = event.key.toLowerCase();
75612
75910
  if (key2 === "z" && !event.shiftKey) {
@@ -75621,18 +75919,6 @@ function handleUndoRedoKey(event, onUndo, onRedo) {
75621
75919
  }
75622
75920
  return false;
75623
75921
  }
75624
- function tryApplyBeatHistory(direction, fileState, showToast) {
75625
- const ps = usePlayerStore.getState();
75626
- const beatStack = direction === "undo" ? ps.beatUndo : ps.beatRedo;
75627
- const beatAt = beatStack[beatStack.length - 1]?.at ?? null;
75628
- if (beatAt === null) return false;
75629
- const fileStack = fileState[direction];
75630
- const fileAt = fileStack[fileStack.length - 1]?.createdAt ?? null;
75631
- if (fileAt !== null && (direction === "undo" ? beatAt < fileAt : beatAt > fileAt)) return false;
75632
- const label = direction === "undo" ? ps.undoBeatEdits() : ps.redoBeatEdits();
75633
- if (label) showToast(`${direction === "undo" ? "Undid" : "Redid"} ${label}`, "info");
75634
- return true;
75635
- }
75636
75922
  function dispatchModifierKey(event, key2, cb) {
75637
75923
  if (!shouldIgnoreHistoryShortcut(event.target) && handleUndoRedoKey(
75638
75924
  event,
@@ -75724,6 +76010,14 @@ function dispatchPlainKey(event, key2, cb) {
75724
76010
  usePlayerStore.getState().setActiveTool("select");
75725
76011
  return;
75726
76012
  }
76013
+ if (key2 === "a" && !event.shiftKey && !event.altKey) {
76014
+ const { activeTool, setActiveTool } = usePlayerStore.getState();
76015
+ if (activeTool === "razor") {
76016
+ event.preventDefault();
76017
+ setActiveTool("select");
76018
+ return;
76019
+ }
76020
+ }
75727
76021
  if (event.key === "Escape") {
75728
76022
  const { activeTool, selectedElementId, setActiveTool, setSelectedElementId } = usePlayerStore.getState();
75729
76023
  if (activeTool === "razor") {
@@ -75769,6 +76063,39 @@ function dispatchPlainKey(event, key2, cb) {
75769
76063
  cb.onToggleRecording();
75770
76064
  }
75771
76065
  }
76066
+
76067
+ // src/hooks/useAppHotkeys.ts
76068
+ function iframeContentWindow(iframe) {
76069
+ try {
76070
+ return iframe?.contentWindow ?? null;
76071
+ } catch {
76072
+ return null;
76073
+ }
76074
+ }
76075
+ function safeAddListener(t, type, h, capture = false) {
76076
+ try {
76077
+ t?.addEventListener(type, h, capture);
76078
+ } catch {
76079
+ }
76080
+ }
76081
+ function safeRemoveListener(t, type, h) {
76082
+ try {
76083
+ t?.removeEventListener(type, h);
76084
+ } catch {
76085
+ }
76086
+ }
76087
+ function tryApplyBeatHistory(direction, fileState, showToast) {
76088
+ const ps = usePlayerStore.getState();
76089
+ const beatStack = direction === "undo" ? ps.beatUndo : ps.beatRedo;
76090
+ const beatAt = beatStack[beatStack.length - 1]?.at ?? null;
76091
+ if (beatAt === null) return false;
76092
+ const fileStack = fileState[direction];
76093
+ const fileAt = fileStack[fileStack.length - 1]?.createdAt ?? null;
76094
+ if (fileAt !== null && (direction === "undo" ? beatAt < fileAt : beatAt > fileAt)) return false;
76095
+ const label = direction === "undo" ? ps.undoBeatEdits() : ps.redoBeatEdits();
76096
+ if (label) showToast(`${direction === "undo" ? "Undid" : "Redid"} ${label}`, "info");
76097
+ return true;
76098
+ }
75772
76099
  function useAppHotkeys({
75773
76100
  handleTimelineElementsDelete,
75774
76101
  handleTimelineElementSplit,
@@ -75794,7 +76121,7 @@ function useAppHotkeys({
75794
76121
  activeCompPath,
75795
76122
  forceReloadSdkSession
75796
76123
  }) {
75797
- const previewHistoryCleanupRef = useRef135(null);
76124
+ const previewHistoryCleanupRef = useRef137(null);
75798
76125
  const readHistoryFile = useCallback128(
75799
76126
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
75800
76127
  [readOptionalProjectFile, readProjectFile]
@@ -75858,7 +76185,7 @@ function useAppHotkeys({
75858
76185
  );
75859
76186
  const handleUndo = useCallback128(() => applyHistory("undo"), [applyHistory]);
75860
76187
  const handleRedo = useCallback128(() => applyHistory("redo"), [applyHistory]);
75861
- const cbRef = useRef135(null);
76188
+ const cbRef = useRef137(null);
75862
76189
  cbRef.current = {
75863
76190
  handleTimelineElementsDelete,
75864
76191
  handleTimelineElementSplit,
@@ -75938,7 +76265,7 @@ function useAppHotkeys({
75938
76265
  }
75939
76266
 
75940
76267
  // src/hooks/useClipboard.ts
75941
- import { useCallback as useCallback129, useRef as useRef136 } from "react";
76268
+ import { useCallback as useCallback129, useRef as useRef138 } from "react";
75942
76269
 
75943
76270
  // src/utils/clipboardPayload.ts
75944
76271
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -76044,8 +76371,8 @@ function useClipboard({
76044
76371
  handleDomEditElementDelete,
76045
76372
  previewIframeRef
76046
76373
  }) {
76047
- const clipboardRef = useRef136(null);
76048
- const projectIdRef = useRef136(projectId);
76374
+ const clipboardRef = useRef138(null);
76375
+ const projectIdRef = useRef138(projectId);
76049
76376
  projectIdRef.current = projectId;
76050
76377
  const handleCopy2 = useCallback129(() => {
76051
76378
  const { selectedElementId, elements } = usePlayerStore.getState();
@@ -76171,7 +76498,7 @@ function useClipboard({
76171
76498
  }
76172
76499
 
76173
76500
  // src/hooks/useCaptionDetection.ts
76174
- import { useEffect as useEffect108, useRef as useRef137 } from "react";
76501
+ import { useEffect as useEffect108, useRef as useRef139 } from "react";
76175
76502
 
76176
76503
  // src/captions/types.ts
76177
76504
  var DEFAULT_STYLE = {
@@ -76423,7 +76750,7 @@ function useCaptionDetection({
76423
76750
  captionSync,
76424
76751
  setRightCollapsed
76425
76752
  }) {
76426
- const prevCompPathRef = useRef137(activeCompPath);
76753
+ const prevCompPathRef = useRef139(activeCompPath);
76427
76754
  useEffect108(() => {
76428
76755
  if (prevCompPathRef.current !== activeCompPath) {
76429
76756
  prevCompPathRef.current = activeCompPath;
@@ -76528,7 +76855,7 @@ import { useCallback as useCallback132 } from "react";
76528
76855
  import { createElement as createElement3 } from "react";
76529
76856
 
76530
76857
  // src/player/components/AudioWaveform.tsx
76531
- import { memo as memo36, useCallback as useCallback130, useMemo as useMemo62, useRef as useRef138 } from "react";
76858
+ import { memo as memo36, useCallback as useCallback130, useMemo as useMemo62, useRef as useRef140 } from "react";
76532
76859
  import { jsx as jsx169, jsxs as jsxs141 } from "react/jsx-runtime";
76533
76860
  var BAR_WIDTH = 2;
76534
76861
  var BAR_STEP = 3;
@@ -76585,8 +76912,8 @@ var AudioWaveform = memo36(function AudioWaveform2({
76585
76912
  sessionEpoch,
76586
76913
  priority
76587
76914
  }) {
76588
- const canvasRef = useRef138(null);
76589
- const observerRef = useRef138(null);
76915
+ const canvasRef = useRef140(null);
76916
+ const observerRef = useRef140(null);
76590
76917
  const cacheKey = waveformUrl ?? audioUrl;
76591
76918
  const request = useMemo62(
76592
76919
  () => ({
@@ -76697,7 +77024,7 @@ var AudioWaveform = memo36(function AudioWaveform2({
76697
77024
  });
76698
77025
 
76699
77026
  // src/player/components/ImageThumbnail.tsx
76700
- import { memo as memo37, useCallback as useCallback131, useMemo as useMemo63, useRef as useRef139, useState as useState111 } from "react";
77027
+ import { memo as memo37, useCallback as useCallback131, useMemo as useMemo63, useRef as useRef141, useState as useState111 } from "react";
76701
77028
  import { jsx as jsx170, jsxs as jsxs142 } from "react/jsx-runtime";
76702
77029
  var ImageThumbnail = memo37(function ImageThumbnail2({
76703
77030
  imageSrc,
@@ -76709,7 +77036,7 @@ var ImageThumbnail = memo37(function ImageThumbnail2({
76709
77036
  rich = false
76710
77037
  }) {
76711
77038
  const [containerWidth, setContainerWidth] = useState111(0);
76712
- const observerRef = useRef139(null);
77039
+ const observerRef = useRef141(null);
76713
77040
  const request = useMemo63(
76714
77041
  () => ({
76715
77042
  key: createThumbnailKey({ kind: "image", source: imageSrc, rich: Number(rich) }),
@@ -76958,10 +77285,10 @@ function useRenderClipContent({
76958
77285
  }
76959
77286
 
76960
77287
  // src/hooks/useConsoleErrorCapture.ts
76961
- import { useCallback as useCallback133, useEffect as useEffect109, useRef as useRef140, useState as useState112 } from "react";
77288
+ import { useCallback as useCallback133, useEffect as useEffect109, useRef as useRef142, useState as useState112 } from "react";
76962
77289
  function useConsoleErrorCapture(previewIframe) {
76963
77290
  const [consoleErrors, setConsoleErrors] = useState112(null);
76964
- const consoleErrorsRef = useRef140([]);
77291
+ const consoleErrorsRef = useRef142([]);
76965
77292
  const resetErrors = useCallback133(() => {
76966
77293
  consoleErrorsRef.current = [];
76967
77294
  setConsoleErrors(null);
@@ -77031,7 +77358,7 @@ function useConsoleErrorCapture(previewIframe) {
77031
77358
  }
77032
77359
 
77033
77360
  // src/hooks/useFrameCapture.ts
77034
- import { useState as useState113, useCallback as useCallback134, useRef as useRef141 } from "react";
77361
+ import { useState as useState113, useCallback as useCallback134, useRef as useRef143 } from "react";
77035
77362
  function useFrameCapture({
77036
77363
  projectId,
77037
77364
  activeCompPath,
@@ -77040,7 +77367,7 @@ function useFrameCapture({
77040
77367
  }) {
77041
77368
  const [captureFrameTime, setCaptureFrameTime] = useState113(0);
77042
77369
  const [capturing, setCapturing] = useState113(false);
77043
- const capturingRef = useRef141(false);
77370
+ const capturingRef = useRef143(false);
77044
77371
  useMountEffect(() => {
77045
77372
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
77046
77373
  return liveTime.subscribe(setCaptureFrameTime);
@@ -77125,7 +77452,7 @@ function useFrameCapture({
77125
77452
  }
77126
77453
 
77127
77454
  // src/hooks/useLintModal.ts
77128
- import { useState as useState114, useCallback as useCallback135, useEffect as useEffect110, useRef as useRef142, useMemo as useMemo64 } from "react";
77455
+ import { useState as useState114, useCallback as useCallback135, useEffect as useEffect110, useRef as useRef144, useMemo as useMemo64 } from "react";
77129
77456
  function parseFinding(f) {
77130
77457
  return {
77131
77458
  severity: f.severity === "error" ? "error" : "warning",
@@ -77139,7 +77466,7 @@ function useLintModal(projectId, refreshKey) {
77139
77466
  const [lintModal, setLintModal] = useState114(null);
77140
77467
  const [linting, setLinting] = useState114(false);
77141
77468
  const [backgroundFindings, setBackgroundFindings] = useState114([]);
77142
- const autoLintRanRef = useRef142(false);
77469
+ const autoLintRanRef = useRef144(false);
77143
77470
  const runLint = useCallback135(
77144
77471
  async (opts) => {
77145
77472
  if (!projectId) return;
@@ -77166,7 +77493,7 @@ function useLintModal(projectId, refreshKey) {
77166
77493
  [projectId]
77167
77494
  );
77168
77495
  const handleLint = useCallback135(() => runLint(), [runLint]);
77169
- const prevProjectIdRef = useRef142(projectId);
77496
+ const prevProjectIdRef = useRef144(projectId);
77170
77497
  useEffect110(() => {
77171
77498
  if (projectId !== prevProjectIdRef.current) {
77172
77499
  autoLintRanRef.current = false;
@@ -77214,14 +77541,14 @@ function useLintModal(projectId, refreshKey) {
77214
77541
  }
77215
77542
 
77216
77543
  // src/hooks/useToast.ts
77217
- import { useState as useState115, useCallback as useCallback136, useRef as useRef143 } from "react";
77544
+ import { useState as useState115, useCallback as useCallback136, useRef as useRef145 } from "react";
77218
77545
  var AUTO_DISMISS_MS2 = 4e3;
77219
77546
  var EXIT_MS2 = 160;
77220
77547
  var MAX_TOASTS = 3;
77221
77548
  var nextToastId = 1;
77222
77549
  function useToast() {
77223
77550
  const [toasts, setToasts] = useState115([]);
77224
- const timersRef = useRef143(/* @__PURE__ */ new Map());
77551
+ const timersRef = useRef145(/* @__PURE__ */ new Map());
77225
77552
  const clearTimer = useCallback136((id) => {
77226
77553
  const timer = timersRef.current.get(id);
77227
77554
  if (timer) {
@@ -77270,41 +77597,17 @@ function useToast() {
77270
77597
  return { toasts, showToast, dismissToast };
77271
77598
  }
77272
77599
 
77273
- // src/hooks/useCompositionContentLoader.ts
77274
- import { useCallback as useCallback137 } from "react";
77275
- function useCompositionContentLoader({
77276
- projectId,
77277
- setEditingFile,
77278
- setActiveCompPath,
77279
- showToast
77280
- }) {
77281
- return useCallback137(
77282
- (comp) => {
77283
- if (!projectId) return;
77284
- setActiveCompPath(comp.endsWith(".html") ? comp : null);
77285
- setEditingFile({ path: comp, content: null });
77286
- fetch(buildProjectApiPath(projectId, `/files/${encodeURIComponent(comp)}`)).then(async (r) => {
77287
- if (!r.ok) throw new Error(`Failed to load ${comp} (${r.status})`);
77288
- return r.json();
77289
- }).then((data) => {
77290
- if (typeof data.content !== "string") throw new Error(`No content returned for ${comp}`);
77291
- setEditingFile({ path: comp, content: data.content });
77292
- }).catch((err) => {
77293
- showToast(err instanceof Error ? err.message : `Failed to load ${comp}`, "error");
77294
- });
77295
- },
77296
- [projectId, setEditingFile, setActiveCompPath, showToast]
77297
- );
77298
- }
77299
-
77300
77600
  // src/hooks/useStudioUrlState.ts
77301
- import { useCallback as useCallback138, useEffect as useEffect111, useRef as useRef144, useState as useState116 } from "react";
77601
+ import { useCallback as useCallback137, useEffect as useEffect111, useRef as useRef146, useState as useState116 } from "react";
77302
77602
 
77303
77603
  // src/utils/studioUrlState.ts
77304
77604
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
77305
- function resolveMasterCompositionPath(fileTree) {
77306
- if (fileTree.includes("index.html")) return "index.html";
77307
- return fileTree.find((p) => p.endsWith(".html")) ?? null;
77605
+ function resolveMasterCompositionPath(compositions) {
77606
+ if (compositions.includes("index.html")) return "index.html";
77607
+ return compositions.find((p) => p.endsWith(".html")) ?? null;
77608
+ }
77609
+ function isHydratedFromUrlState(urlState) {
77610
+ return urlState.activeCompPath == null;
77308
77611
  }
77309
77612
  function normalizeStudioUrlPanelTab(tab) {
77310
77613
  if (!tab) return null;
@@ -77527,14 +77830,14 @@ function useStudioUrlState({
77527
77830
  initialState: initialState2
77528
77831
  }) {
77529
77832
  const currentTime = usePlayerStore((s) => s.currentTime);
77530
- const hydratedSeekRef = useRef144(initialState2.currentTime == null);
77531
- const hydratedInitialTimeRef = useRef144(initialState2.currentTime == null);
77532
- const hydratedSelectionRef = useRef144(initialState2.selection == null);
77833
+ const hydratedSeekRef = useRef146(initialState2.currentTime == null);
77834
+ const hydratedInitialTimeRef = useRef146(initialState2.currentTime == null);
77835
+ const hydratedSelectionRef = useRef146(initialState2.selection == null);
77533
77836
  const [selectionHydrated, setSelectionHydrated] = useState116(initialState2.selection == null);
77534
- const pendingSelectionRef = useRef144(initialState2.selection);
77535
- const stableTimeRef = useRef144(initialState2.currentTime);
77536
- const selectionApplySeqRef = useRef144(0);
77537
- const buildUrlState = useCallback138(
77837
+ const pendingSelectionRef = useRef146(initialState2.selection);
77838
+ const stableTimeRef = useRef146(initialState2.currentTime);
77839
+ const selectionApplySeqRef = useRef146(0);
77840
+ const buildUrlState = useCallback137(
77538
77841
  () => ({
77539
77842
  activeCompPath,
77540
77843
  currentTime: stableTimeRef.current,
@@ -77545,7 +77848,7 @@ function useStudioUrlState({
77545
77848
  }),
77546
77849
  [activeCompPath, domEditGroupSelections, domEditSelection, rightCollapsed, rightPanelTab]
77547
77850
  );
77548
- const applyUrlSelection = useCallback138(
77851
+ const applyUrlSelection = useCallback137(
77549
77852
  (selection) => {
77550
77853
  const applySeq = ++selectionApplySeqRef.current;
77551
77854
  if (!selection) {
@@ -77690,7 +77993,7 @@ function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
77690
77993
  }
77691
77994
 
77692
77995
  // src/hooks/useStudioContextValue.ts
77693
- import { useCallback as useCallback139, useMemo as useMemo66, useRef as useRef145, useState as useState117 } from "react";
77996
+ import { useCallback as useCallback138, useMemo as useMemo66, useRef as useRef147, useState as useState117 } from "react";
77694
77997
  function buildStudioContextValue(input) {
77695
77998
  return {
77696
77999
  projectId: input.projectId,
@@ -77749,22 +78052,22 @@ function useInspectorState(rightPanelTab, rightInspectorPanes, rightCollapsed, i
77749
78052
  }
77750
78053
  function useDragOverlay(onImportFiles) {
77751
78054
  const [active, setActive] = useState117(false);
77752
- const counterRef = useRef145(0);
77753
- const onDragOver = useCallback139((e) => {
78055
+ const counterRef = useRef147(0);
78056
+ const onDragOver = useCallback138((e) => {
77754
78057
  if (!e.dataTransfer.types.includes("Files")) return;
77755
78058
  e.preventDefault();
77756
78059
  }, []);
77757
- const onDragEnter = useCallback139((e) => {
78060
+ const onDragEnter = useCallback138((e) => {
77758
78061
  if (!e.dataTransfer.types.includes("Files")) return;
77759
78062
  e.preventDefault();
77760
78063
  counterRef.current++;
77761
78064
  setActive(true);
77762
78065
  }, []);
77763
- const onDragLeave = useCallback139(() => {
78066
+ const onDragLeave = useCallback138(() => {
77764
78067
  counterRef.current--;
77765
78068
  if (counterRef.current === 0) setActive(false);
77766
78069
  }, []);
77767
- const onDrop = useCallback139(
78070
+ const onDrop = useCallback138(
77768
78071
  (e) => {
77769
78072
  counterRef.current = 0;
77770
78073
  setActive(false);
@@ -77777,7 +78080,7 @@ function useDragOverlay(onImportFiles) {
77777
78080
  return { active, onDragOver, onDragEnter, onDragLeave, onDrop };
77778
78081
  }
77779
78082
  function useGlobalFileDrop(handleTimelineFileDrop) {
77780
- const onDrop = useCallback139(
78083
+ const onDrop = useCallback138(
77781
78084
  (files) => {
77782
78085
  const start = usePlayerStore.getState().currentTime;
77783
78086
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -77788,7 +78091,7 @@ function useGlobalFileDrop(handleTimelineFileDrop) {
77788
78091
  }
77789
78092
 
77790
78093
  // src/components/StudioHeader.tsx
77791
- import { useRef as useRef147 } from "react";
78094
+ import { useRef as useRef149 } from "react";
77792
78095
 
77793
78096
  // src/contexts/PanelLayoutContext.tsx
77794
78097
  import { useContext as useContext8, useMemo as useMemo67 } from "react";
@@ -77866,11 +78169,11 @@ function PanelLayoutProvider({
77866
78169
 
77867
78170
  // src/contexts/ViewModeContext.tsx
77868
78171
  import {
77869
- useCallback as useCallback140,
78172
+ useCallback as useCallback139,
77870
78173
  useContext as useContext9,
77871
78174
  useEffect as useEffect112,
77872
78175
  useMemo as useMemo68,
77873
- useRef as useRef146,
78176
+ useRef as useRef148,
77874
78177
  useState as useState118
77875
78178
  } from "react";
77876
78179
  import { jsx as jsx172 } from "react/jsx-runtime";
@@ -77898,9 +78201,9 @@ function readHistoryLocation() {
77898
78201
  }
77899
78202
  function useViewModeState() {
77900
78203
  const [viewMode, setMode] = useState118(() => readViewModeFromUrl());
77901
- const guardsRef = useRef146(/* @__PURE__ */ new Set());
77902
- const acceptedLocationRef = useRef146(readHistoryLocation());
77903
- const canSetViewMode = useCallback140((mode) => {
78204
+ const guardsRef = useRef148(/* @__PURE__ */ new Set());
78205
+ const acceptedLocationRef = useRef148(readHistoryLocation());
78206
+ const canSetViewMode = useCallback139((mode) => {
77904
78207
  for (const guard of guardsRef.current) {
77905
78208
  if (!guard(mode)) return false;
77906
78209
  }
@@ -77922,7 +78225,7 @@ function useViewModeState() {
77922
78225
  window.addEventListener("popstate", onPopState);
77923
78226
  return () => window.removeEventListener("popstate", onPopState);
77924
78227
  }, [canSetViewMode, viewMode]);
77925
- const setViewMode = useCallback140(
78228
+ const setViewMode = useCallback139(
77926
78229
  (mode) => {
77927
78230
  if (!canSetViewMode(mode)) return false;
77928
78231
  setMode(mode);
@@ -77932,7 +78235,7 @@ function useViewModeState() {
77932
78235
  },
77933
78236
  [canSetViewMode]
77934
78237
  );
77935
- const registerViewModeGuard = useCallback140((guard) => {
78238
+ const registerViewModeGuard = useCallback139((guard) => {
77936
78239
  guardsRef.current.add(guard);
77937
78240
  return () => {
77938
78241
  guardsRef.current.delete(guard);
@@ -78146,7 +78449,7 @@ var VIEW_MODE_OPTIONS = [
78146
78449
  ];
78147
78450
  function ViewModeToggle() {
78148
78451
  const { viewMode, setViewMode } = useViewMode();
78149
- const tabRefs = useRef147([]);
78452
+ const tabRefs = useRef149([]);
78150
78453
  const selectMode = (mode) => {
78151
78454
  if (mode === viewMode) return;
78152
78455
  if (setViewMode(mode)) trackStudioEvent("view_mode_toggle", { mode });
@@ -78373,10 +78676,10 @@ function StudioHeader({
78373
78676
  }
78374
78677
 
78375
78678
  // src/hooks/useGestureCommit.ts
78376
- import { useState as useState120, useCallback as useCallback142, useRef as useRef149, useEffect as useEffect114 } from "react";
78679
+ import { useState as useState120, useCallback as useCallback141, useRef as useRef151, useEffect as useEffect114 } from "react";
78377
78680
 
78378
78681
  // src/hooks/useGestureRecording.ts
78379
- import { useCallback as useCallback141, useEffect as useEffect113, useRef as useRef148, useState as useState119 } from "react";
78682
+ import { useCallback as useCallback140, useEffect as useEffect113, useRef as useRef150, useState as useState119 } from "react";
78380
78683
  function readBasePosition(element, iframeEl) {
78381
78684
  let baseOpacity = 1;
78382
78685
  let baseScale = 1;
@@ -78521,10 +78824,10 @@ function releaseRuntimePreview(r) {
78521
78824
  function useGestureRecording() {
78522
78825
  const [isRecording, setIsRecording] = useState119(false);
78523
78826
  const [recordingDuration, setRecordingDuration] = useState119(0);
78524
- const isRecordingRef = useRef148(false);
78525
- const refs = useRef148(createRecordingRefs());
78526
- const samplesRef = useRef148(refs.current.samples);
78527
- const trailRef = useRef148(refs.current.trail);
78827
+ const isRecordingRef = useRef150(false);
78828
+ const refs = useRef150(createRecordingRefs());
78829
+ const samplesRef = useRef150(refs.current.samples);
78830
+ const trailRef = useRef150(refs.current.trail);
78528
78831
  useEffect113(() => {
78529
78832
  const r = refs.current;
78530
78833
  return () => {
@@ -78534,7 +78837,7 @@ function useGestureRecording() {
78534
78837
  r.cleanup = null;
78535
78838
  };
78536
78839
  }, []);
78537
- const startRecording = useCallback141(
78840
+ const startRecording = useCallback140(
78538
78841
  (element, iframeEl, elementEndTime) => {
78539
78842
  if (isRecordingRef.current) return;
78540
78843
  isRecordingRef.current = true;
@@ -78634,7 +78937,7 @@ function useGestureRecording() {
78634
78937
  []
78635
78938
  // No deps — uses refs only for all mutable state
78636
78939
  );
78637
- const stopRecording = useCallback141(() => {
78940
+ const stopRecording = useCallback140(() => {
78638
78941
  if (!isRecordingRef.current) return [];
78639
78942
  isRecordingRef.current = false;
78640
78943
  const r = refs.current;
@@ -78646,7 +78949,7 @@ function useGestureRecording() {
78646
78949
  setIsRecording(false);
78647
78950
  return frozen;
78648
78951
  }, []);
78649
- const clearSamples = useCallback141(() => {
78952
+ const clearSamples = useCallback140(() => {
78650
78953
  const r = refs.current;
78651
78954
  r.samples = [];
78652
78955
  r.trail = [];
@@ -78903,13 +79206,13 @@ function useGestureCommit({
78903
79206
  }) {
78904
79207
  const gestureRecording = useGestureRecording();
78905
79208
  const [gestureState, setGestureState] = useState120("idle");
78906
- const gestureStateRef = useRef149("idle");
78907
- const recordingAutoStopRef = useRef149(void 0);
78908
- const recordingStartTimeRef = useRef149(0);
78909
- const commitInFlightRef = useRef149(false);
78910
- const capturedSelectionRef = useRef149(null);
79209
+ const gestureStateRef = useRef151("idle");
79210
+ const recordingAutoStopRef = useRef151(void 0);
79211
+ const recordingStartTimeRef = useRef151(0);
79212
+ const commitInFlightRef = useRef151(false);
79213
+ const capturedSelectionRef = useRef151(null);
78911
79214
  useEffect114(() => () => clearInterval(recordingAutoStopRef.current), []);
78912
- const stopAndCommitRecording = useCallback142(async () => {
79215
+ const stopAndCommitRecording = useCallback141(async () => {
78913
79216
  clearInterval(recordingAutoStopRef.current);
78914
79217
  if (commitInFlightRef.current) {
78915
79218
  return;
@@ -79082,7 +79385,7 @@ function useGestureCommit({
79082
79385
  commitInFlightRef.current = false;
79083
79386
  }
79084
79387
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
79085
- const handleToggleRecording = useCallback142(() => {
79388
+ const handleToggleRecording = useCallback141(() => {
79086
79389
  if (gestureStateRef.current === "recording") {
79087
79390
  void stopAndCommitRecording();
79088
79391
  return;
@@ -79240,20 +79543,21 @@ var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
79240
79543
  });
79241
79544
 
79242
79545
  // src/components/StudioLeftSidebar.tsx
79243
- import { useCallback as useCallback151 } from "react";
79546
+ import { useCallback as useCallback150 } from "react";
79244
79547
 
79245
79548
  // src/components/sidebar/LeftSidebar.tsx
79246
79549
  import {
79247
79550
  memo as memo42,
79248
79551
  useState as useState130,
79249
- useCallback as useCallback150,
79552
+ useCallback as useCallback149,
79250
79553
  useImperativeHandle,
79251
- useRef as useRef157,
79554
+ useMemo as useMemo72,
79555
+ useRef as useRef159,
79252
79556
  forwardRef as forwardRef3
79253
79557
  } from "react";
79254
79558
 
79255
79559
  // src/components/sidebar/CompositionsTab.tsx
79256
- import { memo as memo39, useCallback as useCallback143, useEffect as useEffect115, useRef as useRef150, useState as useState121 } from "react";
79560
+ import { memo as memo39, useCallback as useCallback142, useEffect as useEffect115, useRef as useRef152, useState as useState121 } from "react";
79257
79561
  import { jsx as jsx175, jsxs as jsxs145 } from "react/jsx-runtime";
79258
79562
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
79259
79563
  var CARD_W = 80;
@@ -79315,6 +79619,7 @@ function CompCard({
79315
79619
  projectId,
79316
79620
  comp,
79317
79621
  isActive,
79622
+ isRoot,
79318
79623
  onSelect,
79319
79624
  onRender,
79320
79625
  isRendering,
@@ -79326,11 +79631,11 @@ function CompCard({
79326
79631
  const [stageSize, setStageSize] = useState121(DEFAULT_PREVIEW_STAGE);
79327
79632
  const [livePreviewLoaded, setLivePreviewLoaded] = useState121(false);
79328
79633
  const [failedThumbnailUrl, setFailedThumbnailUrl] = useState121(null);
79329
- const iframeRef = useRef150(null);
79330
- const hoverTimer = useRef150(null);
79331
- const syncTimer = useRef150(null);
79332
- const draggedRef = useRef150(false);
79333
- const requestIframePlaybackSync = useCallback143((shouldPlay) => {
79634
+ const iframeRef = useRef152(null);
79635
+ const hoverTimer = useRef152(null);
79636
+ const syncTimer = useRef152(null);
79637
+ const draggedRef = useRef152(false);
79638
+ const requestIframePlaybackSync = useCallback142((shouldPlay) => {
79334
79639
  if (syncTimer.current) {
79335
79640
  clearTimeout(syncTimer.current);
79336
79641
  syncTimer.current = null;
@@ -79469,6 +79774,15 @@ function CompCard({
79469
79774
  children: [
79470
79775
  /* @__PURE__ */ jsxs145("div", { className: "flex items-center gap-1", children: [
79471
79776
  /* @__PURE__ */ jsx175("span", { className: "text-[11px] font-medium text-neutral-300 truncate", children: name }),
79777
+ isRoot && /* @__PURE__ */ jsx175(
79778
+ "span",
79779
+ {
79780
+ "aria-label": "Root composition \u2014 opens automatically on load",
79781
+ title: "Root composition \u2014 opens automatically on load",
79782
+ className: "flex-shrink-0 rounded-full bg-neutral-700/60 px-1.5 py-px text-[8px] font-bold uppercase tracking-wide text-neutral-300",
79783
+ children: "Root"
79784
+ }
79785
+ ),
79472
79786
  lintInfo && lintInfo.count > 0 && /* @__PURE__ */ jsx175(
79473
79787
  "span",
79474
79788
  {
@@ -79536,6 +79850,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79536
79850
  projectId,
79537
79851
  compositions,
79538
79852
  activeComposition,
79853
+ masterCompositionPath = null,
79539
79854
  onSelect,
79540
79855
  onRenderComposition,
79541
79856
  onAddToTimeline,
@@ -79552,6 +79867,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79552
79867
  projectId,
79553
79868
  comp,
79554
79869
  isActive: activeComposition === comp,
79870
+ isRoot: comp === masterCompositionPath,
79555
79871
  onSelect: () => onSelect(comp),
79556
79872
  onRender: onRenderComposition ? () => onRenderComposition(comp) : void 0,
79557
79873
  onAddToTimeline: onAddToTimeline ? () => onAddToTimeline(comp) : void 0,
@@ -79564,7 +79880,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79564
79880
  });
79565
79881
 
79566
79882
  // src/components/sidebar/AssetsTab.tsx
79567
- import { memo as memo40, useState as useState127, useCallback as useCallback147, useRef as useRef154, useMemo as useMemo71, useEffect as useEffect121 } from "react";
79883
+ import { memo as memo40, useState as useState127, useCallback as useCallback146, useRef as useRef156, useMemo as useMemo71, useEffect as useEffect121 } from "react";
79568
79884
 
79569
79885
  // src/components/ui/SearchInput.tsx
79570
79886
  import { jsx as jsx176, jsxs as jsxs146 } from "react/jsx-runtime";
@@ -79675,11 +79991,11 @@ var CATEGORY_LABELS = {
79675
79991
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
79676
79992
 
79677
79993
  // src/components/sidebar/AudioRow.tsx
79678
- import { useState as useState123, useRef as useRef152, useEffect as useEffect117, useCallback as useCallback145 } from "react";
79994
+ import { useState as useState123, useRef as useRef154, useEffect as useEffect117, useCallback as useCallback144 } from "react";
79679
79995
  import { classifyWebAudioMediaRoute } from "@hyperframes/core/runtime/web-audio-route";
79680
79996
 
79681
79997
  // src/components/sidebar/AssetContextMenu.tsx
79682
- import { useCallback as useCallback144, useEffect as useEffect116, useLayoutEffect as useLayoutEffect9, useRef as useRef151, useState as useState122 } from "react";
79998
+ import { useCallback as useCallback143, useEffect as useEffect116, useLayoutEffect as useLayoutEffect9, useRef as useRef153, useState as useState122 } from "react";
79683
79999
  import { Fragment as Fragment51, jsx as jsx177, jsxs as jsxs147 } from "react/jsx-runtime";
79684
80000
  function isValidAssetName(name) {
79685
80001
  return name.length > 0 && !/[/\\]/.test(name) && !name.includes("..");
@@ -79694,7 +80010,7 @@ function ContextMenu2({
79694
80010
  onRename,
79695
80011
  onAddAtPlayhead
79696
80012
  }) {
79697
- const menuRef = useRef151(null);
80013
+ const menuRef = useRef153(null);
79698
80014
  const [pos, setPos] = useState122({ x, y });
79699
80015
  const [mode, setMode] = useState122("menu");
79700
80016
  const [renameDraft, setRenameDraft] = useState122(() => filename(asset));
@@ -79739,7 +80055,7 @@ function ContextMenu2({
79739
80055
  menuRef.current?.querySelector('[role="menuitem"]')?.focus();
79740
80056
  }
79741
80057
  }, [mode]);
79742
- const commitRename = useCallback144(() => {
80058
+ const commitRename = useCallback143(() => {
79743
80059
  const trimmed = renameDraft.trim();
79744
80060
  if (trimmed === filename(asset)) {
79745
80061
  onClose();
@@ -79945,28 +80261,28 @@ function AudioRow({
79945
80261
  const [contextMenu, setContextMenu] = useState123(null);
79946
80262
  const [playing, setPlaying] = useState123(false);
79947
80263
  const [bars, setBars] = useState123([]);
79948
- const audioRef = useRef152(null);
79949
- const actxRef = useRef152(null);
79950
- const analyserRef = useRef152(null);
79951
- const sourceRef = useRef152(null);
79952
- const animRef = useRef152(0);
80264
+ const audioRef = useRef154(null);
80265
+ const actxRef = useRef154(null);
80266
+ const analyserRef = useRef154(null);
80267
+ const sourceRef = useRef154(null);
80268
+ const animRef = useRef154(0);
79953
80269
  const name = basename2(asset);
79954
80270
  const subtype = getAudioSubtype(asset);
79955
80271
  const serveUrl = resolveMediaPreviewUrl(asset, projectId);
79956
80272
  const isCopied = copyFeedback?.path === asset && copyFeedback.ok;
79957
80273
  const copyFailed = copyFeedback?.path === asset && !copyFeedback.ok;
79958
- const stopPlayback = useCallback145(() => {
80274
+ const stopPlayback = useCallback144(() => {
79959
80275
  audioRef.current?.pause();
79960
80276
  setPlaying(false);
79961
80277
  cancelAnimationFrame(animRef.current);
79962
80278
  }, []);
79963
- const pointerDownRef = useRef152(null);
80279
+ const pointerDownRef = useRef154(null);
79964
80280
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
79965
80281
  const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
79966
80282
  const elements = usePlayerStore((s) => s.elements);
79967
80283
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
79968
80284
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
79969
- const activateRow = useCallback145(() => {
80285
+ const activateRow = useCallback144(() => {
79970
80286
  if (used) {
79971
80287
  const clip = findClipForAsset(elements, asset);
79972
80288
  if (clip) {
@@ -79988,10 +80304,10 @@ function AudioRow({
79988
80304
  setPreviewAsset,
79989
80305
  clearPreviewAsset
79990
80306
  ]);
79991
- const handlePointerDown = useCallback145((e) => {
80307
+ const handlePointerDown = useCallback144((e) => {
79992
80308
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
79993
80309
  }, []);
79994
- const handlePointerUp = useCallback145(
80310
+ const handlePointerUp = useCallback144(
79995
80311
  (e) => {
79996
80312
  const origin = pointerDownRef.current;
79997
80313
  pointerDownRef.current = null;
@@ -80037,7 +80353,7 @@ function AudioRow({
80037
80353
  }
80038
80354
  return () => cancelAnimationFrame(animRef.current);
80039
80355
  }, [playing]);
80040
- const togglePlay = useCallback145(async () => {
80356
+ const togglePlay = useCallback144(async () => {
80041
80357
  if (playing) {
80042
80358
  stopPlayback();
80043
80359
  if (stopCurrentPreview === stopPlayback) stopCurrentPreview = null;
@@ -80235,7 +80551,7 @@ function GlobalAssetsView({ searchQuery }) {
80235
80551
  }
80236
80552
 
80237
80553
  // src/components/sidebar/AssetCard.tsx
80238
- import { useState as useState126, useEffect as useEffect120, useRef as useRef153, useCallback as useCallback146 } from "react";
80554
+ import { useState as useState126, useEffect as useEffect120, useRef as useRef155, useCallback as useCallback145 } from "react";
80239
80555
 
80240
80556
  // src/components/ui/VideoFrameThumbnail.tsx
80241
80557
  import { useState as useState125, useEffect as useEffect119 } from "react";
@@ -80373,16 +80689,16 @@ function AssetCard({
80373
80689
  const probedDuration = useProbedDuration(serveUrl, !isVideo || duration != null);
80374
80690
  const resolvedDuration = duration ?? probedDuration ?? void 0;
80375
80691
  const durationLabel = formatDuration(resolvedDuration ?? 0);
80376
- const pointerDownRef = useRef153(null);
80692
+ const pointerDownRef = useRef155(null);
80377
80693
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
80378
80694
  const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
80379
80695
  const elements = usePlayerStore((s) => s.elements);
80380
80696
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
80381
80697
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
80382
- const handlePointerDown = useCallback146((e) => {
80698
+ const handlePointerDown = useCallback145((e) => {
80383
80699
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
80384
80700
  }, []);
80385
- const activateCard = useCallback146(() => {
80701
+ const activateCard = useCallback145(() => {
80386
80702
  if (used) {
80387
80703
  const clip = findClipForAsset(elements, asset);
80388
80704
  if (clip) {
@@ -80404,7 +80720,7 @@ function AssetCard({
80404
80720
  setPreviewAsset,
80405
80721
  clearPreviewAsset
80406
80722
  ]);
80407
- const handlePointerUp = useCallback146(
80723
+ const handlePointerUp = useCallback145(
80408
80724
  (e) => {
80409
80725
  const origin = pointerDownRef.current;
80410
80726
  pointerDownRef.current = null;
@@ -80698,7 +81014,7 @@ var AssetsTab = memo40(function AssetsTab2({
80698
81014
  onRename,
80699
81015
  onAddAssetToTimeline
80700
81016
  }) {
80701
- const fileInputRef = useRef154(null);
81017
+ const fileInputRef = useRef156(null);
80702
81018
  const [dragOver, setDragOver] = useState127(false);
80703
81019
  const [copyFeedback, setCopyFeedback] = useState127(null);
80704
81020
  const [importing, setImporting] = useState127(false);
@@ -80707,7 +81023,7 @@ var AssetsTab = memo40(function AssetsTab2({
80707
81023
  const [searchQuery, setSearchQuery] = useState127("");
80708
81024
  const [viewMode, setViewMode] = useState127("local");
80709
81025
  const [manifest, setManifest] = useState127(/* @__PURE__ */ new Map());
80710
- const manifest404Ref = useRef154(/* @__PURE__ */ new Set());
81026
+ const manifest404Ref = useRef156(/* @__PURE__ */ new Set());
80711
81027
  const assetsKey = assets.join("|");
80712
81028
  useEffect121(() => {
80713
81029
  if (manifest404Ref.current.has(projectId)) return;
@@ -80736,7 +81052,7 @@ var AssetsTab = memo40(function AssetsTab2({
80736
81052
  cancelled = true;
80737
81053
  };
80738
81054
  }, [projectId, assetsKey]);
80739
- const handleImport = useCallback147(
81055
+ const handleImport = useCallback146(
80740
81056
  async (files) => {
80741
81057
  if (!onImport) return;
80742
81058
  setImporting(true);
@@ -80748,7 +81064,7 @@ var AssetsTab = memo40(function AssetsTab2({
80748
81064
  },
80749
81065
  [onImport]
80750
81066
  );
80751
- const handleDrop = useCallback147(
81067
+ const handleDrop = useCallback146(
80752
81068
  (e) => {
80753
81069
  e.preventDefault();
80754
81070
  setDragOver(false);
@@ -80756,7 +81072,7 @@ var AssetsTab = memo40(function AssetsTab2({
80756
81072
  },
80757
81073
  [handleImport]
80758
81074
  );
80759
- const handleCopyPath = useCallback147(async (path) => {
81075
+ const handleCopyPath = useCallback146(async (path) => {
80760
81076
  const copied = await copyTextToClipboard(path);
80761
81077
  setCopyFeedback({ path, ok: copied });
80762
81078
  setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
@@ -80966,11 +81282,11 @@ var AssetsTab = memo40(function AssetsTab2({
80966
81282
  });
80967
81283
 
80968
81284
  // src/components/sidebar/BlocksTab.tsx
80969
- import { memo as memo41, useState as useState129, useCallback as useCallback149, useRef as useRef156, useEffect as useEffect123 } from "react";
81285
+ import { memo as memo41, useState as useState129, useCallback as useCallback148, useRef as useRef158, useEffect as useEffect123 } from "react";
80970
81286
  import { createPortal as createPortal9 } from "react-dom";
80971
81287
 
80972
81288
  // src/components/sidebar/PromptPreviewModal.tsx
80973
- import { useState as useState128, useCallback as useCallback148, useEffect as useEffect122, useRef as useRef155 } from "react";
81289
+ import { useState as useState128, useCallback as useCallback147, useEffect as useEffect122, useRef as useRef157 } from "react";
80974
81290
  import { jsx as jsx183, jsxs as jsxs152 } from "react/jsx-runtime";
80975
81291
  function PromptPreviewModal({
80976
81292
  title,
@@ -80980,9 +81296,9 @@ function PromptPreviewModal({
80980
81296
  const [value, setValue] = useState128(prompt);
80981
81297
  const [copyState, setCopyState] = useState128("idle");
80982
81298
  const [entered, setEntered] = useState128(false);
80983
- const containerRef = useRef155(null);
80984
- const textareaRef = useRef155(null);
80985
- const valueRef = useRef155(value);
81299
+ const containerRef = useRef157(null);
81300
+ const textareaRef = useRef157(null);
81301
+ const valueRef = useRef157(value);
80986
81302
  valueRef.current = value;
80987
81303
  const { requestClose } = useDialogBehavior({
80988
81304
  open: true,
@@ -80996,7 +81312,7 @@ function PromptPreviewModal({
80996
81312
  textareaRef.current?.focus();
80997
81313
  });
80998
81314
  }, []);
80999
- const handleCopy2 = useCallback148(async () => {
81315
+ const handleCopy2 = useCallback147(async () => {
81000
81316
  try {
81001
81317
  await navigator.clipboard.writeText(valueRef.current);
81002
81318
  setCopyState("copied");
@@ -81279,16 +81595,16 @@ function BlockCard({
81279
81595
  }) {
81280
81596
  const [hovered, setHovered] = useState129(false);
81281
81597
  const [addState, setAddState] = useState129("idle");
81282
- const hoverTimer = useRef156(null);
81598
+ const hoverTimer = useRef158(null);
81283
81599
  const colors = getCategoryColors(category);
81284
81600
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
81285
- const handleEnter = useCallback149(() => {
81601
+ const handleEnter = useCallback148(() => {
81286
81602
  hoverTimer.current = setTimeout(() => {
81287
81603
  setHovered(true);
81288
81604
  onPreview?.({ videoUrl, posterUrl, title });
81289
81605
  }, 300);
81290
81606
  }, [onPreview, videoUrl, posterUrl, title]);
81291
- const handleLeave = useCallback149(() => {
81607
+ const handleLeave = useCallback148(() => {
81292
81608
  if (hoverTimer.current) {
81293
81609
  clearTimeout(hoverTimer.current);
81294
81610
  hoverTimer.current = null;
@@ -81301,7 +81617,7 @@ function BlockCard({
81301
81617
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
81302
81618
  };
81303
81619
  }, []);
81304
- const handleAdd = useCallback149(
81620
+ const handleAdd = useCallback148(
81305
81621
  async (e) => {
81306
81622
  e.stopPropagation();
81307
81623
  if (addState !== "idle" || !onAdd) return;
@@ -81317,7 +81633,7 @@ function BlockCard({
81317
81633
  [onAdd, addState]
81318
81634
  );
81319
81635
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
81320
- const handleShowPrompt = useCallback149(
81636
+ const handleShowPrompt = useCallback148(
81321
81637
  (e) => {
81322
81638
  e.stopPropagation();
81323
81639
  const state = usePlayerStore.getState();
@@ -81509,10 +81825,14 @@ var LeftSidebar = memo42(
81509
81825
  onAddCompositionToTimeline
81510
81826
  }, ref) {
81511
81827
  const [tab, setTab] = useState130(getPersistedTab);
81512
- const tabRef = useRef157(tab);
81828
+ const tabRef = useRef159(tab);
81513
81829
  tabRef.current = tab;
81514
- const tablistRef = useRef157(null);
81515
- const selectTab = useCallback150((t) => {
81830
+ const tablistRef = useRef159(null);
81831
+ const masterCompositionPath = useMemo72(
81832
+ () => resolveMasterCompositionPath(compositions),
81833
+ [compositions]
81834
+ );
81835
+ const selectTab = useCallback149((t) => {
81516
81836
  setTab(t);
81517
81837
  try {
81518
81838
  safeLocalStorage()?.setItem(STORAGE_KEY, t);
@@ -81520,9 +81840,9 @@ var LeftSidebar = memo42(
81520
81840
  }
81521
81841
  trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
81522
81842
  }, []);
81523
- const getTab = useCallback150(() => tabRef.current, []);
81843
+ const getTab = useCallback149(() => tabRef.current, []);
81524
81844
  useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
81525
- const handleTablistKeyDown = useCallback150(
81845
+ const handleTablistKeyDown = useCallback149(
81526
81846
  (e) => {
81527
81847
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
81528
81848
  e.preventDefault();
@@ -81607,6 +81927,7 @@ var LeftSidebar = memo42(
81607
81927
  projectId,
81608
81928
  compositions,
81609
81929
  activeComposition,
81930
+ masterCompositionPath,
81610
81931
  onSelect: onSelectComposition,
81611
81932
  onAddToTimeline: onAddCompositionToTimeline,
81612
81933
  onRenderComposition,
@@ -81879,7 +82200,7 @@ function StudioLeftSidebar({
81879
82200
  handleImportFiles,
81880
82201
  handleContentChange
81881
82202
  } = useFileManagerContext();
81882
- const handleRenderComposition = useCallback151(
82203
+ const handleRenderComposition = useCallback150(
81883
82204
  async (comp) => {
81884
82205
  if (renderQueue.ffmpegMissing) {
81885
82206
  setRightPanelTab("renders");
@@ -82004,13 +82325,13 @@ function StudioLeftSidebar({
82004
82325
  }
82005
82326
 
82006
82327
  // src/components/StudioRightPanel.tsx
82007
- import { useCallback as useCallback168 } from "react";
82328
+ import { useCallback as useCallback167 } from "react";
82008
82329
 
82009
82330
  // src/components/editor/LayersPanel.tsx
82010
- import { memo as memo43, useState as useState133, useCallback as useCallback153, useEffect as useEffect124, useRef as useRef159 } from "react";
82331
+ import { memo as memo43, useState as useState133, useCallback as useCallback152, useEffect as useEffect124, useRef as useRef161 } from "react";
82011
82332
 
82012
82333
  // src/components/editor/useLayerDrag.ts
82013
- import { useRef as useRef158, useState as useState132, useCallback as useCallback152 } from "react";
82334
+ import { useRef as useRef160, useState as useState132, useCallback as useCallback151 } from "react";
82014
82335
  var DRAG_THRESHOLD_PX3 = 4;
82015
82336
  function isLayerDraggable(layer) {
82016
82337
  if (!(layer.selector || layer.id)) return false;
@@ -82077,10 +82398,10 @@ function useLayerDrag({
82077
82398
  onReorder,
82078
82399
  onSingleSibling
82079
82400
  }) {
82080
- const dragRef = useRef158(null);
82401
+ const dragRef = useRef160(null);
82081
82402
  const [dragKey, setDragKey] = useState132(null);
82082
82403
  const [insertionLineY, setInsertionLineY] = useState132(null);
82083
- const handleRowPointerDown = useCallback152(
82404
+ const handleRowPointerDown = useCallback151(
82084
82405
  (layerIndex, e) => {
82085
82406
  if (e.button !== 0) return;
82086
82407
  const layer = visibleLayers[layerIndex];
@@ -82107,7 +82428,7 @@ function useLayerDrag({
82107
82428
  },
82108
82429
  [visibleLayers, scrollContainerRef, onSingleSibling]
82109
82430
  );
82110
- const handleContainerPointerMove = useCallback152(
82431
+ const handleContainerPointerMove = useCallback151(
82111
82432
  (e) => {
82112
82433
  const drag = dragRef.current;
82113
82434
  if (!drag) return;
@@ -82133,7 +82454,7 @@ function useLayerDrag({
82133
82454
  },
82134
82455
  [visibleLayers, scrollContainerRef]
82135
82456
  );
82136
- const handleContainerPointerUp = useCallback152(() => {
82457
+ const handleContainerPointerUp = useCallback151(() => {
82137
82458
  const drag = dragRef.current;
82138
82459
  dragRef.current = null;
82139
82460
  setDragKey(null);
@@ -82283,15 +82604,15 @@ var LayersPanel = memo43(function LayersPanel2() {
82283
82604
  } = useDomEditContext();
82284
82605
  const [layers, setLayers] = useState133([]);
82285
82606
  const [collapsed, setCollapsed] = useState133({});
82286
- const prevDocVersionRef = useRef159(0);
82287
- const scrollContainerRef = useRef159(null);
82607
+ const prevDocVersionRef = useRef161(0);
82608
+ const scrollContainerRef = useRef161(null);
82288
82609
  const mirrorLayerReorderToTimeline = useLayerReorderTimelineMirror();
82289
82610
  const { scheduleReveal } = useLayerRevealOverride({
82290
82611
  isPlaying,
82291
82612
  selectedElement: domEditSelection?.element ?? null
82292
82613
  });
82293
82614
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
82294
- const collectLayers = useCallback153(() => {
82615
+ const collectLayers = useCallback152(() => {
82295
82616
  const iframe = previewIframeRef.current;
82296
82617
  if (!iframe) return;
82297
82618
  let doc = null;
@@ -82340,7 +82661,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82340
82661
  throttle.cancel();
82341
82662
  };
82342
82663
  }, [collectLayers]);
82343
- const resolveSelection = useCallback153(
82664
+ const resolveSelection = useCallback152(
82344
82665
  (layer) => {
82345
82666
  let el = layer.element;
82346
82667
  if (!el.isConnected) {
@@ -82360,7 +82681,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82360
82681
  },
82361
82682
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
82362
82683
  );
82363
- const seekToLayer = useCallback153(
82684
+ const seekToLayer = useCallback152(
82364
82685
  async (layer) => {
82365
82686
  const selection = await resolveSelection(layer);
82366
82687
  if (!selection) return;
@@ -82389,7 +82710,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82389
82710
  },
82390
82711
  [currentTime, resolveSelection, timelineElements]
82391
82712
  );
82392
- const handleSelectLayer = useCallback153(
82713
+ const handleSelectLayer = useCallback152(
82393
82714
  async (layer) => {
82394
82715
  const selection = await resolveSelection(layer);
82395
82716
  if (!selection) return;
@@ -82399,7 +82720,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82399
82720
  },
82400
82721
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
82401
82722
  );
82402
- const handleLayerDoubleClick = useCallback153(
82723
+ const handleLayerDoubleClick = useCallback152(
82403
82724
  async (layer) => {
82404
82725
  const selection = await resolveSelection(layer);
82405
82726
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -82410,7 +82731,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82410
82731
  },
82411
82732
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
82412
82733
  );
82413
- const handleLayerHover = useCallback153(
82734
+ const handleLayerHover = useCallback152(
82414
82735
  async (layer) => {
82415
82736
  if (!layer) {
82416
82737
  updateDomEditHoverSelection(null);
@@ -82421,11 +82742,11 @@ var LayersPanel = memo43(function LayersPanel2() {
82421
82742
  },
82422
82743
  [resolveSelection, updateDomEditHoverSelection]
82423
82744
  );
82424
- const toggleCollapse = useCallback153((key2, e) => {
82745
+ const toggleCollapse = useCallback152((key2, e) => {
82425
82746
  e.stopPropagation();
82426
82747
  setCollapsed((prev) => ({ ...prev, [key2]: !prev[key2] }));
82427
82748
  }, []);
82428
- const handleReorder = useCallback153(
82749
+ const handleReorder = useCallback152(
82429
82750
  (event) => {
82430
82751
  const { siblingLayers, fromIndex, toIndex } = event;
82431
82752
  const reordered = [...siblingLayers];
@@ -82488,7 +82809,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82488
82809
  );
82489
82810
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
82490
82811
  const visibleLayers = getVisibleLayers(layers, collapsed);
82491
- const handleSingleSibling = useCallback153(() => {
82812
+ const handleSingleSibling = useCallback152(() => {
82492
82813
  showToast("Only one layer at this level", "info");
82493
82814
  }, [showToast]);
82494
82815
  const {
@@ -82620,13 +82941,13 @@ var LayersPanel = memo43(function LayersPanel2() {
82620
82941
  });
82621
82942
 
82622
82943
  // src/captions/components/CaptionPropertyPanel.tsx
82623
- import { memo as memo45, useCallback as useCallback155, useState as useState135 } from "react";
82944
+ import { memo as memo45, useCallback as useCallback154, useState as useState135 } from "react";
82624
82945
 
82625
82946
  // src/captions/components/CaptionAnimationPanel.tsx
82626
- import { memo as memo44, useCallback as useCallback154 } from "react";
82947
+ import { memo as memo44, useCallback as useCallback153 } from "react";
82627
82948
 
82628
82949
  // src/captions/components/shared.tsx
82629
- import { useEffect as useEffect125, useRef as useRef160, useState as useState134 } from "react";
82950
+ import { useEffect as useEffect125, useRef as useRef162, useState as useState134 } from "react";
82630
82951
  import { jsx as jsx189, jsxs as jsxs158 } from "react/jsx-runtime";
82631
82952
  var inputCls = "w-full bg-neutral-900 border border-neutral-800 rounded px-1.5 py-0.5 text-2xs text-neutral-200 font-mono outline-none focus:border-studio-accent disabled:opacity-40 disabled:cursor-not-allowed";
82632
82953
  function Section2({ label, children }) {
@@ -82652,7 +82973,7 @@ function NumberField({
82652
82973
  onCommit
82653
82974
  }) {
82654
82975
  const [draft, setDraft] = useState134(null);
82655
- const focusedRef = useRef160(false);
82976
+ const focusedRef = useRef162(false);
82656
82977
  useEffect125(() => {
82657
82978
  if (!focusedRef.current) setDraft(null);
82658
82979
  }, [value]);
@@ -82848,25 +83169,25 @@ var CaptionAnimationPanel = memo44(function CaptionAnimationPanel2() {
82848
83169
  }
82849
83170
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
82850
83171
  const animation = group?.animation;
82851
- const handleEntranceChange = useCallback154(
83172
+ const handleEntranceChange = useCallback153(
82852
83173
  (update) => {
82853
83174
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
82854
83175
  },
82855
83176
  [resolvedGroupId, updateGroupAnimation]
82856
83177
  );
82857
- const handleHighlightChange = useCallback154(
83178
+ const handleHighlightChange = useCallback153(
82858
83179
  (update) => {
82859
83180
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
82860
83181
  },
82861
83182
  [resolvedGroupId, updateGroupAnimation]
82862
83183
  );
82863
- const handleExitChange = useCallback154(
83184
+ const handleExitChange = useCallback153(
82864
83185
  (update) => {
82865
83186
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
82866
83187
  },
82867
83188
  [resolvedGroupId, updateGroupAnimation]
82868
83189
  );
82869
- const handleApplyToAll = useCallback154(() => {
83190
+ const handleApplyToAll = useCallback153(() => {
82870
83191
  if (animation) applyAnimationToAll(animation);
82871
83192
  }, [animation, applyAnimationToAll]);
82872
83193
  if (!group || !resolvedGroupId || !animation) {
@@ -82966,7 +83287,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
82966
83287
  ...groupStyle,
82967
83288
  ...segmentOverrides
82968
83289
  };
82969
- const applyToIframeDom = useCallback155(
83290
+ const applyToIframeDom = useCallback154(
82970
83291
  (updates) => {
82971
83292
  const iframe = iframeRef.current;
82972
83293
  if (!iframe || !model) return;
@@ -83040,7 +83361,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
83040
83361
  },
83041
83362
  [iframeRef, model, selectedSegmentIds]
83042
83363
  );
83043
- const handleStyleChange = useCallback155(
83364
+ const handleStyleChange = useCallback154(
83044
83365
  (updates) => {
83045
83366
  if (selectedGroupId) {
83046
83367
  updateGroupStyle(selectedGroupId, updates);
@@ -83145,7 +83466,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
83145
83466
  });
83146
83467
 
83147
83468
  // src/components/editor/BlockParamsPanel.tsx
83148
- import { memo as memo46, useCallback as useCallback156, useEffect as useEffect126, useRef as useRef161, useState as useState136 } from "react";
83469
+ import { memo as memo46, useCallback as useCallback155, useEffect as useEffect126, useRef as useRef163, useState as useState136 } from "react";
83149
83470
  import { jsx as jsx192, jsxs as jsxs161 } from "react/jsx-runtime";
83150
83471
  var BlockParamsPanel = memo46(function BlockParamsPanel2({
83151
83472
  blockName,
@@ -83163,12 +83484,12 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83163
83484
  }
83164
83485
  return initial;
83165
83486
  });
83166
- const appliedRef = useRef161(
83487
+ const appliedRef = useRef163(
83167
83488
  Object.fromEntries(params.map((p) => [p.key, p.default]))
83168
83489
  );
83169
83490
  const [commitState, setCommitState] = useState136({ tone: "idle" });
83170
- const commitTimersRef = useRef161(/* @__PURE__ */ new Map());
83171
- const commitParamNow = useCallback156(
83491
+ const commitTimersRef = useRef163(/* @__PURE__ */ new Map());
83492
+ const commitParamNow = useCallback155(
83172
83493
  async (key2, nextValue) => {
83173
83494
  if (!fileManager) return;
83174
83495
  const previous = appliedRef.current[key2];
@@ -83209,8 +83530,8 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83209
83530
  },
83210
83531
  [fileManager, compositionPath, setRefreshKey, blockName]
83211
83532
  );
83212
- const commitChainRef = useRef161(Promise.resolve());
83213
- const commitParam = useCallback156(
83533
+ const commitChainRef = useRef163(Promise.resolve());
83534
+ const commitParam = useCallback155(
83214
83535
  (key2, nextValue) => {
83215
83536
  const run = commitChainRef.current.then(() => commitParamNow(key2, nextValue));
83216
83537
  commitChainRef.current = run.catch(() => void 0);
@@ -83218,7 +83539,7 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83218
83539
  },
83219
83540
  [commitParamNow]
83220
83541
  );
83221
- const handleChange = useCallback156(
83542
+ const handleChange = useCallback155(
83222
83543
  (key2, value) => {
83223
83544
  setValues((prev) => ({ ...prev, [key2]: value }));
83224
83545
  const timers = commitTimersRef.current;
@@ -83234,7 +83555,7 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83234
83555
  },
83235
83556
  [commitParam]
83236
83557
  );
83237
- const flushRef = useRef161(() => {
83558
+ const flushRef = useRef163(() => {
83238
83559
  });
83239
83560
  flushRef.current = () => {
83240
83561
  for (const [key2, timer] of commitTimersRef.current) {
@@ -83376,12 +83697,12 @@ function ParamControl({
83376
83697
  import { memo as memo50 } from "react";
83377
83698
 
83378
83699
  // src/components/renders/RenderQueue.tsx
83379
- import { memo as memo49, useState as useState139, useRef as useRef163, useEffect as useEffect128, useLayoutEffect as useLayoutEffect10, useId as useId7 } from "react";
83700
+ import { memo as memo49, useState as useState139, useRef as useRef165, useEffect as useEffect128, useLayoutEffect as useLayoutEffect10, useId as useId7 } from "react";
83380
83701
  import { createPortal as createPortal10 } from "react-dom";
83381
83702
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
83382
83703
 
83383
83704
  // src/components/renders/RenderQueueItem.tsx
83384
- import { memo as memo47, useCallback as useCallback157, useState as useState137 } from "react";
83705
+ import { memo as memo47, useCallback as useCallback156, useState as useState137 } from "react";
83385
83706
  import { Fragment as Fragment57, jsx as jsx193, jsxs as jsxs162 } from "react/jsx-runtime";
83386
83707
  function formatDuration2(ms) {
83387
83708
  if (ms < 1e3) return `${ms}ms`;
@@ -83404,10 +83725,10 @@ var RenderQueueItem = memo47(function RenderQueueItem2({
83404
83725
  const [videoReady, setVideoReady] = useState137(false);
83405
83726
  const [confirmingDelete, setConfirmingDelete] = useState137(false);
83406
83727
  const fileSrc = buildProjectApiPath(projectId, `/renders/file/${job.filename}`);
83407
- const handleOpen = useCallback157(() => {
83728
+ const handleOpen = useCallback156(() => {
83408
83729
  window.open(fileSrc, "_blank");
83409
83730
  }, [fileSrc]);
83410
- const handleDownload = useCallback157(
83731
+ const handleDownload = useCallback156(
83411
83732
  (e) => {
83412
83733
  e.stopPropagation();
83413
83734
  const a = document.createElement("a");
@@ -83607,7 +83928,7 @@ var RenderQueueItem = memo47(function RenderQueueItem2({
83607
83928
  });
83608
83929
 
83609
83930
  // src/components/renders/FfmpegRequiredNotice.tsx
83610
- import { memo as memo48, useEffect as useEffect127, useRef as useRef162, useState as useState138 } from "react";
83931
+ import { memo as memo48, useEffect as useEffect127, useRef as useRef164, useState as useState138 } from "react";
83611
83932
  import { jsx as jsx194, jsxs as jsxs163 } from "react/jsx-runtime";
83612
83933
  var DOWNLOAD_URL = "https://ffmpeg.org/download.html";
83613
83934
  var CUE_MS = 1600;
@@ -83619,8 +83940,8 @@ var FfmpegRequiredNotice = memo48(function FfmpegRequiredNotice2({
83619
83940
  }) {
83620
83941
  const [copied, setCopied] = useState138(false);
83621
83942
  const [recheckFailed, setRecheckFailed] = useState138(false);
83622
- const wasChecking = useRef162(false);
83623
- const cueTimer = useRef162(void 0);
83943
+ const wasChecking = useRef164(false);
83944
+ const cueTimer = useRef164(void 0);
83624
83945
  useEffect127(() => {
83625
83946
  if (wasChecking.current && !checking) setRecheckFailed(true);
83626
83947
  wasChecking.current = checking;
@@ -83750,8 +84071,8 @@ var FORMAT_PANEL_SIZE = { width: 208, height: 150 };
83750
84071
  function FormatInfoTooltip({ format }) {
83751
84072
  const [open, setOpen] = useState139(false);
83752
84073
  const [position, setPosition] = useState139(null);
83753
- const triggerRef = useRef163(null);
83754
- const timeoutRef = useRef163(void 0);
84074
+ const triggerRef = useRef165(null);
84075
+ const timeoutRef = useRef165(void 0);
83755
84076
  const panelId = useId7();
83756
84077
  const show = () => {
83757
84078
  clearTimeout(timeoutRef.current);
@@ -84002,7 +84323,7 @@ var RenderQueue = memo49(function RenderQueue2({
84002
84323
  ffmpegChecking,
84003
84324
  onRecheckFfmpeg
84004
84325
  }) {
84005
- const listRef = useRef163(null);
84326
+ const listRef = useRef165(null);
84006
84327
  useEffect128(() => {
84007
84328
  if (listRef.current) {
84008
84329
  listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
@@ -84151,11 +84472,11 @@ var RenderQueuePanel = memo50(function RenderQueuePanel2() {
84151
84472
  });
84152
84473
 
84153
84474
  // src/components/panels/SlideshowPanel.tsx
84154
- import { useState as useState141, useEffect as useEffect129, useCallback as useCallback159, useRef as useRef164 } from "react";
84475
+ import { useState as useState141, useEffect as useEffect129, useCallback as useCallback158, useRef as useRef166 } from "react";
84155
84476
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
84156
84477
 
84157
84478
  // src/components/panels/SlideshowSubPanels.tsx
84158
- import { useState as useState140, useCallback as useCallback158, useId as useId8 } from "react";
84479
+ import { useState as useState140, useCallback as useCallback157, useId as useId8 } from "react";
84159
84480
  import { jsx as jsx197, jsxs as jsxs165 } from "react/jsx-runtime";
84160
84481
  function SectionHeader({
84161
84482
  children,
@@ -84343,7 +84664,7 @@ function BranchTree({
84343
84664
  }) {
84344
84665
  const [newLabel, setNewLabel] = useState140("");
84345
84666
  const inputId = useId8();
84346
- const handleCreate = useCallback158(() => {
84667
+ const handleCreate = useCallback157(() => {
84347
84668
  const label = newLabel.trim();
84348
84669
  if (!label) return;
84349
84670
  onCreateSequence(label);
@@ -84405,7 +84726,7 @@ function BranchItem({
84405
84726
  const [editing, setEditing] = useState140(false);
84406
84727
  const [draft, setDraft] = useState140(seq.label);
84407
84728
  const [confirmingDelete, setConfirmingDelete] = useState140(false);
84408
- const commitRename = useCallback158(() => {
84729
+ const commitRename = useCallback157(() => {
84409
84730
  const label = draft.trim();
84410
84731
  if (label && label !== seq.label) onRename(seq.id, label);
84411
84732
  setEditing(false);
@@ -84538,7 +84859,7 @@ function HotspotTool({
84538
84859
  const selectedElementId = domEditSelection?.element?.id ?? null;
84539
84860
  const selectedHfId = domEditSelection?.hfId ?? null;
84540
84861
  const elementKey2 = selectedElementId || selectedHfId;
84541
- const handleMakeHotspot = useCallback158(() => {
84862
+ const handleMakeHotspot = useCallback157(() => {
84542
84863
  if (!selectedSceneId || !targetSequenceId || !elementKey2) return;
84543
84864
  const id = `hotspot-${elementKey2}-${generateId()}`;
84544
84865
  const label = hotspotLabel.trim() || elementKey2;
@@ -84830,12 +85151,12 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84830
85151
  );
84831
85152
  const [persistError, setPersistError] = useState141(false);
84832
85153
  const [retrying, setRetrying] = useState141(false);
84833
- const undoStackRef = useRef164([]);
85154
+ const undoStackRef = useRef166([]);
84834
85155
  const [undoDepth, setUndoDepth] = useState141(0);
84835
85156
  const currentTime = usePlayerStore((s) => s.currentTime);
84836
85157
  const { domEditSelection } = useDomEditSelectionContext();
84837
- const manifestRef = useRef164(manifest);
84838
- const notesCtrlRef = useRef164(makeSlideshowNotesController());
85158
+ const manifestRef = useRef166(manifest);
85159
+ const notesCtrlRef = useRef166(makeSlideshowNotesController());
84839
85160
  useEffect129(() => {
84840
85161
  if (!compHtml) {
84841
85162
  notesCtrlRef.current.flush();
@@ -84852,7 +85173,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84852
85173
  setUndoDepth(0);
84853
85174
  setPersistError(false);
84854
85175
  }, [compHtml]);
84855
- const applyManifest = useCallback159(
85176
+ const applyManifest = useCallback158(
84856
85177
  async (next, opts) => {
84857
85178
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
84858
85179
  if (!opts?.skipUndo) {
@@ -84872,14 +85193,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84872
85193
  },
84873
85194
  [onPersist]
84874
85195
  );
84875
- const handleUndo = useCallback159(() => {
85196
+ const handleUndo = useCallback158(() => {
84876
85197
  const prev = undoStackRef.current.pop();
84877
85198
  if (!prev) return;
84878
85199
  setUndoDepth(undoStackRef.current.length);
84879
85200
  applyManifest(prev, { skipUndo: true }).catch(() => {
84880
85201
  });
84881
85202
  }, [applyManifest]);
84882
- const handleRetryPersist = useCallback159(async () => {
85203
+ const handleRetryPersist = useCallback158(async () => {
84883
85204
  setRetrying(true);
84884
85205
  try {
84885
85206
  await onPersist(manifestRef.current);
@@ -84890,7 +85211,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84890
85211
  setRetrying(false);
84891
85212
  }
84892
85213
  }, [onPersist]);
84893
- const applyNotesManifest = useCallback159(
85214
+ const applyNotesManifest = useCallback158(
84894
85215
  (next) => {
84895
85216
  setManifest(next);
84896
85217
  manifestRef.current = next;
@@ -84904,7 +85225,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84904
85225
  ctrl.flush();
84905
85226
  };
84906
85227
  }, []);
84907
- const toggleSection = useCallback159((key2) => {
85228
+ const toggleSection = useCallback158((key2) => {
84908
85229
  setExpandedSections((prev) => {
84909
85230
  const next = new Set(prev);
84910
85231
  if (next.has(key2)) {
@@ -84918,25 +85239,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84918
85239
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
84919
85240
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
84920
85241
  const sequences = manifest.slideSequences ?? [];
84921
- const handleSelectBranchSlide = useCallback159((sequenceId, sceneId) => {
85242
+ const handleSelectBranchSlide = useCallback158((sequenceId, sceneId) => {
84922
85243
  setSelectedSceneId(sceneId);
84923
85244
  setSelectedSequenceId(sequenceId);
84924
85245
  }, []);
84925
- const handleToggleSlide = useCallback159(
85246
+ const handleToggleSlide = useCallback158(
84926
85247
  (sceneId) => {
84927
85248
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
84928
85249
  });
84929
85250
  },
84930
85251
  [applyManifest]
84931
85252
  );
84932
- const handleReorder = useCallback159(
85253
+ const handleReorder = useCallback158(
84933
85254
  (sceneId, dir) => {
84934
85255
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
84935
85256
  });
84936
85257
  },
84937
85258
  [applyManifest]
84938
85259
  );
84939
- const handleSetNotes = useCallback159(
85260
+ const handleSetNotes = useCallback158(
84940
85261
  (notes) => {
84941
85262
  if (!selectedSceneId) return;
84942
85263
  applyNotesManifest(
@@ -84945,7 +85266,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84945
85266
  },
84946
85267
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
84947
85268
  );
84948
- const handleMarkFragment = useCallback159(() => {
85269
+ const handleMarkFragment = useCallback158(() => {
84949
85270
  if (!selectedSceneId) return;
84950
85271
  applyManifest(
84951
85272
  addFragment(
@@ -84957,7 +85278,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84957
85278
  ).catch(() => {
84958
85279
  });
84959
85280
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
84960
- const handleRemoveFragment = useCallback159(
85281
+ const handleRemoveFragment = useCallback158(
84961
85282
  (time) => {
84962
85283
  if (!selectedSceneId) return;
84963
85284
  applyManifest(
@@ -84967,7 +85288,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84967
85288
  },
84968
85289
  [selectedSceneId, selectedSequenceId, applyManifest]
84969
85290
  );
84970
- const handleCreateSequence = useCallback159(
85291
+ const handleCreateSequence = useCallback158(
84971
85292
  (label) => {
84972
85293
  const id = `seq-${generateId()}`;
84973
85294
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -84975,21 +85296,21 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84975
85296
  },
84976
85297
  [applyManifest]
84977
85298
  );
84978
- const handleRenameSequence = useCallback159(
85299
+ const handleRenameSequence = useCallback158(
84979
85300
  (id, label) => {
84980
85301
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
84981
85302
  });
84982
85303
  },
84983
85304
  [applyManifest]
84984
85305
  );
84985
- const handleDeleteSequence = useCallback159(
85306
+ const handleDeleteSequence = useCallback158(
84986
85307
  (id) => {
84987
85308
  applyManifest(deleteSequence(manifestRef.current, id)).catch(() => {
84988
85309
  });
84989
85310
  },
84990
85311
  [applyManifest]
84991
85312
  );
84992
- const handleAssign = useCallback159(
85313
+ const handleAssign = useCallback158(
84993
85314
  (sequenceId, sceneId, assign) => {
84994
85315
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
84995
85316
  () => {
@@ -84998,7 +85319,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84998
85319
  },
84999
85320
  [applyManifest]
85000
85321
  );
85001
- const handleAddHotspot = useCallback159(
85322
+ const handleAddHotspot = useCallback158(
85002
85323
  (sceneId, hotspot) => {
85003
85324
  applyManifest(
85004
85325
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -85007,7 +85328,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85007
85328
  },
85008
85329
  [selectedSequenceId, applyManifest]
85009
85330
  );
85010
- const handleRemoveHotspot = useCallback159(
85331
+ const handleRemoveHotspot = useCallback158(
85011
85332
  (sceneId, hotspotId) => {
85012
85333
  applyManifest(
85013
85334
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -85160,10 +85481,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85160
85481
  }
85161
85482
 
85162
85483
  // src/components/panels/VariablesPanel.tsx
85163
- import { memo as memo51, useCallback as useCallback163, useEffect as useEffect131, useMemo as useMemo72, useState as useState145 } from "react";
85484
+ import { memo as memo51, useCallback as useCallback162, useEffect as useEffect131, useMemo as useMemo73, useState as useState145 } from "react";
85164
85485
 
85165
85486
  // src/hooks/useVariablesPersist.ts
85166
- import { useCallback as useCallback160 } from "react";
85487
+ import { useCallback as useCallback159 } from "react";
85167
85488
  function useVariablesPersist({
85168
85489
  sdkSession,
85169
85490
  activeCompPath,
@@ -85173,7 +85494,7 @@ function useVariablesPersist({
85173
85494
  reloadPreview,
85174
85495
  publishSdkSession
85175
85496
  }) {
85176
- return useCallback160(
85497
+ return useCallback159(
85177
85498
  async (label, mutate) => {
85178
85499
  if (!sdkSession) return false;
85179
85500
  const path = activeCompPath ?? "index.html";
@@ -85208,10 +85529,10 @@ function useVariablesPersist({
85208
85529
  }
85209
85530
 
85210
85531
  // src/components/panels/VariablesOtherCompositions.tsx
85211
- import { useCallback as useCallback162, useState as useState144 } from "react";
85532
+ import { useCallback as useCallback161, useState as useState144 } from "react";
85212
85533
 
85213
85534
  // src/hooks/useProjectCompositionVariables.ts
85214
- import { useCallback as useCallback161, useEffect as useEffect130, useState as useState142 } from "react";
85535
+ import { useCallback as useCallback160, useEffect as useEffect130, useState as useState142 } from "react";
85215
85536
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
85216
85537
  async function readGroup(path, readProjectFile) {
85217
85538
  let content;
@@ -85254,7 +85575,7 @@ function useProjectCompositionVariables(fileTree, excludePath, readProjectFile,
85254
85575
  }
85255
85576
  function useEditVariablesInFile(deps) {
85256
85577
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview } = deps;
85257
- return useCallback161(
85578
+ return useCallback160(
85258
85579
  async (path, label, mutate) => {
85259
85580
  const originalContent = await readProjectFile(path);
85260
85581
  await persistSdkSerialize(
@@ -85631,7 +85952,7 @@ function VariablesOtherCompositions({
85631
85952
  reloadPreview
85632
85953
  });
85633
85954
  const [editingKey, setEditingKey] = useState144(null);
85634
- const onSave = useCallback162(
85955
+ const onSave = useCallback161(
85635
85956
  (path, decl) => {
85636
85957
  setEditingKey(null);
85637
85958
  void editInFile(
@@ -85642,7 +85963,7 @@ function VariablesOtherCompositions({
85642
85963
  },
85643
85964
  [editInFile]
85644
85965
  );
85645
- const onRemove = useCallback162(
85966
+ const onRemove = useCallback161(
85646
85967
  (path, id) => {
85647
85968
  void editInFile(
85648
85969
  path,
@@ -85835,9 +86156,9 @@ var VariablesPanel = memo51(function VariablesPanel2({
85835
86156
  }) {
85836
86157
  const { activeCompPath, showToast } = useStudioShellContext();
85837
86158
  const { refreshKey } = useStudioPlaybackContext();
85838
- const { readProjectFile, writeProjectFile, fileTree } = useFileManagerContext();
86159
+ const { readProjectFile, writeProjectFile, fileTree, compositions } = useFileManagerContext();
85839
86160
  const { domEditSelection } = useDomEditContext();
85840
- const effectiveCompPath = activeCompPath ?? resolveMasterCompositionPath(fileTree);
86161
+ const effectiveCompPath = activeCompPath ?? resolveMasterCompositionPath(compositions);
85841
86162
  const previewValues = usePreviewVariablesStore((s) => s.values);
85842
86163
  const setPreviewValues = usePreviewVariablesStore((s) => s.setValues);
85843
86164
  const [revision, setRevision] = useState145(0);
@@ -85856,27 +86177,27 @@ var VariablesPanel = memo51(function VariablesPanel2({
85856
86177
  reloadPreview,
85857
86178
  publishSdkSession
85858
86179
  });
85859
- const declarations = useMemo72(
86180
+ const declarations = useMemo73(
85860
86181
  () => sdkSession?.getVariableDeclarations() ?? [],
85861
86182
  // eslint-disable-next-line react-hooks/exhaustive-deps
85862
86183
  [sdkSession, refreshKey, revision]
85863
86184
  );
85864
- const usage = useMemo72(
86185
+ const usage = useMemo73(
85865
86186
  () => sdkSession?.getVariableUsage() ?? null,
85866
86187
  // eslint-disable-next-line react-hooks/exhaustive-deps
85867
86188
  [sdkSession, refreshKey, revision]
85868
86189
  );
85869
- const issues = useMemo72(
86190
+ const issues = useMemo73(
85870
86191
  () => previewValues && sdkSession ? sdkSession.validateVariableValues(previewValues) : [],
85871
86192
  // eslint-disable-next-line react-hooks/exhaustive-deps
85872
86193
  [sdkSession, previewValues, refreshKey, revision]
85873
86194
  );
85874
- const effectiveValues = useMemo72(
86195
+ const effectiveValues = useMemo73(
85875
86196
  () => sdkSession?.getVariableValues(previewValues ?? void 0) ?? {},
85876
86197
  // eslint-disable-next-line react-hooks/exhaustive-deps
85877
86198
  [sdkSession, previewValues, refreshKey, revision]
85878
86199
  );
85879
- const copyToClipboard = useCallback163(
86200
+ const copyToClipboard = useCallback162(
85880
86201
  (text, what) => {
85881
86202
  void copyTextToClipboard(text).then(
85882
86203
  (ok) => showToast(
@@ -85887,7 +86208,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85887
86208
  },
85888
86209
  [showToast]
85889
86210
  );
85890
- const dropPreviewOverride = useCallback163(
86211
+ const dropPreviewOverride = useCallback162(
85891
86212
  (id) => {
85892
86213
  if (previewValues && id in previewValues) {
85893
86214
  const next = { ...previewValues };
@@ -85897,7 +86218,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85897
86218
  },
85898
86219
  [previewValues, setPreviewValues]
85899
86220
  );
85900
- const commitPreviewValue = useCallback163(
86221
+ const commitPreviewValue = useCallback162(
85901
86222
  (id, value, declDefault) => {
85902
86223
  const next = { ...previewValues ?? {} };
85903
86224
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -85910,7 +86231,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85910
86231
  },
85911
86232
  [previewValues, setPreviewValues, reloadPreview]
85912
86233
  );
85913
- const runSchemaEdit = useCallback163(
86234
+ const runSchemaEdit = useCallback162(
85914
86235
  async (label, mutate) => {
85915
86236
  try {
85916
86237
  const changed = await persistVariables(label, mutate);
@@ -85924,7 +86245,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85924
86245
  },
85925
86246
  [persistVariables, showToast]
85926
86247
  );
85927
- const handleAdd = useCallback163(
86248
+ const handleAdd = useCallback162(
85928
86249
  (decl) => {
85929
86250
  if (!sdkSession) return;
85930
86251
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -85937,7 +86258,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85937
86258
  },
85938
86259
  [sdkSession, runSchemaEdit, showToast]
85939
86260
  );
85940
- const handleUpdate = useCallback163(
86261
+ const handleUpdate = useCallback162(
85941
86262
  (decl) => {
85942
86263
  if (!sdkSession) return;
85943
86264
  const check = sdkSession.can({
@@ -85957,7 +86278,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85957
86278
  },
85958
86279
  [sdkSession, runSchemaEdit, showToast]
85959
86280
  );
85960
- const handleRemove = useCallback163(
86281
+ const handleRemove = useCallback162(
85961
86282
  (id) => {
85962
86283
  if (!sdkSession) return;
85963
86284
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -85973,18 +86294,18 @@ var VariablesPanel = memo51(function VariablesPanel2({
85973
86294
  },
85974
86295
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
85975
86296
  );
85976
- const handleSetDefault = useCallback163(
86297
+ const handleSetDefault = useCallback162(
85977
86298
  (id, value) => {
85978
86299
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
85979
86300
  dropPreviewOverride(id);
85980
86301
  },
85981
86302
  [runSchemaEdit, dropPreviewOverride]
85982
86303
  );
85983
- const resetPreview = useCallback163(() => {
86304
+ const resetPreview = useCallback162(() => {
85984
86305
  setPreviewValues(null);
85985
86306
  reloadPreview();
85986
86307
  }, [setPreviewValues, reloadPreview]);
85987
- const handleBind = useCallback163(
86308
+ const handleBind = useCallback162(
85988
86309
  // Guard chain (session, selection, type-compat) — one branch per guard.
85989
86310
  // fallow-ignore-next-line complexity
85990
86311
  (action, id) => {
@@ -86116,7 +86437,7 @@ function PanelTabButton({
86116
86437
  }
86117
86438
 
86118
86439
  // src/hooks/useSlideshowPersist.ts
86119
- import { useCallback as useCallback164 } from "react";
86440
+ import { useCallback as useCallback163 } from "react";
86120
86441
 
86121
86442
  // src/utils/setSlideshowManifest.ts
86122
86443
  import {
@@ -86173,7 +86494,7 @@ function useSlideshowPersist({
86173
86494
  publishSdkSession,
86174
86495
  coalesceKey
86175
86496
  }) {
86176
- return useCallback164(
86497
+ return useCallback163(
86177
86498
  async (manifest) => {
86178
86499
  if (!sdkSession) return;
86179
86500
  const path = activeCompPath ?? "index.html";
@@ -86206,15 +86527,15 @@ function useSlideshowPersist({
86206
86527
  }
86207
86528
 
86208
86529
  // src/hooks/useSlideshowTabState.ts
86209
- import { useEffect as useEffect132, useMemo as useMemo73 } from "react";
86530
+ import { useEffect as useEffect132, useMemo as useMemo74 } from "react";
86210
86531
  import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
86211
86532
  function useSlideshowTabState(params) {
86212
86533
  const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } = params;
86213
- const isSlideshowComposition = useMemo73(() => {
86534
+ const isSlideshowComposition = useMemo74(() => {
86214
86535
  if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
86215
86536
  return slideshowIslandRegex2("i").test(editingFileContent);
86216
86537
  }, [editingFileContent]);
86217
- const slideshowScenes = useMemo73(() => {
86538
+ const slideshowScenes = useMemo74(() => {
86218
86539
  try {
86219
86540
  const win = previewIframeRef.current?.contentWindow;
86220
86541
  return (win?.__clipManifest?.scenes ?? []).map((s) => ({
@@ -86236,7 +86557,7 @@ function useSlideshowTabState(params) {
86236
86557
  }
86237
86558
 
86238
86559
  // src/components/DesignPanelPromoteProvider.tsx
86239
- import { useCallback as useCallback165 } from "react";
86560
+ import { useCallback as useCallback164 } from "react";
86240
86561
  import { jsx as jsx204 } from "react/jsx-runtime";
86241
86562
  function DesignPanelPromoteProvider({
86242
86563
  selection,
@@ -86255,7 +86576,7 @@ function DesignPanelPromoteProvider({
86255
86576
  publishSdkSession: handle.publish,
86256
86577
  activeCompPath: targetPath
86257
86578
  });
86258
- const persist2 = useCallback165(
86579
+ const persist2 = useCallback164(
86259
86580
  async (label, mutate) => {
86260
86581
  const committed = await rawPersist(label, mutate);
86261
86582
  if (committed) forceReloadSharedSdkSession?.();
@@ -86263,7 +86584,7 @@ function DesignPanelPromoteProvider({
86263
86584
  },
86264
86585
  [rawPersist, forceReloadSharedSdkSession]
86265
86586
  );
86266
- const handlePersistError = useCallback165(
86587
+ const handlePersistError = useCallback164(
86267
86588
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
86268
86589
  [showToast]
86269
86590
  );
@@ -86496,14 +86817,14 @@ async function applyColorGradingScopeUpdate({
86496
86817
  }
86497
86818
 
86498
86819
  // src/hooks/useInspectorSplitResize.ts
86499
- import { useCallback as useCallback166, useRef as useRef165, useState as useState146 } from "react";
86820
+ import { useCallback as useCallback165, useRef as useRef167, useState as useState146 } from "react";
86500
86821
  var MIN_INSPECTOR_SPLIT_PERCENT = 20;
86501
86822
  var MAX_INSPECTOR_SPLIT_PERCENT = 75;
86502
86823
  function useInspectorSplitResize() {
86503
86824
  const [layersPanePercent, setLayersPanePercent] = useState146(40);
86504
- const splitContainerRef = useRef165(null);
86505
- const splitDragRef = useRef165(null);
86506
- const handleInspectorSplitResizeStart = useCallback166(
86825
+ const splitContainerRef = useRef167(null);
86826
+ const splitDragRef = useRef167(null);
86827
+ const handleInspectorSplitResizeStart = useCallback165(
86507
86828
  (event) => {
86508
86829
  event.preventDefault();
86509
86830
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -86516,7 +86837,7 @@ function useInspectorSplitResize() {
86516
86837
  },
86517
86838
  [layersPanePercent]
86518
86839
  );
86519
- const handleInspectorSplitResizeMove = useCallback166((event) => {
86840
+ const handleInspectorSplitResizeMove = useCallback165((event) => {
86520
86841
  const drag = splitDragRef.current;
86521
86842
  if (!drag || drag.height <= 0) return;
86522
86843
  const deltaPercent = (event.clientY - drag.startY) / drag.height * 100;
@@ -86526,7 +86847,7 @@ function useInspectorSplitResize() {
86526
86847
  );
86527
86848
  setLayersPanePercent(next);
86528
86849
  }, []);
86529
- const handleInspectorSplitResizeEnd = useCallback166(() => {
86850
+ const handleInspectorSplitResizeEnd = useCallback165(() => {
86530
86851
  splitDragRef.current = null;
86531
86852
  }, []);
86532
86853
  return {
@@ -86539,16 +86860,16 @@ function useInspectorSplitResize() {
86539
86860
  }
86540
86861
 
86541
86862
  // src/hooks/useRemoveBackground.ts
86542
- import { useCallback as useCallback167, useEffect as useEffect133, useRef as useRef166 } from "react";
86863
+ import { useCallback as useCallback166, useEffect as useEffect133, useRef as useRef168 } from "react";
86543
86864
  function useRemoveBackground(projectId, refreshFileTree, showToast) {
86544
- const abortRef = useRef166(null);
86865
+ const abortRef = useRef168(null);
86545
86866
  useEffect133(
86546
86867
  () => () => {
86547
86868
  abortRef.current?.abort();
86548
86869
  },
86549
86870
  []
86550
86871
  );
86551
- return useCallback167(
86872
+ return useCallback166(
86552
86873
  // fallow-ignore-next-line complexity
86553
86874
  async (inputPath, options) => {
86554
86875
  const response = await fetch(
@@ -86730,7 +87051,7 @@ function StudioRightPanel({
86730
87051
  }
86731
87052
  toggleRightInspectorPane(pane);
86732
87053
  };
86733
- const handleApplyColorGradingScope = useCallback168(
87054
+ const handleApplyColorGradingScope = useCallback167(
86734
87055
  async (scope, value) => applyColorGradingScopeUpdate({
86735
87056
  scope,
86736
87057
  value,
@@ -86764,7 +87085,7 @@ function StudioRightPanel({
86764
87085
  ]
86765
87086
  );
86766
87087
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
86767
- const setAttributeWhileDragging = useCallback168(
87088
+ const setAttributeWhileDragging = useCallback167(
86768
87089
  (attr, value) => handleDomAttributeLiveCommit(attr, value, void 0, { previewOnly: true }),
86769
87090
  [handleDomAttributeLiveCommit]
86770
87091
  );
@@ -87012,11 +87333,17 @@ function StudioRightPanel({
87012
87333
  }
87013
87334
 
87014
87335
  // src/components/TimelineToolbar.tsx
87015
- import { useEffect as useEffect135, useRef as useRef167 } from "react";
87016
- import { Image as Image2, Magnet, MagnifyingGlassMinus, MagnifyingGlassPlus } from "@phosphor-icons/react";
87336
+ import { useEffect as useEffect135, useRef as useRef169 } from "react";
87337
+ import {
87338
+ Image as Image2,
87339
+ Magnet,
87340
+ MagnifyingGlassMinus,
87341
+ MagnifyingGlassPlus,
87342
+ Waves
87343
+ } from "@phosphor-icons/react";
87017
87344
 
87018
87345
  // src/hooks/useEnableKeyframes.ts
87019
- import { useCallback as useCallback169 } from "react";
87346
+ import { useCallback as useCallback168 } from "react";
87020
87347
  var enableKeyframesTransactionCounter = 0;
87021
87348
  function animatedProps(anim) {
87022
87349
  if (!anim) return ["x", "y"];
@@ -87269,7 +87596,7 @@ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t, iframe) {
87269
87596
  );
87270
87597
  }
87271
87598
  function useEnableKeyframes(sessionRef) {
87272
- return useCallback169(async () => {
87599
+ return useCallback168(async () => {
87273
87600
  const session = sessionRef.current;
87274
87601
  if (!session) return;
87275
87602
  const sel = session.domEditSelection;
@@ -87351,7 +87678,7 @@ function useEnableKeyframes(sessionRef) {
87351
87678
  }
87352
87679
 
87353
87680
  // src/hooks/useKeyframeKeyboard.ts
87354
- import { useEffect as useEffect134, useCallback as useCallback170 } from "react";
87681
+ import { useEffect as useEffect134, useCallback as useCallback169 } from "react";
87355
87682
  function isTextInput2(el) {
87356
87683
  if (!el) return false;
87357
87684
  const tag = el.tagName;
@@ -87368,7 +87695,7 @@ function useKeyframeKeyboard({
87368
87695
  onToggleExpand,
87369
87696
  onNudgeKeyframe
87370
87697
  }) {
87371
- const handler = useCallback170(
87698
+ const handler = useCallback169(
87372
87699
  (e) => {
87373
87700
  if (!enabled) return;
87374
87701
  if (isTextInput2(document.activeElement)) return;
@@ -87469,7 +87796,7 @@ function useKeyframeToggle(session) {
87469
87796
  const currentTime = usePlayerStore((s) => s.currentTime);
87470
87797
  const selectedElementId = usePlayerStore((s) => s.selectedElementId);
87471
87798
  const elements = usePlayerStore((s) => s.elements);
87472
- const sessionRef = useRef167(session);
87799
+ const sessionRef = useRef169(session);
87473
87800
  sessionRef.current = session;
87474
87801
  const onToggle = useEnableKeyframes(
87475
87802
  sessionRef
@@ -87487,6 +87814,8 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
87487
87814
  const setActiveTool = usePlayerStore((s) => s.setActiveTool);
87488
87815
  const timelineSnapEnabled = usePlayerStore((s) => s.timelineSnapEnabled);
87489
87816
  const setTimelineSnapEnabled = usePlayerStore((s) => s.setTimelineSnapEnabled);
87817
+ const rippleEditEnabled = usePlayerStore((s) => s.rippleEditEnabled);
87818
+ const setRippleEditEnabled = usePlayerStore((s) => s.setRippleEditEnabled);
87490
87819
  const autoKeyframeEnabled = usePlayerStore((s) => s.autoKeyframeEnabled);
87491
87820
  const setAutoKeyframeEnabled = usePlayerStore((s) => s.setAutoKeyframeEnabled);
87492
87821
  const thumbnailMode = usePlayerStore((s) => s.thumbnailMode);
@@ -87571,6 +87900,23 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
87571
87900
  children: /* @__PURE__ */ jsx206(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
87572
87901
  }
87573
87902
  ) }),
87903
+ /* @__PURE__ */ jsx206(
87904
+ Tooltip,
87905
+ {
87906
+ label: rippleEditEnabled ? "Ripple on \u2014 keeps the main track gapless" : "Ripple off \u2014 deleting a main-track clip leaves a gap",
87907
+ children: /* @__PURE__ */ jsx206(
87908
+ "button",
87909
+ {
87910
+ type: "button",
87911
+ onClick: () => setRippleEditEnabled(!rippleEditEnabled),
87912
+ "aria-label": "Toggle ripple edit",
87913
+ "aria-pressed": rippleEditEnabled,
87914
+ className: rippleEditEnabled ? flatActive : flatIdle,
87915
+ children: /* @__PURE__ */ jsx206(Waves, { size: 16, weight: "bold", "aria-hidden": "true" })
87916
+ }
87917
+ )
87918
+ }
87919
+ ),
87574
87920
  /* @__PURE__ */ jsx206(
87575
87921
  Tooltip,
87576
87922
  {
@@ -87792,15 +88138,15 @@ import { useState as useState154 } from "react";
87792
88138
  import { Copy as Copy2, Check as Check2 } from "@phosphor-icons/react";
87793
88139
 
87794
88140
  // src/hooks/useStoryboard.ts
87795
- import { useCallback as useCallback171, useEffect as useEffect136, useRef as useRef168, useState as useState147 } from "react";
88141
+ import { useCallback as useCallback170, useEffect as useEffect136, useRef as useRef170, useState as useState147 } from "react";
87796
88142
  function useStoryboard(projectId) {
87797
88143
  const [data, setData] = useState147(null);
87798
88144
  const [loading, setLoading] = useState147(true);
87799
88145
  const [error, setError] = useState147(null);
87800
88146
  const [reloadKey, setReloadKey] = useState147(0);
87801
- const hasDataRef = useRef168(false);
87802
- const lastProjectRef = useRef168(null);
87803
- const reload = useCallback171(() => setReloadKey((k) => k + 1), []);
88147
+ const hasDataRef = useRef170(false);
88148
+ const lastProjectRef = useRef170(null);
88149
+ const reload = useCallback170(() => setReloadKey((k) => k + 1), []);
87804
88150
  useEffect136(() => {
87805
88151
  if (!projectId) return;
87806
88152
  let cancelled = false;
@@ -87833,7 +88179,7 @@ function useStoryboard(projectId) {
87833
88179
  }
87834
88180
 
87835
88181
  // src/hooks/useProjectSignaturePoll.ts
87836
- import { useEffect as useEffect137, useRef as useRef169 } from "react";
88182
+ import { useEffect as useEffect137, useRef as useRef171 } from "react";
87837
88183
  var POLL_INTERVAL_MS = 2e3;
87838
88184
  async function fetchProjectSignature(projectId) {
87839
88185
  try {
@@ -87846,8 +88192,8 @@ async function fetchProjectSignature(projectId) {
87846
88192
  }
87847
88193
  }
87848
88194
  function useProjectSignaturePoll(projectId, currentSignature, onChange) {
87849
- const signatureRef = useRef169(currentSignature);
87850
- const onChangeRef = useRef169(onChange);
88195
+ const signatureRef = useRef171(currentSignature);
88196
+ const onChangeRef = useRef171(onChange);
87851
88197
  signatureRef.current = currentSignature;
87852
88198
  onChangeRef.current = onChange;
87853
88199
  useEffect137(() => {
@@ -87877,7 +88223,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
87877
88223
  }
87878
88224
 
87879
88225
  // src/components/storyboard/StoryboardLoaded.tsx
87880
- import { useEffect as useEffect143, useMemo as useMemo76, useState as useState153 } from "react";
88226
+ import { useEffect as useEffect143, useMemo as useMemo77, useState as useState153 } from "react";
87881
88227
 
87882
88228
  // src/components/storyboard/StoryboardDirection.tsx
87883
88229
  import { jsx as jsx207, jsxs as jsxs172 } from "react/jsx-runtime";
@@ -88114,7 +88460,7 @@ function StoryboardScriptPanel({ script }) {
88114
88460
  }
88115
88461
 
88116
88462
  // src/components/storyboard/StoryboardSourceEditor.tsx
88117
- import { useCallback as useCallback172, useEffect as useEffect139, useMemo as useMemo74, useRef as useRef170, useState as useState149 } from "react";
88463
+ import { useCallback as useCallback171, useEffect as useEffect139, useMemo as useMemo75, useRef as useRef172, useState as useState149 } from "react";
88118
88464
  import { marked } from "marked";
88119
88465
  import DOMPurify from "dompurify";
88120
88466
  import { jsx as jsx212, jsxs as jsxs175 } from "react/jsx-runtime";
@@ -88144,7 +88490,7 @@ function useEditableFile(path, onSaved) {
88144
88490
  cancelled = true;
88145
88491
  };
88146
88492
  }, [path, readProjectFile]);
88147
- const save = useCallback172(() => {
88493
+ const save = useCallback171(() => {
88148
88494
  if (saving) return;
88149
88495
  setSaving(true);
88150
88496
  setError(null);
@@ -88163,7 +88509,7 @@ function hardenLinks(node) {
88163
88509
  }
88164
88510
  function useMarkdownPreview(source) {
88165
88511
  const [debounced, setDebounced] = useState149(source);
88166
- const primed = useRef170(false);
88512
+ const primed = useRef172(false);
88167
88513
  useEffect139(() => {
88168
88514
  if (!primed.current && source !== "") {
88169
88515
  primed.current = true;
@@ -88173,7 +88519,7 @@ function useMarkdownPreview(source) {
88173
88519
  const id = window.setTimeout(() => setDebounced(source), 200);
88174
88520
  return () => window.clearTimeout(id);
88175
88521
  }, [source]);
88176
- return useMemo74(() => {
88522
+ return useMemo75(() => {
88177
88523
  const html2 = marked.parse(debounced, { async: false });
88178
88524
  DOMPurify.addHook("afterSanitizeAttributes", hardenLinks);
88179
88525
  try {
@@ -88269,7 +88615,7 @@ function StoryboardSourceEditor({
88269
88615
  }
88270
88616
 
88271
88617
  // src/components/storyboard/StoryboardFrameFocus.tsx
88272
- import { useCallback as useCallback173, useEffect as useEffect141, useState as useState151 } from "react";
88618
+ import { useCallback as useCallback172, useEffect as useEffect141, useState as useState151 } from "react";
88273
88619
  import { setFrameVoiceover } from "@hyperframes/core/storyboard";
88274
88620
 
88275
88621
  // src/components/storyboard/AgentChatMessageButton.tsx
@@ -88329,7 +88675,7 @@ function StoryboardFrameFocus({
88329
88675
  const [savedVoiceover, setSavedVoiceover] = useState151(frame.voiceover ?? "");
88330
88676
  const [busy, setBusy] = useState151(false);
88331
88677
  const [error, setError] = useState151(null);
88332
- const applyEdit = useCallback173(
88678
+ const applyEdit = useCallback172(
88333
88679
  async (edit) => {
88334
88680
  if (busy) return false;
88335
88681
  setBusy(true);
@@ -88351,7 +88697,7 @@ function StoryboardFrameFocus({
88351
88697
  const title = frame.title ?? `Frame ${frame.index}`;
88352
88698
  const dirty = draft !== savedVoiceover;
88353
88699
  const canOpenPreview = frame.status !== "outline" && frame.srcExists && Boolean(frame.src);
88354
- const saveVoiceover = useCallback173(async () => {
88700
+ const saveVoiceover = useCallback172(async () => {
88355
88701
  const saved2 = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
88356
88702
  if (saved2) setSavedVoiceover(draft);
88357
88703
  }, [applyEdit, frame.index, draft]);
@@ -88363,7 +88709,7 @@ function StoryboardFrameFocus({
88363
88709
  window.addEventListener("beforeunload", onBeforeUnload);
88364
88710
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
88365
88711
  }, [dirty]);
88366
- const confirmLeave = useCallback173(
88712
+ const confirmLeave = useCallback172(
88367
88713
  () => !dirty || window.confirm("Discard unsaved voiceover changes?"),
88368
88714
  [dirty]
88369
88715
  );
@@ -88845,7 +89191,7 @@ function progressLabel(summary) {
88845
89191
  }
88846
89192
 
88847
89193
  // src/components/storyboard/useFrameComments.ts
88848
- import { useCallback as useCallback174, useEffect as useEffect142, useMemo as useMemo75, useState as useState152 } from "react";
89194
+ import { useCallback as useCallback173, useEffect as useEffect142, useMemo as useMemo76, useState as useState152 } from "react";
88849
89195
 
88850
89196
  // src/components/storyboard/frameComments.ts
88851
89197
  var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
@@ -88911,7 +89257,7 @@ function useFrameComments(frames) {
88911
89257
  const [submitState, setSubmitState] = useState152("idle");
88912
89258
  const [submitError, setSubmitError] = useState152(null);
88913
89259
  const [pending2, setPending] = useState152(null);
88914
- const refreshPending = useCallback174(async () => {
89260
+ const refreshPending = useCallback173(async () => {
88915
89261
  try {
88916
89262
  const parsed = parseCommentsFile(await readOptionalProjectFile(FRAME_COMMENTS_PATH));
88917
89263
  setPending(parsed && parsed.comments.length > 0 ? parsed.comments : null);
@@ -88924,14 +89270,14 @@ function useFrameComments(frames) {
88924
89270
  window.addEventListener("focus", onFocus);
88925
89271
  return () => window.removeEventListener("focus", onFocus);
88926
89272
  }, [refreshPending]);
88927
- const setDraft = useCallback174((index, text) => {
89273
+ const setDraft = useCallback173((index, text) => {
88928
89274
  setDrafts((prev) => ({ ...prev, [index]: text }));
88929
89275
  }, []);
88930
- const draftCount = useMemo75(
89276
+ const draftCount = useMemo76(
88931
89277
  () => Object.values(drafts).filter((text) => text.trim().length > 0).length,
88932
89278
  [drafts]
88933
89279
  );
88934
- const submit = useCallback174(async () => {
89280
+ const submit = useCallback173(async () => {
88935
89281
  if (draftCount === 0 || submitState === "saving") return false;
88936
89282
  setSubmitState("saving");
88937
89283
  setSubmitError(null);
@@ -88995,7 +89341,7 @@ function StoryboardLoaded({
88995
89341
  setFeedbackMessageCopied(false);
88996
89342
  }
88997
89343
  };
88998
- const sourceFiles = useMemo76(() => {
89344
+ const sourceFiles = useMemo77(() => {
88999
89345
  const files = [{ path: data.path, label: data.path }];
89000
89346
  if (data.script?.exists) files.push({ path: data.script.path, label: data.script.path });
89001
89347
  return files;
@@ -89394,18 +89740,21 @@ function useStudioSessionStart(projectId, resolving, waitingForServer) {
89394
89740
  }
89395
89741
 
89396
89742
  // src/hooks/useTimelineAddAtPlayhead.ts
89397
- import { useCallback as useCallback175 } from "react";
89743
+ import { useCallback as useCallback174 } from "react";
89398
89744
  function useTimelineAddAtPlayhead(addAsset, addComposition) {
89399
89745
  const placement = () => ({ start: usePlayerStore.getState().currentTime, track: 0 });
89400
89746
  return {
89401
- addAssetAtPlayhead: useCallback175((path) => addAsset(path, placement()), [addAsset]),
89402
- addCompositionAtPlayhead: useCallback175(
89747
+ addAssetAtPlayhead: useCallback174((path) => addAsset(path, placement()), [addAsset]),
89748
+ addCompositionAtPlayhead: useCallback174(
89403
89749
  (path) => addComposition(path, placement()),
89404
89750
  [addComposition]
89405
89751
  )
89406
89752
  };
89407
89753
  }
89408
89754
 
89755
+ // src/hooks/useActiveComposition.ts
89756
+ import { useState as useState156 } from "react";
89757
+
89409
89758
  // src/hooks/useHydrateActiveCompPathFromUrl.ts
89410
89759
  import { useEffect as useEffect146 } from "react";
89411
89760
  function useHydrateActiveCompPathFromUrl({
@@ -89428,29 +89777,136 @@ function useHydrateActiveCompPathFromUrl({
89428
89777
  }, [hydrated, fileTree, fileTreeLoaded, initialUrlStateRef, setActiveCompPath, setHydrated]);
89429
89778
  }
89430
89779
 
89780
+ // src/hooks/useAutoOpenRootComposition.ts
89781
+ import { useEffect as useEffect147, useRef as useRef173 } from "react";
89782
+ function useAutoOpenRootComposition({
89783
+ projectId,
89784
+ activeCompPath,
89785
+ activeCompPathHydrated,
89786
+ masterCompPath,
89787
+ onSelectComposition
89788
+ }) {
89789
+ const settledForProjectRef = useRef173(void 0);
89790
+ useEffect147(() => {
89791
+ if (settledForProjectRef.current === projectId) return;
89792
+ if (!activeCompPathHydrated) return;
89793
+ if (activeCompPath !== null) {
89794
+ settledForProjectRef.current = projectId;
89795
+ return;
89796
+ }
89797
+ if (!masterCompPath) return;
89798
+ settledForProjectRef.current = projectId;
89799
+ onSelectComposition(masterCompPath);
89800
+ }, [projectId, activeCompPath, activeCompPathHydrated, masterCompPath, onSelectComposition]);
89801
+ }
89802
+
89803
+ // src/hooks/useCompositionContentLoader.ts
89804
+ import { useCallback as useCallback175 } from "react";
89805
+ function useCompositionContentLoader({
89806
+ projectId,
89807
+ setEditingFile,
89808
+ setActiveCompPath,
89809
+ showToast
89810
+ }) {
89811
+ return useCallback175(
89812
+ (comp) => {
89813
+ if (!projectId) return;
89814
+ setActiveCompPath(comp.endsWith(".html") ? comp : null);
89815
+ setEditingFile({ path: comp, content: null });
89816
+ fetch(buildProjectApiPath(projectId, `/files/${encodeURIComponent(comp)}`)).then(async (r) => {
89817
+ if (!r.ok) throw new Error(`Failed to load ${comp} (${r.status})`);
89818
+ return r.json();
89819
+ }).then((data) => {
89820
+ if (typeof data.content !== "string") throw new Error(`No content returned for ${comp}`);
89821
+ setEditingFile({ path: comp, content: data.content });
89822
+ }).catch((err) => {
89823
+ showToast(err instanceof Error ? err.message : `Failed to load ${comp}`, "error");
89824
+ });
89825
+ },
89826
+ [projectId, setEditingFile, setActiveCompPath, showToast]
89827
+ );
89828
+ }
89829
+
89830
+ // src/hooks/useResetSelectionOnProjectSwitch.ts
89831
+ import { useRef as useRef174 } from "react";
89832
+ function useResetSelectionOnProjectSwitch({
89833
+ projectId,
89834
+ initialUrlStateRef,
89835
+ setActiveCompPath,
89836
+ setActiveCompPathHydrated
89837
+ }) {
89838
+ const previousProjectIdRef = useRef174(projectId);
89839
+ if (previousProjectIdRef.current !== projectId) {
89840
+ const isSwitch = previousProjectIdRef.current !== null;
89841
+ previousProjectIdRef.current = projectId;
89842
+ if (isSwitch) {
89843
+ initialUrlStateRef.current = readStudioUrlStateFromWindow();
89844
+ setActiveCompPath(null);
89845
+ setActiveCompPathHydrated(isHydratedFromUrlState(initialUrlStateRef.current));
89846
+ }
89847
+ }
89848
+ }
89849
+
89850
+ // src/hooks/useActiveComposition.ts
89851
+ function useActiveComposition({
89852
+ projectId,
89853
+ initialUrlStateRef,
89854
+ fileTree,
89855
+ fileTreeLoaded,
89856
+ masterCompPath,
89857
+ setEditingFile,
89858
+ showToast
89859
+ }) {
89860
+ const [activeCompPath, setActiveCompPath] = useState156(null);
89861
+ const [activeCompPathHydrated, setActiveCompPathHydrated] = useState156(
89862
+ () => isHydratedFromUrlState(initialUrlStateRef.current)
89863
+ );
89864
+ useResetSelectionOnProjectSwitch({
89865
+ projectId,
89866
+ initialUrlStateRef,
89867
+ setActiveCompPath,
89868
+ setActiveCompPathHydrated
89869
+ });
89870
+ useHydrateActiveCompPathFromUrl({
89871
+ hydrated: activeCompPathHydrated,
89872
+ fileTreeLoaded,
89873
+ fileTree,
89874
+ initialUrlStateRef,
89875
+ setActiveCompPath,
89876
+ setHydrated: setActiveCompPathHydrated
89877
+ });
89878
+ const handleSelectComposition = useCompositionContentLoader({
89879
+ projectId,
89880
+ setEditingFile,
89881
+ setActiveCompPath,
89882
+ showToast
89883
+ });
89884
+ useAutoOpenRootComposition({
89885
+ projectId,
89886
+ activeCompPath,
89887
+ activeCompPathHydrated,
89888
+ masterCompPath,
89889
+ onSelectComposition: handleSelectComposition
89890
+ });
89891
+ return { activeCompPath, activeCompPathHydrated, setActiveCompPath, handleSelectComposition };
89892
+ }
89893
+
89431
89894
  // src/App.tsx
89432
89895
  import { jsx as jsx219, jsxs as jsxs181 } from "react/jsx-runtime";
89433
89896
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
89434
89897
  function StudioApp() {
89435
89898
  const { projectId, resolving, waitingForServer } = useServerConnection();
89436
- const initialUrlStateRef = useRef171(readStudioUrlStateFromWindow());
89899
+ const initialUrlStateRef = useRef175(readStudioUrlStateFromWindow());
89437
89900
  const viewModeValue = useViewModeState();
89438
89901
  useStudioSessionStart(projectId, resolving, waitingForServer);
89439
- const [activeCompPath, setActiveCompPath] = useState156(null);
89440
- const [activeCompPathHydrated, setActiveCompPathHydrated] = useState156(
89441
- () => initialUrlStateRef.current.activeCompPath == null
89442
- );
89443
- const [compIdToSrc, setCompIdToSrc] = useState156(/* @__PURE__ */ new Map());
89444
- const [previewIframe, setPreviewIframe] = useState156(null);
89445
- const [compositionLoading, setCompositionLoading] = useState156(true);
89446
- const [refreshKey, setRefreshKey] = useState156(0);
89902
+ const [compIdToSrc, setCompIdToSrc] = useState157(/* @__PURE__ */ new Map());
89903
+ const [previewIframe, setPreviewIframe] = useState157(null);
89904
+ const [compositionLoading, setCompositionLoading] = useState157(true);
89905
+ const [refreshKey, setRefreshKey] = useState157(0);
89447
89906
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
89448
- const [blockPreview, setBlockPreview] = useState156(null);
89449
- const previewIframeRef = useRef171(null);
89450
- const activeCompPathRef = useRef171(activeCompPath);
89451
- activeCompPathRef.current = activeCompPath;
89452
- const leftSidebarRef = useRef171(null);
89453
- const renderQueue = useRenderQueue(projectId, activeCompPathRef);
89907
+ const [blockPreview, setBlockPreview] = useState157(null);
89908
+ const previewIframeRef = useRef175(null);
89909
+ const leftSidebarRef = useRef175(null);
89454
89910
  const captionEditMode = useCaptionStore((s) => s.isEditMode);
89455
89911
  const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0);
89456
89912
  const captionSync = useCaptionSync(projectId);
@@ -89469,9 +89925,9 @@ function StudioApp() {
89469
89925
  rightPanelTab: initialUrlStateRef.current.rightPanelTab
89470
89926
  });
89471
89927
  const editHistory = usePersistentEditHistory({ projectId });
89472
- const handleDomZIndexReorderCommitRef = useRef171(null);
89473
- const pendingTimelineEditPathRef = useRef171(/* @__PURE__ */ new Set());
89474
- const isGestureRecordingRef = useRef171(false);
89928
+ const handleDomZIndexReorderCommitRef = useRef175(null);
89929
+ const pendingTimelineEditPathRef = useRef175(/* @__PURE__ */ new Set());
89930
+ const isGestureRecordingRef = useRef175(false);
89475
89931
  const reloadPreview = useCallback176(() => setRefreshKey((k) => k + 1), []);
89476
89932
  const fileManager = useFileManager({
89477
89933
  projectId,
@@ -89479,23 +89935,27 @@ function StudioApp() {
89479
89935
  recordEdit: editHistory.recordEdit,
89480
89936
  setRefreshKey
89481
89937
  });
89482
- const masterCompPath = useMemo77(
89483
- () => resolveMasterCompositionPath(fileManager.fileTree),
89484
- [fileManager.fileTree]
89938
+ const masterCompPath = useMemo78(
89939
+ () => resolveMasterCompositionPath(fileManager.compositions),
89940
+ [fileManager.compositions]
89485
89941
  );
89942
+ const { activeCompPath, activeCompPathHydrated, setActiveCompPath, handleSelectComposition } = useActiveComposition({
89943
+ projectId,
89944
+ initialUrlStateRef,
89945
+ fileTree: fileManager.fileTree,
89946
+ fileTreeLoaded: fileManager.fileTreeLoaded,
89947
+ masterCompPath,
89948
+ setEditingFile: fileManager.setEditingFile,
89949
+ showToast
89950
+ });
89486
89951
  const { sdkHandle, editFlowSdkSession } = useStudioSdkSessions(
89487
89952
  projectId,
89488
89953
  activeCompPath,
89489
89954
  masterCompPath
89490
89955
  );
89491
- useHydrateActiveCompPathFromUrl({
89492
- hydrated: activeCompPathHydrated,
89493
- fileTreeLoaded: fileManager.fileTreeLoaded,
89494
- fileTree: fileManager.fileTree,
89495
- initialUrlStateRef,
89496
- setActiveCompPath,
89497
- setHydrated: setActiveCompPathHydrated
89498
- });
89956
+ const activeCompPathRef = useRef175(activeCompPath);
89957
+ activeCompPathRef.current = activeCompPath;
89958
+ const renderQueue = useRenderQueue(projectId, activeCompPathRef);
89499
89959
  const previewPersistence = usePreviewPersistence({
89500
89960
  showToast,
89501
89961
  readOptionalProjectFile: fileManager.readOptionalProjectFile,
@@ -89514,7 +89974,7 @@ function StudioApp() {
89514
89974
  pendingTimelineEditPathRef,
89515
89975
  reloadPreview
89516
89976
  });
89517
- const invalidateGsapCacheRef = useRef171(() => {
89977
+ const invalidateGsapCacheRef = useRef175(() => {
89518
89978
  });
89519
89979
  const invalidateGsapCache = useCallback176(() => invalidateGsapCacheRef.current(), []);
89520
89980
  const timelineEditing = useTimelineEditing({
@@ -89573,14 +90033,14 @@ function StudioApp() {
89573
90033
  setRightCollapsed: panelLayout.setRightCollapsed,
89574
90034
  setRightPanelTab: panelLayout.setRightPanelTab
89575
90035
  });
89576
- const clearDomSelectionRef = useRef171(() => {
90036
+ const clearDomSelectionRef = useRef175(() => {
89577
90037
  });
89578
- const domEditSelectionBridgeRef = useRef171(null);
89579
- const handleDomEditElementDeleteRef = useRef171(async () => {
90038
+ const domEditSelectionBridgeRef = useRef175(null);
90039
+ const handleDomEditElementDeleteRef = useRef175(async () => {
89580
90040
  });
89581
90041
  const domEditDeleteBridge = (s, o) => handleDomEditElementDeleteRef.current(s, o);
89582
- const resetKeyframesRef = useRef171(() => false);
89583
- const deleteSelectedKeyframesRef = useRef171(() => {
90042
+ const resetKeyframesRef = useRef175(() => false);
90043
+ const deleteSelectedKeyframesRef = useRef175(() => {
89584
90044
  });
89585
90045
  const { handleCopy: handleCopy2, handlePaste: handlePaste2, handleCut } = useClipboard({
89586
90046
  projectId,
@@ -89620,7 +90080,7 @@ function StudioApp() {
89620
90080
  forceReloadSdkSession: sdkHandle.forceReload,
89621
90081
  onToggleRecording: () => handleToggleRecordingRef.current()
89622
90082
  });
89623
- const sidebarTabRef = useRef171({
90083
+ const sidebarTabRef = useRef175({
89624
90084
  select: (t) => leftSidebarRef.current?.selectTab(t),
89625
90085
  get: () => leftSidebarRef.current?.getTab() ?? "compositions"
89626
90086
  });
@@ -89662,7 +90122,8 @@ function StudioApp() {
89662
90122
  getSidebarTab: sidebarTabRef.current.get,
89663
90123
  sdkSession: editFlowSdkSession,
89664
90124
  publishSdkSession: sdkHandle.publish,
89665
- forceReloadSdkSession: sdkHandle.forceReload
90125
+ forceReloadSdkSession: sdkHandle.forceReload,
90126
+ handleTimelineElementsDelete: timelineEditing.handleTimelineElementsDelete
89666
90127
  });
89667
90128
  domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
89668
90129
  handleDomZIndexReorderCommitRef.current = domEditSession.handleDomZIndexReorderCommit;
@@ -89709,9 +90170,9 @@ function StudioApp() {
89709
90170
  resetErrors: resetConsoleErrors
89710
90171
  } = useConsoleErrorCapture(previewIframe);
89711
90172
  const dragOverlay = useGlobalFileDrop(timelineEditing.handleTimelineFileDrop);
89712
- const handleToggleRecordingRef = useRef171(() => {
90173
+ const handleToggleRecordingRef = useRef175(() => {
89713
90174
  });
89714
- const domEditSessionRef = useRef171(domEditSession);
90175
+ const domEditSessionRef = useRef175(domEditSession);
89715
90176
  domEditSessionRef.current = domEditSession;
89716
90177
  const { gestureState, gestureRecording, handleToggleRecording } = useGestureCommit({
89717
90178
  domEditSessionRef,
@@ -89720,7 +90181,7 @@ function StudioApp() {
89720
90181
  isGestureRecordingRef
89721
90182
  });
89722
90183
  handleToggleRecordingRef.current = handleToggleRecording;
89723
- const canvasRectRef = useRef171(null);
90184
+ const canvasRectRef = useRef175(null);
89724
90185
  useLayoutEffect11(() => {
89725
90186
  if (gestureState !== "recording" || !previewIframe) {
89726
90187
  canvasRectRef.current = null;
@@ -89738,13 +90199,6 @@ function StudioApp() {
89738
90199
  },
89739
90200
  [appHotkeys, resetConsoleErrors, refreshPreviewDocumentVersion]
89740
90201
  );
89741
- const { setEditingFile } = fileManager;
89742
- const handleSelectComposition = useCompositionContentLoader({
89743
- projectId,
89744
- setEditingFile,
89745
- setActiveCompPath,
89746
- showToast
89747
- });
89748
90202
  const {
89749
90203
  designPanelActive,
89750
90204
  inspectorPanelActive,
@@ -89801,7 +90255,7 @@ function StudioApp() {
89801
90255
  handlePreviewIframeRef,
89802
90256
  refreshPreviewDocumentVersion
89803
90257
  });
89804
- const timelineToolbar = useMemo77(
90258
+ const timelineToolbar = useMemo78(
89805
90259
  () => /* @__PURE__ */ jsx219(
89806
90260
  TimelineToolbar,
89807
90261
  {
@@ -89959,11 +90413,11 @@ function StudioApp() {
89959
90413
  }
89960
90414
 
89961
90415
  // src/hooks/useElementPicker.ts
89962
- import { useState as useState157, useCallback as useCallback177, useRef as useRef172 } from "react";
90416
+ import { useState as useState158, useCallback as useCallback177, useRef as useRef176 } from "react";
89963
90417
  function useElementPicker(iframeRef, options) {
89964
- const [isPickMode, setIsPickMode] = useState157(false);
89965
- const [pickedElement, setPickedElement] = useState157(null);
89966
- const activeOverrideRef = useRef172(null);
90418
+ const [isPickMode, setIsPickMode] = useState158(false);
90419
+ const [pickedElement, setPickedElement] = useState158(null);
90420
+ const activeOverrideRef = useRef176(null);
89967
90421
  const getActiveIframe = useCallback177(() => {
89968
90422
  return activeOverrideRef.current ?? iframeRef.current;
89969
90423
  }, [iframeRef]);
@@ -90037,7 +90491,7 @@ function useElementPicker(iframeRef, options) {
90037
90491
  window.addEventListener("message", handleMessage);
90038
90492
  return () => window.removeEventListener("message", handleMessage);
90039
90493
  });
90040
- const optionsRef = useRef172(options);
90494
+ const optionsRef = useRef176(options);
90041
90495
  optionsRef.current = options;
90042
90496
  const syncToSource = useCallback177(
90043
90497
  (elementId2, selector, op) => {
@@ -90148,7 +90602,7 @@ function useElementPicker(iframeRef, options) {
90148
90602
  },
90149
90603
  [pickedElement, getActiveIframe, syncToSource]
90150
90604
  );
90151
- const activeIframeRef = useRef172(null);
90605
+ const activeIframeRef = useRef176(null);
90152
90606
  activeIframeRef.current = getActiveIframe();
90153
90607
  return {
90154
90608
  isPickMode,
@@ -90314,6 +90768,7 @@ function findElementBlock(html2, elementId2) {
90314
90768
  return null;
90315
90769
  }
90316
90770
  export {
90771
+ AskAgentModal,
90317
90772
  CompositionBreadcrumb,
90318
90773
  CompositionThumbnail,
90319
90774
  EditorShell,
@@ -90327,6 +90782,8 @@ export {
90327
90782
  Timeline,
90328
90783
  VideoThumbnail,
90329
90784
  applyPatch,
90785
+ buildPickerAgentContextPreview,
90786
+ buildPickerAgentPrompt,
90330
90787
  findElementBlock,
90331
90788
  formatTime,
90332
90789
  liveTime,