@hyperframes/studio 0.7.57 → 0.7.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/dist/assets/{hyperframes-player--Z69cEkE.js → hyperframes-player-Bm07FLkl.js} +1 -1
  2. package/dist/assets/{index-KsfE1bUu.js → index-B995FG46.js} +1 -1
  3. package/dist/assets/index-CrkAdJkb.js +426 -0
  4. package/dist/assets/index-Dj5p8U_A.css +1 -0
  5. package/dist/assets/{index-Bf1x1y8H.js → index-FvzmPhfG.js} +1 -1
  6. package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
  7. package/dist/chunk-OBAG3GWK.js.map +1 -0
  8. package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
  9. package/dist/index.d.ts +27 -7
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +18029 -11563
  12. package/dist/index.js.map +1 -1
  13. package/dist/styles/tailwind-preset.d.ts +5 -0
  14. package/dist/styles/tailwind-preset.js +8 -1
  15. package/dist/styles/tailwind-preset.js.map +1 -1
  16. package/package.json +7 -7
  17. package/src/App.tsx +6 -6
  18. package/src/components/DesignPanelPromoteProvider.tsx +15 -2
  19. package/src/components/StudioFeedbackBar.tsx +2 -2
  20. package/src/components/StudioRightPanel.tsx +29 -51
  21. package/src/components/editor/AnimationCard.test.tsx +134 -0
  22. package/src/components/editor/AnimationCard.tsx +19 -4
  23. package/src/components/editor/ArcPathControls.tsx +1 -0
  24. package/src/components/editor/CanvasContextMenu.test.tsx +27 -14
  25. package/src/components/editor/CanvasContextMenu.tsx +71 -6
  26. package/src/components/editor/DomEditOverlay.tsx +5 -2
  27. package/src/components/editor/GestureRecordControl.tsx +7 -1
  28. package/src/components/editor/GsapAnimationSection.tsx +109 -14
  29. package/src/components/editor/InspectorHeaderActions.tsx +42 -4
  30. package/src/components/editor/LayersPanel.tsx +110 -99
  31. package/src/components/editor/PropertyPanel.test.tsx +948 -0
  32. package/src/components/editor/PropertyPanel.tsx +144 -148
  33. package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
  34. package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
  35. package/src/components/editor/PropertyPanelFlat.tsx +580 -0
  36. package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
  37. package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
  38. package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
  39. package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
  40. package/src/components/editor/canvasContextMenuZOrder.test.ts +57 -0
  41. package/src/components/editor/canvasContextMenuZOrder.ts +40 -1
  42. package/src/components/editor/domEditingDom.ts +5 -6
  43. package/src/components/editor/domEditingElement.ts +15 -32
  44. package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
  45. package/src/components/editor/gsapLivePreview.ts +34 -0
  46. package/src/components/editor/layersPanelSort.ts +80 -0
  47. package/src/components/editor/manualEditingAvailability.test.ts +10 -0
  48. package/src/components/editor/manualEditingAvailability.ts +9 -0
  49. package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
  50. package/src/components/editor/propertyPanelColor.test.tsx +28 -0
  51. package/src/components/editor/propertyPanelColor.tsx +31 -1
  52. package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
  53. package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
  54. package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
  55. package/src/components/editor/propertyPanelFill.tsx +22 -5
  56. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
  57. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
  58. package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
  59. package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
  60. package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
  61. package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
  62. package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
  63. package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
  64. package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
  65. package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
  66. package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
  67. package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
  68. package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
  69. package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
  70. package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
  71. package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
  72. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
  73. package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
  74. package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
  75. package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
  76. package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
  77. package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
  78. package/src/components/editor/propertyPanelFont.test.tsx +30 -0
  79. package/src/components/editor/propertyPanelFont.tsx +136 -95
  80. package/src/components/editor/propertyPanelHelpers.ts +73 -0
  81. package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
  82. package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
  83. package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
  84. package/src/components/editor/propertyPanelSections.test.tsx +161 -0
  85. package/src/components/editor/propertyPanelSections.tsx +154 -90
  86. package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
  87. package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
  88. package/src/components/editor/propertyPanelTypes.ts +14 -1
  89. package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
  90. package/src/components/editor/propertyPanelValueTier.ts +28 -0
  91. package/src/components/editor/useColorGradingController.test.ts +418 -0
  92. package/src/components/editor/useColorGradingController.ts +596 -0
  93. package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
  94. package/src/components/editor/useLayerRevealOverride.ts +389 -0
  95. package/src/components/nle/PreviewOverlays.tsx +27 -2
  96. package/src/components/nle/TimelinePane.test.ts +3 -0
  97. package/src/components/nle/TimelinePane.tsx +11 -1
  98. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
  99. package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
  100. package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
  101. package/src/components/nle/zLaneGesture.test.ts +176 -0
  102. package/src/components/nle/zLaneGesture.ts +77 -0
  103. package/src/components/panels/VariablesPanel.tsx +4 -0
  104. package/src/components/sidebar/CompositionsTab.test.ts +28 -2
  105. package/src/components/sidebar/CompositionsTab.tsx +3 -1
  106. package/src/components/storyboard/FramePoster.tsx +24 -4
  107. package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
  108. package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
  109. package/src/components/storyboard/StoryboardGrid.tsx +23 -1
  110. package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
  111. package/src/components/storyboard/StoryboardView.tsx +9 -2
  112. package/src/components/storyboard/frameComments.test.ts +95 -0
  113. package/src/components/storyboard/frameComments.ts +115 -0
  114. package/src/components/storyboard/useFrameComments.ts +82 -0
  115. package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
  116. package/src/contexts/DesignPanelInputContext.tsx +48 -0
  117. package/src/contexts/DomEditContext.tsx +4 -0
  118. package/src/contexts/VariablePromoteContext.test.tsx +67 -0
  119. package/src/contexts/VariablePromoteContext.tsx +10 -5
  120. package/src/hooks/domEditCommitRunner.ts +11 -0
  121. package/src/hooks/domEditCommitTypes.ts +17 -1
  122. package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
  123. package/src/hooks/fetchStubTestUtils.ts +16 -0
  124. package/src/hooks/gsapScriptCommitTypes.ts +6 -4
  125. package/src/hooks/timelineEditingHelpers.test.ts +92 -1
  126. package/src/hooks/timelineEditingHelpers.ts +32 -15
  127. package/src/hooks/timelineMoveAdapter.ts +2 -1
  128. package/src/hooks/timelineTimingSync.test.ts +703 -33
  129. package/src/hooks/timelineTimingSync.ts +282 -97
  130. package/src/hooks/timelineTrackVisibility.test.ts +55 -1
  131. package/src/hooks/timelineTrackVisibility.ts +31 -13
  132. package/src/hooks/useAppHotkeys.ts +9 -0
  133. package/src/hooks/useClipboard.ts +19 -17
  134. package/src/hooks/useDomEditAttributeCommits.ts +123 -1
  135. package/src/hooks/useDomEditCommits.test.tsx +316 -38
  136. package/src/hooks/useDomEditCommits.ts +81 -162
  137. package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
  138. package/src/hooks/useDomEditSession.test.tsx +3 -1
  139. package/src/hooks/useDomEditSession.ts +18 -6
  140. package/src/hooks/useDomEditTextCommits.ts +14 -9
  141. package/src/hooks/useDomEditWiring.ts +10 -9
  142. package/src/hooks/useElementLifecycleOps.test.tsx +426 -60
  143. package/src/hooks/useElementLifecycleOps.ts +182 -84
  144. package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
  145. package/src/hooks/useFileManager.ts +82 -60
  146. package/src/hooks/useGsapAnimationOps.ts +7 -6
  147. package/src/hooks/useGsapKeyframeOps.ts +6 -5
  148. package/src/hooks/useGsapPropertyDebounce.ts +43 -28
  149. package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
  150. package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
  151. package/src/hooks/useGsapScriptCommits.ts +150 -50
  152. package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
  153. package/src/hooks/useGsapSelectionHandlers.ts +94 -31
  154. package/src/hooks/useInspectorSplitResize.ts +51 -0
  155. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
  156. package/src/hooks/usePersistentEditHistory.test.ts +49 -1
  157. package/src/hooks/usePersistentEditHistory.ts +71 -31
  158. package/src/hooks/usePreviewPersistence.ts +2 -2
  159. package/src/hooks/useProjectCompositionVariables.ts +10 -10
  160. package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
  161. package/src/hooks/useProjectSignaturePoll.ts +68 -0
  162. package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
  163. package/src/hooks/useSdkSession.ts +155 -14
  164. package/src/hooks/useSlideshowPersist.ts +7 -1
  165. package/src/hooks/useStoryboard.ts +23 -4
  166. package/src/hooks/useTimelineEditing.test.tsx +306 -155
  167. package/src/hooks/useTimelineEditing.ts +111 -101
  168. package/src/hooks/useTimelineEditingTypes.ts +4 -1
  169. package/src/hooks/useTimelineGroupEditing.ts +51 -5
  170. package/src/hooks/useVariablesPersist.ts +9 -7
  171. package/src/icons/SystemIcons.tsx +2 -0
  172. package/src/player/components/Timeline.test.ts +31 -21
  173. package/src/player/components/Timeline.tsx +62 -91
  174. package/src/player/components/TimelineCanvas.tsx +36 -5
  175. package/src/player/components/TimelineLanes.tsx +32 -10
  176. package/src/player/components/TimelineOverlays.tsx +36 -0
  177. package/src/player/components/TimelineRuler.tsx +18 -5
  178. package/src/player/components/TrackGapContextMenu.tsx +116 -0
  179. package/src/player/components/timelineCallbacks.ts +4 -1
  180. package/src/player/components/timelineClipDragCommit.test.ts +279 -3
  181. package/src/player/components/timelineClipDragCommit.ts +173 -71
  182. package/src/player/components/timelineGapCommit.test.ts +163 -0
  183. package/src/player/components/timelineGapCommit.ts +103 -0
  184. package/src/player/components/timelineGaps.test.ts +221 -0
  185. package/src/player/components/timelineGaps.ts +182 -0
  186. package/src/player/components/timelineLayout.test.ts +2 -1
  187. package/src/player/components/timelineLayout.ts +22 -6
  188. package/src/player/components/timelineMarquee.test.ts +20 -10
  189. package/src/player/components/timelineMarquee.ts +9 -2
  190. package/src/player/components/timelineStackingSync.test.ts +42 -1
  191. package/src/player/components/timelineStackingSync.ts +31 -16
  192. package/src/player/components/timelineZMirror.test.ts +667 -0
  193. package/src/player/components/timelineZMirror.ts +393 -0
  194. package/src/player/components/timelineZoom.test.ts +2 -2
  195. package/src/player/components/timelineZoom.ts +7 -3
  196. package/src/player/components/useTimelineClipDrag.ts +1 -1
  197. package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
  198. package/src/player/components/useTimelineGapHighlights.ts +110 -0
  199. package/src/player/components/useTimelinePlayhead.ts +4 -3
  200. package/src/player/components/useTimelineRangeSelection.ts +6 -4
  201. package/src/player/components/useTimelineScrollViewport.ts +73 -0
  202. package/src/player/components/useTimelineStackingSync.test.tsx +0 -1
  203. package/src/player/components/useTimelineStackingSync.ts +5 -3
  204. package/src/player/components/useTimelineTrackDerivations.ts +38 -0
  205. package/src/player/components/useTrackGapMenu.ts +155 -0
  206. package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
  207. package/src/player/hooks/useExpandedTimelineElements.ts +12 -1
  208. package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
  209. package/src/player/lib/layerOrdering.test.ts +1 -15
  210. package/src/player/lib/layerOrdering.ts +5 -28
  211. package/src/player/lib/timelineDOM.test.ts +72 -0
  212. package/src/player/lib/timelineDOM.ts +3 -0
  213. package/src/player/lib/timelineElementHelpers.ts +35 -11
  214. package/src/player/store/playerStore.ts +12 -1
  215. package/src/styles/studio.css +22 -0
  216. package/src/styles/tailwind-preset.shared.js +7 -0
  217. package/src/telemetry/events.test.ts +10 -0
  218. package/src/telemetry/events.ts +1 -0
  219. package/src/utils/designInputTracking.test.ts +97 -0
  220. package/src/utils/designInputTracking.ts +80 -0
  221. package/src/utils/domEditSaveQueue.ts +4 -3
  222. package/src/utils/editHistory.test.ts +82 -0
  223. package/src/utils/editHistory.ts +14 -2
  224. package/src/utils/gsapSoftReload.test.ts +54 -115
  225. package/src/utils/gsapSoftReload.ts +42 -152
  226. package/src/utils/gsapUndoRestore.test.ts +269 -0
  227. package/src/utils/gsapUndoRestore.ts +269 -0
  228. package/src/utils/sdkCutover.gate.test.ts +7 -7
  229. package/src/utils/sdkCutover.test.ts +490 -57
  230. package/src/utils/sdkCutover.ts +181 -219
  231. package/src/utils/sdkEditTransaction.ts +292 -0
  232. package/src/utils/sdkResolverAttempts.ts +94 -0
  233. package/src/utils/sdkResolverShadow.test.ts +148 -0
  234. package/src/utils/sdkResolverShadow.ts +139 -106
  235. package/src/utils/setSlideshowManifest.test.ts +0 -9
  236. package/src/utils/setSlideshowManifest.ts +20 -27
  237. package/src/utils/sourceScopedSelectorIndex.ts +29 -0
  238. package/src/utils/studioFileHistory.test.ts +41 -0
  239. package/src/utils/studioFileHistory.ts +30 -27
  240. package/src/utils/studioFileMutationCoordinator.ts +45 -0
  241. package/src/utils/studioHelpers.test.ts +45 -0
  242. package/src/utils/studioHelpers.ts +45 -14
  243. package/src/utils/studioSaveDiagnostics.ts +17 -0
  244. package/dist/assets/index-DfmYkU44.js +0 -423
  245. package/dist/assets/index-_pqzyxB1.css +0 -1
  246. package/dist/chunk-5QSIMBEJ.js.map +0 -1
  247. /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
