hyperframes 0.8.47 → 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.
@@ -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
  }
@@ -1837,6 +1840,12 @@ var usePlayerStore = create2((set, get) => ({
1837
1840
  writeStudioUiPreferences({ timelineSnapEnabled: enabled });
1838
1841
  set({ timelineSnapEnabled: enabled });
1839
1842
  },
1843
+ rippleEditEnabled: readStudioUiPreferences().rippleEditEnabled ?? true,
1844
+ // default on
1845
+ setRippleEditEnabled: (enabled) => {
1846
+ writeStudioUiPreferences({ rippleEditEnabled: enabled });
1847
+ set({ rippleEditEnabled: enabled });
1848
+ },
1840
1849
  timeDisplayMode: readStudioUiPreferences().timeDisplayMode ?? "time",
1841
1850
  setTimeDisplayMode: (mode) => {
1842
1851
  writeStudioUiPreferences({ timeDisplayMode: mode });
@@ -3052,6 +3061,10 @@ function isAudioTimelineElement(element) {
3052
3061
  if (AUDIO_TIMELINE_TAGS.has(tag)) return true;
3053
3062
  return Boolean(element.src && AUDIO_SOURCE_EXT_RE.test(element.src));
3054
3063
  }
3064
+ function isAudioOrVideoTimelineElement(element) {
3065
+ if (!element) return false;
3066
+ return isAudioTimelineElement(element) || element.tag.trim().toLowerCase() === "video";
3067
+ }
3055
3068
  function isMusicTrack(element) {
3056
3069
  if (!element) return false;
3057
3070
  if (!isAudioTimelineElement(element)) return false;
@@ -3127,7 +3140,6 @@ function FileManagerProvider({
3127
3140
  projectDir,
3128
3141
  fileTree,
3129
3142
  fileTreeLoaded,
3130
- setFileTree,
3131
3143
  editingPathRef,
3132
3144
  projectIdRef,
3133
3145
  saveRafRef,
@@ -3168,7 +3180,6 @@ function FileManagerProvider({
3168
3180
  projectDir,
3169
3181
  fileTree,
3170
3182
  fileTreeLoaded,
3171
- setFileTree,
3172
3183
  editingPathRef,
3173
3184
  projectIdRef,
3174
3185
  saveRafRef,
@@ -3206,7 +3217,6 @@ function FileManagerProvider({
3206
3217
  projectDir,
3207
3218
  fileTree,
3208
3219
  fileTreeLoaded,
3209
- setFileTree,
3210
3220
  editingPathRef,
3211
3221
  projectIdRef,
3212
3222
  saveRafRef,
@@ -3962,6 +3972,7 @@ function buildMissingCompositionEntry(params) {
3962
3972
  start: window2.start,
3963
3973
  duration: window2.duration,
3964
3974
  track: timing.trackIndex,
3975
+ authoredTrack: timing.trackIndex,
3965
3976
  domId: optionalNonEmpty(element.id),
3966
3977
  hfId: optionalNonEmpty(element.getAttribute("data-hf-id")),
3967
3978
  selector,
@@ -5237,10 +5248,7 @@ function resolveTimelineAssetDrop(input, clientX, clientY) {
5237
5248
  });
5238
5249
  const contentY = clientY - input.rectTop + input.scrollTop;
5239
5250
  const pointerStart = Math.round(x / Math.max(input.pixelsPerSecond, 1) * 100) / 100;
5240
- const start = Math.max(
5241
- 0,
5242
- input.clampStartToDuration === false ? pointerStart : Math.min(input.duration, pointerStart)
5243
- );
5251
+ const start = Math.max(0, pointerStart);
5244
5252
  const rowIndex = Math.floor(getTimelineRowFromY(contentY, input.rowHeights));
5245
5253
  return {
5246
5254
  start,
@@ -6602,10 +6610,6 @@ function invokeDropCallback(callback) {
6602
6610
  } catch {
6603
6611
  }
6604
6612
  }
6605
- function resolveDropStart(usePointerStart, pointerStart) {
6606
- if (usePointerStart) return pointerStart;
6607
- return Math.max(0, usePlayerStore.getState().currentTime);
6608
- }
6609
6613
  function applyFileDrop(transfer, onFileDrop, placement) {
6610
6614
  if (!onFileDrop || transfer.files.length === 0) return false;
6611
6615
  invokeDropCallback(() => onFileDrop(Array.from(transfer.files), placement));
@@ -6625,7 +6629,6 @@ function applyTypedJsonDrop(transfer, mime, field, apply, placement) {
6625
6629
  function useTimelineAssetDrop({
6626
6630
  scrollRef,
6627
6631
  ppsRef,
6628
- durationRef,
6629
6632
  trackOrderRef,
6630
6633
  rowGeometryRef,
6631
6634
  contentOrigin,
@@ -6706,10 +6709,10 @@ function useTimelineAssetDrop({
6706
6709
  [clearDropPreview]
6707
6710
  );
6708
6711
  const resolveDropPlacement2 = useCallback7(
6709
- (clientX, clientY, usePointerStart = false) => {
6712
+ (clientX, clientY) => {
6710
6713
  const scroll = scrollRef.current;
6711
6714
  const rect = scroll?.getBoundingClientRect();
6712
- const pointer = resolveTimelineAssetDrop(
6715
+ return resolveTimelineAssetDrop(
6713
6716
  {
6714
6717
  rectLeft: rect?.left ?? 0,
6715
6718
  rectTop: rect?.top ?? 0,
@@ -6717,20 +6720,14 @@ function useTimelineAssetDrop({
6717
6720
  scrollTop: scroll?.scrollTop ?? 0,
6718
6721
  contentOrigin,
6719
6722
  pixelsPerSecond: ppsRef.current,
6720
- duration: durationRef.current,
6721
- clampStartToDuration: !usePointerStart,
6722
6723
  rowHeights: rowGeometryRef.current.rowHeights,
6723
6724
  trackOrder: trackOrderRef.current
6724
6725
  },
6725
6726
  clientX,
6726
6727
  clientY
6727
6728
  );
6728
- return {
6729
- start: resolveDropStart(usePointerStart, pointer.start),
6730
- track: pointer.track
6731
- };
6732
6729
  },
6733
- [scrollRef, ppsRef, durationRef, trackOrderRef, rowGeometryRef, contentOrigin]
6730
+ [scrollRef, ppsRef, trackOrderRef, rowGeometryRef, contentOrigin]
6734
6731
  );
6735
6732
  const handleAssetDrop = useCallback7(
6736
6733
  (e) => {
@@ -6738,15 +6735,14 @@ function useTimelineAssetDrop({
6738
6735
  const canCommit = activeDropEpochRef.current === sessionEpoch;
6739
6736
  clearDropPreview();
6740
6737
  if (!canCommit) return;
6738
+ const placement = resolveDropPlacement2(e.clientX, e.clientY);
6741
6739
  const compositionPayload = parseTimelineCompositionPayload(
6742
6740
  e.dataTransfer.getData(TIMELINE_COMPOSITION_MIME)
6743
6741
  );
6744
6742
  if (compositionPayload && onCompositionDrop) {
6745
- const placement2 = resolveDropPlacement2(e.clientX, e.clientY, true);
6746
- invokeDropCallback(() => onCompositionDrop(compositionPayload.sourcePath, placement2));
6743
+ invokeDropCallback(() => onCompositionDrop(compositionPayload.sourcePath, placement));
6747
6744
  return;
6748
6745
  }
6749
- const placement = resolveDropPlacement2(e.clientX, e.clientY);
6750
6746
  if (applyFileDrop(e.dataTransfer, onFileDrop, placement)) return;
6751
6747
  if (applyTypedJsonDrop(e.dataTransfer, TIMELINE_ASSET_MIME, "path", onAssetDrop, placement)) {
6752
6748
  return;
@@ -9065,6 +9061,11 @@ function keyframeTarget(keyframe) {
9065
9061
 
9066
9062
  // src/player/components/TimelineDiamondConnectors.tsx
9067
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
+ }
9068
9069
  function TimelineDiamondConnectors({
9069
9070
  markers,
9070
9071
  centerY,
@@ -9123,6 +9124,7 @@ function TimelineDiamondConnectors({
9123
9124
  previous.keyframe.percentage
9124
9125
  ),
9125
9126
  roomForFullTarget: connectorWidth >= 24,
9127
+ restingVisible: hasRoomForRestingEase(connectorWidth),
9126
9128
  onSelectSegment
9127
9129
  }
9128
9130
  )
@@ -9142,6 +9144,7 @@ function SegmentEaseControl({
9142
9144
  rovingTargetId,
9143
9145
  afterLabel,
9144
9146
  roomForFullTarget,
9147
+ restingVisible,
9145
9148
  onSelectSegment
9146
9149
  }) {
9147
9150
  const pressXRef = useRef12(null);
@@ -9174,7 +9177,7 @@ function SegmentEaseControl({
9174
9177
  tabIndex: focusId === rovingTargetId ? 0 : -1,
9175
9178
  "aria-label": `Edit ${ease} easing after ${afterLabel}`,
9176
9179
  title: `Edit ${ease} easing`,
9177
- 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-['']" : ""}`,
9178
9181
  style: {
9179
9182
  left: "50%",
9180
9183
  top: "50%",
@@ -10919,6 +10922,11 @@ import {
10919
10922
  } from "@hyperframes/core/audio-automation";
10920
10923
  import { getAudioFxDef } from "@hyperframes/core/audio-fx";
10921
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
+ }
10922
10930
  var MIN_POINT_GAP_SEC = 1e-3;
10923
10931
  var GRAB_PX = 7;
10924
10932
  var SEGMENT_GRAB_PX = 5;
@@ -11817,12 +11825,7 @@ function useAutomationLaneGestures({
11817
11825
  return;
11818
11826
  }
11819
11827
  const { t, v } = pointAt(e.clientX, e.clientY);
11820
- const kept = lane.points.filter((p) => Math.abs(p.t - t) > POINT_MERGE_SEC);
11821
- const seeded = lane.points.length === 0 && t > POINT_MERGE_SEC ? [{ t: 0, v }] : [];
11822
- commitPoints(
11823
- [...seeded, ...kept, { t, v }].sort((a, b) => a.t - b.t),
11824
- true
11825
- );
11828
+ commitPoints(mergeInsertPoint(lane.points, t, v), true);
11826
11829
  },
11827
11830
  [lane, pointAt, commitPoints, readOnly, hitIndex, segmentIndex]
11828
11831
  );
@@ -12545,7 +12548,7 @@ function laneGroupKey(target, chain) {
12545
12548
  function groupAutomationLanes(elements) {
12546
12549
  const groups = /* @__PURE__ */ new Map();
12547
12550
  for (const element of elements) {
12548
- if (!isAudioTimelineElement(element)) continue;
12551
+ if (!isAudioOrVideoTimelineElement(element)) continue;
12549
12552
  const chain = elementFxChain(element);
12550
12553
  for (const lane of elementAutomationLanes(element)) {
12551
12554
  const key2 = laneGroupKey(lane.target, chain);
@@ -12642,7 +12645,7 @@ function TimelineAutomationLaneSlot({
12642
12645
  currentTime,
12643
12646
  beatTimes
12644
12647
  }) {
12645
- const clips = elements.filter(isAudioTimelineElement);
12648
+ const clips = elements.filter(isAudioOrVideoTimelineElement);
12646
12649
  const rowsByClip = /* @__PURE__ */ new Map();
12647
12650
  groupAutomationLanes(clips).forEach((group, rowIndex) => {
12648
12651
  for (const entry2 of group.entries) {
@@ -13074,6 +13077,10 @@ function matchesByDomId(selection, element, selectionSourceFile) {
13074
13077
  if (!selection.id) return false;
13075
13078
  return element.domId === selection.id && (element.sourceFile || "index.html") === selectionSourceFile;
13076
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
+ }
13077
13084
  function matchesByCompositionHost(selection, element) {
13078
13085
  if (!selection.isCompositionHost || !selection.compositionSrc) return false;
13079
13086
  return element.compositionSrc === selection.compositionSrc;
@@ -13086,6 +13093,8 @@ function findMatchingTimelineElementId(selection, elements) {
13086
13093
  const selectionSourceFile = selection.sourceFile || "index.html";
13087
13094
  const byId = selection.id ? elements.find((el) => matchesByDomId(selection, el, selectionSourceFile)) : void 0;
13088
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;
13089
13098
  const bySelector = selection.selector ? elements.find((el) => matchesBySelector(selection, el)) : void 0;
13090
13099
  if (bySelector) return bySelector.key ?? bySelector.id;
13091
13100
  const byHost = elements.find((el) => matchesByCompositionHost(selection, el));
@@ -13095,6 +13104,11 @@ function findMatchingTimelineElementId(selection, elements) {
13095
13104
  }
13096
13105
  return null;
13097
13106
  }
13107
+ function resolveElementTrack(element) {
13108
+ return Math.round(
13109
+ Number.isFinite(element.authoredTrack) ? element.authoredTrack : element.track
13110
+ );
13111
+ }
13098
13112
  function findTimelineIdByAncestor(element, elements, sourceFile) {
13099
13113
  let ancestor = element?.parentElement ?? null;
13100
13114
  while (ancestor) {
@@ -15670,7 +15684,7 @@ function trackShowsBeatStrip(els, beatTimes, ctx3) {
15670
15684
  return ctx3.selectedElementId ? els.some((e) => (e.key ?? e.id) === ctx3.selectedElementId) : els.some((e) => ctx3.isMusicTrack(e));
15671
15685
  }
15672
15686
  function automationLaneCountOf(element) {
15673
- return isAudioTimelineElement(element) ? elementAutomationLanes(element).length : 0;
15687
+ return isAudioOrVideoTimelineElement(element) ? elementAutomationLanes(element).length : 0;
15674
15688
  }
15675
15689
  function trackAutomationLaneCount(elements) {
15676
15690
  return groupAutomationLanes(elements).length;
@@ -19169,6 +19183,9 @@ import { useRef as useRef29, useState as useState17, useCallback as useCallback1
19169
19183
  function classifyZone(el) {
19170
19184
  return isAudioTimelineElement(el) ? "audio" : "visual";
19171
19185
  }
19186
+ function isMainTrackElement(el) {
19187
+ return el.track === 0 && classifyZone(el) === "visual" && el.expandedParentStart == null;
19188
+ }
19172
19189
  var keyOf = (el) => el.key ?? el.id;
19173
19190
  var EPS = 1e-6;
19174
19191
  function overlaps2(a, b) {
@@ -21962,20 +21979,23 @@ function resolveCloseGapShifts(elements, gap) {
21962
21979
  // src/player/components/timelineGapCommit.ts
21963
21980
  var keyOf5 = (e) => e.key ?? e.id;
21964
21981
  var gapCloseGestureSeq = 0;
21965
- function canShiftTrackGapClips(laneElements, shifts) {
21966
- const byKey = new Map(laneElements.map((e) => [keyOf5(e), e]));
21967
- 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) => {
21968
21985
  const element = byKey.get(s.key);
21969
- return element != null && getTimelineEditCapabilities(element).canMove;
21986
+ return element ? [{ element, start: s.newStart }] : [];
21970
21987
  });
21971
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
+ }
21972
21993
  function buildShiftEdits(laneElements, shifts) {
21973
21994
  if (shifts.length === 0 || !canShiftTrackGapClips(laneElements, shifts)) return null;
21974
- const byKey = new Map(laneElements.map((e) => [keyOf5(e), e]));
21975
- return shifts.map((s) => {
21976
- const element = byKey.get(s.key);
21977
- return { element, updates: { start: s.newStart, track: element.track } };
21978
- });
21995
+ return resolveShiftedElements(laneElements, shifts).map(({ element, start }) => ({
21996
+ element,
21997
+ updates: { start, track: element.track }
21998
+ }));
21979
21999
  }
21980
22000
  function commitShifts(laneElements, shifts, deps) {
21981
22001
  const edits = buildShiftEdits(laneElements, shifts);
@@ -21995,6 +22015,22 @@ function commitCloseAllTrackGaps(laneElements, deps) {
21995
22015
  deps
21996
22016
  );
21997
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
+ }
21998
22034
 
21999
22035
  // src/player/components/useTrackGapMenu.ts
22000
22036
  function useTrackGapMenu({
@@ -23202,7 +23238,6 @@ var Timeline = memo16(function Timeline2({
23202
23238
  const assetDrop = useTimelineAssetDrop({
23203
23239
  scrollRef,
23204
23240
  ppsRef,
23205
- durationRef,
23206
23241
  trackOrderRef,
23207
23242
  rowGeometryRef,
23208
23243
  contentOrigin,
@@ -24684,6 +24719,9 @@ function canvasNudgeKeysClaimed() {
24684
24719
  }
24685
24720
 
24686
24721
  // src/player/hooks/usePlaybackKeyboard.ts
24722
+ function shouldRazorClaimPlainA(e) {
24723
+ return !e.shiftKey && !e.altKey && usePlayerStore.getState().activeTool === "razor";
24724
+ }
24687
24725
  function usePlaybackKeyboard({
24688
24726
  iframeRef,
24689
24727
  shuttleDirectionRef,
@@ -24813,6 +24851,7 @@ function usePlaybackKeyboard({
24813
24851
  return;
24814
24852
  }
24815
24853
  if (key2 === "a") {
24854
+ if (shouldRazorClaimPlainA(e)) return;
24816
24855
  e.preventDefault();
24817
24856
  seek(usePlayerStore.getState().inPoint ?? 0, { keepPlaying: true });
24818
24857
  return;
@@ -28547,6 +28586,13 @@ function pauseStudioPreviewPlayback(iframe) {
28547
28586
  }
28548
28587
 
28549
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
+ ];
28550
28596
  function formatBoundingBox(bounds) {
28551
28597
  return `x=${Math.round(bounds.x)}, y=${Math.round(bounds.y)}, width=${Math.round(bounds.width)}, height=${Math.round(bounds.height)}`;
28552
28598
  }
@@ -28558,6 +28604,35 @@ function formatTextFields(fields) {
28558
28604
  (field) => `- key=${field.key}; tag=<${field.tagName}>; source=${field.source}; text=${JSON.stringify(field.value)}`
28559
28605
  ).join("\n");
28560
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
+ }
28561
28636
  function buildElementAgentPrompt({
28562
28637
  selection,
28563
28638
  currentTime,
@@ -28567,6 +28642,17 @@ function buildElementAgentPrompt({
28567
28642
  sourceFilePath
28568
28643
  }) {
28569
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
+ };
28570
28656
  const lines = [
28571
28657
  "## HyperFrames element edit request v1",
28572
28658
  "Schema version: 1",
@@ -28576,52 +28662,62 @@ function buildElementAgentPrompt({
28576
28662
  `Composition: ${selection.compositionPath}`,
28577
28663
  `Playback time: ${formatTime(currentTime)}`,
28578
28664
  `Source file: ${displayedSourceFile}`,
28579
- `DOM id: ${selection.id ?? "(none)"}`,
28580
- `Selector: ${selection.selector ?? "(none)"}`,
28581
- `Selector index: ${selection.selectorIndex ?? 0}`,
28582
- `Tag: <${selection.tagName}>`,
28583
- `Bounds: ${formatBoundingBox(selection.boundingBox)}`
28665
+ ...buildElementInfoLines(info)
28584
28666
  ];
28585
- if (selection.textContent) {
28586
- lines.push(`Text: ${selection.textContent}`);
28587
- }
28588
28667
  const trimmedSelectionContext = selectionContext?.trim();
28589
28668
  if (trimmedSelectionContext) {
28590
28669
  lines.push("", "Selection context:", trimmedSelectionContext);
28591
28670
  }
28592
- const textFieldsBlock = formatTextFields(selection.textFields);
28593
- if (textFieldsBlock) {
28594
- lines.push("", "Text fields:", textFieldsBlock);
28595
- }
28596
- const inlineStyleBlock = formatStyleBlock(selection.inlineStyles);
28597
- if (inlineStyleBlock) {
28598
- lines.push("", "Inline styles:", inlineStyleBlock);
28599
- }
28600
- const computedStyleBlock = formatStyleBlock(selection.computedStyles);
28601
- if (computedStyleBlock) {
28602
- lines.push("", "Computed styles (browser-resolved):", computedStyleBlock);
28603
- }
28671
+ lines.push(...buildElementDetailLines(info));
28604
28672
  if (tagSnippet) {
28605
28673
  lines.push("", "Target HTML:", tagSnippet);
28606
28674
  }
28607
- lines.push(
28608
- "",
28609
- "Guardrails:",
28610
- "- Make a targeted change to this element only.",
28611
- "- Preserve the rest of the composition and its timing.",
28612
- "- Do not modify other elements' data-* attributes or positioning.",
28613
- "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors."
28614
- );
28675
+ lines.push("", ...GUARDRAIL_LINES);
28615
28676
  return lines.join("\n");
28616
28677
  }
28678
+ function buildSelectorAndTextLines(info) {
28679
+ return [formatSelectorTagLine(info), formatTextLine(info.textContent)];
28680
+ }
28617
28681
  function buildAgentContextPreview(selection, activeCompPath) {
28618
28682
  return [
28619
28683
  `Composition: ${selection.compositionPath}`,
28620
28684
  `Source: ${selection.sourceFile || activeCompPath || "index.html"}`,
28621
- `Selector: ${selection.selector ?? "(none)"} Tag: <${selection.tagName}>`,
28622
- selection.textContent ? `Text: ${selection.textContent}` : ""
28685
+ ...buildSelectorAndTextLines(selection)
28623
28686
  ].filter(Boolean).join("\n");
28624
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
+ }
28625
28721
 
28626
28722
  // src/components/editor/domEditOverlayBasis.ts
28627
28723
  function readPositiveDimension(value) {
@@ -39570,14 +39666,7 @@ function useElementLifecycleOps({
39570
39666
  },
39571
39667
  [commitDomEditPatchBatches, onReorderShadow]
39572
39668
  );
39573
- const handleDomEditElementDelete = useCallback54(
39574
- async (selection) => {
39575
- await handleDomEditElementsDelete([selection]);
39576
- },
39577
- [handleDomEditElementsDelete]
39578
- );
39579
39669
  return {
39580
- handleDomEditElementDelete,
39581
39670
  handleDomEditElementsDelete,
39582
39671
  handleDomZIndexReorderCommit
39583
39672
  };
@@ -54128,6 +54217,10 @@ function withLane2(automation, lane) {
54128
54217
  lanes: [...automation.lanes.filter((l) => l.target !== lane.target), lane]
54129
54218
  };
54130
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
+ }
54131
54224
  function automationAttrValue(automation) {
54132
54225
  return automation.lanes.length > 0 ? serializeAutomation2(automation) : "";
54133
54226
  }
@@ -56991,24 +57084,33 @@ function AudioFxGroup({
56991
57084
  }
56992
57085
 
56993
57086
  // src/components/editor/useVolumeAutomation.ts
56994
- import { HF_AUDIO_AUTOMATION_DATA_KEY as HF_AUDIO_AUTOMATION_DATA_KEY2, VOLUME_TARGET as VOLUME_TARGET2 } from "@hyperframes/core/audio-automation";
56995
- 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) {
56996
57093
  const automation = readPanelAutomation(
56997
57094
  element.dataAttributes?.[HF_AUDIO_AUTOMATION_DATA_KEY2],
56998
57095
  void 0
56999
57096
  );
57097
+ const { start: elStart, duration: elDuration } = deriveElementTiming(element);
57098
+ const clipTimeSec = clampNumber(currentTime - elStart, 0, elDuration > 0 ? elDuration : Infinity);
57000
57099
  const write = (next) => {
57001
57100
  void onSetAttributeQuiet(HF_AUDIO_AUTOMATION_ATTR3, automationAttrValue(next) || null);
57002
57101
  };
57003
57102
  const raw = element.dataAttributes?.["volume"];
57004
57103
  const parsed = raw ? Number(raw) : 1;
57005
57104
  const current = Number.isFinite(parsed) ? parsed : 1;
57105
+ const volumeLane = automation.lanes.find((lane) => lane.target === VOLUME_TARGET2);
57006
57106
  return {
57007
- volumeAutomated: automation.lanes.some((lane) => lane.target === VOLUME_TARGET2),
57107
+ volumeAutomated: volumeLane !== void 0,
57108
+ automatedVolumeValue: volumeLane ? sampleAutomationLane9(volumeLane, clipTimeSec) : void 0,
57008
57109
  // Seeded at the level the slider already shows, so automating the track does
57009
57110
  // not change how loud it is.
57010
57111
  onAutomateVolume: () => write(withSeededLane(automation, VOLUME_TARGET2, Number.isFinite(current) ? current : 1)),
57011
- onRemoveVolumeAutomation: () => write(withoutLane(automation, VOLUME_TARGET2))
57112
+ onRemoveVolumeAutomation: () => write(withoutLane(automation, VOLUME_TARGET2)),
57113
+ onCommitVolumeAt: (v) => write(withPointAt(automation, VOLUME_TARGET2, clipTimeSec, v))
57012
57114
  };
57013
57115
  }
57014
57116
 
@@ -57101,7 +57203,9 @@ function FlatMediaSection({
57101
57203
  onRemoveBackground,
57102
57204
  volumeAutomated,
57103
57205
  onAutomateVolume,
57104
- onRemoveVolumeAutomation
57206
+ onRemoveVolumeAutomation,
57207
+ onCommitVolumeAt,
57208
+ automatedVolumeValue
57105
57209
  }) {
57106
57210
  const track2 = useTrackDesignInput();
57107
57211
  const isVideo = element.tagName === "video";
@@ -57109,7 +57213,7 @@ function FlatMediaSection({
57109
57213
  const isImage = element.tagName === "img";
57110
57214
  const isVisualMedia = isVideo || isImage;
57111
57215
  const el = element.element;
57112
- const volume = parseNumericValue(element.dataAttributes.volume ?? "") ?? 1;
57216
+ const volume = volumeAutomated && automatedVolumeValue !== void 0 ? automatedVolumeValue : parseNumericValue(element.dataAttributes.volume ?? "") ?? 1;
57113
57217
  const volumeFaderPosition = audioGainToFaderPosition2(volume);
57114
57218
  const mediaStart = Number.parseFloat(
57115
57219
  element.dataAttributes["media-start"] ?? element.dataAttributes["playback-start"] ?? "0"
@@ -57258,9 +57362,15 @@ function FlatMediaSection({
57258
57362
  max: AUDIO_GAIN_FADER_MAX2,
57259
57363
  tier: volume === 1 ? "default" : "explicitCustom",
57260
57364
  displayValue: audioGainToText2(volume),
57261
- disabled: volumeAutomated,
57262
57365
  centerTick: true,
57263
- 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
+ }
57264
57374
  }
57265
57375
  ) }),
57266
57376
  /* @__PURE__ */ jsx142(
@@ -60939,7 +61049,11 @@ function PropertyPanelFlat({
60939
61049
  const audioSelection = isAudioDomElement(element.element);
60940
61050
  const showMotionEffects = gsapEffectHandlers !== null && !audioSelection;
60941
61051
  const showMotionGroup = showMotionTiming || showMotionEffects;
60942
- const volumeAutomation = useVolumeAutomation(element, onSetAttributeQuiet ?? onSetAttributeLive);
61052
+ const volumeAutomation = useVolumeAutomation(
61053
+ element,
61054
+ currentTime,
61055
+ onSetAttributeQuiet ?? onSetAttributeLive
61056
+ );
60943
61057
  const audioGroupLabel = useMemo52(() => {
60944
61058
  const doc = element.element?.ownerDocument;
60945
61059
  const id = element.id;
@@ -62782,7 +62896,7 @@ var FileTree = memo34(function FileTree2({
62782
62896
  });
62783
62897
 
62784
62898
  // src/App.tsx
62785
- 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";
62786
62900
 
62787
62901
  // src/components/renders/useRenderQueue.ts
62788
62902
  import { useState as useState95, useEffect as useEffect89, useCallback as useCallback80, useRef as useRef104, useMemo as useMemo56 } from "react";
@@ -65199,45 +65313,70 @@ function usePanelLayout(initialState2) {
65199
65313
  }
65200
65314
 
65201
65315
  // src/hooks/useFileManager.ts
65202
- 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";
65203
65317
 
65204
65318
  // src/hooks/useFileTree.ts
65205
- 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 = [];
65206
65321
  function useFileTree({ projectId, projectIdRef }) {
65207
- const [projectDir, setProjectDir] = useState102(null);
65208
- const [fileTree, setFileTree] = useState102([]);
65209
- const [compositionPaths, setCompositionPaths] = useState102([]);
65210
- 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;
65211
65328
  useEffect94(() => {
65212
- if (!projectId) {
65213
- setFileTreeLoaded(false);
65214
- return;
65215
- }
65329
+ if (!projectId) return;
65216
65330
  let cancelled = false;
65217
- setFileTreeLoaded(false);
65331
+ setFetched({ projectId, loaded: false, fileTree: [], compositionPaths: [], projectDir: null });
65218
65332
  fetch(buildProjectApiPath(projectId)).then((r) => r.json()).then((data) => {
65219
65333
  if (cancelled) return;
65220
- if (data.files) setFileTree(data.files);
65221
- if (data.compositions) setCompositionPaths(data.compositions);
65222
- 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
+ });
65223
65341
  void captureProjectProvenance(projectId, data.files ?? [], data.compositions ?? []);
65224
65342
  }).catch(() => {
65225
- if (!cancelled) setProjectDir(null);
65226
- }).finally(() => {
65227
- if (!cancelled) setFileTreeLoaded(true);
65343
+ if (!cancelled) {
65344
+ setFetched((prev) => prev?.projectId === projectId ? { ...prev, loaded: true } : prev);
65345
+ }
65228
65346
  });
65229
65347
  return () => {
65230
65348
  cancelled = true;
65231
65349
  };
65232
65350
  }, [projectId]);
65351
+ const refreshRequestRef = useRef113(0);
65352
+ const refreshAbortRef = useRef113(null);
65233
65353
  const refreshFileTree = useCallback86(async () => {
65234
65354
  const pid = projectIdRef.current;
65235
65355
  if (!pid) return;
65236
- const res = await fetch(buildProjectApiPath(pid));
65237
- const data = await res.json();
65238
- 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
+ }
65239
65379
  }, [projectIdRef]);
65240
- const compositions = compositionPaths;
65241
65380
  const assets = useMemo58(
65242
65381
  () => fileTree.filter((f) => !f.endsWith(".html") && !f.endsWith(".md") && !f.endsWith(".json")),
65243
65382
  [fileTree]
@@ -65253,17 +65392,16 @@ function useFileTree({ projectId, projectIdRef }) {
65253
65392
  return {
65254
65393
  projectDir,
65255
65394
  fileTree,
65256
- setFileTree,
65257
65395
  fileTreeLoaded,
65258
65396
  refreshFileTree,
65259
- compositions,
65397
+ compositions: compositionPaths,
65260
65398
  assets,
65261
65399
  fontAssets
65262
65400
  };
65263
65401
  }
65264
65402
 
65265
65403
  // src/hooks/useEditorSave.ts
65266
- import { useCallback as useCallback87, useRef as useRef113 } from "react";
65404
+ import { useCallback as useCallback87, useRef as useRef114 } from "react";
65267
65405
  var FAILURE_BURST_MS = 5e3;
65268
65406
  function useEditorSave({
65269
65407
  editingPathRef,
@@ -65274,13 +65412,13 @@ function useEditorSave({
65274
65412
  setRefreshKey,
65275
65413
  showToast
65276
65414
  }) {
65277
- const saveRafRef = useRef113(null);
65278
- const refreshRafRef = useRef113(null);
65279
- const lastFailureToastAtRef = useRef113(null);
65280
- const lastFailureReportRef = useRef113(null);
65281
- const pendingCandidateRef = useRef113(null);
65282
- const inFlightRef = useRef113(null);
65283
- 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);
65284
65422
  const reportFailure = useCallback87(
65285
65423
  (path, error) => {
65286
65424
  const now2 = Date.now();
@@ -65396,11 +65534,11 @@ function useFileManager({
65396
65534
  }) {
65397
65535
  const [editingFile, setEditingFile] = useState103(null);
65398
65536
  const [revealSourceOffset, setRevealSourceOffset] = useState103(null);
65399
- const editingPathRef = useRef114(editingFile?.path);
65537
+ const editingPathRef = useRef115(editingFile?.path);
65400
65538
  editingPathRef.current = editingFile?.path;
65401
- const projectIdRef = useRef114(projectId);
65539
+ const projectIdRef = useRef115(projectId);
65402
65540
  projectIdRef.current = projectId;
65403
- const importedFontAssetsRef = useRef114([]);
65541
+ const importedFontAssetsRef = useRef115([]);
65404
65542
  const fileVersionScope = useMemo59(
65405
65543
  () => ({ projectId, versions: /* @__PURE__ */ new Map() }),
65406
65544
  [projectId]
@@ -65415,7 +65553,6 @@ function useFileManager({
65415
65553
  const {
65416
65554
  projectDir,
65417
65555
  fileTree,
65418
- setFileTree,
65419
65556
  fileTreeLoaded,
65420
65557
  refreshFileTree,
65421
65558
  compositions,
@@ -65550,8 +65687,8 @@ function useFileManager({
65550
65687
  },
65551
65688
  [fileVersions, updateEditingFileContent, writeProjectFile]
65552
65689
  );
65553
- const revealRequestIdRef = useRef114(0);
65554
- const revealAbortRef = useRef114(null);
65690
+ const revealRequestIdRef = useRef115(0);
65691
+ const revealAbortRef = useRef115(null);
65555
65692
  const handleFileSelect = useCallback88(
65556
65693
  (path) => {
65557
65694
  const pid = projectIdRef.current;
@@ -65799,7 +65936,6 @@ function useFileManager({
65799
65936
  projectDir,
65800
65937
  fileTree,
65801
65938
  fileTreeLoaded,
65802
- setFileTree,
65803
65939
  // Refs
65804
65940
  editingPathRef,
65805
65941
  projectIdRef,
@@ -65839,7 +65975,7 @@ function useFileManager({
65839
65975
  }
65840
65976
 
65841
65977
  // src/hooks/usePreviewPersistence.ts
65842
- 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";
65843
65979
 
65844
65980
  // src/utils/gsapUndoRestore.ts
65845
65981
  function elementIdentityKey(el) {
@@ -66075,11 +66211,11 @@ function usePreviewPersistence({
66075
66211
  }) {
66076
66212
  void _recordEdit;
66077
66213
  const [domEditSaveQueuePaused, setDomEditSaveQueuePaused] = useState104(null);
66078
- const domTextCommitVersionRef = useRef115(0);
66079
- const showToastRef = useRef115(showToast);
66214
+ const domTextCommitVersionRef = useRef116(0);
66215
+ const showToastRef = useRef116(showToast);
66080
66216
  showToastRef.current = showToast;
66081
- const domEditSaveQueueRef = useRef115(null);
66082
- const applyStudioManualEditsToPreviewRef = useRef115(async () => {
66217
+ const domEditSaveQueueRef = useRef116(null);
66218
+ const applyStudioManualEditsToPreviewRef = useRef116(async () => {
66083
66219
  });
66084
66220
  if (!domEditSaveQueueRef.current) {
66085
66221
  domEditSaveQueueRef.current = createDomEditSaveQueue({
@@ -66172,10 +66308,10 @@ function usePreviewPersistence({
66172
66308
  }
66173
66309
 
66174
66310
  // src/hooks/usePreviewDocumentVersion.ts
66175
- 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";
66176
66312
  function usePreviewDocumentVersion() {
66177
66313
  const [previewDocumentVersion, setPreviewDocumentVersion] = useState105(0);
66178
- const refreshTimersRef = useRef116([]);
66314
+ const refreshTimersRef = useRef117([]);
66179
66315
  const refresh = useCallback90(() => {
66180
66316
  for (const id of refreshTimersRef.current) clearTimeout(id);
66181
66317
  refreshTimersRef.current = [];
@@ -66195,10 +66331,10 @@ function usePreviewDocumentVersion() {
66195
66331
  }
66196
66332
 
66197
66333
  // src/hooks/useTimelineEditing.ts
66198
- import { useCallback as useCallback100, useRef as useRef119 } from "react";
66334
+ import { useCallback as useCallback100, useRef as useRef121 } from "react";
66199
66335
 
66200
66336
  // src/hooks/useRazorSplit.ts
66201
- import { useCallback as useCallback91, useRef as useRef117 } from "react";
66337
+ import { useCallback as useCallback91, useRef as useRef118 } from "react";
66202
66338
 
66203
66339
  // src/utils/razorSplitTransaction.ts
66204
66340
  function targetIdentity(path, target) {
@@ -66216,7 +66352,10 @@ function buildCutTarget(element, target, splitTime) {
66216
66352
  elementDuration: element.duration,
66217
66353
  ...element.playbackStart != null ? { playbackStart: element.playbackStart } : {},
66218
66354
  ...element.playbackRate != null ? { playbackRate: element.playbackRate } : {},
66219
- ...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)
66220
66359
  };
66221
66360
  }
66222
66361
  function buildAtomicCutIntents(elements, splitTime, activeCompPath) {
@@ -66330,7 +66469,7 @@ function useRazorSplit({
66330
66469
  forceReloadSdkSession,
66331
66470
  isRecordingRef
66332
66471
  }) {
66333
- const projectIdRef = useRef117(projectId);
66472
+ const projectIdRef = useRef118(projectId);
66334
66473
  projectIdRef.current = projectId;
66335
66474
  const synchronize = useCallback91(() => {
66336
66475
  let failure;
@@ -66531,22 +66670,25 @@ function useTimelineAssetDropOps({
66531
66670
  (te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath
66532
66671
  );
66533
66672
  const newElementZIndex = Math.max(1, relevantElements.length + 1);
66534
- const patchedContent = insertTimelineAssetIntoSource(
66535
- originalContent,
66536
- buildTimelineAssetInsertHtml({
66537
- id: newId,
66538
- hfId: `hf-${generateId()}`,
66539
- assetPath: resolvedAssetSrc,
66540
- kind,
66541
- start: normalizedStart,
66542
- duration: normalizedDuration,
66543
- track: placement.track,
66544
- zIndex: newElementZIndex,
66545
- geometry: fitTimelineAssetGeometry(
66546
- null,
66547
- resolveTimelineAssetCompositionSize(originalContent)
66548
- )
66549
- })
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
66550
66692
  );
66551
66693
  await saveProjectFilesWithHistory({
66552
66694
  projectId: pid,
@@ -66887,7 +67029,16 @@ function useSetElementAttribute({
66887
67029
  }
66888
67030
 
66889
67031
  // src/hooks/useTimelineDeleteOps.ts
66890
- 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;
66891
67042
  function useTimelineDeleteOps({
66892
67043
  projectIdRef,
66893
67044
  activeCompPath,
@@ -66898,8 +67049,10 @@ function useTimelineDeleteOps({
66898
67049
  reloadPreview,
66899
67050
  isRecordingRef,
66900
67051
  forceReloadSdkSession,
66901
- previewIframeRef
67052
+ previewIframeRef,
67053
+ handleTimelineGroupMove
66902
67054
  }) {
67055
+ const rippleNoticeShownRef = useRef119(false);
66903
67056
  const handleTimelineElementsDelete = useCallback95(
66904
67057
  // fallow-ignore-next-line complexity
66905
67058
  async (selection) => {
@@ -66947,11 +67100,14 @@ function useTimelineDeleteOps({
66947
67100
  if (deleteContentEnd > 0 && targetPath === (activeCompPath || "index.html")) {
66948
67101
  usePlayerStore.getState().setDuration(deleteContentEnd);
66949
67102
  }
67103
+ const coalesceKey = `main-track-ripple-delete:${deleteGestureSeq++}`;
67104
+ const deleteHistoryLabel = "Delete timeline clip";
66950
67105
  try {
66951
67106
  await saveProjectFilesWithHistory({
66952
67107
  projectId: pid,
66953
- label: "Delete timeline clip",
67108
+ label: deleteHistoryLabel,
66954
67109
  kind: "timeline",
67110
+ coalesceKey,
66955
67111
  files: { [targetPath]: patchedContent },
66956
67112
  readFile: async () => originalContent,
66957
67113
  // remove-element already wrote the removal, so disk holds THAT — not the
@@ -66965,15 +67121,53 @@ function useTimelineDeleteOps({
66965
67121
  throw error;
66966
67122
  }
66967
67123
  const deletedKeys = new Set(sameFile.map((te) => te.key ?? te.id));
66968
- 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));
66969
67154
  usePlayerStore.getState().setSelectedElementId(null);
66970
67155
  usePlayerStore.getState().setSelectedElementIds(/* @__PURE__ */ new Set());
66971
67156
  forceReloadSdkSession?.();
66972
67157
  reloadPreview();
66973
- showToast(
66974
- `Deleted ${label}. Use Undo to restore ${sameFile.length === 1 ? "it" : "them"}.`,
66975
- "info"
66976
- );
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
+ }
66977
67171
  } catch (error) {
66978
67172
  const message = error instanceof Error ? error.message : "Failed to delete timeline clip";
66979
67173
  showToast(message);
@@ -66989,7 +67183,8 @@ function useTimelineDeleteOps({
66989
67183
  reloadPreview,
66990
67184
  isRecordingRef,
66991
67185
  forceReloadSdkSession,
66992
- previewIframeRef
67186
+ previewIframeRef,
67187
+ handleTimelineGroupMove
66993
67188
  ]
66994
67189
  );
66995
67190
  const handleTimelineElementDelete = useCallback95(
@@ -67593,21 +67788,22 @@ function useTimelineGroupEditing({
67593
67788
  syncPreviewContentDuration(previewIframeRef.current);
67594
67789
  const coalesceKey = options?.coalesceKey ?? moveCoalesceKey(changes);
67595
67790
  const coalesceMs = options?.coalesceMs;
67596
- return enqueueGroupOperation("Move timeline clips", async (projectId) => {
67791
+ const label = options?.label ?? "Move timeline clips";
67792
+ return enqueueGroupOperation(label, async (projectId) => {
67597
67793
  await options?.beforeTiming;
67598
67794
  const handledBySdk = await trySdkBatchPersist({
67599
67795
  changes,
67600
67796
  sdkChanges: toSdkTimingChanges(changes, (change) => ({ start: change.start })),
67601
67797
  eligible: changes.every((change) => change.track == null),
67602
67798
  needsExtension,
67603
- label: "Move timeline clips",
67799
+ label,
67604
67800
  coalesceKey,
67605
67801
  coalesceMs
67606
67802
  });
67607
67803
  if (!handledBySdk) {
67608
67804
  await persistServerBatch(
67609
67805
  projectId,
67610
- "Move timeline clips",
67806
+ label,
67611
67807
  changes.map((change) => ({
67612
67808
  element: change.element,
67613
67809
  buildPatches: (original, target) => buildTimelineMoveTimingPatch(
@@ -67628,7 +67824,7 @@ function useTimelineGroupEditing({
67628
67824
  projectId,
67629
67825
  iframe: previewIframeRef.current,
67630
67826
  reloadPreview,
67631
- label: "Move timeline clips",
67827
+ label,
67632
67828
  errorLabel: "Failed to shift GSAP positions",
67633
67829
  coalesceKey,
67634
67830
  recordEdit,
@@ -67647,7 +67843,11 @@ function useTimelineGroupEditing({
67647
67843
  }
67648
67844
  }).catch((error) => {
67649
67845
  rollbackDuration();
67650
- 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
+ }
67651
67851
  throw error;
67652
67852
  });
67653
67853
  },
@@ -67765,10 +67965,10 @@ function useTimelineGroupEditing({
67765
67965
  }
67766
67966
 
67767
67967
  // src/hooks/useBlockedTimelineEditToast.ts
67768
- import { useCallback as useCallback99, useRef as useRef118 } from "react";
67968
+ import { useCallback as useCallback99, useRef as useRef120 } from "react";
67769
67969
  var BLOCKED_TOAST_INTERVAL_MS = 1500;
67770
67970
  function useBlockedTimelineEditToast(showToast) {
67771
- const lastAtRef = useRef118(0);
67971
+ const lastAtRef = useRef120(0);
67772
67972
  return useCallback99(
67773
67973
  (_element) => {
67774
67974
  const now2 = Date.now();
@@ -67800,9 +68000,9 @@ function useTimelineEditing({
67800
68000
  invalidateGsapCache,
67801
68001
  handleDomZIndexReorderCommitRef
67802
68002
  }) {
67803
- const projectIdRef = useRef119(projectId);
68003
+ const projectIdRef = useRef121(projectId);
67804
68004
  projectIdRef.current = projectId;
67805
- const editQueueRef = useRef119(Promise.resolve());
68005
+ const editQueueRef = useRef121(Promise.resolve());
67806
68006
  const enqueueEdit = useCallback100(
67807
68007
  (element, label, buildPatches, coalesceKey) => {
67808
68008
  if (isRecordingRef?.current) {
@@ -68104,7 +68304,8 @@ function useTimelineEditing({
68104
68304
  reloadPreview,
68105
68305
  isRecordingRef,
68106
68306
  forceReloadSdkSession,
68107
- previewIframeRef
68307
+ previewIframeRef,
68308
+ handleTimelineGroupMove: groupEditing.handleTimelineGroupMove
68108
68309
  });
68109
68310
  const { handleTimelineAssetDrop, handleTimelineFileDrop, handleTimelineCompositionDrop } = useTimelineAssetDropOps({
68110
68311
  projectIdRef,
@@ -68172,7 +68373,7 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
68172
68373
  import { useCallback as useCallback123 } from "react";
68173
68374
 
68174
68375
  // src/hooks/useAskAgentModal.ts
68175
- 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";
68176
68377
  function useAskAgentModal({
68177
68378
  activeCompPath,
68178
68379
  projectDir,
@@ -68188,7 +68389,7 @@ function useAskAgentModal({
68188
68389
  );
68189
68390
  const [copiedAgentPrompt, setCopiedAgentPrompt] = useState106(false);
68190
68391
  const [agentModalOpen, setAgentModalOpen] = useState106(false);
68191
- const copiedAgentTimerRef = useRef120(null);
68392
+ const copiedAgentTimerRef = useRef122(null);
68192
68393
  const preloadAgentPromptSnippet = useCallback101(
68193
68394
  async (selection) => {
68194
68395
  const pid = projectIdRef.current;
@@ -68283,7 +68484,7 @@ function useAskAgentModal({
68283
68484
  }
68284
68485
 
68285
68486
  // src/hooks/useDomSelection.ts
68286
- 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";
68287
68488
 
68288
68489
  // src/utils/domEditHelpers.ts
68289
68490
  function domEditSelectionsTargetSame(a, b) {
@@ -68510,14 +68711,14 @@ function useDomSelection({
68510
68711
  const [domEditGroupSelections, setDomEditGroupSelections] = useState107([]);
68511
68712
  const [domEditHoverSelection, setDomEditHoverSelection] = useState107(null);
68512
68713
  const [activeGroupElement, setActiveGroupElementState] = useState107(null);
68513
- const rightPanelTabRef = useRef121(rightPanelTab);
68714
+ const rightPanelTabRef = useRef123(rightPanelTab);
68514
68715
  rightPanelTabRef.current = rightPanelTab;
68515
- const domEditSelectionRef = useRef121(domEditSelection);
68516
- const domEditGroupSelectionsRef = useRef121(domEditGroupSelections);
68517
- const domEditHoverSelectionRef = useRef121(domEditHoverSelection);
68518
- const activeGroupElementRef = useRef121(activeGroupElement);
68519
- const compositionIdentityRef = useRef121({ activeCompPath, projectId });
68520
- 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);
68521
68722
  domEditSelectionRef.current = domEditSelection;
68522
68723
  domEditGroupSelectionsRef.current = domEditGroupSelections;
68523
68724
  domEditHoverSelectionRef.current = domEditHoverSelection;
@@ -68712,7 +68913,7 @@ function useDomSelection({
68712
68913
  },
68713
68914
  [applyDomSelection, buildDomSelectionForTimelineElement]
68714
68915
  );
68715
- const refreshDomEditGroupSelectionsFromPreviewRef = useRef121(async () => {
68916
+ const refreshDomEditGroupSelectionsFromPreviewRef = useRef123(async () => {
68716
68917
  });
68717
68918
  const refreshDomEditSelectionFromPreview = useCallback102(
68718
68919
  // fallow-ignore-next-line complexity
@@ -68870,7 +69071,7 @@ function useDomSelection({
68870
69071
  }
68871
69072
 
68872
69073
  // src/hooks/usePreviewInteraction.ts
68873
- import { useCallback as useCallback103, useRef as useRef122 } from "react";
69074
+ import { useCallback as useCallback103, useRef as useRef124 } from "react";
68874
69075
  var CYCLE_RADIUS_PX = 6;
68875
69076
  var CYCLE_WINDOW_MS = 600;
68876
69077
  var DOUBLE_CLICK_MS = 400;
@@ -68887,8 +69088,8 @@ function usePreviewInteraction({
68887
69088
  setActiveGroupElement,
68888
69089
  onClickToSource
68889
69090
  }) {
68890
- const cycleRef = useRef122(null);
68891
- const lastDownRef = useRef122(null);
69091
+ const cycleRef = useRef124(null);
69092
+ const lastDownRef = useRef124(null);
68892
69093
  const pausePreviewPlayback = useCallback103(() => {
68893
69094
  const pausedTime = pauseStudioPreviewPlayback(previewIframeRef.current);
68894
69095
  const playerStore = usePlayerStore.getState();
@@ -69046,7 +69247,7 @@ function usePreviewInteraction({
69046
69247
  }
69047
69248
 
69048
69249
  // src/hooks/useDomEditCommits.ts
69049
- import { useCallback as useCallback108, useRef as useRef125 } from "react";
69250
+ import { useCallback as useCallback108, useRef as useRef127 } from "react";
69050
69251
  import { findUnsafeDomPatchValues as findUnsafeDomPatchValues2 } from "@hyperframes/core/studio-api/finite-mutation";
69051
69252
 
69052
69253
  // src/utils/studioFontHelpers.ts
@@ -69207,7 +69408,7 @@ function useDomEditPositionPatchCommit({
69207
69408
  }
69208
69409
 
69209
69410
  // src/hooks/useDomEditTextCommits.ts
69210
- import { useCallback as useCallback106, useRef as useRef124 } from "react";
69411
+ import { useCallback as useCallback106, useRef as useRef126 } from "react";
69211
69412
 
69212
69413
  // src/hooks/domEditTextFieldCommitOps.ts
69213
69414
  function hasSameKeysInSamePositions(originalFields, nextFields) {
@@ -69262,7 +69463,7 @@ function planDomTextCommit(originalTextFields, nextTextFields, plainTextContent)
69262
69463
  }
69263
69464
 
69264
69465
  // src/hooks/useDomEditAttributeCommits.ts
69265
- import { useCallback as useCallback105, useRef as useRef123 } from "react";
69466
+ import { useCallback as useCallback105, useRef as useRef125 } from "react";
69266
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";
69267
69468
  function resolveFullAttrName(attr, prefixData) {
69268
69469
  return prefixData && !attr.startsWith("data-") ? `data-${attr}` : attr;
@@ -69302,7 +69503,7 @@ function useDomEditAttributeCommits({
69302
69503
  refreshDomEditSelectionFromPreview,
69303
69504
  persistDomEditOperations
69304
69505
  }) {
69305
- const domAttributeCommitVersionRef = useRef123(/* @__PURE__ */ new Map());
69506
+ const domAttributeCommitVersionRef = useRef125(/* @__PURE__ */ new Map());
69306
69507
  const commitDataAttribute = useCallback105(
69307
69508
  async (attr, value, options) => {
69308
69509
  if (!domEditSelection) return;
@@ -69584,8 +69785,8 @@ function useDomEditTextCommits({
69584
69785
  persistDomEditOperations,
69585
69786
  resolveImportedFontAsset
69586
69787
  }) {
69587
- const domTextCommitVersionRef = useRef124(/* @__PURE__ */ new Map());
69588
- const domStyleCommitVersionRef = useRef124(/* @__PURE__ */ new Map());
69788
+ const domTextCommitVersionRef = useRef126(/* @__PURE__ */ new Map());
69789
+ const domStyleCommitVersionRef = useRef126(/* @__PURE__ */ new Map());
69589
69790
  const {
69590
69791
  handleDomAttributeCommit,
69591
69792
  handleDomAttributeLiveCommit,
@@ -70496,7 +70697,7 @@ function useDomEditCommits({
70496
70697
  },
70497
70698
  [fileTree, projectId, importedFontAssetsRef]
70498
70699
  );
70499
- const reportedUnresolvableRef = useRef125(/* @__PURE__ */ new Set());
70700
+ const reportedUnresolvableRef = useRef127(/* @__PURE__ */ new Set());
70500
70701
  const performPersistDomEditOperations = useCallback108(
70501
70702
  // fallow-ignore-next-line complexity
70502
70703
  async (selection, operations, options, expectedProjectId) => {
@@ -70741,7 +70942,7 @@ function useDomEditCommits({
70741
70942
  showToast,
70742
70943
  commitPositionPatchToHtml
70743
70944
  });
70744
- const { handleDomEditElementDelete, handleDomEditElementsDelete, handleDomZIndexReorderCommit } = useElementLifecycleOps({
70945
+ const { handleDomEditElementsDelete, handleDomZIndexReorderCommit } = useElementLifecycleOps({
70745
70946
  activeCompPath,
70746
70947
  showToast,
70747
70948
  writeProjectFile,
@@ -70774,7 +70975,6 @@ function useDomEditCommits({
70774
70975
  handleDomBoxSizeCommit,
70775
70976
  handleDomRotationCommit,
70776
70977
  handleDomManualEditsReset,
70777
- handleDomEditElementDelete,
70778
70978
  handleDomEditElementsDelete,
70779
70979
  handleDomZIndexReorderCommit
70780
70980
  };
@@ -70782,13 +70982,21 @@ function useDomEditCommits({
70782
70982
 
70783
70983
  // src/hooks/useGroupCommits.ts
70784
70984
  import { useCallback as useCallback109 } from "react";
70785
- 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) {
70786
70993
  const boxes = members.map((m) => ({
70787
70994
  target: buildDomEditPatchTarget(m),
70788
70995
  left: m.element.offsetLeft,
70789
70996
  top: m.element.offsetTop,
70790
70997
  right: m.element.offsetLeft + m.element.offsetWidth,
70791
- bottom: m.element.offsetTop + m.element.offsetHeight
70998
+ bottom: m.element.offsetTop + m.element.offsetHeight,
70999
+ track: resolveAuthoredTrack(m, timelineElements)
70792
71000
  }));
70793
71001
  const left = Math.min(...boxes.map((b) => b.left));
70794
71002
  const top = Math.min(...boxes.map((b) => b.top));
@@ -70797,9 +71005,29 @@ function computeGroupGeometry(members) {
70797
71005
  return {
70798
71006
  bbox: { left, top, width, height },
70799
71007
  targets: boxes.map((b) => b.target),
70800
- 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
+ }))
70801
71014
  };
70802
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
+ }
70803
71031
  async function commitStructuralMutation(pid, targetPath, route, body, label, deps) {
70804
71032
  const originalContent = await readProjectFileContent(pid, targetPath);
70805
71033
  const mutateResponse = await fetch(
@@ -70831,7 +71059,7 @@ async function commitStructuralMutation(pid, targetPath, route, body, label, dep
70831
71059
  return mutateData;
70832
71060
  }
70833
71061
  function useGroupCommits(params) {
70834
- const { activeCompPath, showToast, projectIdRef } = params;
71062
+ const { activeCompPath, showToast, projectIdRef, timelineElements } = params;
70835
71063
  const groupSelection = useCallback109(
70836
71064
  async (members) => {
70837
71065
  const pid = projectIdRef.current;
@@ -70843,7 +71071,7 @@ function useGroupCommits(params) {
70843
71071
  }
70844
71072
  const doc = members[0].element.ownerDocument;
70845
71073
  const groupId = `Group ${doc.querySelectorAll("[data-hf-group]").length + 1}`;
70846
- const { bbox, targets, rebases } = computeGroupGeometry(members);
71074
+ const { bbox, targets, rebases } = computeGroupGeometry(members, timelineElements);
70847
71075
  try {
70848
71076
  const data = await commitStructuralMutation(
70849
71077
  pid,
@@ -70859,19 +71087,20 @@ function useGroupCommits(params) {
70859
71087
  return null;
70860
71088
  }
70861
71089
  },
70862
- [activeCompPath, projectIdRef, showToast, params]
71090
+ [activeCompPath, projectIdRef, showToast, params, timelineElements]
70863
71091
  );
70864
71092
  const ungroupSelection = useCallback109(
70865
71093
  async (group) => {
70866
71094
  const pid = projectIdRef.current;
70867
71095
  if (!pid) return;
70868
71096
  const targetPath = group.sourceFile || activeCompPath || "index.html";
71097
+ const childTracks = resolveGroupChildTracks(group, timelineElements);
70869
71098
  try {
70870
71099
  await commitStructuralMutation(
70871
71100
  pid,
70872
71101
  targetPath,
70873
71102
  "unwrap-elements",
70874
- { target: buildDomEditPatchTarget(group) },
71103
+ { target: buildDomEditPatchTarget(group), childTracks },
70875
71104
  "Ungroup elements",
70876
71105
  params
70877
71106
  );
@@ -70879,13 +71108,13 @@ function useGroupCommits(params) {
70879
71108
  showToast(error instanceof Error ? error.message : "Failed to ungroup elements", "error");
70880
71109
  }
70881
71110
  },
70882
- [activeCompPath, projectIdRef, showToast, params]
71111
+ [activeCompPath, projectIdRef, showToast, params, timelineElements]
70883
71112
  );
70884
71113
  return { groupSelection, ungroupSelection };
70885
71114
  }
70886
71115
 
70887
71116
  // src/hooks/useGsapScriptCommits.ts
70888
- 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";
70889
71118
  import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
70890
71119
 
70891
71120
  // src/hooks/gsapRuntimePatch.ts
@@ -71680,7 +71909,7 @@ function useGsapKeyframeOps({
71680
71909
  }
71681
71910
 
71682
71911
  // src/hooks/useGsapPropertyDebounce.ts
71683
- 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";
71684
71913
  import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
71685
71914
  var DEBOUNCE_MS = 150;
71686
71915
  function mergeTweenProperties(sdkSession, animationId, edited, kind) {
@@ -71696,9 +71925,9 @@ function mergeTweenProperties(sdkSession, animationId, edited, kind) {
71696
71925
  }
71697
71926
  }
71698
71927
  function useGsapPropertyDebounce(commitMutationSafely, sdk) {
71699
- const pendingPropertyEditRef = useRef126(null);
71700
- const debounceTimerRef = useRef126(null);
71701
- const sdkRef = useRef126(sdk);
71928
+ const pendingPropertyEditRef = useRef128(null);
71929
+ const debounceTimerRef = useRef128(null);
71930
+ const sdkRef = useRef128(sdk);
71702
71931
  sdkRef.current = sdk;
71703
71932
  const flushPendingPropertyEdit = useCallback113(async () => {
71704
71933
  const pending2 = pendingPropertyEditRef.current;
@@ -72129,9 +72358,9 @@ function instantPatchesFor(options) {
72129
72358
  }
72130
72359
  function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef, editHistory, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast, sdkSession, publishSdkSession, writeProjectFile, forceReloadSdkSession }) {
72131
72360
  const activeProjectId = projectIdRef.current;
72132
- const activeCompPathRef = useRef127(activeCompPath);
72361
+ const activeCompPathRef = useRef129(activeCompPath);
72133
72362
  activeCompPathRef.current = activeCompPath;
72134
- const serializerRef = useRef127(createKeyedSerializer());
72363
+ const serializerRef = useRef129(createKeyedSerializer());
72135
72364
  const recordMutationEdit = useCallback115(async (targetPath, result, options) => {
72136
72365
  if (result.before == null || result.after == null) return;
72137
72366
  await editHistory.recordEdit({
@@ -72312,10 +72541,10 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
72312
72541
  }
72313
72542
 
72314
72543
  // src/hooks/useDomEditWiring.ts
72315
- 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";
72316
72545
 
72317
72546
  // src/hooks/useDomEditPreviewSync.ts
72318
- import { useEffect as useEffect100, useRef as useRef128 } from "react";
72547
+ import { useEffect as useEffect100, useRef as useRef130 } from "react";
72319
72548
  function useDomEditPreviewSync({
72320
72549
  previewIframe,
72321
72550
  activeCompPath,
@@ -72394,7 +72623,7 @@ function useDomEditPreviewSync({
72394
72623
  applyStudioManualEditsToPreviewRef,
72395
72624
  gsapCacheVersion
72396
72625
  ]);
72397
- const openSourceRef = useRef128(openSourceForSelection);
72626
+ const openSourceRef = useRef130(openSourceForSelection);
72398
72627
  openSourceRef.current = openSourceForSelection;
72399
72628
  useEffect100(
72400
72629
  // fallow-ignore-next-line complexity
@@ -72492,7 +72721,7 @@ function useGsapInteractionFailureTelemetry(activeCompPath, showToast) {
72492
72721
  }
72493
72722
 
72494
72723
  // src/hooks/useGsapSelectionHandlers.ts
72495
- import { useCallback as useCallback118, useRef as useRef129 } from "react";
72724
+ import { useCallback as useCallback118, useRef as useRef131 } from "react";
72496
72725
  function useGsapSelectionHandlers({
72497
72726
  domEditSelection,
72498
72727
  updateGsapProperty,
@@ -72516,7 +72745,7 @@ function useGsapSelectionHandlers({
72516
72745
  selectedGsapAnimations,
72517
72746
  showToast
72518
72747
  }) {
72519
- const lastSelectionRef = useRef129(null);
72748
+ const lastSelectionRef = useRef131(null);
72520
72749
  if (domEditSelection) lastSelectionRef.current = domEditSelection;
72521
72750
  const resolveWriteSelection2 = useCallback118(
72522
72751
  (selectionOverride) => selectionOverride === void 0 ? domEditSelection ?? lastSelectionRef.current : selectionOverride,
@@ -72860,7 +73089,7 @@ function useDomEditWiring({
72860
73089
  const key2 = matchKey ? matchKey.key ?? matchKey.id : null;
72861
73090
  if (key2 && key2 !== selectedElementId) setSelectedElementId(key2);
72862
73091
  }, [domEditSelection?.id]);
72863
- const prevRefreshKeyRef = useRef130(refreshKey);
73092
+ const prevRefreshKeyRef = useRef132(refreshKey);
72864
73093
  useEffect101(() => {
72865
73094
  if (refreshKey !== prevRefreshKeyRef.current) {
72866
73095
  prevRefreshKeyRef.current = refreshKey;
@@ -73961,7 +74190,7 @@ function useGsapAwareEditing({
73961
74190
  }
73962
74191
 
73963
74192
  // src/hooks/useStudioSelectionPublisher.ts
73964
- import { useEffect as useEffect102, useRef as useRef131 } from "react";
74193
+ import { useEffect as useEffect102, useRef as useRef133 } from "react";
73965
74194
 
73966
74195
  // src/utils/studioSelectionSnapshot.ts
73967
74196
  function round34(value) {
@@ -74052,8 +74281,8 @@ function useStudioSelectionPublisher({
74052
74281
  previewDocumentVersion,
74053
74282
  refreshDomEditSelectionFromPreview
74054
74283
  }) {
74055
- const lastSelectionRefreshKeyRef = useRef131(refreshKey);
74056
- const pendingSelectionRefreshKeyRef = useRef131(null);
74284
+ const lastSelectionRefreshKeyRef = useRef133(refreshKey);
74285
+ const pendingSelectionRefreshKeyRef = useRef133(null);
74057
74286
  useEffect102(() => {
74058
74287
  if (!projectId) return;
74059
74288
  const selection = domEditSelection?.element.isConnected ? buildStudioSelectionSnapshot({
@@ -74156,6 +74385,16 @@ function useKeyframeEaseCommits({
74156
74385
  function membersForDelete(selection, group, options) {
74157
74386
  return options?.expandGroup && group.length > 0 ? group : [selection];
74158
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
+ }
74159
74398
 
74160
74399
  // src/hooks/useDomEditSession.ts
74161
74400
  function useDomEditSession({
@@ -74196,7 +74435,8 @@ function useDomEditSession({
74196
74435
  getSidebarTab,
74197
74436
  sdkSession,
74198
74437
  publishSdkSession,
74199
- forceReloadSdkSession
74438
+ forceReloadSdkSession,
74439
+ handleTimelineElementsDelete
74200
74440
  }) {
74201
74441
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
74202
74442
  void _setRefreshKey;
@@ -74366,9 +74606,8 @@ function useDomEditSession({
74366
74606
  readProjectFile,
74367
74607
  publishSession: publishSdkSession
74368
74608
  }) : void 0,
74369
- // Resolver shadow for the z-index reorder edit: it takes the server path (no
74370
- // SDK persist), but the tripwire is decoupled from cutover — record whether
74371
- // 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.
74372
74611
  onReorderShadow: sdkSession ? (targets) => {
74373
74612
  let reorderSrcPromise;
74374
74613
  const reorderSrc = activeCompPath ? () => reorderSrcPromise ??= readProjectFile(activeCompPath) : void 0;
@@ -74384,7 +74623,8 @@ function useDomEditSession({
74384
74623
  projectIdRef,
74385
74624
  reloadPreview,
74386
74625
  clearDomSelection,
74387
- forceReloadSdkSession
74626
+ forceReloadSdkSession,
74627
+ timelineElements
74388
74628
  });
74389
74629
  const handleDomEditElementDelete = useCallback123(
74390
74630
  async (selection, options) => {
@@ -74393,9 +74633,21 @@ function useDomEditSession({
74393
74633
  return;
74394
74634
  }
74395
74635
  const members = membersForDelete(selection, domEditGroupSelectionsRef.current, options);
74636
+ const timelineTargets = timelineElementsForDelete(members, timelineElements);
74637
+ if (timelineTargets) {
74638
+ await handleTimelineElementsDelete(timelineTargets);
74639
+ return;
74640
+ }
74396
74641
  await handleDomEditElementsDelete(members);
74397
74642
  },
74398
- [domEditGroupSelectionsRef, handleDomEditElementsDelete, isRecordingRef, showToast]
74643
+ [
74644
+ domEditGroupSelectionsRef,
74645
+ handleDomEditElementsDelete,
74646
+ handleTimelineElementsDelete,
74647
+ isRecordingRef,
74648
+ showToast,
74649
+ timelineElements
74650
+ ]
74399
74651
  );
74400
74652
  const handleGroupSelection = useCallback123(() => {
74401
74653
  const group = domEditGroupSelectionsRef.current;
@@ -74646,7 +74898,7 @@ function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections)
74646
74898
  import { useEffect as useEffect105 } from "react";
74647
74899
 
74648
74900
  // src/hooks/useSdkSession.ts
74649
- 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";
74650
74902
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
74651
74903
 
74652
74904
  // src/hooks/externalFileReloadBus.ts
@@ -74696,15 +74948,15 @@ function disposeSdkSession(session) {
74696
74948
  }
74697
74949
  function useSdkSession(projectId, activeCompPath) {
74698
74950
  const [ownedSession, setOwnedSession] = useState108(null);
74699
- const ownedSessionRef = useRef132(null);
74700
- const sessionOwnersRef = useRef132(/* @__PURE__ */ new WeakMap());
74701
- const generationRef = useRef132(0);
74702
- 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);
74703
74955
  projectIdRef.current = projectId;
74704
- const activeCompPathRef = useRef132(activeCompPath);
74956
+ const activeCompPathRef = useRef134(activeCompPath);
74705
74957
  activeCompPathRef.current = activeCompPath;
74706
74958
  const [reloadToken, setReloadToken] = useState108(0);
74707
- const reloadTokenRef = useRef132(reloadToken);
74959
+ const reloadTokenRef = useRef134(reloadToken);
74708
74960
  reloadTokenRef.current = reloadToken;
74709
74961
  useEffect104(
74710
74962
  () => addExternalFileReloadListener((changedPath) => {
@@ -74915,7 +75167,7 @@ async function deleteExternalConflictSnapshot(projectId, filePath) {
74915
75167
  }
74916
75168
 
74917
75169
  // src/hooks/useExternalFileChangeCoordinator.ts
74918
- 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";
74919
75171
  function testHotAdapter() {
74920
75172
  const value = globalThis.__HF_STUDIO_HOT_TEST_ADAPTER__;
74921
75173
  if (!value || typeof value !== "object") return null;
@@ -74967,13 +75219,13 @@ function useExternalFileChangeCoordinator({
74967
75219
  onAcceptedPersistedFileChange
74968
75220
  }) {
74969
75221
  const [blocked, setBlocked] = useState109(null);
74970
- const generationRef = useRef133(0);
74971
- const mountedRef = useRef133(true);
74972
- const lastEventIdentityRef = useRef133(null);
74973
- const blockedRef = useRef133(blocked);
74974
- const snapshotWriteTailRef = useRef133(Promise.resolve());
74975
- const drainingRef = useRef133(false);
74976
- 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);
74977
75229
  blockedRef.current = blocked;
74978
75230
  useEffect106(() => {
74979
75231
  mountedRef.current = true;
@@ -75330,7 +75582,7 @@ function useStudioExternalFileChanges({
75330
75582
  }
75331
75583
 
75332
75584
  // src/hooks/useBlockHandlers.ts
75333
- 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";
75334
75586
 
75335
75587
  // src/utils/blockInstaller.ts
75336
75588
  function getMaxZIndexFromIframe(iframe) {
@@ -75539,7 +75791,7 @@ function useBlockHandlers({
75539
75791
  blockCtxDeps.showToast
75540
75792
  ]
75541
75793
  );
75542
- const installingBlockRef = useRef134(false);
75794
+ const installingBlockRef = useRef136(false);
75543
75795
  const runBlockInstall = useCallback127(
75544
75796
  async (blockName, install) => {
75545
75797
  if (installingBlockRef.current) {
@@ -75650,26 +75902,9 @@ function useBlockHandlers({
75650
75902
  }
75651
75903
 
75652
75904
  // src/hooks/useAppHotkeys.ts
75653
- import { useCallback as useCallback128, useEffect as useEffect107, useRef as useRef135 } from "react";
75654
- function iframeContentWindow(iframe) {
75655
- try {
75656
- return iframe?.contentWindow ?? null;
75657
- } catch {
75658
- return null;
75659
- }
75660
- }
75661
- function safeAddListener(t, type, h, capture = false) {
75662
- try {
75663
- t?.addEventListener(type, h, capture);
75664
- } catch {
75665
- }
75666
- }
75667
- function safeRemoveListener(t, type, h) {
75668
- try {
75669
- t?.removeEventListener(type, h);
75670
- } catch {
75671
- }
75672
- }
75905
+ import { useCallback as useCallback128, useEffect as useEffect107, useRef as useRef137 } from "react";
75906
+
75907
+ // src/hooks/appHotkeysDispatch.ts
75673
75908
  function handleUndoRedoKey(event, onUndo, onRedo) {
75674
75909
  const key2 = event.key.toLowerCase();
75675
75910
  if (key2 === "z" && !event.shiftKey) {
@@ -75684,18 +75919,6 @@ function handleUndoRedoKey(event, onUndo, onRedo) {
75684
75919
  }
75685
75920
  return false;
75686
75921
  }
75687
- function tryApplyBeatHistory(direction, fileState, showToast) {
75688
- const ps = usePlayerStore.getState();
75689
- const beatStack = direction === "undo" ? ps.beatUndo : ps.beatRedo;
75690
- const beatAt = beatStack[beatStack.length - 1]?.at ?? null;
75691
- if (beatAt === null) return false;
75692
- const fileStack = fileState[direction];
75693
- const fileAt = fileStack[fileStack.length - 1]?.createdAt ?? null;
75694
- if (fileAt !== null && (direction === "undo" ? beatAt < fileAt : beatAt > fileAt)) return false;
75695
- const label = direction === "undo" ? ps.undoBeatEdits() : ps.redoBeatEdits();
75696
- if (label) showToast(`${direction === "undo" ? "Undid" : "Redid"} ${label}`, "info");
75697
- return true;
75698
- }
75699
75922
  function dispatchModifierKey(event, key2, cb) {
75700
75923
  if (!shouldIgnoreHistoryShortcut(event.target) && handleUndoRedoKey(
75701
75924
  event,
@@ -75787,6 +76010,14 @@ function dispatchPlainKey(event, key2, cb) {
75787
76010
  usePlayerStore.getState().setActiveTool("select");
75788
76011
  return;
75789
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
+ }
75790
76021
  if (event.key === "Escape") {
75791
76022
  const { activeTool, selectedElementId, setActiveTool, setSelectedElementId } = usePlayerStore.getState();
75792
76023
  if (activeTool === "razor") {
@@ -75832,6 +76063,39 @@ function dispatchPlainKey(event, key2, cb) {
75832
76063
  cb.onToggleRecording();
75833
76064
  }
75834
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
+ }
75835
76099
  function useAppHotkeys({
75836
76100
  handleTimelineElementsDelete,
75837
76101
  handleTimelineElementSplit,
@@ -75857,7 +76121,7 @@ function useAppHotkeys({
75857
76121
  activeCompPath,
75858
76122
  forceReloadSdkSession
75859
76123
  }) {
75860
- const previewHistoryCleanupRef = useRef135(null);
76124
+ const previewHistoryCleanupRef = useRef137(null);
75861
76125
  const readHistoryFile = useCallback128(
75862
76126
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
75863
76127
  [readOptionalProjectFile, readProjectFile]
@@ -75921,7 +76185,7 @@ function useAppHotkeys({
75921
76185
  );
75922
76186
  const handleUndo = useCallback128(() => applyHistory("undo"), [applyHistory]);
75923
76187
  const handleRedo = useCallback128(() => applyHistory("redo"), [applyHistory]);
75924
- const cbRef = useRef135(null);
76188
+ const cbRef = useRef137(null);
75925
76189
  cbRef.current = {
75926
76190
  handleTimelineElementsDelete,
75927
76191
  handleTimelineElementSplit,
@@ -76001,7 +76265,7 @@ function useAppHotkeys({
76001
76265
  }
76002
76266
 
76003
76267
  // src/hooks/useClipboard.ts
76004
- import { useCallback as useCallback129, useRef as useRef136 } from "react";
76268
+ import { useCallback as useCallback129, useRef as useRef138 } from "react";
76005
76269
 
76006
76270
  // src/utils/clipboardPayload.ts
76007
76271
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -76107,8 +76371,8 @@ function useClipboard({
76107
76371
  handleDomEditElementDelete,
76108
76372
  previewIframeRef
76109
76373
  }) {
76110
- const clipboardRef = useRef136(null);
76111
- const projectIdRef = useRef136(projectId);
76374
+ const clipboardRef = useRef138(null);
76375
+ const projectIdRef = useRef138(projectId);
76112
76376
  projectIdRef.current = projectId;
76113
76377
  const handleCopy2 = useCallback129(() => {
76114
76378
  const { selectedElementId, elements } = usePlayerStore.getState();
@@ -76234,7 +76498,7 @@ function useClipboard({
76234
76498
  }
76235
76499
 
76236
76500
  // src/hooks/useCaptionDetection.ts
76237
- import { useEffect as useEffect108, useRef as useRef137 } from "react";
76501
+ import { useEffect as useEffect108, useRef as useRef139 } from "react";
76238
76502
 
76239
76503
  // src/captions/types.ts
76240
76504
  var DEFAULT_STYLE = {
@@ -76486,7 +76750,7 @@ function useCaptionDetection({
76486
76750
  captionSync,
76487
76751
  setRightCollapsed
76488
76752
  }) {
76489
- const prevCompPathRef = useRef137(activeCompPath);
76753
+ const prevCompPathRef = useRef139(activeCompPath);
76490
76754
  useEffect108(() => {
76491
76755
  if (prevCompPathRef.current !== activeCompPath) {
76492
76756
  prevCompPathRef.current = activeCompPath;
@@ -76591,7 +76855,7 @@ import { useCallback as useCallback132 } from "react";
76591
76855
  import { createElement as createElement3 } from "react";
76592
76856
 
76593
76857
  // src/player/components/AudioWaveform.tsx
76594
- 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";
76595
76859
  import { jsx as jsx169, jsxs as jsxs141 } from "react/jsx-runtime";
76596
76860
  var BAR_WIDTH = 2;
76597
76861
  var BAR_STEP = 3;
@@ -76648,8 +76912,8 @@ var AudioWaveform = memo36(function AudioWaveform2({
76648
76912
  sessionEpoch,
76649
76913
  priority
76650
76914
  }) {
76651
- const canvasRef = useRef138(null);
76652
- const observerRef = useRef138(null);
76915
+ const canvasRef = useRef140(null);
76916
+ const observerRef = useRef140(null);
76653
76917
  const cacheKey = waveformUrl ?? audioUrl;
76654
76918
  const request = useMemo62(
76655
76919
  () => ({
@@ -76760,7 +77024,7 @@ var AudioWaveform = memo36(function AudioWaveform2({
76760
77024
  });
76761
77025
 
76762
77026
  // src/player/components/ImageThumbnail.tsx
76763
- 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";
76764
77028
  import { jsx as jsx170, jsxs as jsxs142 } from "react/jsx-runtime";
76765
77029
  var ImageThumbnail = memo37(function ImageThumbnail2({
76766
77030
  imageSrc,
@@ -76772,7 +77036,7 @@ var ImageThumbnail = memo37(function ImageThumbnail2({
76772
77036
  rich = false
76773
77037
  }) {
76774
77038
  const [containerWidth, setContainerWidth] = useState111(0);
76775
- const observerRef = useRef139(null);
77039
+ const observerRef = useRef141(null);
76776
77040
  const request = useMemo63(
76777
77041
  () => ({
76778
77042
  key: createThumbnailKey({ kind: "image", source: imageSrc, rich: Number(rich) }),
@@ -77021,10 +77285,10 @@ function useRenderClipContent({
77021
77285
  }
77022
77286
 
77023
77287
  // src/hooks/useConsoleErrorCapture.ts
77024
- 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";
77025
77289
  function useConsoleErrorCapture(previewIframe) {
77026
77290
  const [consoleErrors, setConsoleErrors] = useState112(null);
77027
- const consoleErrorsRef = useRef140([]);
77291
+ const consoleErrorsRef = useRef142([]);
77028
77292
  const resetErrors = useCallback133(() => {
77029
77293
  consoleErrorsRef.current = [];
77030
77294
  setConsoleErrors(null);
@@ -77094,7 +77358,7 @@ function useConsoleErrorCapture(previewIframe) {
77094
77358
  }
77095
77359
 
77096
77360
  // src/hooks/useFrameCapture.ts
77097
- 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";
77098
77362
  function useFrameCapture({
77099
77363
  projectId,
77100
77364
  activeCompPath,
@@ -77103,7 +77367,7 @@ function useFrameCapture({
77103
77367
  }) {
77104
77368
  const [captureFrameTime, setCaptureFrameTime] = useState113(0);
77105
77369
  const [capturing, setCapturing] = useState113(false);
77106
- const capturingRef = useRef141(false);
77370
+ const capturingRef = useRef143(false);
77107
77371
  useMountEffect(() => {
77108
77372
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
77109
77373
  return liveTime.subscribe(setCaptureFrameTime);
@@ -77188,7 +77452,7 @@ function useFrameCapture({
77188
77452
  }
77189
77453
 
77190
77454
  // src/hooks/useLintModal.ts
77191
- 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";
77192
77456
  function parseFinding(f) {
77193
77457
  return {
77194
77458
  severity: f.severity === "error" ? "error" : "warning",
@@ -77202,7 +77466,7 @@ function useLintModal(projectId, refreshKey) {
77202
77466
  const [lintModal, setLintModal] = useState114(null);
77203
77467
  const [linting, setLinting] = useState114(false);
77204
77468
  const [backgroundFindings, setBackgroundFindings] = useState114([]);
77205
- const autoLintRanRef = useRef142(false);
77469
+ const autoLintRanRef = useRef144(false);
77206
77470
  const runLint = useCallback135(
77207
77471
  async (opts) => {
77208
77472
  if (!projectId) return;
@@ -77229,7 +77493,7 @@ function useLintModal(projectId, refreshKey) {
77229
77493
  [projectId]
77230
77494
  );
77231
77495
  const handleLint = useCallback135(() => runLint(), [runLint]);
77232
- const prevProjectIdRef = useRef142(projectId);
77496
+ const prevProjectIdRef = useRef144(projectId);
77233
77497
  useEffect110(() => {
77234
77498
  if (projectId !== prevProjectIdRef.current) {
77235
77499
  autoLintRanRef.current = false;
@@ -77277,14 +77541,14 @@ function useLintModal(projectId, refreshKey) {
77277
77541
  }
77278
77542
 
77279
77543
  // src/hooks/useToast.ts
77280
- 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";
77281
77545
  var AUTO_DISMISS_MS2 = 4e3;
77282
77546
  var EXIT_MS2 = 160;
77283
77547
  var MAX_TOASTS = 3;
77284
77548
  var nextToastId = 1;
77285
77549
  function useToast() {
77286
77550
  const [toasts, setToasts] = useState115([]);
77287
- const timersRef = useRef143(/* @__PURE__ */ new Map());
77551
+ const timersRef = useRef145(/* @__PURE__ */ new Map());
77288
77552
  const clearTimer = useCallback136((id) => {
77289
77553
  const timer = timersRef.current.get(id);
77290
77554
  if (timer) {
@@ -77333,41 +77597,17 @@ function useToast() {
77333
77597
  return { toasts, showToast, dismissToast };
77334
77598
  }
77335
77599
 
77336
- // src/hooks/useCompositionContentLoader.ts
77337
- import { useCallback as useCallback137 } from "react";
77338
- function useCompositionContentLoader({
77339
- projectId,
77340
- setEditingFile,
77341
- setActiveCompPath,
77342
- showToast
77343
- }) {
77344
- return useCallback137(
77345
- (comp) => {
77346
- if (!projectId) return;
77347
- setActiveCompPath(comp.endsWith(".html") ? comp : null);
77348
- setEditingFile({ path: comp, content: null });
77349
- fetch(buildProjectApiPath(projectId, `/files/${encodeURIComponent(comp)}`)).then(async (r) => {
77350
- if (!r.ok) throw new Error(`Failed to load ${comp} (${r.status})`);
77351
- return r.json();
77352
- }).then((data) => {
77353
- if (typeof data.content !== "string") throw new Error(`No content returned for ${comp}`);
77354
- setEditingFile({ path: comp, content: data.content });
77355
- }).catch((err) => {
77356
- showToast(err instanceof Error ? err.message : `Failed to load ${comp}`, "error");
77357
- });
77358
- },
77359
- [projectId, setEditingFile, setActiveCompPath, showToast]
77360
- );
77361
- }
77362
-
77363
77600
  // src/hooks/useStudioUrlState.ts
77364
- 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";
77365
77602
 
77366
77603
  // src/utils/studioUrlState.ts
77367
77604
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
77368
- function resolveMasterCompositionPath(fileTree) {
77369
- if (fileTree.includes("index.html")) return "index.html";
77370
- 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;
77371
77611
  }
77372
77612
  function normalizeStudioUrlPanelTab(tab) {
77373
77613
  if (!tab) return null;
@@ -77590,14 +77830,14 @@ function useStudioUrlState({
77590
77830
  initialState: initialState2
77591
77831
  }) {
77592
77832
  const currentTime = usePlayerStore((s) => s.currentTime);
77593
- const hydratedSeekRef = useRef144(initialState2.currentTime == null);
77594
- const hydratedInitialTimeRef = useRef144(initialState2.currentTime == null);
77595
- 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);
77596
77836
  const [selectionHydrated, setSelectionHydrated] = useState116(initialState2.selection == null);
77597
- const pendingSelectionRef = useRef144(initialState2.selection);
77598
- const stableTimeRef = useRef144(initialState2.currentTime);
77599
- const selectionApplySeqRef = useRef144(0);
77600
- const buildUrlState = useCallback138(
77837
+ const pendingSelectionRef = useRef146(initialState2.selection);
77838
+ const stableTimeRef = useRef146(initialState2.currentTime);
77839
+ const selectionApplySeqRef = useRef146(0);
77840
+ const buildUrlState = useCallback137(
77601
77841
  () => ({
77602
77842
  activeCompPath,
77603
77843
  currentTime: stableTimeRef.current,
@@ -77608,7 +77848,7 @@ function useStudioUrlState({
77608
77848
  }),
77609
77849
  [activeCompPath, domEditGroupSelections, domEditSelection, rightCollapsed, rightPanelTab]
77610
77850
  );
77611
- const applyUrlSelection = useCallback138(
77851
+ const applyUrlSelection = useCallback137(
77612
77852
  (selection) => {
77613
77853
  const applySeq = ++selectionApplySeqRef.current;
77614
77854
  if (!selection) {
@@ -77753,7 +77993,7 @@ function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
77753
77993
  }
77754
77994
 
77755
77995
  // src/hooks/useStudioContextValue.ts
77756
- 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";
77757
77997
  function buildStudioContextValue(input) {
77758
77998
  return {
77759
77999
  projectId: input.projectId,
@@ -77812,22 +78052,22 @@ function useInspectorState(rightPanelTab, rightInspectorPanes, rightCollapsed, i
77812
78052
  }
77813
78053
  function useDragOverlay(onImportFiles) {
77814
78054
  const [active, setActive] = useState117(false);
77815
- const counterRef = useRef145(0);
77816
- const onDragOver = useCallback139((e) => {
78055
+ const counterRef = useRef147(0);
78056
+ const onDragOver = useCallback138((e) => {
77817
78057
  if (!e.dataTransfer.types.includes("Files")) return;
77818
78058
  e.preventDefault();
77819
78059
  }, []);
77820
- const onDragEnter = useCallback139((e) => {
78060
+ const onDragEnter = useCallback138((e) => {
77821
78061
  if (!e.dataTransfer.types.includes("Files")) return;
77822
78062
  e.preventDefault();
77823
78063
  counterRef.current++;
77824
78064
  setActive(true);
77825
78065
  }, []);
77826
- const onDragLeave = useCallback139(() => {
78066
+ const onDragLeave = useCallback138(() => {
77827
78067
  counterRef.current--;
77828
78068
  if (counterRef.current === 0) setActive(false);
77829
78069
  }, []);
77830
- const onDrop = useCallback139(
78070
+ const onDrop = useCallback138(
77831
78071
  (e) => {
77832
78072
  counterRef.current = 0;
77833
78073
  setActive(false);
@@ -77840,7 +78080,7 @@ function useDragOverlay(onImportFiles) {
77840
78080
  return { active, onDragOver, onDragEnter, onDragLeave, onDrop };
77841
78081
  }
77842
78082
  function useGlobalFileDrop(handleTimelineFileDrop) {
77843
- const onDrop = useCallback139(
78083
+ const onDrop = useCallback138(
77844
78084
  (files) => {
77845
78085
  const start = usePlayerStore.getState().currentTime;
77846
78086
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -77851,7 +78091,7 @@ function useGlobalFileDrop(handleTimelineFileDrop) {
77851
78091
  }
77852
78092
 
77853
78093
  // src/components/StudioHeader.tsx
77854
- import { useRef as useRef147 } from "react";
78094
+ import { useRef as useRef149 } from "react";
77855
78095
 
77856
78096
  // src/contexts/PanelLayoutContext.tsx
77857
78097
  import { useContext as useContext8, useMemo as useMemo67 } from "react";
@@ -77929,11 +78169,11 @@ function PanelLayoutProvider({
77929
78169
 
77930
78170
  // src/contexts/ViewModeContext.tsx
77931
78171
  import {
77932
- useCallback as useCallback140,
78172
+ useCallback as useCallback139,
77933
78173
  useContext as useContext9,
77934
78174
  useEffect as useEffect112,
77935
78175
  useMemo as useMemo68,
77936
- useRef as useRef146,
78176
+ useRef as useRef148,
77937
78177
  useState as useState118
77938
78178
  } from "react";
77939
78179
  import { jsx as jsx172 } from "react/jsx-runtime";
@@ -77961,9 +78201,9 @@ function readHistoryLocation() {
77961
78201
  }
77962
78202
  function useViewModeState() {
77963
78203
  const [viewMode, setMode] = useState118(() => readViewModeFromUrl());
77964
- const guardsRef = useRef146(/* @__PURE__ */ new Set());
77965
- const acceptedLocationRef = useRef146(readHistoryLocation());
77966
- const canSetViewMode = useCallback140((mode) => {
78204
+ const guardsRef = useRef148(/* @__PURE__ */ new Set());
78205
+ const acceptedLocationRef = useRef148(readHistoryLocation());
78206
+ const canSetViewMode = useCallback139((mode) => {
77967
78207
  for (const guard of guardsRef.current) {
77968
78208
  if (!guard(mode)) return false;
77969
78209
  }
@@ -77985,7 +78225,7 @@ function useViewModeState() {
77985
78225
  window.addEventListener("popstate", onPopState);
77986
78226
  return () => window.removeEventListener("popstate", onPopState);
77987
78227
  }, [canSetViewMode, viewMode]);
77988
- const setViewMode = useCallback140(
78228
+ const setViewMode = useCallback139(
77989
78229
  (mode) => {
77990
78230
  if (!canSetViewMode(mode)) return false;
77991
78231
  setMode(mode);
@@ -77995,7 +78235,7 @@ function useViewModeState() {
77995
78235
  },
77996
78236
  [canSetViewMode]
77997
78237
  );
77998
- const registerViewModeGuard = useCallback140((guard) => {
78238
+ const registerViewModeGuard = useCallback139((guard) => {
77999
78239
  guardsRef.current.add(guard);
78000
78240
  return () => {
78001
78241
  guardsRef.current.delete(guard);
@@ -78209,7 +78449,7 @@ var VIEW_MODE_OPTIONS = [
78209
78449
  ];
78210
78450
  function ViewModeToggle() {
78211
78451
  const { viewMode, setViewMode } = useViewMode();
78212
- const tabRefs = useRef147([]);
78452
+ const tabRefs = useRef149([]);
78213
78453
  const selectMode = (mode) => {
78214
78454
  if (mode === viewMode) return;
78215
78455
  if (setViewMode(mode)) trackStudioEvent("view_mode_toggle", { mode });
@@ -78436,10 +78676,10 @@ function StudioHeader({
78436
78676
  }
78437
78677
 
78438
78678
  // src/hooks/useGestureCommit.ts
78439
- 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";
78440
78680
 
78441
78681
  // src/hooks/useGestureRecording.ts
78442
- 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";
78443
78683
  function readBasePosition(element, iframeEl) {
78444
78684
  let baseOpacity = 1;
78445
78685
  let baseScale = 1;
@@ -78584,10 +78824,10 @@ function releaseRuntimePreview(r) {
78584
78824
  function useGestureRecording() {
78585
78825
  const [isRecording, setIsRecording] = useState119(false);
78586
78826
  const [recordingDuration, setRecordingDuration] = useState119(0);
78587
- const isRecordingRef = useRef148(false);
78588
- const refs = useRef148(createRecordingRefs());
78589
- const samplesRef = useRef148(refs.current.samples);
78590
- 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);
78591
78831
  useEffect113(() => {
78592
78832
  const r = refs.current;
78593
78833
  return () => {
@@ -78597,7 +78837,7 @@ function useGestureRecording() {
78597
78837
  r.cleanup = null;
78598
78838
  };
78599
78839
  }, []);
78600
- const startRecording = useCallback141(
78840
+ const startRecording = useCallback140(
78601
78841
  (element, iframeEl, elementEndTime) => {
78602
78842
  if (isRecordingRef.current) return;
78603
78843
  isRecordingRef.current = true;
@@ -78697,7 +78937,7 @@ function useGestureRecording() {
78697
78937
  []
78698
78938
  // No deps — uses refs only for all mutable state
78699
78939
  );
78700
- const stopRecording = useCallback141(() => {
78940
+ const stopRecording = useCallback140(() => {
78701
78941
  if (!isRecordingRef.current) return [];
78702
78942
  isRecordingRef.current = false;
78703
78943
  const r = refs.current;
@@ -78709,7 +78949,7 @@ function useGestureRecording() {
78709
78949
  setIsRecording(false);
78710
78950
  return frozen;
78711
78951
  }, []);
78712
- const clearSamples = useCallback141(() => {
78952
+ const clearSamples = useCallback140(() => {
78713
78953
  const r = refs.current;
78714
78954
  r.samples = [];
78715
78955
  r.trail = [];
@@ -78966,13 +79206,13 @@ function useGestureCommit({
78966
79206
  }) {
78967
79207
  const gestureRecording = useGestureRecording();
78968
79208
  const [gestureState, setGestureState] = useState120("idle");
78969
- const gestureStateRef = useRef149("idle");
78970
- const recordingAutoStopRef = useRef149(void 0);
78971
- const recordingStartTimeRef = useRef149(0);
78972
- const commitInFlightRef = useRef149(false);
78973
- 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);
78974
79214
  useEffect114(() => () => clearInterval(recordingAutoStopRef.current), []);
78975
- const stopAndCommitRecording = useCallback142(async () => {
79215
+ const stopAndCommitRecording = useCallback141(async () => {
78976
79216
  clearInterval(recordingAutoStopRef.current);
78977
79217
  if (commitInFlightRef.current) {
78978
79218
  return;
@@ -79145,7 +79385,7 @@ function useGestureCommit({
79145
79385
  commitInFlightRef.current = false;
79146
79386
  }
79147
79387
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
79148
- const handleToggleRecording = useCallback142(() => {
79388
+ const handleToggleRecording = useCallback141(() => {
79149
79389
  if (gestureStateRef.current === "recording") {
79150
79390
  void stopAndCommitRecording();
79151
79391
  return;
@@ -79303,20 +79543,21 @@ var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
79303
79543
  });
79304
79544
 
79305
79545
  // src/components/StudioLeftSidebar.tsx
79306
- import { useCallback as useCallback151 } from "react";
79546
+ import { useCallback as useCallback150 } from "react";
79307
79547
 
79308
79548
  // src/components/sidebar/LeftSidebar.tsx
79309
79549
  import {
79310
79550
  memo as memo42,
79311
79551
  useState as useState130,
79312
- useCallback as useCallback150,
79552
+ useCallback as useCallback149,
79313
79553
  useImperativeHandle,
79314
- useRef as useRef157,
79554
+ useMemo as useMemo72,
79555
+ useRef as useRef159,
79315
79556
  forwardRef as forwardRef3
79316
79557
  } from "react";
79317
79558
 
79318
79559
  // src/components/sidebar/CompositionsTab.tsx
79319
- 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";
79320
79561
  import { jsx as jsx175, jsxs as jsxs145 } from "react/jsx-runtime";
79321
79562
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
79322
79563
  var CARD_W = 80;
@@ -79378,6 +79619,7 @@ function CompCard({
79378
79619
  projectId,
79379
79620
  comp,
79380
79621
  isActive,
79622
+ isRoot,
79381
79623
  onSelect,
79382
79624
  onRender,
79383
79625
  isRendering,
@@ -79389,11 +79631,11 @@ function CompCard({
79389
79631
  const [stageSize, setStageSize] = useState121(DEFAULT_PREVIEW_STAGE);
79390
79632
  const [livePreviewLoaded, setLivePreviewLoaded] = useState121(false);
79391
79633
  const [failedThumbnailUrl, setFailedThumbnailUrl] = useState121(null);
79392
- const iframeRef = useRef150(null);
79393
- const hoverTimer = useRef150(null);
79394
- const syncTimer = useRef150(null);
79395
- const draggedRef = useRef150(false);
79396
- 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) => {
79397
79639
  if (syncTimer.current) {
79398
79640
  clearTimeout(syncTimer.current);
79399
79641
  syncTimer.current = null;
@@ -79532,6 +79774,15 @@ function CompCard({
79532
79774
  children: [
79533
79775
  /* @__PURE__ */ jsxs145("div", { className: "flex items-center gap-1", children: [
79534
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
+ ),
79535
79786
  lintInfo && lintInfo.count > 0 && /* @__PURE__ */ jsx175(
79536
79787
  "span",
79537
79788
  {
@@ -79599,6 +79850,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79599
79850
  projectId,
79600
79851
  compositions,
79601
79852
  activeComposition,
79853
+ masterCompositionPath = null,
79602
79854
  onSelect,
79603
79855
  onRenderComposition,
79604
79856
  onAddToTimeline,
@@ -79615,6 +79867,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79615
79867
  projectId,
79616
79868
  comp,
79617
79869
  isActive: activeComposition === comp,
79870
+ isRoot: comp === masterCompositionPath,
79618
79871
  onSelect: () => onSelect(comp),
79619
79872
  onRender: onRenderComposition ? () => onRenderComposition(comp) : void 0,
79620
79873
  onAddToTimeline: onAddToTimeline ? () => onAddToTimeline(comp) : void 0,
@@ -79627,7 +79880,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
79627
79880
  });
79628
79881
 
79629
79882
  // src/components/sidebar/AssetsTab.tsx
79630
- 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";
79631
79884
 
79632
79885
  // src/components/ui/SearchInput.tsx
79633
79886
  import { jsx as jsx176, jsxs as jsxs146 } from "react/jsx-runtime";
@@ -79738,11 +79991,11 @@ var CATEGORY_LABELS = {
79738
79991
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
79739
79992
 
79740
79993
  // src/components/sidebar/AudioRow.tsx
79741
- 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";
79742
79995
  import { classifyWebAudioMediaRoute } from "@hyperframes/core/runtime/web-audio-route";
79743
79996
 
79744
79997
  // src/components/sidebar/AssetContextMenu.tsx
79745
- 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";
79746
79999
  import { Fragment as Fragment51, jsx as jsx177, jsxs as jsxs147 } from "react/jsx-runtime";
79747
80000
  function isValidAssetName(name) {
79748
80001
  return name.length > 0 && !/[/\\]/.test(name) && !name.includes("..");
@@ -79757,7 +80010,7 @@ function ContextMenu2({
79757
80010
  onRename,
79758
80011
  onAddAtPlayhead
79759
80012
  }) {
79760
- const menuRef = useRef151(null);
80013
+ const menuRef = useRef153(null);
79761
80014
  const [pos, setPos] = useState122({ x, y });
79762
80015
  const [mode, setMode] = useState122("menu");
79763
80016
  const [renameDraft, setRenameDraft] = useState122(() => filename(asset));
@@ -79802,7 +80055,7 @@ function ContextMenu2({
79802
80055
  menuRef.current?.querySelector('[role="menuitem"]')?.focus();
79803
80056
  }
79804
80057
  }, [mode]);
79805
- const commitRename = useCallback144(() => {
80058
+ const commitRename = useCallback143(() => {
79806
80059
  const trimmed = renameDraft.trim();
79807
80060
  if (trimmed === filename(asset)) {
79808
80061
  onClose();
@@ -80008,28 +80261,28 @@ function AudioRow({
80008
80261
  const [contextMenu, setContextMenu] = useState123(null);
80009
80262
  const [playing, setPlaying] = useState123(false);
80010
80263
  const [bars, setBars] = useState123([]);
80011
- const audioRef = useRef152(null);
80012
- const actxRef = useRef152(null);
80013
- const analyserRef = useRef152(null);
80014
- const sourceRef = useRef152(null);
80015
- 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);
80016
80269
  const name = basename2(asset);
80017
80270
  const subtype = getAudioSubtype(asset);
80018
80271
  const serveUrl = resolveMediaPreviewUrl(asset, projectId);
80019
80272
  const isCopied = copyFeedback?.path === asset && copyFeedback.ok;
80020
80273
  const copyFailed = copyFeedback?.path === asset && !copyFeedback.ok;
80021
- const stopPlayback = useCallback145(() => {
80274
+ const stopPlayback = useCallback144(() => {
80022
80275
  audioRef.current?.pause();
80023
80276
  setPlaying(false);
80024
80277
  cancelAnimationFrame(animRef.current);
80025
80278
  }, []);
80026
- const pointerDownRef = useRef152(null);
80279
+ const pointerDownRef = useRef154(null);
80027
80280
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
80028
80281
  const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
80029
80282
  const elements = usePlayerStore((s) => s.elements);
80030
80283
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
80031
80284
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
80032
- const activateRow = useCallback145(() => {
80285
+ const activateRow = useCallback144(() => {
80033
80286
  if (used) {
80034
80287
  const clip = findClipForAsset(elements, asset);
80035
80288
  if (clip) {
@@ -80051,10 +80304,10 @@ function AudioRow({
80051
80304
  setPreviewAsset,
80052
80305
  clearPreviewAsset
80053
80306
  ]);
80054
- const handlePointerDown = useCallback145((e) => {
80307
+ const handlePointerDown = useCallback144((e) => {
80055
80308
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
80056
80309
  }, []);
80057
- const handlePointerUp = useCallback145(
80310
+ const handlePointerUp = useCallback144(
80058
80311
  (e) => {
80059
80312
  const origin = pointerDownRef.current;
80060
80313
  pointerDownRef.current = null;
@@ -80100,7 +80353,7 @@ function AudioRow({
80100
80353
  }
80101
80354
  return () => cancelAnimationFrame(animRef.current);
80102
80355
  }, [playing]);
80103
- const togglePlay = useCallback145(async () => {
80356
+ const togglePlay = useCallback144(async () => {
80104
80357
  if (playing) {
80105
80358
  stopPlayback();
80106
80359
  if (stopCurrentPreview === stopPlayback) stopCurrentPreview = null;
@@ -80298,7 +80551,7 @@ function GlobalAssetsView({ searchQuery }) {
80298
80551
  }
80299
80552
 
80300
80553
  // src/components/sidebar/AssetCard.tsx
80301
- 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";
80302
80555
 
80303
80556
  // src/components/ui/VideoFrameThumbnail.tsx
80304
80557
  import { useState as useState125, useEffect as useEffect119 } from "react";
@@ -80436,16 +80689,16 @@ function AssetCard({
80436
80689
  const probedDuration = useProbedDuration(serveUrl, !isVideo || duration != null);
80437
80690
  const resolvedDuration = duration ?? probedDuration ?? void 0;
80438
80691
  const durationLabel = formatDuration(resolvedDuration ?? 0);
80439
- const pointerDownRef = useRef153(null);
80692
+ const pointerDownRef = useRef155(null);
80440
80693
  const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
80441
80694
  const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
80442
80695
  const elements = usePlayerStore((s) => s.elements);
80443
80696
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
80444
80697
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
80445
- const handlePointerDown = useCallback146((e) => {
80698
+ const handlePointerDown = useCallback145((e) => {
80446
80699
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
80447
80700
  }, []);
80448
- const activateCard = useCallback146(() => {
80701
+ const activateCard = useCallback145(() => {
80449
80702
  if (used) {
80450
80703
  const clip = findClipForAsset(elements, asset);
80451
80704
  if (clip) {
@@ -80467,7 +80720,7 @@ function AssetCard({
80467
80720
  setPreviewAsset,
80468
80721
  clearPreviewAsset
80469
80722
  ]);
80470
- const handlePointerUp = useCallback146(
80723
+ const handlePointerUp = useCallback145(
80471
80724
  (e) => {
80472
80725
  const origin = pointerDownRef.current;
80473
80726
  pointerDownRef.current = null;
@@ -80761,7 +81014,7 @@ var AssetsTab = memo40(function AssetsTab2({
80761
81014
  onRename,
80762
81015
  onAddAssetToTimeline
80763
81016
  }) {
80764
- const fileInputRef = useRef154(null);
81017
+ const fileInputRef = useRef156(null);
80765
81018
  const [dragOver, setDragOver] = useState127(false);
80766
81019
  const [copyFeedback, setCopyFeedback] = useState127(null);
80767
81020
  const [importing, setImporting] = useState127(false);
@@ -80770,7 +81023,7 @@ var AssetsTab = memo40(function AssetsTab2({
80770
81023
  const [searchQuery, setSearchQuery] = useState127("");
80771
81024
  const [viewMode, setViewMode] = useState127("local");
80772
81025
  const [manifest, setManifest] = useState127(/* @__PURE__ */ new Map());
80773
- const manifest404Ref = useRef154(/* @__PURE__ */ new Set());
81026
+ const manifest404Ref = useRef156(/* @__PURE__ */ new Set());
80774
81027
  const assetsKey = assets.join("|");
80775
81028
  useEffect121(() => {
80776
81029
  if (manifest404Ref.current.has(projectId)) return;
@@ -80799,7 +81052,7 @@ var AssetsTab = memo40(function AssetsTab2({
80799
81052
  cancelled = true;
80800
81053
  };
80801
81054
  }, [projectId, assetsKey]);
80802
- const handleImport = useCallback147(
81055
+ const handleImport = useCallback146(
80803
81056
  async (files) => {
80804
81057
  if (!onImport) return;
80805
81058
  setImporting(true);
@@ -80811,7 +81064,7 @@ var AssetsTab = memo40(function AssetsTab2({
80811
81064
  },
80812
81065
  [onImport]
80813
81066
  );
80814
- const handleDrop = useCallback147(
81067
+ const handleDrop = useCallback146(
80815
81068
  (e) => {
80816
81069
  e.preventDefault();
80817
81070
  setDragOver(false);
@@ -80819,7 +81072,7 @@ var AssetsTab = memo40(function AssetsTab2({
80819
81072
  },
80820
81073
  [handleImport]
80821
81074
  );
80822
- const handleCopyPath = useCallback147(async (path) => {
81075
+ const handleCopyPath = useCallback146(async (path) => {
80823
81076
  const copied = await copyTextToClipboard(path);
80824
81077
  setCopyFeedback({ path, ok: copied });
80825
81078
  setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
@@ -81029,11 +81282,11 @@ var AssetsTab = memo40(function AssetsTab2({
81029
81282
  });
81030
81283
 
81031
81284
  // src/components/sidebar/BlocksTab.tsx
81032
- 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";
81033
81286
  import { createPortal as createPortal9 } from "react-dom";
81034
81287
 
81035
81288
  // src/components/sidebar/PromptPreviewModal.tsx
81036
- 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";
81037
81290
  import { jsx as jsx183, jsxs as jsxs152 } from "react/jsx-runtime";
81038
81291
  function PromptPreviewModal({
81039
81292
  title,
@@ -81043,9 +81296,9 @@ function PromptPreviewModal({
81043
81296
  const [value, setValue] = useState128(prompt);
81044
81297
  const [copyState, setCopyState] = useState128("idle");
81045
81298
  const [entered, setEntered] = useState128(false);
81046
- const containerRef = useRef155(null);
81047
- const textareaRef = useRef155(null);
81048
- const valueRef = useRef155(value);
81299
+ const containerRef = useRef157(null);
81300
+ const textareaRef = useRef157(null);
81301
+ const valueRef = useRef157(value);
81049
81302
  valueRef.current = value;
81050
81303
  const { requestClose } = useDialogBehavior({
81051
81304
  open: true,
@@ -81059,7 +81312,7 @@ function PromptPreviewModal({
81059
81312
  textareaRef.current?.focus();
81060
81313
  });
81061
81314
  }, []);
81062
- const handleCopy2 = useCallback148(async () => {
81315
+ const handleCopy2 = useCallback147(async () => {
81063
81316
  try {
81064
81317
  await navigator.clipboard.writeText(valueRef.current);
81065
81318
  setCopyState("copied");
@@ -81342,16 +81595,16 @@ function BlockCard({
81342
81595
  }) {
81343
81596
  const [hovered, setHovered] = useState129(false);
81344
81597
  const [addState, setAddState] = useState129("idle");
81345
- const hoverTimer = useRef156(null);
81598
+ const hoverTimer = useRef158(null);
81346
81599
  const colors = getCategoryColors(category);
81347
81600
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
81348
- const handleEnter = useCallback149(() => {
81601
+ const handleEnter = useCallback148(() => {
81349
81602
  hoverTimer.current = setTimeout(() => {
81350
81603
  setHovered(true);
81351
81604
  onPreview?.({ videoUrl, posterUrl, title });
81352
81605
  }, 300);
81353
81606
  }, [onPreview, videoUrl, posterUrl, title]);
81354
- const handleLeave = useCallback149(() => {
81607
+ const handleLeave = useCallback148(() => {
81355
81608
  if (hoverTimer.current) {
81356
81609
  clearTimeout(hoverTimer.current);
81357
81610
  hoverTimer.current = null;
@@ -81364,7 +81617,7 @@ function BlockCard({
81364
81617
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
81365
81618
  };
81366
81619
  }, []);
81367
- const handleAdd = useCallback149(
81620
+ const handleAdd = useCallback148(
81368
81621
  async (e) => {
81369
81622
  e.stopPropagation();
81370
81623
  if (addState !== "idle" || !onAdd) return;
@@ -81380,7 +81633,7 @@ function BlockCard({
81380
81633
  [onAdd, addState]
81381
81634
  );
81382
81635
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
81383
- const handleShowPrompt = useCallback149(
81636
+ const handleShowPrompt = useCallback148(
81384
81637
  (e) => {
81385
81638
  e.stopPropagation();
81386
81639
  const state = usePlayerStore.getState();
@@ -81572,10 +81825,14 @@ var LeftSidebar = memo42(
81572
81825
  onAddCompositionToTimeline
81573
81826
  }, ref) {
81574
81827
  const [tab, setTab] = useState130(getPersistedTab);
81575
- const tabRef = useRef157(tab);
81828
+ const tabRef = useRef159(tab);
81576
81829
  tabRef.current = tab;
81577
- const tablistRef = useRef157(null);
81578
- const selectTab = useCallback150((t) => {
81830
+ const tablistRef = useRef159(null);
81831
+ const masterCompositionPath = useMemo72(
81832
+ () => resolveMasterCompositionPath(compositions),
81833
+ [compositions]
81834
+ );
81835
+ const selectTab = useCallback149((t) => {
81579
81836
  setTab(t);
81580
81837
  try {
81581
81838
  safeLocalStorage()?.setItem(STORAGE_KEY, t);
@@ -81583,9 +81840,9 @@ var LeftSidebar = memo42(
81583
81840
  }
81584
81841
  trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
81585
81842
  }, []);
81586
- const getTab = useCallback150(() => tabRef.current, []);
81843
+ const getTab = useCallback149(() => tabRef.current, []);
81587
81844
  useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
81588
- const handleTablistKeyDown = useCallback150(
81845
+ const handleTablistKeyDown = useCallback149(
81589
81846
  (e) => {
81590
81847
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
81591
81848
  e.preventDefault();
@@ -81670,6 +81927,7 @@ var LeftSidebar = memo42(
81670
81927
  projectId,
81671
81928
  compositions,
81672
81929
  activeComposition,
81930
+ masterCompositionPath,
81673
81931
  onSelect: onSelectComposition,
81674
81932
  onAddToTimeline: onAddCompositionToTimeline,
81675
81933
  onRenderComposition,
@@ -81942,7 +82200,7 @@ function StudioLeftSidebar({
81942
82200
  handleImportFiles,
81943
82201
  handleContentChange
81944
82202
  } = useFileManagerContext();
81945
- const handleRenderComposition = useCallback151(
82203
+ const handleRenderComposition = useCallback150(
81946
82204
  async (comp) => {
81947
82205
  if (renderQueue.ffmpegMissing) {
81948
82206
  setRightPanelTab("renders");
@@ -82067,13 +82325,13 @@ function StudioLeftSidebar({
82067
82325
  }
82068
82326
 
82069
82327
  // src/components/StudioRightPanel.tsx
82070
- import { useCallback as useCallback168 } from "react";
82328
+ import { useCallback as useCallback167 } from "react";
82071
82329
 
82072
82330
  // src/components/editor/LayersPanel.tsx
82073
- 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";
82074
82332
 
82075
82333
  // src/components/editor/useLayerDrag.ts
82076
- 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";
82077
82335
  var DRAG_THRESHOLD_PX3 = 4;
82078
82336
  function isLayerDraggable(layer) {
82079
82337
  if (!(layer.selector || layer.id)) return false;
@@ -82140,10 +82398,10 @@ function useLayerDrag({
82140
82398
  onReorder,
82141
82399
  onSingleSibling
82142
82400
  }) {
82143
- const dragRef = useRef158(null);
82401
+ const dragRef = useRef160(null);
82144
82402
  const [dragKey, setDragKey] = useState132(null);
82145
82403
  const [insertionLineY, setInsertionLineY] = useState132(null);
82146
- const handleRowPointerDown = useCallback152(
82404
+ const handleRowPointerDown = useCallback151(
82147
82405
  (layerIndex, e) => {
82148
82406
  if (e.button !== 0) return;
82149
82407
  const layer = visibleLayers[layerIndex];
@@ -82170,7 +82428,7 @@ function useLayerDrag({
82170
82428
  },
82171
82429
  [visibleLayers, scrollContainerRef, onSingleSibling]
82172
82430
  );
82173
- const handleContainerPointerMove = useCallback152(
82431
+ const handleContainerPointerMove = useCallback151(
82174
82432
  (e) => {
82175
82433
  const drag = dragRef.current;
82176
82434
  if (!drag) return;
@@ -82196,7 +82454,7 @@ function useLayerDrag({
82196
82454
  },
82197
82455
  [visibleLayers, scrollContainerRef]
82198
82456
  );
82199
- const handleContainerPointerUp = useCallback152(() => {
82457
+ const handleContainerPointerUp = useCallback151(() => {
82200
82458
  const drag = dragRef.current;
82201
82459
  dragRef.current = null;
82202
82460
  setDragKey(null);
@@ -82346,15 +82604,15 @@ var LayersPanel = memo43(function LayersPanel2() {
82346
82604
  } = useDomEditContext();
82347
82605
  const [layers, setLayers] = useState133([]);
82348
82606
  const [collapsed, setCollapsed] = useState133({});
82349
- const prevDocVersionRef = useRef159(0);
82350
- const scrollContainerRef = useRef159(null);
82607
+ const prevDocVersionRef = useRef161(0);
82608
+ const scrollContainerRef = useRef161(null);
82351
82609
  const mirrorLayerReorderToTimeline = useLayerReorderTimelineMirror();
82352
82610
  const { scheduleReveal } = useLayerRevealOverride({
82353
82611
  isPlaying,
82354
82612
  selectedElement: domEditSelection?.element ?? null
82355
82613
  });
82356
82614
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
82357
- const collectLayers = useCallback153(() => {
82615
+ const collectLayers = useCallback152(() => {
82358
82616
  const iframe = previewIframeRef.current;
82359
82617
  if (!iframe) return;
82360
82618
  let doc = null;
@@ -82403,7 +82661,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82403
82661
  throttle.cancel();
82404
82662
  };
82405
82663
  }, [collectLayers]);
82406
- const resolveSelection = useCallback153(
82664
+ const resolveSelection = useCallback152(
82407
82665
  (layer) => {
82408
82666
  let el = layer.element;
82409
82667
  if (!el.isConnected) {
@@ -82423,7 +82681,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82423
82681
  },
82424
82682
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
82425
82683
  );
82426
- const seekToLayer = useCallback153(
82684
+ const seekToLayer = useCallback152(
82427
82685
  async (layer) => {
82428
82686
  const selection = await resolveSelection(layer);
82429
82687
  if (!selection) return;
@@ -82452,7 +82710,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82452
82710
  },
82453
82711
  [currentTime, resolveSelection, timelineElements]
82454
82712
  );
82455
- const handleSelectLayer = useCallback153(
82713
+ const handleSelectLayer = useCallback152(
82456
82714
  async (layer) => {
82457
82715
  const selection = await resolveSelection(layer);
82458
82716
  if (!selection) return;
@@ -82462,7 +82720,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82462
82720
  },
82463
82721
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
82464
82722
  );
82465
- const handleLayerDoubleClick = useCallback153(
82723
+ const handleLayerDoubleClick = useCallback152(
82466
82724
  async (layer) => {
82467
82725
  const selection = await resolveSelection(layer);
82468
82726
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -82473,7 +82731,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82473
82731
  },
82474
82732
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
82475
82733
  );
82476
- const handleLayerHover = useCallback153(
82734
+ const handleLayerHover = useCallback152(
82477
82735
  async (layer) => {
82478
82736
  if (!layer) {
82479
82737
  updateDomEditHoverSelection(null);
@@ -82484,11 +82742,11 @@ var LayersPanel = memo43(function LayersPanel2() {
82484
82742
  },
82485
82743
  [resolveSelection, updateDomEditHoverSelection]
82486
82744
  );
82487
- const toggleCollapse = useCallback153((key2, e) => {
82745
+ const toggleCollapse = useCallback152((key2, e) => {
82488
82746
  e.stopPropagation();
82489
82747
  setCollapsed((prev) => ({ ...prev, [key2]: !prev[key2] }));
82490
82748
  }, []);
82491
- const handleReorder = useCallback153(
82749
+ const handleReorder = useCallback152(
82492
82750
  (event) => {
82493
82751
  const { siblingLayers, fromIndex, toIndex } = event;
82494
82752
  const reordered = [...siblingLayers];
@@ -82551,7 +82809,7 @@ var LayersPanel = memo43(function LayersPanel2() {
82551
82809
  );
82552
82810
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
82553
82811
  const visibleLayers = getVisibleLayers(layers, collapsed);
82554
- const handleSingleSibling = useCallback153(() => {
82812
+ const handleSingleSibling = useCallback152(() => {
82555
82813
  showToast("Only one layer at this level", "info");
82556
82814
  }, [showToast]);
82557
82815
  const {
@@ -82683,13 +82941,13 @@ var LayersPanel = memo43(function LayersPanel2() {
82683
82941
  });
82684
82942
 
82685
82943
  // src/captions/components/CaptionPropertyPanel.tsx
82686
- 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";
82687
82945
 
82688
82946
  // src/captions/components/CaptionAnimationPanel.tsx
82689
- import { memo as memo44, useCallback as useCallback154 } from "react";
82947
+ import { memo as memo44, useCallback as useCallback153 } from "react";
82690
82948
 
82691
82949
  // src/captions/components/shared.tsx
82692
- 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";
82693
82951
  import { jsx as jsx189, jsxs as jsxs158 } from "react/jsx-runtime";
82694
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";
82695
82953
  function Section2({ label, children }) {
@@ -82715,7 +82973,7 @@ function NumberField({
82715
82973
  onCommit
82716
82974
  }) {
82717
82975
  const [draft, setDraft] = useState134(null);
82718
- const focusedRef = useRef160(false);
82976
+ const focusedRef = useRef162(false);
82719
82977
  useEffect125(() => {
82720
82978
  if (!focusedRef.current) setDraft(null);
82721
82979
  }, [value]);
@@ -82911,25 +83169,25 @@ var CaptionAnimationPanel = memo44(function CaptionAnimationPanel2() {
82911
83169
  }
82912
83170
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
82913
83171
  const animation = group?.animation;
82914
- const handleEntranceChange = useCallback154(
83172
+ const handleEntranceChange = useCallback153(
82915
83173
  (update) => {
82916
83174
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
82917
83175
  },
82918
83176
  [resolvedGroupId, updateGroupAnimation]
82919
83177
  );
82920
- const handleHighlightChange = useCallback154(
83178
+ const handleHighlightChange = useCallback153(
82921
83179
  (update) => {
82922
83180
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
82923
83181
  },
82924
83182
  [resolvedGroupId, updateGroupAnimation]
82925
83183
  );
82926
- const handleExitChange = useCallback154(
83184
+ const handleExitChange = useCallback153(
82927
83185
  (update) => {
82928
83186
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
82929
83187
  },
82930
83188
  [resolvedGroupId, updateGroupAnimation]
82931
83189
  );
82932
- const handleApplyToAll = useCallback154(() => {
83190
+ const handleApplyToAll = useCallback153(() => {
82933
83191
  if (animation) applyAnimationToAll(animation);
82934
83192
  }, [animation, applyAnimationToAll]);
82935
83193
  if (!group || !resolvedGroupId || !animation) {
@@ -83029,7 +83287,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
83029
83287
  ...groupStyle,
83030
83288
  ...segmentOverrides
83031
83289
  };
83032
- const applyToIframeDom = useCallback155(
83290
+ const applyToIframeDom = useCallback154(
83033
83291
  (updates) => {
83034
83292
  const iframe = iframeRef.current;
83035
83293
  if (!iframe || !model) return;
@@ -83103,7 +83361,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
83103
83361
  },
83104
83362
  [iframeRef, model, selectedSegmentIds]
83105
83363
  );
83106
- const handleStyleChange = useCallback155(
83364
+ const handleStyleChange = useCallback154(
83107
83365
  (updates) => {
83108
83366
  if (selectedGroupId) {
83109
83367
  updateGroupStyle(selectedGroupId, updates);
@@ -83208,7 +83466,7 @@ var CaptionPropertyPanel = memo45(function CaptionPropertyPanel2({
83208
83466
  });
83209
83467
 
83210
83468
  // src/components/editor/BlockParamsPanel.tsx
83211
- 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";
83212
83470
  import { jsx as jsx192, jsxs as jsxs161 } from "react/jsx-runtime";
83213
83471
  var BlockParamsPanel = memo46(function BlockParamsPanel2({
83214
83472
  blockName,
@@ -83226,12 +83484,12 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83226
83484
  }
83227
83485
  return initial;
83228
83486
  });
83229
- const appliedRef = useRef161(
83487
+ const appliedRef = useRef163(
83230
83488
  Object.fromEntries(params.map((p) => [p.key, p.default]))
83231
83489
  );
83232
83490
  const [commitState, setCommitState] = useState136({ tone: "idle" });
83233
- const commitTimersRef = useRef161(/* @__PURE__ */ new Map());
83234
- const commitParamNow = useCallback156(
83491
+ const commitTimersRef = useRef163(/* @__PURE__ */ new Map());
83492
+ const commitParamNow = useCallback155(
83235
83493
  async (key2, nextValue) => {
83236
83494
  if (!fileManager) return;
83237
83495
  const previous = appliedRef.current[key2];
@@ -83272,8 +83530,8 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83272
83530
  },
83273
83531
  [fileManager, compositionPath, setRefreshKey, blockName]
83274
83532
  );
83275
- const commitChainRef = useRef161(Promise.resolve());
83276
- const commitParam = useCallback156(
83533
+ const commitChainRef = useRef163(Promise.resolve());
83534
+ const commitParam = useCallback155(
83277
83535
  (key2, nextValue) => {
83278
83536
  const run = commitChainRef.current.then(() => commitParamNow(key2, nextValue));
83279
83537
  commitChainRef.current = run.catch(() => void 0);
@@ -83281,7 +83539,7 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83281
83539
  },
83282
83540
  [commitParamNow]
83283
83541
  );
83284
- const handleChange = useCallback156(
83542
+ const handleChange = useCallback155(
83285
83543
  (key2, value) => {
83286
83544
  setValues((prev) => ({ ...prev, [key2]: value }));
83287
83545
  const timers = commitTimersRef.current;
@@ -83297,7 +83555,7 @@ var BlockParamsPanel = memo46(function BlockParamsPanel2({
83297
83555
  },
83298
83556
  [commitParam]
83299
83557
  );
83300
- const flushRef = useRef161(() => {
83558
+ const flushRef = useRef163(() => {
83301
83559
  });
83302
83560
  flushRef.current = () => {
83303
83561
  for (const [key2, timer] of commitTimersRef.current) {
@@ -83439,12 +83697,12 @@ function ParamControl({
83439
83697
  import { memo as memo50 } from "react";
83440
83698
 
83441
83699
  // src/components/renders/RenderQueue.tsx
83442
- 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";
83443
83701
  import { createPortal as createPortal10 } from "react-dom";
83444
83702
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
83445
83703
 
83446
83704
  // src/components/renders/RenderQueueItem.tsx
83447
- 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";
83448
83706
  import { Fragment as Fragment57, jsx as jsx193, jsxs as jsxs162 } from "react/jsx-runtime";
83449
83707
  function formatDuration2(ms) {
83450
83708
  if (ms < 1e3) return `${ms}ms`;
@@ -83467,10 +83725,10 @@ var RenderQueueItem = memo47(function RenderQueueItem2({
83467
83725
  const [videoReady, setVideoReady] = useState137(false);
83468
83726
  const [confirmingDelete, setConfirmingDelete] = useState137(false);
83469
83727
  const fileSrc = buildProjectApiPath(projectId, `/renders/file/${job.filename}`);
83470
- const handleOpen = useCallback157(() => {
83728
+ const handleOpen = useCallback156(() => {
83471
83729
  window.open(fileSrc, "_blank");
83472
83730
  }, [fileSrc]);
83473
- const handleDownload = useCallback157(
83731
+ const handleDownload = useCallback156(
83474
83732
  (e) => {
83475
83733
  e.stopPropagation();
83476
83734
  const a = document.createElement("a");
@@ -83670,7 +83928,7 @@ var RenderQueueItem = memo47(function RenderQueueItem2({
83670
83928
  });
83671
83929
 
83672
83930
  // src/components/renders/FfmpegRequiredNotice.tsx
83673
- 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";
83674
83932
  import { jsx as jsx194, jsxs as jsxs163 } from "react/jsx-runtime";
83675
83933
  var DOWNLOAD_URL = "https://ffmpeg.org/download.html";
83676
83934
  var CUE_MS = 1600;
@@ -83682,8 +83940,8 @@ var FfmpegRequiredNotice = memo48(function FfmpegRequiredNotice2({
83682
83940
  }) {
83683
83941
  const [copied, setCopied] = useState138(false);
83684
83942
  const [recheckFailed, setRecheckFailed] = useState138(false);
83685
- const wasChecking = useRef162(false);
83686
- const cueTimer = useRef162(void 0);
83943
+ const wasChecking = useRef164(false);
83944
+ const cueTimer = useRef164(void 0);
83687
83945
  useEffect127(() => {
83688
83946
  if (wasChecking.current && !checking) setRecheckFailed(true);
83689
83947
  wasChecking.current = checking;
@@ -83813,8 +84071,8 @@ var FORMAT_PANEL_SIZE = { width: 208, height: 150 };
83813
84071
  function FormatInfoTooltip({ format }) {
83814
84072
  const [open, setOpen] = useState139(false);
83815
84073
  const [position, setPosition] = useState139(null);
83816
- const triggerRef = useRef163(null);
83817
- const timeoutRef = useRef163(void 0);
84074
+ const triggerRef = useRef165(null);
84075
+ const timeoutRef = useRef165(void 0);
83818
84076
  const panelId = useId7();
83819
84077
  const show = () => {
83820
84078
  clearTimeout(timeoutRef.current);
@@ -84065,7 +84323,7 @@ var RenderQueue = memo49(function RenderQueue2({
84065
84323
  ffmpegChecking,
84066
84324
  onRecheckFfmpeg
84067
84325
  }) {
84068
- const listRef = useRef163(null);
84326
+ const listRef = useRef165(null);
84069
84327
  useEffect128(() => {
84070
84328
  if (listRef.current) {
84071
84329
  listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
@@ -84214,11 +84472,11 @@ var RenderQueuePanel = memo50(function RenderQueuePanel2() {
84214
84472
  });
84215
84473
 
84216
84474
  // src/components/panels/SlideshowPanel.tsx
84217
- 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";
84218
84476
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
84219
84477
 
84220
84478
  // src/components/panels/SlideshowSubPanels.tsx
84221
- 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";
84222
84480
  import { jsx as jsx197, jsxs as jsxs165 } from "react/jsx-runtime";
84223
84481
  function SectionHeader({
84224
84482
  children,
@@ -84406,7 +84664,7 @@ function BranchTree({
84406
84664
  }) {
84407
84665
  const [newLabel, setNewLabel] = useState140("");
84408
84666
  const inputId = useId8();
84409
- const handleCreate = useCallback158(() => {
84667
+ const handleCreate = useCallback157(() => {
84410
84668
  const label = newLabel.trim();
84411
84669
  if (!label) return;
84412
84670
  onCreateSequence(label);
@@ -84468,7 +84726,7 @@ function BranchItem({
84468
84726
  const [editing, setEditing] = useState140(false);
84469
84727
  const [draft, setDraft] = useState140(seq.label);
84470
84728
  const [confirmingDelete, setConfirmingDelete] = useState140(false);
84471
- const commitRename = useCallback158(() => {
84729
+ const commitRename = useCallback157(() => {
84472
84730
  const label = draft.trim();
84473
84731
  if (label && label !== seq.label) onRename(seq.id, label);
84474
84732
  setEditing(false);
@@ -84601,7 +84859,7 @@ function HotspotTool({
84601
84859
  const selectedElementId = domEditSelection?.element?.id ?? null;
84602
84860
  const selectedHfId = domEditSelection?.hfId ?? null;
84603
84861
  const elementKey2 = selectedElementId || selectedHfId;
84604
- const handleMakeHotspot = useCallback158(() => {
84862
+ const handleMakeHotspot = useCallback157(() => {
84605
84863
  if (!selectedSceneId || !targetSequenceId || !elementKey2) return;
84606
84864
  const id = `hotspot-${elementKey2}-${generateId()}`;
84607
84865
  const label = hotspotLabel.trim() || elementKey2;
@@ -84893,12 +85151,12 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84893
85151
  );
84894
85152
  const [persistError, setPersistError] = useState141(false);
84895
85153
  const [retrying, setRetrying] = useState141(false);
84896
- const undoStackRef = useRef164([]);
85154
+ const undoStackRef = useRef166([]);
84897
85155
  const [undoDepth, setUndoDepth] = useState141(0);
84898
85156
  const currentTime = usePlayerStore((s) => s.currentTime);
84899
85157
  const { domEditSelection } = useDomEditSelectionContext();
84900
- const manifestRef = useRef164(manifest);
84901
- const notesCtrlRef = useRef164(makeSlideshowNotesController());
85158
+ const manifestRef = useRef166(manifest);
85159
+ const notesCtrlRef = useRef166(makeSlideshowNotesController());
84902
85160
  useEffect129(() => {
84903
85161
  if (!compHtml) {
84904
85162
  notesCtrlRef.current.flush();
@@ -84915,7 +85173,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84915
85173
  setUndoDepth(0);
84916
85174
  setPersistError(false);
84917
85175
  }, [compHtml]);
84918
- const applyManifest = useCallback159(
85176
+ const applyManifest = useCallback158(
84919
85177
  async (next, opts) => {
84920
85178
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
84921
85179
  if (!opts?.skipUndo) {
@@ -84935,14 +85193,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84935
85193
  },
84936
85194
  [onPersist]
84937
85195
  );
84938
- const handleUndo = useCallback159(() => {
85196
+ const handleUndo = useCallback158(() => {
84939
85197
  const prev = undoStackRef.current.pop();
84940
85198
  if (!prev) return;
84941
85199
  setUndoDepth(undoStackRef.current.length);
84942
85200
  applyManifest(prev, { skipUndo: true }).catch(() => {
84943
85201
  });
84944
85202
  }, [applyManifest]);
84945
- const handleRetryPersist = useCallback159(async () => {
85203
+ const handleRetryPersist = useCallback158(async () => {
84946
85204
  setRetrying(true);
84947
85205
  try {
84948
85206
  await onPersist(manifestRef.current);
@@ -84953,7 +85211,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84953
85211
  setRetrying(false);
84954
85212
  }
84955
85213
  }, [onPersist]);
84956
- const applyNotesManifest = useCallback159(
85214
+ const applyNotesManifest = useCallback158(
84957
85215
  (next) => {
84958
85216
  setManifest(next);
84959
85217
  manifestRef.current = next;
@@ -84967,7 +85225,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84967
85225
  ctrl.flush();
84968
85226
  };
84969
85227
  }, []);
84970
- const toggleSection = useCallback159((key2) => {
85228
+ const toggleSection = useCallback158((key2) => {
84971
85229
  setExpandedSections((prev) => {
84972
85230
  const next = new Set(prev);
84973
85231
  if (next.has(key2)) {
@@ -84981,25 +85239,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
84981
85239
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
84982
85240
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
84983
85241
  const sequences = manifest.slideSequences ?? [];
84984
- const handleSelectBranchSlide = useCallback159((sequenceId, sceneId) => {
85242
+ const handleSelectBranchSlide = useCallback158((sequenceId, sceneId) => {
84985
85243
  setSelectedSceneId(sceneId);
84986
85244
  setSelectedSequenceId(sequenceId);
84987
85245
  }, []);
84988
- const handleToggleSlide = useCallback159(
85246
+ const handleToggleSlide = useCallback158(
84989
85247
  (sceneId) => {
84990
85248
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
84991
85249
  });
84992
85250
  },
84993
85251
  [applyManifest]
84994
85252
  );
84995
- const handleReorder = useCallback159(
85253
+ const handleReorder = useCallback158(
84996
85254
  (sceneId, dir) => {
84997
85255
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
84998
85256
  });
84999
85257
  },
85000
85258
  [applyManifest]
85001
85259
  );
85002
- const handleSetNotes = useCallback159(
85260
+ const handleSetNotes = useCallback158(
85003
85261
  (notes) => {
85004
85262
  if (!selectedSceneId) return;
85005
85263
  applyNotesManifest(
@@ -85008,7 +85266,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85008
85266
  },
85009
85267
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
85010
85268
  );
85011
- const handleMarkFragment = useCallback159(() => {
85269
+ const handleMarkFragment = useCallback158(() => {
85012
85270
  if (!selectedSceneId) return;
85013
85271
  applyManifest(
85014
85272
  addFragment(
@@ -85020,7 +85278,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85020
85278
  ).catch(() => {
85021
85279
  });
85022
85280
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
85023
- const handleRemoveFragment = useCallback159(
85281
+ const handleRemoveFragment = useCallback158(
85024
85282
  (time) => {
85025
85283
  if (!selectedSceneId) return;
85026
85284
  applyManifest(
@@ -85030,7 +85288,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85030
85288
  },
85031
85289
  [selectedSceneId, selectedSequenceId, applyManifest]
85032
85290
  );
85033
- const handleCreateSequence = useCallback159(
85291
+ const handleCreateSequence = useCallback158(
85034
85292
  (label) => {
85035
85293
  const id = `seq-${generateId()}`;
85036
85294
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -85038,21 +85296,21 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85038
85296
  },
85039
85297
  [applyManifest]
85040
85298
  );
85041
- const handleRenameSequence = useCallback159(
85299
+ const handleRenameSequence = useCallback158(
85042
85300
  (id, label) => {
85043
85301
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
85044
85302
  });
85045
85303
  },
85046
85304
  [applyManifest]
85047
85305
  );
85048
- const handleDeleteSequence = useCallback159(
85306
+ const handleDeleteSequence = useCallback158(
85049
85307
  (id) => {
85050
85308
  applyManifest(deleteSequence(manifestRef.current, id)).catch(() => {
85051
85309
  });
85052
85310
  },
85053
85311
  [applyManifest]
85054
85312
  );
85055
- const handleAssign = useCallback159(
85313
+ const handleAssign = useCallback158(
85056
85314
  (sequenceId, sceneId, assign) => {
85057
85315
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
85058
85316
  () => {
@@ -85061,7 +85319,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85061
85319
  },
85062
85320
  [applyManifest]
85063
85321
  );
85064
- const handleAddHotspot = useCallback159(
85322
+ const handleAddHotspot = useCallback158(
85065
85323
  (sceneId, hotspot) => {
85066
85324
  applyManifest(
85067
85325
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -85070,7 +85328,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85070
85328
  },
85071
85329
  [selectedSequenceId, applyManifest]
85072
85330
  );
85073
- const handleRemoveHotspot = useCallback159(
85331
+ const handleRemoveHotspot = useCallback158(
85074
85332
  (sceneId, hotspotId) => {
85075
85333
  applyManifest(
85076
85334
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -85223,10 +85481,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
85223
85481
  }
85224
85482
 
85225
85483
  // src/components/panels/VariablesPanel.tsx
85226
- 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";
85227
85485
 
85228
85486
  // src/hooks/useVariablesPersist.ts
85229
- import { useCallback as useCallback160 } from "react";
85487
+ import { useCallback as useCallback159 } from "react";
85230
85488
  function useVariablesPersist({
85231
85489
  sdkSession,
85232
85490
  activeCompPath,
@@ -85236,7 +85494,7 @@ function useVariablesPersist({
85236
85494
  reloadPreview,
85237
85495
  publishSdkSession
85238
85496
  }) {
85239
- return useCallback160(
85497
+ return useCallback159(
85240
85498
  async (label, mutate) => {
85241
85499
  if (!sdkSession) return false;
85242
85500
  const path = activeCompPath ?? "index.html";
@@ -85271,10 +85529,10 @@ function useVariablesPersist({
85271
85529
  }
85272
85530
 
85273
85531
  // src/components/panels/VariablesOtherCompositions.tsx
85274
- import { useCallback as useCallback162, useState as useState144 } from "react";
85532
+ import { useCallback as useCallback161, useState as useState144 } from "react";
85275
85533
 
85276
85534
  // src/hooks/useProjectCompositionVariables.ts
85277
- 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";
85278
85536
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
85279
85537
  async function readGroup(path, readProjectFile) {
85280
85538
  let content;
@@ -85317,7 +85575,7 @@ function useProjectCompositionVariables(fileTree, excludePath, readProjectFile,
85317
85575
  }
85318
85576
  function useEditVariablesInFile(deps) {
85319
85577
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview } = deps;
85320
- return useCallback161(
85578
+ return useCallback160(
85321
85579
  async (path, label, mutate) => {
85322
85580
  const originalContent = await readProjectFile(path);
85323
85581
  await persistSdkSerialize(
@@ -85694,7 +85952,7 @@ function VariablesOtherCompositions({
85694
85952
  reloadPreview
85695
85953
  });
85696
85954
  const [editingKey, setEditingKey] = useState144(null);
85697
- const onSave = useCallback162(
85955
+ const onSave = useCallback161(
85698
85956
  (path, decl) => {
85699
85957
  setEditingKey(null);
85700
85958
  void editInFile(
@@ -85705,7 +85963,7 @@ function VariablesOtherCompositions({
85705
85963
  },
85706
85964
  [editInFile]
85707
85965
  );
85708
- const onRemove = useCallback162(
85966
+ const onRemove = useCallback161(
85709
85967
  (path, id) => {
85710
85968
  void editInFile(
85711
85969
  path,
@@ -85898,9 +86156,9 @@ var VariablesPanel = memo51(function VariablesPanel2({
85898
86156
  }) {
85899
86157
  const { activeCompPath, showToast } = useStudioShellContext();
85900
86158
  const { refreshKey } = useStudioPlaybackContext();
85901
- const { readProjectFile, writeProjectFile, fileTree } = useFileManagerContext();
86159
+ const { readProjectFile, writeProjectFile, fileTree, compositions } = useFileManagerContext();
85902
86160
  const { domEditSelection } = useDomEditContext();
85903
- const effectiveCompPath = activeCompPath ?? resolveMasterCompositionPath(fileTree);
86161
+ const effectiveCompPath = activeCompPath ?? resolveMasterCompositionPath(compositions);
85904
86162
  const previewValues = usePreviewVariablesStore((s) => s.values);
85905
86163
  const setPreviewValues = usePreviewVariablesStore((s) => s.setValues);
85906
86164
  const [revision, setRevision] = useState145(0);
@@ -85919,27 +86177,27 @@ var VariablesPanel = memo51(function VariablesPanel2({
85919
86177
  reloadPreview,
85920
86178
  publishSdkSession
85921
86179
  });
85922
- const declarations = useMemo72(
86180
+ const declarations = useMemo73(
85923
86181
  () => sdkSession?.getVariableDeclarations() ?? [],
85924
86182
  // eslint-disable-next-line react-hooks/exhaustive-deps
85925
86183
  [sdkSession, refreshKey, revision]
85926
86184
  );
85927
- const usage = useMemo72(
86185
+ const usage = useMemo73(
85928
86186
  () => sdkSession?.getVariableUsage() ?? null,
85929
86187
  // eslint-disable-next-line react-hooks/exhaustive-deps
85930
86188
  [sdkSession, refreshKey, revision]
85931
86189
  );
85932
- const issues = useMemo72(
86190
+ const issues = useMemo73(
85933
86191
  () => previewValues && sdkSession ? sdkSession.validateVariableValues(previewValues) : [],
85934
86192
  // eslint-disable-next-line react-hooks/exhaustive-deps
85935
86193
  [sdkSession, previewValues, refreshKey, revision]
85936
86194
  );
85937
- const effectiveValues = useMemo72(
86195
+ const effectiveValues = useMemo73(
85938
86196
  () => sdkSession?.getVariableValues(previewValues ?? void 0) ?? {},
85939
86197
  // eslint-disable-next-line react-hooks/exhaustive-deps
85940
86198
  [sdkSession, previewValues, refreshKey, revision]
85941
86199
  );
85942
- const copyToClipboard = useCallback163(
86200
+ const copyToClipboard = useCallback162(
85943
86201
  (text, what) => {
85944
86202
  void copyTextToClipboard(text).then(
85945
86203
  (ok) => showToast(
@@ -85950,7 +86208,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85950
86208
  },
85951
86209
  [showToast]
85952
86210
  );
85953
- const dropPreviewOverride = useCallback163(
86211
+ const dropPreviewOverride = useCallback162(
85954
86212
  (id) => {
85955
86213
  if (previewValues && id in previewValues) {
85956
86214
  const next = { ...previewValues };
@@ -85960,7 +86218,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85960
86218
  },
85961
86219
  [previewValues, setPreviewValues]
85962
86220
  );
85963
- const commitPreviewValue = useCallback163(
86221
+ const commitPreviewValue = useCallback162(
85964
86222
  (id, value, declDefault) => {
85965
86223
  const next = { ...previewValues ?? {} };
85966
86224
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -85973,7 +86231,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85973
86231
  },
85974
86232
  [previewValues, setPreviewValues, reloadPreview]
85975
86233
  );
85976
- const runSchemaEdit = useCallback163(
86234
+ const runSchemaEdit = useCallback162(
85977
86235
  async (label, mutate) => {
85978
86236
  try {
85979
86237
  const changed = await persistVariables(label, mutate);
@@ -85987,7 +86245,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
85987
86245
  },
85988
86246
  [persistVariables, showToast]
85989
86247
  );
85990
- const handleAdd = useCallback163(
86248
+ const handleAdd = useCallback162(
85991
86249
  (decl) => {
85992
86250
  if (!sdkSession) return;
85993
86251
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -86000,7 +86258,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
86000
86258
  },
86001
86259
  [sdkSession, runSchemaEdit, showToast]
86002
86260
  );
86003
- const handleUpdate = useCallback163(
86261
+ const handleUpdate = useCallback162(
86004
86262
  (decl) => {
86005
86263
  if (!sdkSession) return;
86006
86264
  const check = sdkSession.can({
@@ -86020,7 +86278,7 @@ var VariablesPanel = memo51(function VariablesPanel2({
86020
86278
  },
86021
86279
  [sdkSession, runSchemaEdit, showToast]
86022
86280
  );
86023
- const handleRemove = useCallback163(
86281
+ const handleRemove = useCallback162(
86024
86282
  (id) => {
86025
86283
  if (!sdkSession) return;
86026
86284
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -86036,18 +86294,18 @@ var VariablesPanel = memo51(function VariablesPanel2({
86036
86294
  },
86037
86295
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
86038
86296
  );
86039
- const handleSetDefault = useCallback163(
86297
+ const handleSetDefault = useCallback162(
86040
86298
  (id, value) => {
86041
86299
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
86042
86300
  dropPreviewOverride(id);
86043
86301
  },
86044
86302
  [runSchemaEdit, dropPreviewOverride]
86045
86303
  );
86046
- const resetPreview = useCallback163(() => {
86304
+ const resetPreview = useCallback162(() => {
86047
86305
  setPreviewValues(null);
86048
86306
  reloadPreview();
86049
86307
  }, [setPreviewValues, reloadPreview]);
86050
- const handleBind = useCallback163(
86308
+ const handleBind = useCallback162(
86051
86309
  // Guard chain (session, selection, type-compat) — one branch per guard.
86052
86310
  // fallow-ignore-next-line complexity
86053
86311
  (action, id) => {
@@ -86179,7 +86437,7 @@ function PanelTabButton({
86179
86437
  }
86180
86438
 
86181
86439
  // src/hooks/useSlideshowPersist.ts
86182
- import { useCallback as useCallback164 } from "react";
86440
+ import { useCallback as useCallback163 } from "react";
86183
86441
 
86184
86442
  // src/utils/setSlideshowManifest.ts
86185
86443
  import {
@@ -86236,7 +86494,7 @@ function useSlideshowPersist({
86236
86494
  publishSdkSession,
86237
86495
  coalesceKey
86238
86496
  }) {
86239
- return useCallback164(
86497
+ return useCallback163(
86240
86498
  async (manifest) => {
86241
86499
  if (!sdkSession) return;
86242
86500
  const path = activeCompPath ?? "index.html";
@@ -86269,15 +86527,15 @@ function useSlideshowPersist({
86269
86527
  }
86270
86528
 
86271
86529
  // src/hooks/useSlideshowTabState.ts
86272
- import { useEffect as useEffect132, useMemo as useMemo73 } from "react";
86530
+ import { useEffect as useEffect132, useMemo as useMemo74 } from "react";
86273
86531
  import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
86274
86532
  function useSlideshowTabState(params) {
86275
86533
  const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } = params;
86276
- const isSlideshowComposition = useMemo73(() => {
86534
+ const isSlideshowComposition = useMemo74(() => {
86277
86535
  if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
86278
86536
  return slideshowIslandRegex2("i").test(editingFileContent);
86279
86537
  }, [editingFileContent]);
86280
- const slideshowScenes = useMemo73(() => {
86538
+ const slideshowScenes = useMemo74(() => {
86281
86539
  try {
86282
86540
  const win = previewIframeRef.current?.contentWindow;
86283
86541
  return (win?.__clipManifest?.scenes ?? []).map((s) => ({
@@ -86299,7 +86557,7 @@ function useSlideshowTabState(params) {
86299
86557
  }
86300
86558
 
86301
86559
  // src/components/DesignPanelPromoteProvider.tsx
86302
- import { useCallback as useCallback165 } from "react";
86560
+ import { useCallback as useCallback164 } from "react";
86303
86561
  import { jsx as jsx204 } from "react/jsx-runtime";
86304
86562
  function DesignPanelPromoteProvider({
86305
86563
  selection,
@@ -86318,7 +86576,7 @@ function DesignPanelPromoteProvider({
86318
86576
  publishSdkSession: handle.publish,
86319
86577
  activeCompPath: targetPath
86320
86578
  });
86321
- const persist2 = useCallback165(
86579
+ const persist2 = useCallback164(
86322
86580
  async (label, mutate) => {
86323
86581
  const committed = await rawPersist(label, mutate);
86324
86582
  if (committed) forceReloadSharedSdkSession?.();
@@ -86326,7 +86584,7 @@ function DesignPanelPromoteProvider({
86326
86584
  },
86327
86585
  [rawPersist, forceReloadSharedSdkSession]
86328
86586
  );
86329
- const handlePersistError = useCallback165(
86587
+ const handlePersistError = useCallback164(
86330
86588
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
86331
86589
  [showToast]
86332
86590
  );
@@ -86559,14 +86817,14 @@ async function applyColorGradingScopeUpdate({
86559
86817
  }
86560
86818
 
86561
86819
  // src/hooks/useInspectorSplitResize.ts
86562
- 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";
86563
86821
  var MIN_INSPECTOR_SPLIT_PERCENT = 20;
86564
86822
  var MAX_INSPECTOR_SPLIT_PERCENT = 75;
86565
86823
  function useInspectorSplitResize() {
86566
86824
  const [layersPanePercent, setLayersPanePercent] = useState146(40);
86567
- const splitContainerRef = useRef165(null);
86568
- const splitDragRef = useRef165(null);
86569
- const handleInspectorSplitResizeStart = useCallback166(
86825
+ const splitContainerRef = useRef167(null);
86826
+ const splitDragRef = useRef167(null);
86827
+ const handleInspectorSplitResizeStart = useCallback165(
86570
86828
  (event) => {
86571
86829
  event.preventDefault();
86572
86830
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -86579,7 +86837,7 @@ function useInspectorSplitResize() {
86579
86837
  },
86580
86838
  [layersPanePercent]
86581
86839
  );
86582
- const handleInspectorSplitResizeMove = useCallback166((event) => {
86840
+ const handleInspectorSplitResizeMove = useCallback165((event) => {
86583
86841
  const drag = splitDragRef.current;
86584
86842
  if (!drag || drag.height <= 0) return;
86585
86843
  const deltaPercent = (event.clientY - drag.startY) / drag.height * 100;
@@ -86589,7 +86847,7 @@ function useInspectorSplitResize() {
86589
86847
  );
86590
86848
  setLayersPanePercent(next);
86591
86849
  }, []);
86592
- const handleInspectorSplitResizeEnd = useCallback166(() => {
86850
+ const handleInspectorSplitResizeEnd = useCallback165(() => {
86593
86851
  splitDragRef.current = null;
86594
86852
  }, []);
86595
86853
  return {
@@ -86602,16 +86860,16 @@ function useInspectorSplitResize() {
86602
86860
  }
86603
86861
 
86604
86862
  // src/hooks/useRemoveBackground.ts
86605
- 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";
86606
86864
  function useRemoveBackground(projectId, refreshFileTree, showToast) {
86607
- const abortRef = useRef166(null);
86865
+ const abortRef = useRef168(null);
86608
86866
  useEffect133(
86609
86867
  () => () => {
86610
86868
  abortRef.current?.abort();
86611
86869
  },
86612
86870
  []
86613
86871
  );
86614
- return useCallback167(
86872
+ return useCallback166(
86615
86873
  // fallow-ignore-next-line complexity
86616
86874
  async (inputPath, options) => {
86617
86875
  const response = await fetch(
@@ -86793,7 +87051,7 @@ function StudioRightPanel({
86793
87051
  }
86794
87052
  toggleRightInspectorPane(pane);
86795
87053
  };
86796
- const handleApplyColorGradingScope = useCallback168(
87054
+ const handleApplyColorGradingScope = useCallback167(
86797
87055
  async (scope, value) => applyColorGradingScopeUpdate({
86798
87056
  scope,
86799
87057
  value,
@@ -86827,7 +87085,7 @@ function StudioRightPanel({
86827
87085
  ]
86828
87086
  );
86829
87087
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
86830
- const setAttributeWhileDragging = useCallback168(
87088
+ const setAttributeWhileDragging = useCallback167(
86831
87089
  (attr, value) => handleDomAttributeLiveCommit(attr, value, void 0, { previewOnly: true }),
86832
87090
  [handleDomAttributeLiveCommit]
86833
87091
  );
@@ -87075,11 +87333,17 @@ function StudioRightPanel({
87075
87333
  }
87076
87334
 
87077
87335
  // src/components/TimelineToolbar.tsx
87078
- import { useEffect as useEffect135, useRef as useRef167 } from "react";
87079
- 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";
87080
87344
 
87081
87345
  // src/hooks/useEnableKeyframes.ts
87082
- import { useCallback as useCallback169 } from "react";
87346
+ import { useCallback as useCallback168 } from "react";
87083
87347
  var enableKeyframesTransactionCounter = 0;
87084
87348
  function animatedProps(anim) {
87085
87349
  if (!anim) return ["x", "y"];
@@ -87332,7 +87596,7 @@ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t, iframe) {
87332
87596
  );
87333
87597
  }
87334
87598
  function useEnableKeyframes(sessionRef) {
87335
- return useCallback169(async () => {
87599
+ return useCallback168(async () => {
87336
87600
  const session = sessionRef.current;
87337
87601
  if (!session) return;
87338
87602
  const sel = session.domEditSelection;
@@ -87414,7 +87678,7 @@ function useEnableKeyframes(sessionRef) {
87414
87678
  }
87415
87679
 
87416
87680
  // src/hooks/useKeyframeKeyboard.ts
87417
- import { useEffect as useEffect134, useCallback as useCallback170 } from "react";
87681
+ import { useEffect as useEffect134, useCallback as useCallback169 } from "react";
87418
87682
  function isTextInput2(el) {
87419
87683
  if (!el) return false;
87420
87684
  const tag = el.tagName;
@@ -87431,7 +87695,7 @@ function useKeyframeKeyboard({
87431
87695
  onToggleExpand,
87432
87696
  onNudgeKeyframe
87433
87697
  }) {
87434
- const handler = useCallback170(
87698
+ const handler = useCallback169(
87435
87699
  (e) => {
87436
87700
  if (!enabled) return;
87437
87701
  if (isTextInput2(document.activeElement)) return;
@@ -87532,7 +87796,7 @@ function useKeyframeToggle(session) {
87532
87796
  const currentTime = usePlayerStore((s) => s.currentTime);
87533
87797
  const selectedElementId = usePlayerStore((s) => s.selectedElementId);
87534
87798
  const elements = usePlayerStore((s) => s.elements);
87535
- const sessionRef = useRef167(session);
87799
+ const sessionRef = useRef169(session);
87536
87800
  sessionRef.current = session;
87537
87801
  const onToggle = useEnableKeyframes(
87538
87802
  sessionRef
@@ -87550,6 +87814,8 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
87550
87814
  const setActiveTool = usePlayerStore((s) => s.setActiveTool);
87551
87815
  const timelineSnapEnabled = usePlayerStore((s) => s.timelineSnapEnabled);
87552
87816
  const setTimelineSnapEnabled = usePlayerStore((s) => s.setTimelineSnapEnabled);
87817
+ const rippleEditEnabled = usePlayerStore((s) => s.rippleEditEnabled);
87818
+ const setRippleEditEnabled = usePlayerStore((s) => s.setRippleEditEnabled);
87553
87819
  const autoKeyframeEnabled = usePlayerStore((s) => s.autoKeyframeEnabled);
87554
87820
  const setAutoKeyframeEnabled = usePlayerStore((s) => s.setAutoKeyframeEnabled);
87555
87821
  const thumbnailMode = usePlayerStore((s) => s.thumbnailMode);
@@ -87634,6 +87900,23 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
87634
87900
  children: /* @__PURE__ */ jsx206(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
87635
87901
  }
87636
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
+ ),
87637
87920
  /* @__PURE__ */ jsx206(
87638
87921
  Tooltip,
87639
87922
  {
@@ -87855,15 +88138,15 @@ import { useState as useState154 } from "react";
87855
88138
  import { Copy as Copy2, Check as Check2 } from "@phosphor-icons/react";
87856
88139
 
87857
88140
  // src/hooks/useStoryboard.ts
87858
- 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";
87859
88142
  function useStoryboard(projectId) {
87860
88143
  const [data, setData] = useState147(null);
87861
88144
  const [loading, setLoading] = useState147(true);
87862
88145
  const [error, setError] = useState147(null);
87863
88146
  const [reloadKey, setReloadKey] = useState147(0);
87864
- const hasDataRef = useRef168(false);
87865
- const lastProjectRef = useRef168(null);
87866
- 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), []);
87867
88150
  useEffect136(() => {
87868
88151
  if (!projectId) return;
87869
88152
  let cancelled = false;
@@ -87896,7 +88179,7 @@ function useStoryboard(projectId) {
87896
88179
  }
87897
88180
 
87898
88181
  // src/hooks/useProjectSignaturePoll.ts
87899
- import { useEffect as useEffect137, useRef as useRef169 } from "react";
88182
+ import { useEffect as useEffect137, useRef as useRef171 } from "react";
87900
88183
  var POLL_INTERVAL_MS = 2e3;
87901
88184
  async function fetchProjectSignature(projectId) {
87902
88185
  try {
@@ -87909,8 +88192,8 @@ async function fetchProjectSignature(projectId) {
87909
88192
  }
87910
88193
  }
87911
88194
  function useProjectSignaturePoll(projectId, currentSignature, onChange) {
87912
- const signatureRef = useRef169(currentSignature);
87913
- const onChangeRef = useRef169(onChange);
88195
+ const signatureRef = useRef171(currentSignature);
88196
+ const onChangeRef = useRef171(onChange);
87914
88197
  signatureRef.current = currentSignature;
87915
88198
  onChangeRef.current = onChange;
87916
88199
  useEffect137(() => {
@@ -87940,7 +88223,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
87940
88223
  }
87941
88224
 
87942
88225
  // src/components/storyboard/StoryboardLoaded.tsx
87943
- 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";
87944
88227
 
87945
88228
  // src/components/storyboard/StoryboardDirection.tsx
87946
88229
  import { jsx as jsx207, jsxs as jsxs172 } from "react/jsx-runtime";
@@ -88177,7 +88460,7 @@ function StoryboardScriptPanel({ script }) {
88177
88460
  }
88178
88461
 
88179
88462
  // src/components/storyboard/StoryboardSourceEditor.tsx
88180
- 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";
88181
88464
  import { marked } from "marked";
88182
88465
  import DOMPurify from "dompurify";
88183
88466
  import { jsx as jsx212, jsxs as jsxs175 } from "react/jsx-runtime";
@@ -88207,7 +88490,7 @@ function useEditableFile(path, onSaved) {
88207
88490
  cancelled = true;
88208
88491
  };
88209
88492
  }, [path, readProjectFile]);
88210
- const save = useCallback172(() => {
88493
+ const save = useCallback171(() => {
88211
88494
  if (saving) return;
88212
88495
  setSaving(true);
88213
88496
  setError(null);
@@ -88226,7 +88509,7 @@ function hardenLinks(node) {
88226
88509
  }
88227
88510
  function useMarkdownPreview(source) {
88228
88511
  const [debounced, setDebounced] = useState149(source);
88229
- const primed = useRef170(false);
88512
+ const primed = useRef172(false);
88230
88513
  useEffect139(() => {
88231
88514
  if (!primed.current && source !== "") {
88232
88515
  primed.current = true;
@@ -88236,7 +88519,7 @@ function useMarkdownPreview(source) {
88236
88519
  const id = window.setTimeout(() => setDebounced(source), 200);
88237
88520
  return () => window.clearTimeout(id);
88238
88521
  }, [source]);
88239
- return useMemo74(() => {
88522
+ return useMemo75(() => {
88240
88523
  const html2 = marked.parse(debounced, { async: false });
88241
88524
  DOMPurify.addHook("afterSanitizeAttributes", hardenLinks);
88242
88525
  try {
@@ -88332,7 +88615,7 @@ function StoryboardSourceEditor({
88332
88615
  }
88333
88616
 
88334
88617
  // src/components/storyboard/StoryboardFrameFocus.tsx
88335
- 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";
88336
88619
  import { setFrameVoiceover } from "@hyperframes/core/storyboard";
88337
88620
 
88338
88621
  // src/components/storyboard/AgentChatMessageButton.tsx
@@ -88392,7 +88675,7 @@ function StoryboardFrameFocus({
88392
88675
  const [savedVoiceover, setSavedVoiceover] = useState151(frame.voiceover ?? "");
88393
88676
  const [busy, setBusy] = useState151(false);
88394
88677
  const [error, setError] = useState151(null);
88395
- const applyEdit = useCallback173(
88678
+ const applyEdit = useCallback172(
88396
88679
  async (edit) => {
88397
88680
  if (busy) return false;
88398
88681
  setBusy(true);
@@ -88414,7 +88697,7 @@ function StoryboardFrameFocus({
88414
88697
  const title = frame.title ?? `Frame ${frame.index}`;
88415
88698
  const dirty = draft !== savedVoiceover;
88416
88699
  const canOpenPreview = frame.status !== "outline" && frame.srcExists && Boolean(frame.src);
88417
- const saveVoiceover = useCallback173(async () => {
88700
+ const saveVoiceover = useCallback172(async () => {
88418
88701
  const saved2 = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
88419
88702
  if (saved2) setSavedVoiceover(draft);
88420
88703
  }, [applyEdit, frame.index, draft]);
@@ -88426,7 +88709,7 @@ function StoryboardFrameFocus({
88426
88709
  window.addEventListener("beforeunload", onBeforeUnload);
88427
88710
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
88428
88711
  }, [dirty]);
88429
- const confirmLeave = useCallback173(
88712
+ const confirmLeave = useCallback172(
88430
88713
  () => !dirty || window.confirm("Discard unsaved voiceover changes?"),
88431
88714
  [dirty]
88432
88715
  );
@@ -88908,7 +89191,7 @@ function progressLabel(summary) {
88908
89191
  }
88909
89192
 
88910
89193
  // src/components/storyboard/useFrameComments.ts
88911
- 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";
88912
89195
 
88913
89196
  // src/components/storyboard/frameComments.ts
88914
89197
  var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
@@ -88974,7 +89257,7 @@ function useFrameComments(frames) {
88974
89257
  const [submitState, setSubmitState] = useState152("idle");
88975
89258
  const [submitError, setSubmitError] = useState152(null);
88976
89259
  const [pending2, setPending] = useState152(null);
88977
- const refreshPending = useCallback174(async () => {
89260
+ const refreshPending = useCallback173(async () => {
88978
89261
  try {
88979
89262
  const parsed = parseCommentsFile(await readOptionalProjectFile(FRAME_COMMENTS_PATH));
88980
89263
  setPending(parsed && parsed.comments.length > 0 ? parsed.comments : null);
@@ -88987,14 +89270,14 @@ function useFrameComments(frames) {
88987
89270
  window.addEventListener("focus", onFocus);
88988
89271
  return () => window.removeEventListener("focus", onFocus);
88989
89272
  }, [refreshPending]);
88990
- const setDraft = useCallback174((index, text) => {
89273
+ const setDraft = useCallback173((index, text) => {
88991
89274
  setDrafts((prev) => ({ ...prev, [index]: text }));
88992
89275
  }, []);
88993
- const draftCount = useMemo75(
89276
+ const draftCount = useMemo76(
88994
89277
  () => Object.values(drafts).filter((text) => text.trim().length > 0).length,
88995
89278
  [drafts]
88996
89279
  );
88997
- const submit = useCallback174(async () => {
89280
+ const submit = useCallback173(async () => {
88998
89281
  if (draftCount === 0 || submitState === "saving") return false;
88999
89282
  setSubmitState("saving");
89000
89283
  setSubmitError(null);
@@ -89058,7 +89341,7 @@ function StoryboardLoaded({
89058
89341
  setFeedbackMessageCopied(false);
89059
89342
  }
89060
89343
  };
89061
- const sourceFiles = useMemo76(() => {
89344
+ const sourceFiles = useMemo77(() => {
89062
89345
  const files = [{ path: data.path, label: data.path }];
89063
89346
  if (data.script?.exists) files.push({ path: data.script.path, label: data.script.path });
89064
89347
  return files;
@@ -89457,18 +89740,21 @@ function useStudioSessionStart(projectId, resolving, waitingForServer) {
89457
89740
  }
89458
89741
 
89459
89742
  // src/hooks/useTimelineAddAtPlayhead.ts
89460
- import { useCallback as useCallback175 } from "react";
89743
+ import { useCallback as useCallback174 } from "react";
89461
89744
  function useTimelineAddAtPlayhead(addAsset, addComposition) {
89462
89745
  const placement = () => ({ start: usePlayerStore.getState().currentTime, track: 0 });
89463
89746
  return {
89464
- addAssetAtPlayhead: useCallback175((path) => addAsset(path, placement()), [addAsset]),
89465
- addCompositionAtPlayhead: useCallback175(
89747
+ addAssetAtPlayhead: useCallback174((path) => addAsset(path, placement()), [addAsset]),
89748
+ addCompositionAtPlayhead: useCallback174(
89466
89749
  (path) => addComposition(path, placement()),
89467
89750
  [addComposition]
89468
89751
  )
89469
89752
  };
89470
89753
  }
89471
89754
 
89755
+ // src/hooks/useActiveComposition.ts
89756
+ import { useState as useState156 } from "react";
89757
+
89472
89758
  // src/hooks/useHydrateActiveCompPathFromUrl.ts
89473
89759
  import { useEffect as useEffect146 } from "react";
89474
89760
  function useHydrateActiveCompPathFromUrl({
@@ -89491,29 +89777,136 @@ function useHydrateActiveCompPathFromUrl({
89491
89777
  }, [hydrated, fileTree, fileTreeLoaded, initialUrlStateRef, setActiveCompPath, setHydrated]);
89492
89778
  }
89493
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
+
89494
89894
  // src/App.tsx
89495
89895
  import { jsx as jsx219, jsxs as jsxs181 } from "react/jsx-runtime";
89496
89896
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
89497
89897
  function StudioApp() {
89498
89898
  const { projectId, resolving, waitingForServer } = useServerConnection();
89499
- const initialUrlStateRef = useRef171(readStudioUrlStateFromWindow());
89899
+ const initialUrlStateRef = useRef175(readStudioUrlStateFromWindow());
89500
89900
  const viewModeValue = useViewModeState();
89501
89901
  useStudioSessionStart(projectId, resolving, waitingForServer);
89502
- const [activeCompPath, setActiveCompPath] = useState156(null);
89503
- const [activeCompPathHydrated, setActiveCompPathHydrated] = useState156(
89504
- () => initialUrlStateRef.current.activeCompPath == null
89505
- );
89506
- const [compIdToSrc, setCompIdToSrc] = useState156(/* @__PURE__ */ new Map());
89507
- const [previewIframe, setPreviewIframe] = useState156(null);
89508
- const [compositionLoading, setCompositionLoading] = useState156(true);
89509
- 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);
89510
89906
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
89511
- const [blockPreview, setBlockPreview] = useState156(null);
89512
- const previewIframeRef = useRef171(null);
89513
- const activeCompPathRef = useRef171(activeCompPath);
89514
- activeCompPathRef.current = activeCompPath;
89515
- const leftSidebarRef = useRef171(null);
89516
- const renderQueue = useRenderQueue(projectId, activeCompPathRef);
89907
+ const [blockPreview, setBlockPreview] = useState157(null);
89908
+ const previewIframeRef = useRef175(null);
89909
+ const leftSidebarRef = useRef175(null);
89517
89910
  const captionEditMode = useCaptionStore((s) => s.isEditMode);
89518
89911
  const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0);
89519
89912
  const captionSync = useCaptionSync(projectId);
@@ -89532,9 +89925,9 @@ function StudioApp() {
89532
89925
  rightPanelTab: initialUrlStateRef.current.rightPanelTab
89533
89926
  });
89534
89927
  const editHistory = usePersistentEditHistory({ projectId });
89535
- const handleDomZIndexReorderCommitRef = useRef171(null);
89536
- const pendingTimelineEditPathRef = useRef171(/* @__PURE__ */ new Set());
89537
- const isGestureRecordingRef = useRef171(false);
89928
+ const handleDomZIndexReorderCommitRef = useRef175(null);
89929
+ const pendingTimelineEditPathRef = useRef175(/* @__PURE__ */ new Set());
89930
+ const isGestureRecordingRef = useRef175(false);
89538
89931
  const reloadPreview = useCallback176(() => setRefreshKey((k) => k + 1), []);
89539
89932
  const fileManager = useFileManager({
89540
89933
  projectId,
@@ -89542,23 +89935,27 @@ function StudioApp() {
89542
89935
  recordEdit: editHistory.recordEdit,
89543
89936
  setRefreshKey
89544
89937
  });
89545
- const masterCompPath = useMemo77(
89546
- () => resolveMasterCompositionPath(fileManager.fileTree),
89547
- [fileManager.fileTree]
89938
+ const masterCompPath = useMemo78(
89939
+ () => resolveMasterCompositionPath(fileManager.compositions),
89940
+ [fileManager.compositions]
89548
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
+ });
89549
89951
  const { sdkHandle, editFlowSdkSession } = useStudioSdkSessions(
89550
89952
  projectId,
89551
89953
  activeCompPath,
89552
89954
  masterCompPath
89553
89955
  );
89554
- useHydrateActiveCompPathFromUrl({
89555
- hydrated: activeCompPathHydrated,
89556
- fileTreeLoaded: fileManager.fileTreeLoaded,
89557
- fileTree: fileManager.fileTree,
89558
- initialUrlStateRef,
89559
- setActiveCompPath,
89560
- setHydrated: setActiveCompPathHydrated
89561
- });
89956
+ const activeCompPathRef = useRef175(activeCompPath);
89957
+ activeCompPathRef.current = activeCompPath;
89958
+ const renderQueue = useRenderQueue(projectId, activeCompPathRef);
89562
89959
  const previewPersistence = usePreviewPersistence({
89563
89960
  showToast,
89564
89961
  readOptionalProjectFile: fileManager.readOptionalProjectFile,
@@ -89577,7 +89974,7 @@ function StudioApp() {
89577
89974
  pendingTimelineEditPathRef,
89578
89975
  reloadPreview
89579
89976
  });
89580
- const invalidateGsapCacheRef = useRef171(() => {
89977
+ const invalidateGsapCacheRef = useRef175(() => {
89581
89978
  });
89582
89979
  const invalidateGsapCache = useCallback176(() => invalidateGsapCacheRef.current(), []);
89583
89980
  const timelineEditing = useTimelineEditing({
@@ -89636,14 +90033,14 @@ function StudioApp() {
89636
90033
  setRightCollapsed: panelLayout.setRightCollapsed,
89637
90034
  setRightPanelTab: panelLayout.setRightPanelTab
89638
90035
  });
89639
- const clearDomSelectionRef = useRef171(() => {
90036
+ const clearDomSelectionRef = useRef175(() => {
89640
90037
  });
89641
- const domEditSelectionBridgeRef = useRef171(null);
89642
- const handleDomEditElementDeleteRef = useRef171(async () => {
90038
+ const domEditSelectionBridgeRef = useRef175(null);
90039
+ const handleDomEditElementDeleteRef = useRef175(async () => {
89643
90040
  });
89644
90041
  const domEditDeleteBridge = (s, o) => handleDomEditElementDeleteRef.current(s, o);
89645
- const resetKeyframesRef = useRef171(() => false);
89646
- const deleteSelectedKeyframesRef = useRef171(() => {
90042
+ const resetKeyframesRef = useRef175(() => false);
90043
+ const deleteSelectedKeyframesRef = useRef175(() => {
89647
90044
  });
89648
90045
  const { handleCopy: handleCopy2, handlePaste: handlePaste2, handleCut } = useClipboard({
89649
90046
  projectId,
@@ -89683,7 +90080,7 @@ function StudioApp() {
89683
90080
  forceReloadSdkSession: sdkHandle.forceReload,
89684
90081
  onToggleRecording: () => handleToggleRecordingRef.current()
89685
90082
  });
89686
- const sidebarTabRef = useRef171({
90083
+ const sidebarTabRef = useRef175({
89687
90084
  select: (t) => leftSidebarRef.current?.selectTab(t),
89688
90085
  get: () => leftSidebarRef.current?.getTab() ?? "compositions"
89689
90086
  });
@@ -89725,7 +90122,8 @@ function StudioApp() {
89725
90122
  getSidebarTab: sidebarTabRef.current.get,
89726
90123
  sdkSession: editFlowSdkSession,
89727
90124
  publishSdkSession: sdkHandle.publish,
89728
- forceReloadSdkSession: sdkHandle.forceReload
90125
+ forceReloadSdkSession: sdkHandle.forceReload,
90126
+ handleTimelineElementsDelete: timelineEditing.handleTimelineElementsDelete
89729
90127
  });
89730
90128
  domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
89731
90129
  handleDomZIndexReorderCommitRef.current = domEditSession.handleDomZIndexReorderCommit;
@@ -89772,9 +90170,9 @@ function StudioApp() {
89772
90170
  resetErrors: resetConsoleErrors
89773
90171
  } = useConsoleErrorCapture(previewIframe);
89774
90172
  const dragOverlay = useGlobalFileDrop(timelineEditing.handleTimelineFileDrop);
89775
- const handleToggleRecordingRef = useRef171(() => {
90173
+ const handleToggleRecordingRef = useRef175(() => {
89776
90174
  });
89777
- const domEditSessionRef = useRef171(domEditSession);
90175
+ const domEditSessionRef = useRef175(domEditSession);
89778
90176
  domEditSessionRef.current = domEditSession;
89779
90177
  const { gestureState, gestureRecording, handleToggleRecording } = useGestureCommit({
89780
90178
  domEditSessionRef,
@@ -89783,7 +90181,7 @@ function StudioApp() {
89783
90181
  isGestureRecordingRef
89784
90182
  });
89785
90183
  handleToggleRecordingRef.current = handleToggleRecording;
89786
- const canvasRectRef = useRef171(null);
90184
+ const canvasRectRef = useRef175(null);
89787
90185
  useLayoutEffect11(() => {
89788
90186
  if (gestureState !== "recording" || !previewIframe) {
89789
90187
  canvasRectRef.current = null;
@@ -89801,13 +90199,6 @@ function StudioApp() {
89801
90199
  },
89802
90200
  [appHotkeys, resetConsoleErrors, refreshPreviewDocumentVersion]
89803
90201
  );
89804
- const { setEditingFile } = fileManager;
89805
- const handleSelectComposition = useCompositionContentLoader({
89806
- projectId,
89807
- setEditingFile,
89808
- setActiveCompPath,
89809
- showToast
89810
- });
89811
90202
  const {
89812
90203
  designPanelActive,
89813
90204
  inspectorPanelActive,
@@ -89864,7 +90255,7 @@ function StudioApp() {
89864
90255
  handlePreviewIframeRef,
89865
90256
  refreshPreviewDocumentVersion
89866
90257
  });
89867
- const timelineToolbar = useMemo77(
90258
+ const timelineToolbar = useMemo78(
89868
90259
  () => /* @__PURE__ */ jsx219(
89869
90260
  TimelineToolbar,
89870
90261
  {
@@ -90022,11 +90413,11 @@ function StudioApp() {
90022
90413
  }
90023
90414
 
90024
90415
  // src/hooks/useElementPicker.ts
90025
- 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";
90026
90417
  function useElementPicker(iframeRef, options) {
90027
- const [isPickMode, setIsPickMode] = useState157(false);
90028
- const [pickedElement, setPickedElement] = useState157(null);
90029
- const activeOverrideRef = useRef172(null);
90418
+ const [isPickMode, setIsPickMode] = useState158(false);
90419
+ const [pickedElement, setPickedElement] = useState158(null);
90420
+ const activeOverrideRef = useRef176(null);
90030
90421
  const getActiveIframe = useCallback177(() => {
90031
90422
  return activeOverrideRef.current ?? iframeRef.current;
90032
90423
  }, [iframeRef]);
@@ -90100,7 +90491,7 @@ function useElementPicker(iframeRef, options) {
90100
90491
  window.addEventListener("message", handleMessage);
90101
90492
  return () => window.removeEventListener("message", handleMessage);
90102
90493
  });
90103
- const optionsRef = useRef172(options);
90494
+ const optionsRef = useRef176(options);
90104
90495
  optionsRef.current = options;
90105
90496
  const syncToSource = useCallback177(
90106
90497
  (elementId2, selector, op) => {
@@ -90211,7 +90602,7 @@ function useElementPicker(iframeRef, options) {
90211
90602
  },
90212
90603
  [pickedElement, getActiveIframe, syncToSource]
90213
90604
  );
90214
- const activeIframeRef = useRef172(null);
90605
+ const activeIframeRef = useRef176(null);
90215
90606
  activeIframeRef.current = getActiveIframe();
90216
90607
  return {
90217
90608
  isPickMode,
@@ -90377,6 +90768,7 @@ function findElementBlock(html2, elementId2) {
90377
90768
  return null;
90378
90769
  }
90379
90770
  export {
90771
+ AskAgentModal,
90380
90772
  CompositionBreadcrumb,
90381
90773
  CompositionThumbnail,
90382
90774
  EditorShell,
@@ -90390,6 +90782,8 @@ export {
90390
90782
  Timeline,
90391
90783
  VideoThumbnail,
90392
90784
  applyPatch,
90785
+ buildPickerAgentContextPreview,
90786
+ buildPickerAgentPrompt,
90393
90787
  findElementBlock,
90394
90788
  formatTime,
90395
90789
  liveTime,