@hyperframes/studio 0.7.63 → 0.7.65

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 (38) hide show
  1. package/dist/assets/{hyperframes-player-BO2UXx-E.js → hyperframes-player-D2zuA3cx.js} +1 -1
  2. package/dist/assets/{index-C_7k3kwr.js → index-BqX4YLnH.js} +190 -190
  3. package/dist/assets/{index-_72wCKSP.js → index-DwwsYjuk.js} +1 -1
  4. package/dist/assets/{index-C0A0DlLk.js → index-hLCqiyjx.js} +1 -1
  5. package/dist/chunk-PZ5AY32C.js +10 -0
  6. package/dist/{domEditingLayers-2CKWGEHS.js → domEditingLayers-AT7G6F4L.js} +2 -1
  7. package/dist/domEditingLayers-AT7G6F4L.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/dist/index.js +171 -53
  10. package/dist/index.js.map +1 -1
  11. package/dist/styles/tailwind-preset.js +2 -0
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +9 -8
  14. package/src/components/StudioRightPanel.tsx +2 -2
  15. package/src/components/editor/keyframeRetime.test.ts +56 -17
  16. package/src/components/editor/keyframeRetime.ts +48 -4
  17. package/src/components/editor/manualEditingAvailability.ts +7 -0
  18. package/src/components/editor/manualEditsTypes.ts +1 -1
  19. package/src/components/nle/useTimelineEditCallbacks.test.ts +85 -0
  20. package/src/components/nle/useTimelineEditCallbacks.ts +52 -12
  21. package/src/components/panels/VariablesPanel.tsx +3 -1
  22. package/src/contexts/FileManagerContext.tsx +1 -0
  23. package/src/hooks/timelineEditingHelpers.test.ts +26 -0
  24. package/src/hooks/timelineEditingHelpers.ts +10 -1
  25. package/src/hooks/useGsapKeyframeOps.test.tsx +1 -0
  26. package/src/hooks/useGsapKeyframeOps.ts +2 -0
  27. package/src/hooks/useGsapPropertyDebounce.ts +1 -0
  28. package/src/hooks/useRazorSplit.ts +1 -0
  29. package/src/hooks/useTimelineEditing.test.tsx +1 -0
  30. package/src/hooks/useTimelineGroupEditing.ts +2 -0
  31. package/src/player/index.ts +0 -2
  32. package/src/utils/sdkCutover.familyGate.test.ts +47 -0
  33. package/src/utils/sdkCutover.test.ts +1 -0
  34. package/src/utils/sdkCutover.ts +95 -38
  35. package/src/utils/sdkCutoverPolicy.report.ts +3 -0
  36. package/src/utils/sdkCutoverPolicy.test.ts +38 -0
  37. package/src/utils/sdkCutoverPolicy.ts +98 -0
  38. /package/dist/{domEditingLayers-2CKWGEHS.js.map → chunk-PZ5AY32C.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import{n as Qi}from"./index-C_7k3kwr.js";/*!
1
+ import{n as Qi}from"./index-BqX4YLnH.js";/*!
2
2
  * Copyright (c) 2026-present, Vanilagy and contributors
3
3
  *
4
4
  * This Source Code Form is subject to the terms of the Mozilla Public
@@ -1 +1 @@
1
- import{g as P}from"./index-C_7k3kwr.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
1
+ import{g as P}from"./index-BqX4YLnH.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
@@ -0,0 +1,10 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };
10
+ //# sourceMappingURL=chunk-PZ5AY32C.js.map
@@ -19,6 +19,7 @@ import {
19
19
  resolveDomEditSelection,
20
20
  serializeDomEditTextFields
21
21
  } from "./chunk-OBAG3GWK.js";
22
+ import "./chunk-PZ5AY32C.js";
22
23
  export {
23
24
  buildDefaultDomEditTextField,
24
25
  buildDomEditPatchTarget,
@@ -40,4 +41,4 @@ export {
40
41
  resolveDomEditSelection,
41
42
  serializeDomEditTextFields
42
43
  };
43
- //# sourceMappingURL=domEditingLayers-2CKWGEHS.js.map
44
+ //# sourceMappingURL=domEditingLayers-AT7G6F4L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-C_7k3kwr.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BqX4YLnH.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-Ceyz8Qt2.css">
10
10
  </head>
11
11
  <body>
package/dist/index.js CHANGED
@@ -24,6 +24,9 @@ import {
24
24
  serializeDomEditTextFields,
25
25
  setCompositionSourceMap
26
26
  } from "./chunk-OBAG3GWK.js";
27
+ import {
28
+ __export
29
+ } from "./chunk-PZ5AY32C.js";
27
30
 
28
31
  // src/components/EditorShell.tsx
29
32
  import { useCallback as useCallback45 } from "react";
@@ -2952,6 +2955,7 @@ function useFileManagerContextOptional() {
2952
2955
  }
2953
2956
  function FileManagerProvider({
2954
2957
  value: {
2958
+ // fallow-ignore-next-line code-duplication
2955
2959
  editingFile,
2956
2960
  setEditingFile,
2957
2961
  projectDir,
@@ -6527,6 +6531,48 @@ function clampGroupMoveDelta(rawDelta, memberStarts) {
6527
6531
  return rawDelta < minDelta ? minDelta : rawDelta;
6528
6532
  }
6529
6533
 
6534
+ // src/components/editor/manualEditingAvailability.ts
6535
+ var manualEditingAvailability_exports = {};
6536
+ __export(manualEditingAvailability_exports, {
6537
+ STUDIO_BLOCKS_PANEL_ENABLED: () => STUDIO_BLOCKS_PANEL_ENABLED,
6538
+ STUDIO_FLAT_INSPECTOR_ENABLED: () => STUDIO_FLAT_INSPECTOR_ENABLED,
6539
+ STUDIO_GSAP_PANEL_ENABLED: () => STUDIO_GSAP_PANEL_ENABLED,
6540
+ STUDIO_INSPECTOR_PANELS_ENABLED: () => STUDIO_INSPECTOR_PANELS_ENABLED,
6541
+ STUDIO_KEYFRAMES_ENABLED: () => STUDIO_KEYFRAMES_ENABLED,
6542
+ STUDIO_MANUAL_EDITING_DISABLED_TITLE: () => STUDIO_MANUAL_EDITING_DISABLED_TITLE,
6543
+ STUDIO_PREVIEW_MANUAL_EDITING_ENABLED: () => STUDIO_PREVIEW_MANUAL_EDITING_ENABLED,
6544
+ STUDIO_PREVIEW_SELECTION_ENABLED: () => STUDIO_PREVIEW_SELECTION_ENABLED,
6545
+ STUDIO_RAZOR_TOOL_ENABLED: () => STUDIO_RAZOR_TOOL_ENABLED,
6546
+ STUDIO_SDK_CUTOVER_ENABLED: () => STUDIO_SDK_CUTOVER_ENABLED,
6547
+ STUDIO_SDK_CUTOVER_FAMILIES: () => STUDIO_SDK_CUTOVER_FAMILIES,
6548
+ STUDIO_SDK_RESOLVER_SHADOW_ENABLED: () => STUDIO_SDK_RESOLVER_SHADOW_ENABLED,
6549
+ resolveStudioBooleanEnvFlag: () => resolveStudioBooleanEnvFlag
6550
+ });
6551
+
6552
+ // src/utils/sdkCutoverPolicy.ts
6553
+ var STUDIO_SDK_OPERATION_FAMILIES = [
6554
+ "dom",
6555
+ "timing",
6556
+ "gsap-animation",
6557
+ "gsap-keyframe",
6558
+ "lifecycle"
6559
+ ];
6560
+ function resolveEnabledSdkFamilies(env2, masterEnabled) {
6561
+ if (!masterEnabled) return /* @__PURE__ */ new Set();
6562
+ const raw = env2["VITE_STUDIO_SDK_CUTOVER_FAMILIES"];
6563
+ if (typeof raw !== "string" || raw.trim() === "") return /* @__PURE__ */ new Set();
6564
+ const requested = raw.split(",").map((family) => family.trim()).filter(Boolean);
6565
+ const known = new Set(STUDIO_SDK_OPERATION_FAMILIES);
6566
+ const unknown = requested.filter((family) => !known.has(family));
6567
+ if (unknown.length > 0) {
6568
+ throw new Error(`Unknown Studio SDK cutover families: ${unknown.join(", ")}`);
6569
+ }
6570
+ return new Set(requested);
6571
+ }
6572
+ function isSdkFamilyEnabled(masterEnabled, configured, family) {
6573
+ return masterEnabled && (configured?.has(family) ?? true);
6574
+ }
6575
+
6530
6576
  // src/components/editor/manualEditingAvailability.ts