@@ -2,6 +2,7 @@ import type { ParsedGsap } from "@hyperframes/core/gsap-parser";
2
2
  import type { Composition } from "@hyperframes/sdk";
3
3
  import type { DomEditSelection } from "../components/editor/domEditingTypes";
4
4
  import type { EditHistoryKind } from "../utils/editHistory";
5
+ import type { PublishSdkSession } from "../utils/sdkCutover";
5
6
  import type { RuntimeTweenChange } from "./gsapRuntimePatch";
6
7
 
7
8
  export interface MutationResult {
@@ -22,10 +23,9 @@ export interface CommitMutationOptions {
22
23
  beforeReload?: () => void;
23
24
  /**
24
25
  * Serialize this commit against others sharing the same key. Used to chain
25
- * per-animationId GSAP meta updates so overlapping read-modify-write POSTs to
26
- * one file can't interleave which would pair the shadow fidelity diff with a
27
- * stale server result and report false ease mismatches. Commits without a key
28
- * (and under distinct keys) run concurrently as before.
26
+ * per-animationId GSAP meta updates. Every commit independently takes the
27
+ * project/file mutation lock, so this key only adds ordering and can never
28
+ * bypass whole-file serialization.
29
29
  */
30
30
  serializeKey?: string;
31
31
  /**
@@ -87,6 +87,8 @@ export interface GsapScriptCommitsParams {
87
87
  showToast: (message: string, tone?: "error" | "info") => void;
88
88
  /** Stage 7 §3.5: SDK session for routing GSAP tween ops through addGsapTween/setGsapTween/removeGsapTween. */
89
89
  sdkSession?: Composition | null;
90
+ /** Publish a fully persisted candidate SDK session. */
91
+ publishSdkSession?: PublishSdkSession;
90
92
  writeProjectFile?: (path: string, content: string) => Promise<void>;
91
93
  /** Resync the in-memory SDK session after a server-authoritative write. */
92
94
  forceReloadSdkSession?: () => void;
@@ -5,6 +5,7 @@ import {
5
5
  buildTimelineMoveTimingPatch,
6
6
  deleteSelectedKeyframes,
7
7
  extendRootDurationIfNeeded,
8
+ patchIframeDomTiming,
8
9
  persistTimelineBatchEdit,
9
10
  type PersistTimelineBatchChange,
10
11
  } from "./timelineEditingHelpers";
@@ -42,7 +43,11 @@ describe("applyTimelineStackingReorder", () => {
42
43
  it("commits via the change's own locator even when the element is not in timelineElements", () => {
43
44
  // Sub-comp children live in the preview iframe but NOT in the top-level
44
45
  // timelineElements list — the intent must be self-contained.
45
- const iframe = makeIframeWith(`<div id="chip" style="z-index: 1"></div>`);
46
+ const iframe = makeIframeWith(`
47
+ <div data-composition-id="scene" data-composition-file="scenes/scene.html">
48
+ <div id="chip" style="z-index: 1"></div>
49
+ </div>
50
+ `);
46
51
  const commit = vi.fn<(entries: unknown[]) => void>();
47
52
 
48
53
  applyTimelineStackingReorder({
@@ -96,6 +101,92 @@ describe("applyTimelineStackingReorder", () => {
96
101
 
97
102
  expect(commit).not.toHaveBeenCalled();
98
103
  });
104
+
105
+ it("resolves selectorIndex within the change's source file", () => {
106
+ const iframe = makeIframeWith(`
107
+ <div data-composition-id="root">
108
+ <div class="chip">root</div>
109
+ <div data-composition-id="scene" data-composition-file="scenes/scene.html">
110
+ <div class="chip">scene zero</div>
111
+ <div class="chip" style="z-index: 1">scene one</div>
112
+ </div>
113
+ </div>
114
+ `);
115
+ const sceneOne = iframe.contentDocument?.querySelectorAll(".chip")[2];
116
+ const commit = vi.fn<(entries: unknown[]) => void>();
117
+
118
+ applyTimelineStackingReorder({
119
+ element: el({ id: "scene-one", tag: "div" }),
120
+ stackingReorder: {
121
+ contextKey: "scene",
122
+ placement: { type: "above", layerId: "layer:scene:x" },
123
+ zIndexChanges: [
124
+ {
125
+ key: "scenes/scene.html#.chip:1",
126
+ zIndex: 5,
127
+ selector: ".chip",
128
+ selectorIndex: 1,
129
+ sourceFile: "scenes/scene.html",
130
+ },
131
+ ],
132
+ },
133
+ timelineElements: [],
134
+ iframe,
135
+ activeCompPath: "index.html",
136
+ commit,
137
+ });
138
+
139
+ const entries = commit.mock.calls[0]![0] as Array<{ element: Element }>;
140
+ expect(entries[0]!.element).toBe(sceneOne);
141
+ });
142
+ });
143
+
144
+ describe("patchIframeDomTiming", () => {
145
+ it("resolves selectorIndex within the element's source file", () => {
146
+ const iframe = makeIframeWith(`
147
+ <div data-composition-id="root">
148
+ <div class="clip" data-start="1">root</div>
149
+ <div data-composition-id="scene" data-composition-file="scenes/scene.html">
150
+ <div class="clip" data-start="2">scene zero</div>
151
+ <div class="clip" data-start="3">scene one</div>
152
+ </div>
153
+ </div>
154
+ `);
155
+ const clips = iframe.contentDocument?.querySelectorAll<HTMLElement>(".clip");
156
+ const target = el({
157
+ id: "scene-one",
158
+ tag: "div",
159
+ selector: ".clip",
160
+ selectorIndex: 1,
161
+ sourceFile: "scenes/scene.html",
162
+ });
163
+
164
+ patchIframeDomTiming(iframe, target, [["data-start", "9"]], "index.html");
165
+
166
+ expect([...clips!].map((clip) => clip.dataset.start)).toEqual(["1", "2", "9"]);
167
+ });
168
+
169
+ it("resolves duplicate domId within the element's source file", () => {
170
+ const iframe = makeIframeWith(`
171
+ <div data-composition-id="root">
172
+ <div id="card" data-start="1">root</div>
173
+ <div data-composition-id="scene" data-composition-file="scenes/scene.html">
174
+ <div id="card" data-start="2">scene</div>
175
+ </div>
176
+ </div>
177
+ `);
178
+ const cards = iframe.contentDocument?.querySelectorAll<HTMLElement>("#card");
179
+ const target = el({
180
+ id: "scene-card",
181
+ tag: "div",
182
+ domId: "card",
183
+ sourceFile: "scenes/scene.html",
184
+ });
185
+
186
+ patchIframeDomTiming(iframe, target, [["data-start", "9"]], "index.html");
187
+
188
+ expect([...cards!].map((card) => card.dataset.start)).toEqual(["1", "9"]);
189
+ });
99
190
  });
100
191
 
101
192
  describe("extendRootDurationIfNeeded", () => {
@@ -13,6 +13,7 @@ import { saveProjectFilesWithHistory, type RecordEditInput } from "../utils/stud
13
13
  import type { TimelineZIndexReorderCommit } from "./useTimelineEditingTypes";
14
14
  import { setCompositionDurationToContent } from "../utils/timelineAssetDrop";
15
15
  import { readFileContent } from "./timelineTimingSync";
16
+ import { findElementForSelection } from "../components/editor/domEditingElement";
16
17
  export { deleteSelectedKeyframes } from "./deleteSelectedKeyframes";
17
18
  export { readFileContent };
18
19
  function isHTMLElement(element: Element | null): element is HTMLElement {
@@ -53,12 +54,6 @@ export function applyTimelineStackingReorder(input: {
53
54
  input.timelineElements.map((el) => [getTimelineElementIdentity(el), el]),
54
55
  );
55
56
  const doc = input.iframe?.contentDocument ?? null;
56
- const findLive = (domId?: string, selector?: string, selectorIndex?: number): Element | null => {
57
- if (!doc) return null;
58
- if (domId) return doc.getElementById(domId);
59
- if (selector) return doc.querySelectorAll(selector)[selectorIndex ?? 0] ?? null;
60
- return null;
61
- };
62
57
  const commitEntries: Array<{
63
58
  element: HTMLElement;
64
59
  zIndex: number;
@@ -73,7 +68,15 @@ export function applyTimelineStackingReorder(input: {
73
68
  const domId = change.domId ?? sibling?.domId;
74
69
  const selector = change.selector ?? sibling?.selector;
75
70
  const selectorIndex = change.selectorIndex ?? sibling?.selectorIndex;
76
- const element = findLive(domId, selector, selectorIndex);
71
+ const sourceFile =
72
+ change.sourceFile ?? sibling?.sourceFile ?? input.activeCompPath ?? "index.html";
73
+ const element = doc
74
+ ? findElementForSelection(
75
+ doc,
76
+ { id: domId, selector, selectorIndex, sourceFile },
77
+ input.activeCompPath,
78
+ )
79
+ : null;
77
80
  if (!isHTMLElement(element)) return Promise.resolve();
78
81
  if (getElementZIndex(element) === change.zIndex) continue;
79
82
  commitEntries.push({
@@ -82,12 +85,15 @@ export function applyTimelineStackingReorder(input: {
82
85
  id: domId ?? sibling?.id ?? change.key,
83
86
  selector,
84
87
  selectorIndex,
85
- sourceFile: change.sourceFile ?? sibling?.sourceFile ?? input.activeCompPath ?? "index.html",
88
+ sourceFile,
86
89
  key: change.key,
87
90
  });
88
91
  }
89
92
  if (commitEntries.length === 0) return Promise.resolve();
90
- return input.commit?.(commitEntries, input.coalesceKey) ?? Promise.resolve();
93
+ // The durability report is for gesture-level callers (z→lane mirror); this
94
+ // lane-drag z-sync path has no dependent follow-up write — swallow it.
95
+ // Promise.resolve-wrapped: a commit implementation may return void.
96
+ return Promise.resolve(input.commit?.(commitEntries, input.coalesceKey)).then(() => undefined);
91
97
  }
92
98
  export function extendRootDurationIfNeeded(newEnd: number): boolean {
93
99
  const store = usePlayerStore.getState();
@@ -126,15 +132,22 @@ export type PatchTarget = NonNullable<ReturnType<typeof buildPatchTarget>>;
126
132
  export function findTimelineElementInIframe(
127
133
  iframe: HTMLIFrameElement | null,
128
134
  element: TimelineElement,
135
+ activeCompositionPath: string | null = null,
129
136
  ): Element | null {
130
137
  try {
131
138
  const doc = iframe?.contentDocument;
132
139
  if (!doc) return null;
133
- return element.domId
134
- ? doc.getElementById(element.domId)
135
- : element.selector
136
- ? (doc.querySelectorAll(element.selector)[element.selectorIndex ?? 0] ?? null)
137
- : null;
140
+ return findElementForSelection(
141
+ doc,
142
+ {
143
+ hfId: element.hfId,
144
+ id: element.domId,
145
+ selector: element.selector,
146
+ selectorIndex: element.selectorIndex,
147
+ sourceFile: element.sourceFile || activeCompositionPath || "index.html",
148
+ },
149
+ activeCompositionPath,
150
+ );
138
151
  } catch {
139
152
  return null;
140
153
  }
@@ -143,9 +156,10 @@ export function patchIframeDomTiming(
143
156
  iframe: HTMLIFrameElement | null,
144
157
  element: TimelineElement,
145
158
  attrs: Array<[string, string]>,
159
+ activeCompositionPath: string | null = null,
146
160
  ): void {
147
161
  try {
148
- const el = findTimelineElementInIframe(iframe, element);
162
+ const el = findTimelineElementInIframe(iframe, element, activeCompositionPath);
149
163
  if (!el) return;
150
164
  for (const [name, value] of attrs) el.setAttribute(name, value);
151
165
  } catch {
@@ -299,6 +313,8 @@ export interface PersistTimelineBatchEditInput {
299
313
  domEditSaveTimestampRef: React.MutableRefObject<number>;
300
314
  pendingTimelineEditPathRef: React.MutableRefObject<Set<string>>;
301
315
  coalesceKey?: string;
316
+ /** Per-entry undo coalesce window override (ms) — see EditHistoryEntry.coalesceMs. */
317
+ coalesceMs?: number;
302
318
  }
303
319
 
304
320
  export async function persistTimelineBatchEdit(
@@ -347,6 +363,7 @@ export async function persistTimelineBatchEdit(
347
363
  label: input.label,
348
364
  kind: "timeline",
349
365
  coalesceKey: input.coalesceKey,
366
+ coalesceMs: input.coalesceMs,
350
367
  files,
351
368
  readFile: async (path) => originals.get(path) ?? readFileContent(input.projectId, path),
352
369
  writeFile: input.writeProjectFile,
@@ -23,6 +23,7 @@ export function persistTimelineMoveEditsAtomically(
23
23
  coalesceKey: string | undefined,
24
24
  operation: TimelineMoveOperation,
25
25
  deps: AtomicMoveDeps,
26
+ coalesceMs?: number,
26
27
  ): Promise<void> {
27
28
  return deps.handleTimelineGroupMove(
28
29
  edits.map(({ element, updates }) => ({
@@ -34,6 +35,6 @@ export function persistTimelineMoveEditsAtomically(
34
35
  // ("timing") omit it so they stay eligible for the SDK fast path.
35
36
  track: operation === "timing" ? undefined : updates.track,
36
37
  })),
37
- { coalesceKey },
38
+ { coalesceKey, coalesceMs },
38
39
  );
39
40
  }