6531
6577
  var STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING";
6532
6578
  var STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS";
@@ -6582,6 +6628,10 @@ var STUDIO_SDK_CUTOVER_ENABLED = resolveStudioBooleanEnvFlag(
6582
6628
  ["VITE_STUDIO_SDK_CUTOVER_ENABLED"],
6583
6629
  false
6584
6630
  );
6631
+ var STUDIO_SDK_CUTOVER_FAMILIES = resolveEnabledSdkFamilies(
6632
+ env,
6633
+ STUDIO_SDK_CUTOVER_ENABLED
6634
+ );
6585
6635
  var STUDIO_SDK_RESOLVER_SHADOW_ENABLED = resolveStudioBooleanEnvFlag(
6586
6636
  env,
6587
6637
  ["VITE_STUDIO_SDK_RESOLVER_SHADOW_ENABLED"],
@@ -7911,6 +7961,12 @@ function findTimelineElementInIframe(iframe, element, activeCompositionPath = nu
7911
7961
  try {
7912
7962
  const doc = iframe?.contentDocument;
7913
7963
  if (!doc) return null;
7964
+ if (element.kind === "composition" && element.compositionSrc) {
7965
+ return findElementForTimelineElement(doc, element, {
7966
+ activeCompositionPath,
7967
+ isMasterView: true
7968
+ });
7969
+ }
7914
7970
  return findElementForSelection(
7915
7971
  doc,
7916
7972
  {
@@ -17822,7 +17878,7 @@ function OffCanvasIndicators({
17822
17878
  const selectOffCanvas = async () => {
17823
17879
  const el = elements.current.get(r.key);
17824
17880
  if (!el) return;
17825
- const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-2CKWGEHS.js");
17881
+ const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-AT7G6F4L.js");
17826
17882
  const acp = activeCompositionPathRef.current ?? "index.html";
17827
17883
  const sel = await resolveDomEditSelection2(el, {
17828
17884
  activeCompositionPath: acp,
@@ -17864,7 +17920,7 @@ import {
17864
17920
  STUDIO_WIDTH_PROP,
17865
17921
  STUDIO_HEIGHT_PROP,
17866
17922
  STUDIO_MANUAL_EDIT_GESTURE_ATTR
17867
- } from "@hyperframes/studio-server/draft-markers";
17923
+ } from "@hyperframes/core/editing/draft-markers";
17868
17924
  var STUDIO_ROTATION_PROP = "--hf-studio-rotation";
17869
17925
  var STUDIO_PATH_OFFSET_ATTR = "data-hf-studio-path-offset";
17870
17926
  var STUDIO_BOX_SIZE_ATTR = "data-hf-studio-box-size";
@@ -24804,6 +24860,20 @@ async function persistSdkSerialize(buildAfter, targetPath, originalContent, deps
24804
24860
  }
24805
24861
 
24806
24862
  // src/utils/sdkCutover.ts
24863
+ function sdkFamilyEnabled(family) {
24864
+ const configured = Object.prototype.hasOwnProperty.call(
24865
+ manualEditingAvailability_exports,
24866
+ "STUDIO_SDK_CUTOVER_FAMILIES"
24867
+ ) ? STUDIO_SDK_CUTOVER_FAMILIES : void 0;
24868
+ return isSdkFamilyEnabled(STUDIO_SDK_CUTOVER_ENABLED, configured, family);
24869
+ }
24870
+ function trackCutoverResult(result, context) {
24871
+ if (result.status === "committed") {
24872
+ trackStudioEvent("sdk_cutover_success", context);
24873
+ } else if (result.status === "failed") {
24874
+ trackStudioEvent("sdk_cutover_failed", { ...context, error: result.error.message });
24875
+ }
24876
+ }
24807
24877
  function wrongCompositionFile(deps, targetPath) {
24808
24878
  return deps.compositionPath != null && targetPath !== deps.compositionPath;
24809
24879
  }
@@ -24812,7 +24882,7 @@ function gsapReadSource(deps, targetPath) {
24812
24882
  return read ? () => read(targetPath) : void 0;
24813
24883
  }
24814
24884
  async function sdkCutoverPersist(selection, ops, originalContent, targetPath, sdkSession, deps, options) {
24815
- if (!shouldUseSdkCutover(STUDIO_SDK_CUTOVER_ENABLED, !!sdkSession, selection.hfId, ops))
24885
+ if (!shouldUseSdkCutover(sdkFamilyEnabled("dom"), !!sdkSession, selection.hfId, ops))
24816
24886
  return declinedCutover("ineligible_operation");
24817
24887
  if (!sdkSession) return declinedCutover("session_unavailable");
24818
24888
  const hfId = selection.hfId;
@@ -24832,11 +24902,7 @@ async function sdkCutoverPersist(selection, ops, originalContent, targetPath, sd
24832
24902
  },
24833
24903
  options
24834
24904
  );
24835
- if (result.status === "committed") {
24836
- trackStudioEvent("sdk_cutover_success", { hfId, opCount: ops.length });
24837
- } else if (result.status === "failed") {
24838
- trackStudioEvent("sdk_cutover_failed", { hfId, error: result.error.message });
24839
- }
24905
+ trackCutoverResult(result, { hfId, opCount: ops.length });
24840
24906
  return result;
24841
24907
  }
24842
24908
  async function sdkTimingPersist(hfId, targetPath, timingUpdate, sdkSession, deps, options) {
@@ -24848,7 +24914,7 @@ async function sdkTimingPersist(hfId, targetPath, timingUpdate, sdkSession, deps
24848
24914
  timingSrc ? () => timingSrc(targetPath) : void 0,
24849
24915
  { targetPath, compositionPath: deps.compositionPath }
24850
24916
  );
24851
- if (!STUDIO_SDK_CUTOVER_ENABLED) return declinedCutover("feature_disabled");
24917
+ if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled");
24852
24918
  if (!sdkSession) return declinedCutover("session_unavailable");
24853
24919
  if (!sdkSession.getElement(hfId)) return declinedCutover("target_not_found");
24854
24920
  if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
@@ -24863,11 +24929,7 @@ async function sdkTimingPersist(hfId, targetPath, timingUpdate, sdkSession, deps
24863
24929
  options,
24864
24930
  serializedBefore
24865
24931
  );
24866
- if (result.status === "committed") {
24867
- trackStudioEvent("sdk_cutover_success", { hfId, opCount: 1 });
24868
- } else if (result.status === "failed") {
24869
- trackStudioEvent("sdk_cutover_failed", { hfId, error: result.error.message });
24870
- }
24932
+ trackCutoverResult(result, { hfId, opCount: 1 });
24871
24933
  return result;
24872
24934
  } catch (error) {
24873
24935
  const failed2 = { status: "failed", error: asCutoverError(error) };
@@ -24886,7 +24948,7 @@ async function sdkTimingBatchPersist(changes, targetPath, sdkSession, deps, opti
24886
24948
  { targetPath, compositionPath: deps.compositionPath }
24887
24949
  );
24888
24950
  }
24889
- if (!STUDIO_SDK_CUTOVER_ENABLED) return declinedCutover("feature_disabled");
24951
+ if (!sdkFamilyEnabled("timing")) return declinedCutover("feature_disabled");
24890
24952
  if (!sdkSession) return declinedCutover("session_unavailable");
24891
24953
  if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
24892
24954
  if (changes.some((change) => !sdkSession.getElement(change.hfId)))
@@ -24944,10 +25006,11 @@ function sdkGsapTweenPersist(targetPath, op, sdkSession, deps, options) {
24944
25006
  { targetPath, compositionPath: deps.compositionPath }
24945
25007
  );
24946
25008
  }
24947
- if (!STUDIO_SDK_CUTOVER_ENABLED) return Promise.resolve(declinedCutover("feature_disabled"));
25009
+ if (!sdkFamilyEnabled("gsap-animation"))
25010
+ return Promise.resolve(declinedCutover("feature_disabled"));
24948
25011
  if (op.kind === "add" && sdkSession && !sdkSession.getElement(op.target))
24949
25012
  return Promise.resolve(declinedCutover("target_not_found"));
24950
- return dispatchGsapOpAndPersist(targetPath, sdkSession, deps, options, (s) => {
25013
+ return dispatchGsapOpAndPersist("gsap-animation", targetPath, sdkSession, deps, options, (s) => {
24951
25014
  s.batch(() => {
24952
25015
  if (op.kind === "add") {
24953
25016
  s.addGsapTween(op.target, op.spec);
@@ -24959,7 +25022,7 @@ function sdkGsapTweenPersist(targetPath, op, sdkSession, deps, options) {
24959
25022
  });
24960
25023
  });
24961
25024
  }
24962
- async function dispatchGsapOpAndPersist(targetPath, sdkSession, deps, options, dispatch, resolverTarget) {
25025
+ async function dispatchGsapOpAndPersist(family, targetPath, sdkSession, deps, options, dispatch, resolverTarget) {
24963
25026
  if (resolverTarget) {
24964
25027
  void recordAnimationResolverParity(
24965
25028
  sdkSession,
@@ -24969,7 +25032,7 @@ async function dispatchGsapOpAndPersist(targetPath, sdkSession, deps, options, d
24969
25032
  { targetPath, compositionPath: deps.compositionPath }
24970
25033
  );
24971
25034
  }
24972
- if (!STUDIO_SDK_CUTOVER_ENABLED) return declinedCutover("feature_disabled");
25035
+ if (!sdkFamilyEnabled(family)) return declinedCutover("feature_disabled");
24973
25036
  if (!sdkSession) return declinedCutover("session_unavailable");
24974
25037
  if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
24975
25038
  const session = sdkSession;
@@ -24998,6 +25061,7 @@ async function dispatchGsapOpAndPersist(targetPath, sdkSession, deps, options, d
24998
25061
  }
24999
25062
  function sdkGsapKeyframePersist(targetPath, animationId, position, value, sdkSession, deps, options) {
25000
25063
  return dispatchGsapOpAndPersist(
25064
+ "gsap-keyframe",
25001
25065
  targetPath,
25002
25066
  sdkSession,
25003
25067
  deps,
@@ -25008,6 +25072,7 @@ function sdkGsapKeyframePersist(targetPath, animationId, position, value, sdkSes
25008
25072
  }
25009
25073
  function sdkGsapRemoveKeyframePersist(targetPath, animationId, percentage, sdkSession, deps, options) {
25010
25074
  return dispatchGsapOpAndPersist(
25075
+ "gsap-keyframe",
25011
25076
  targetPath,
25012
25077
  sdkSession,
25013
25078
  deps,
@@ -25018,6 +25083,7 @@ function sdkGsapRemoveKeyframePersist(targetPath, animationId, percentage, sdkSe
25018
25083
  }
25019
25084
  function sdkGsapRemovePropertyPersist(targetPath, animationId, property, from, sdkSession, deps, options) {
25020
25085
  return dispatchGsapOpAndPersist(
25086
+ "gsap-animation",
25021
25087
  targetPath,
25022
25088
  sdkSession,
25023
25089
  deps,
@@ -25028,6 +25094,7 @@ function sdkGsapRemovePropertyPersist(targetPath, animationId, property, from, s
25028
25094
  }
25029
25095
  function sdkGsapDeleteAllForSelectorPersist(targetPath, selector, sdkSession, deps, options) {
25030
25096
  return dispatchGsapOpAndPersist(
25097
+ "gsap-animation",
25031
25098
  targetPath,
25032
25099
  sdkSession,
25033
25100
  deps,
@@ -25037,6 +25104,7 @@ function sdkGsapDeleteAllForSelectorPersist(targetPath, selector, sdkSession, de
25037
25104
  }
25038
25105
  function sdkGsapRemoveAllKeyframesPersist(targetPath, animationId, sdkSession, deps, options) {
25039
25106
  return dispatchGsapOpAndPersist(
25107
+ "gsap-keyframe",
25040
25108
  targetPath,
25041
25109
  sdkSession,
25042
25110
  deps,
@@ -25047,6 +25115,7 @@ function sdkGsapRemoveAllKeyframesPersist(targetPath, animationId, sdkSession, d
25047
25115
  }
25048
25116
  function sdkGsapConvertToKeyframesPersist(targetPath, animationId, resolvedFromValues, sdkSession, deps, options) {
25049
25117
  return dispatchGsapOpAndPersist(
25118
+ "gsap-keyframe",
25050
25119
  targetPath,
25051
25120
  sdkSession,
25052
25121
  deps,
@@ -25055,6 +25124,9 @@ function sdkGsapConvertToKeyframesPersist(targetPath, animationId, resolvedFromV
25055
25124
  { animationId, opLabel: "convertToKeyframes" }
25056
25125
  );
25057
25126
  }
25127
+ function keyframesPayload(targetSelector, position, duration, keyframes, ease) {
25128
+ return { targetSelector, position, duration, keyframes, ...ease ? { ease } : {} };
25129
+ }
25058
25130
  function dispatchWithKeyframes(s, payload, animationId) {
25059
25131
  if (animationId !== void 0) {
25060
25132
  s.dispatch({ type: "replaceWithKeyframes", animationId, ...payload });
@@ -25062,38 +25134,50 @@ function dispatchWithKeyframes(s, payload, animationId) {
25062
25134
  s.dispatch({ type: "addWithKeyframes", ...payload });
25063
25135
  }
25064
25136
  }
25137
+ function persistKeyframesOperation(input) {
25138
+ const payload = keyframesPayload(
25139
+ input.targetSelector,
25140
+ input.position,
25141
+ input.duration,
25142
+ input.keyframes,
25143
+ input.ease
25144
+ );
25145
+ return dispatchGsapOpAndPersist(
25146
+ "gsap-keyframe",
25147
+ input.targetPath,
25148
+ input.sdkSession,
25149
+ input.deps,
25150
+ input.options,
25151
+ (session) => dispatchWithKeyframes(session, payload, input.animationId),
25152
+ input.animationId ? { animationId: input.animationId, opLabel: "replaceWithKeyframes" } : void 0
25153
+ );
25154
+ }
25065
25155
  function sdkAddWithKeyframesPersist(targetPath, targetSelector, position, duration, keyframes, ease, sdkSession, deps, options) {
25066
- const payload = {
25156
+ return persistKeyframesOperation({
25157
+ targetPath,
25067
25158
  targetSelector,
25068
25159
  position,
25069
25160
  duration,
25070
25161
  keyframes,
25071
- ...ease ? { ease } : {}
25072
- };
25073
- return dispatchGsapOpAndPersist(
25074
- targetPath,
25162
+ ease,
25075
25163
  sdkSession,
25076
25164
  deps,
25077
- options,
25078
- (s) => dispatchWithKeyframes(s, payload)
25079
- );
25165
+ options
25166
+ });
25080
25167
  }
25081
25168
  function sdkReplaceWithKeyframesPersist(targetPath, animationId, targetSelector, position, duration, keyframes, ease, sdkSession, deps, options) {
25082
- const payload = {
25169
+ return persistKeyframesOperation({
25170
+ targetPath,
25171
+ animationId,
25083
25172
  targetSelector,
25084
25173
  position,
25085
25174
  duration,
25086
25175
  keyframes,
25087
- ...ease ? { ease } : {}
25088
- };
25089
- return dispatchGsapOpAndPersist(
25090
- targetPath,
25176
+ ease,
25091
25177
  sdkSession,
25092
25178
  deps,
25093
- options,
25094
- (s) => dispatchWithKeyframes(s, payload, animationId),
25095
- { animationId, opLabel: "replaceWithKeyframes" }
25096
- );
25179
+ options
25180
+ });
25097
25181
  }
25098
25182
  async function sdkDeletePersist(hfId, originalContent, targetPath, sdkSession, deps) {
25099
25183
  void recordResolverParity(
@@ -25103,7 +25187,7 @@ async function sdkDeletePersist(hfId, originalContent, targetPath, sdkSession, d
25103
25187
  () => Promise.resolve(originalContent),
25104
25188
  { targetPath, compositionPath: deps.compositionPath }
25105
25189
  );
25106
- if (!STUDIO_SDK_CUTOVER_ENABLED) return declinedCutover("feature_disabled");
25190
+ if (!sdkFamilyEnabled("lifecycle")) return declinedCutover("feature_disabled");
25107
25191
  if (!sdkSession) return declinedCutover("session_unavailable");
25108
25192
  if (!sdkSession.getElement(hfId)) return declinedCutover("target_not_found");
25109
25193
  if (wrongCompositionFile(deps, targetPath)) return declinedCutover("wrong_composition_file");
@@ -25115,11 +25199,7 @@ async function sdkDeletePersist(hfId, originalContent, targetPath, sdkSession, d
25115
25199
  (session) => session.removeElement(hfId),
25116
25200
  { label: "Delete element" }
25117
25201
  );
25118
- if (result.status === "committed") {
25119
- trackStudioEvent("sdk_cutover_success", { hfId, opCount: 1 });
25120
- } else if (result.status === "failed") {
25121
- trackStudioEvent("sdk_cutover_failed", { hfId, error: result.error.message });
25122
- }
25202
+ trackCutoverResult(result, { hfId, opCount: 1 });
25123
25203
  return result;
25124
25204
  }
25125
25205
 
@@ -26954,13 +27034,39 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
26954
27034
  // src/components/editor/keyframeRetime.ts
26955
27035
  var NOOP_EPSILON_PCT2 = 0.1;
26956
27036
  var EPSILON_TIME = 1e-4;
27037
+ var MIN_TWEEN_DURATION = 0.01;
26957
27038
  var round33 = (n) => Math.round(n * 1e3) / 1e3;
26958
27039
  var round1 = (n) => Math.round(n * 10) / 10;
26959
27040
  var clamp4 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
27041
+ function resolveFlatTweenBoundaryRetime(opts) {
27042
+ const { keyframeCount, draggedTweenPct, tweenStart, tweenEnd, dropAbsTime } = opts;
27043
+ if (keyframeCount > 0) return null;
27044
+ if (draggedTweenPct !== 0 && draggedTweenPct !== 100) return { kind: "noop" };
27045
+ const draggedTime = draggedTweenPct === 0 ? tweenStart : tweenEnd;
27046
+ if (Math.abs(dropAbsTime - draggedTime) <= EPSILON_TIME) return { kind: "noop" };
27047
+ const newStart = draggedTweenPct === 0 ? dropAbsTime : tweenStart;
27048
+ const newEnd = draggedTweenPct === 100 ? dropAbsTime : tweenEnd;
27049
+ const newDuration = newEnd - newStart;
27050
+ if (newDuration < MIN_TWEEN_DURATION) return { kind: "noop" };
27051
+ return {
27052
+ kind: "resize",
27053
+ position: round33(newStart),
27054
+ duration: round33(newDuration),
27055
+ pctRemap: []
27056
+ };
27057
+ }
26960
27058
  function resolveKeyframeRetime(opts) {
26961
27059
  const { keyframes, draggedTweenPct, tweenStart, tweenDuration, dropAbsTime } = opts;
26962
27060
  if (tweenDuration <= 0) return { kind: "noop" };
26963
27061
  const tweenEnd = tweenStart + tweenDuration;
27062
+ const flatBoundary = resolveFlatTweenBoundaryRetime({
27063
+ keyframeCount: keyframes.length,
27064
+ draggedTweenPct,
27065
+ tweenStart,
27066
+ tweenEnd,
27067
+ dropAbsTime
27068
+ });
27069
+ if (flatBoundary) return flatBoundary;
26964
27070
  if (dropAbsTime >= tweenStart - EPSILON_TIME && dropAbsTime <= tweenEnd + EPSILON_TIME) {
26965
27071
  const toTweenPct = clamp4((dropAbsTime - tweenStart) / tweenDuration * 100, 0, 100);
26966
27072
  if (Math.abs(toTweenPct - draggedTweenPct) < NOOP_EPSILON_PCT2) return { kind: "noop" };
@@ -26969,7 +27075,7 @@ function resolveKeyframeRetime(opts) {
26969
27075
  if (keyframes.length === 0) return { kind: "noop" };
26970
27076
  const newStart = Math.min(dropAbsTime, tweenStart);
26971
27077
  const newEnd = Math.max(dropAbsTime, tweenEnd);
26972
- const newDuration = Math.max(0.01, newEnd - newStart);
27078
+ const newDuration = Math.max(MIN_TWEEN_DURATION, newEnd - newStart);
26973
27079
  let draggedIdx = 0;
26974
27080
  let best = Infinity;
26975
27081
  keyframes.forEach((kf, i) => {
@@ -26992,6 +27098,14 @@ function resolveKeyframeRetime(opts) {
26992
27098
  }
26993
27099
 
26994
27100
  // src/components/nle/useTimelineEditCallbacks.ts
27101
+ function resolveTimelineKeyframeTarget(pct, keyframes, animations) {
27102
+ const kf = keyframes.find((item) => Math.abs(item.percentage - pct) < 0.2);
27103
+ if (!kf) return null;
27104
+ const group = kf?.propertyGroup;
27105
+ const candidates = group ? animations.filter((animation2) => animation2.propertyGroup === group) : animations.filter((animation2) => !animation2.propertyGroup);
27106
+ const animation = candidates.length === 1 ? candidates[0] : void 0;
27107
+ return animation ? { animId: animation.id, tweenPct: kf.tweenPercentage ?? pct } : null;
27108
+ }
26995
27109
  function useTimelineEditCallbacks({
26996
27110
  handleTimelineElementMove,
26997
27111
  handleTimelineElementsMove,
@@ -27020,10 +27134,7 @@ function useTimelineEditCallbacks({
27020
27134
  // fallow-ignore-next-line complexity
27021
27135
  (pct) => {
27022
27136
  const cached2 = usePlayerStore.getState().keyframeCache.get(domEditSelection?.id ?? "");
27023
- const kf = cached2?.keyframes.find((k) => Math.abs(k.percentage - pct) < 0.2);
27024
- const group = kf?.propertyGroup;
27025
- const anim = (group ? selectedGsapAnimations.find((a) => a.propertyGroup === group) : void 0) ?? selectedGsapAnimations.find((a) => a.keyframes);
27026
- return anim ? { animId: anim.id, tweenPct: kf?.tweenPercentage ?? pct } : null;
27137
+ return resolveTimelineKeyframeTarget(pct, cached2?.keyframes ?? [], selectedGsapAnimations);
27027
27138
  },
27028
27139
  [domEditSelection?.id, selectedGsapAnimations]
27029
27140
  );
@@ -27087,12 +27198,19 @@ function useTimelineEditCallbacks({
27087
27198
  if (decision.kind === "move" && decision.toTweenPct != null) {
27088
27199
  handleGsapMoveKeyframe(target.animId, target.tweenPct, decision.toTweenPct);
27089
27200
  } else if (decision.kind === "resize" && decision.pctRemap && decision.position != null && decision.duration != null) {
27090
- handleGsapResizeKeyframedTween(
27091
- target.animId,
27092
- decision.position,
27093
- decision.duration,
27094
- decision.pctRemap
27095
- );
27201
+ if (anim.keyframes) {
27202
+ handleGsapResizeKeyframedTween(
27203
+ target.animId,
27204
+ decision.position,
27205
+ decision.duration,
27206
+ decision.pctRemap
27207
+ );
27208
+ } else {
27209
+ handleGsapUpdateMeta(target.animId, {
27210
+ position: decision.position,
27211
+ duration: decision.duration
27212
+ });
27213
+ }
27096
27214
  }
27097
27215
  },
27098
27216
  onChangeKeyframeEase: (_elId, _pct, ease) => {