@hyperframes/studio 0.8.126 → 0.8.127

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 (29) hide show
  1. package/dist/assets/{hyperframes-player-BbbN-wWN.js → hyperframes-player-uqzDaxVL.js} +1 -1
  2. package/dist/assets/{index-nvLF29ZH.js → index-CLBNYlDY.js} +1 -1
  3. package/dist/assets/{index-B8kU2xXl.js → index-COhbL1xq.js} +171 -171
  4. package/dist/assets/{index-Cll7uTNy.js → index-DXSYlqmV.js} +1 -1
  5. package/dist/index.html +1 -1
  6. package/dist/index.js +1008 -893
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -8
  9. package/src/components/editor/MotionPathOverlay.layerPress.test.tsx +42 -0
  10. package/src/components/editor/MotionPathOverlay.tsx +20 -26
  11. package/src/components/editor/motionPathCommit.nodeDrop.test.ts +212 -0
  12. package/src/components/editor/motionPathCommit.ts +58 -0
  13. package/src/components/editor/motionPathGeometry.test.ts +13 -0
  14. package/src/components/editor/motionPathGeometry.ts +4 -2
  15. package/src/hooks/gsapDragCommit.ts +33 -1
  16. package/src/hooks/gsapEditOutcome.ts +27 -2
  17. package/src/hooks/gsapParsedTween.test-helpers.ts +28 -1
  18. package/src/hooks/gsapParsedTween.ts +23 -2
  19. package/src/hooks/gsapRuntimeBridge.ts +7 -1
  20. package/src/hooks/gsapRuntimeKeyframes.ts +1 -1
  21. package/src/hooks/gsapShared.test.ts +1 -0
  22. package/src/hooks/gsapShared.ts +6 -2
  23. package/src/hooks/gsapValueAtPlayhead.realGsap.test.ts +107 -5
  24. package/src/hooks/gsapValueAtPlayhead.test.ts +129 -27
  25. package/src/hooks/gsapValueAtPlayhead.ts +46 -6
  26. package/src/hooks/gsapWholePropertyOffsetCommit.test.ts +72 -1
  27. package/src/hooks/gsapWholePropertyOffsetCommit.ts +6 -1
  28. package/src/hooks/useCommitPreflightCapabilities.ts +2 -2
  29. package/src/hooks/useGsapAwareEditing.groupPlan.test.tsx +25 -0
package/dist/index.js CHANGED
@@ -166,7 +166,7 @@ import {
166
166
  import "./chunk-PZ5AY32C.js";
167
167
 
168
168
  // src/components/EditorShell.tsx
169
- import { memo as memo33, useCallback as useCallback74 } from "react";
169
+ import { memo as memo33, useCallback as useCallback75 } from "react";
170
170
 
171
171
  // src/components/nle/PreviewPane.tsx
172
172
  import { useCallback as useCallback52, useRef as useRef58, useSyncExternalStore as useSyncExternalStore4 } from "react";
@@ -4736,7 +4736,7 @@ function parsePercentageKeyframes(kfObj) {
4736
4736
  if (!entry2 || typeof entry2 !== "object") return;
4737
4737
  const percentage = steps.length > 1 ? Math.round(i / (steps.length - 1) * 1e3) / 10 : 0;
4738
4738
  const properties = collectAnimatableKeyframeProperties(entry2);
4739
- if (Object.keys(properties).length > 0) keyframes.push({ percentage, properties });
4739
+ if (Object.keys(properties).length > 0) keyframes.push({ percentage, properties, step: i });
4740
4740
  });
4741
4741
  return keyframes.length > 0 ? { keyframes } : null;
4742
4742
  }
@@ -29547,7 +29547,7 @@ var BUILT_IN_EASES = [
29547
29547
  function withExactStepTimes(anim, tween) {
29548
29548
  const data = anim.keyframes;
29549
29549
  const parts = tween?.timeline?.getChildren?.() ?? [];
29550
- const total = tween?.duration?.() ?? 0;
29550
+ const total = tween?.timeline?.duration?.() ?? 0;
29551
29551
  if (data?.format !== "object-array" || parts.length !== data.keyframes.length || !(total > 0))
29552
29552
  return anim;
29553
29553
  const ends = parts.map((part) => ((part.startTime?.() ?? 0) + (part.duration?.() ?? 0)) / total);
@@ -29558,6 +29558,20 @@ function withExactStepTimes(anim, tween) {
29558
29558
  }));
29559
29559
  return { ...anim, keyframes: { ...data, keyframes } };
29560
29560
  }
29561
+ function nearestKeyframeIndex(keyframes, pct) {
29562
+ let best = -1;
29563
+ keyframes.forEach((kf, i) => {
29564
+ const off = Math.abs(kf.percentage - pct);
29565
+ if (off <= KEYFRAME_PCT_MATCH && (best < 0 || off < Math.abs(keyframes[best].percentage - pct)))
29566
+ best = i;
29567
+ });
29568
+ return best;
29569
+ }
29570
+ function exactKeyframePct(anim, tween, pct) {
29571
+ const authored = anim.keyframes?.keyframes ?? [];
29572
+ const i = nearestKeyframeIndex(authored, pct);
29573
+ return withExactStepTimes(anim, tween).keyframes?.keyframes[i]?.percentage ?? pct;
29574
+ }
29561
29575
 
29562
29576
  // src/components/editor/gsapAnimatesProperty.ts
29563
29577
  var gsapRendersTransform = (el) => !!el._gsap?.renderTransform;
@@ -34317,7 +34331,7 @@ function TimelinePane({
34317
34331
  }
34318
34332
 
34319
34333
  // src/components/nle/PreviewOverlays.tsx
34320
- import { useCallback as useCallback66 } from "react";
34334
+ import { useCallback as useCallback67 } from "react";
34321
34335
 
34322
34336
  // src/captions/components/CaptionOverlay.tsx
34323
34337
  import { memo as memo25, useEffect as useEffect43, useState as useState40, useCallback as useCallback54, useRef as useRef60 } from "react";
@@ -44855,114 +44869,6 @@ function TopologyLens({ iframeRef, activeCompositionPath }) {
44855
44869
  );
44856
44870
  }
44857
44871
 
44858
- // src/utils/keyframeUsage.ts
44859
- function keyframeUsageActions(mutations, semanticActions) {
44860
- const actions = /* @__PURE__ */ new Set();
44861
- for (const [index, mutation] of mutations.entries()) {
44862
- const semanticAction = semanticActions?.[index];
44863
- if (semanticAction) {
44864
- actions.add(semanticAction);
44865
- continue;
44866
- }
44867
- switch (mutation.type) {
44868
- case "add-keyframe":
44869
- case "add-with-keyframes":
44870
- actions.add("add");
44871
- break;
44872
- case "convert-to-keyframes":
44873
- actions.add("convert");
44874
- break;
44875
- case "remove-all-keyframes":
44876
- actions.add("remove_all");
44877
- break;
44878
- }
44879
- }
44880
- return actions;
44881
- }
44882
- function trackKeyframeUsage(action, property) {
44883
- trackStudioEvent("keyframe", property === void 0 ? { action } : { action, property });
44884
- }
44885
- function primaryKeyframeAction(actions) {
44886
- return ["add", "convert", "remove_all", "reset"].find((action) => actions.has(action));
44887
- }
44888
- function changedMutationIndices(result, count, options) {
44889
- if (!result.ok || result.changed !== true) return [];
44890
- const changes = count === 1 ? [true] : result.mutationChanges;
44891
- if (!Array.isArray(changes) || changes.length !== count || changes.some((value) => typeof value !== "boolean"))
44892
- return [];
44893
- return changes.flatMap(
44894
- (changed, index) => changed && options?.[index]?.keyframeTelemetry !== false ? [index] : []
44895
- );
44896
- }
44897
- function trackKeyframeCommit(mutations, result, options, memberOptions) {
44898
- if (!result.ok || result.changed !== true || options.keyframeTelemetry === false) return;
44899
- const indices = changedMutationIndices(result, mutations.length, memberOptions);
44900
- const actions = keyframeUsageActions(
44901
- indices.map((index) => mutations[index]),
44902
- indices.map(
44903
- (index) => memberOptions?.[index]?.keyframeAction ?? (mutations.length === 1 ? options.keyframeAction : void 0)
44904
- )
44905
- );
44906
- const action = primaryKeyframeAction(actions);
44907
- if (action) trackKeyframeUsage(action, action === "add" ? options.keyframeProperty : void 0);
44908
- }
44909
-
44910
- // src/hooks/gsapGestureOutcome.ts
44911
- function observeGsapGesture(writer) {
44912
- const edit = adoptingStudioPendingEdit();
44913
- const join = (run) => edit ? edit.within(run) : run();
44914
- let changed = false;
44915
- let pendingResults = 0;
44916
- const actions = /* @__PURE__ */ new Set();
44917
- const observe = (calls, options) => {
44918
- pendingResults += 1;
44919
- return {
44920
- ...options,
44921
- ...edit && { pendingEdit: edit },
44922
- keyframeTelemetry: false,
44923
- onResult: (result) => {
44924
- pendingResults -= 1;
44925
- options.onResult?.(result);
44926
- if (!result.ok || result.changed !== true) return;
44927
- changed = true;
44928
- edit?.markSaved();
44929
- const members = changedMutationIndices(
44930
- result,
44931
- calls.length,
44932
- calls.map((call) => call.options)
44933
- ).map((index) => calls[index]);
44934
- for (const action of keyframeUsageActions(
44935
- members.map((call) => call.mutation),
44936
- members.map((call) => call.options.keyframeAction)
44937
- ))
44938
- actions.add(action);
44939
- }
44940
- };
44941
- };
44942
- let commit2 = null;
44943
- if (writer) {
44944
- commit2 = (selection, mutation, options) => join(() => writer(selection, mutation, observe([{ selection, mutation, options }], options)));
44945
- if (writer.batch) {
44946
- const batch = writer.batch;
44947
- commit2.batch = (calls, options) => join(() => batch(calls, observe(calls, options)));
44948
- }
44949
- }
44950
- return {
44951
- commit: commit2,
44952
- drawKeepingUndone: (draw) => edit ? edit.drawKeepingUndone(draw) : draw(),
44953
- recordDomResult: (result) => {
44954
- changed ||= result?.changed === true;
44955
- if (result?.changed) edit?.markSaved();
44956
- },
44957
- finish: (domChanged = false) => {
44958
- if (pendingResults !== 0) return { ok: true, changed: false };
44959
- const action = primaryKeyframeAction(actions);
44960
- if (action) trackKeyframeUsage(action);
44961
- return { ok: true, changed: changed || domChanged };
44962
- }
44963
- };
44964
- }
44965
-
44966
44872
  // src/hooks/gsapKeyframeCacheHelpers.ts
44967
44873
  function sameEntries(before, after) {
44968
44874
  if (before.size !== after.size) return false;
@@ -45126,6 +45032,171 @@ function writeKeyframeCache(sourceFile, elementId2, data) {
45126
45032
  // src/components/editor/MotionPathOverlay.tsx
45127
45033
  import { memo as memo29, useEffect as useEffect59, useRef as useRef75, useState as useState52 } from "react";
45128
45034
 
45035
+ // src/hooks/useGsapInteractionFailureTelemetry.ts
45036
+ import { useCallback as useCallback65 } from "react";
45037
+
45038
+ // src/hooks/gsapEditOutcome.ts
45039
+ import { editabilityForProvenance } from "@hyperframes/core/gsap-parser";
45040
+ var GSAP_EDIT_BLOCK_COPY = {
45041
+ "no-selector": "This layer needs a stable selector before Studio can save the edit.",
45042
+ "unroll-required": "This motion comes from a helper or loop. Choose Unroll to edit it explicitly.",
45043
+ "source-uneditable": "This animation is computed at runtime. Edit the animation in the Code tab.",
45044
+ "keyframes-uneditable": "Studio can't add this edit to the animation's keyframes. Edit this animation in the Code tab.",
45045
+ "mixed-files": "These layers are animated in different files. Move each file's layers separately."
45046
+ };
45047
+ var STEP_LIST = "This animation's keyframes have a step";
45048
+ var IN_CODE = "Edit them in the Code tab.";
45049
+ var GSAP_EDIT_DETAIL_COPY = {
45050
+ "array-step-delay": `${STEP_LIST} with its own delay, which Studio can't keep while it edits them. ${IN_CODE}`,
45051
+ "array-step-callback": `${STEP_LIST} that runs code as it plays (like onComplete), which an edit would run again. ${IN_CODE}`,
45052
+ "array-step-config": `${STEP_LIST} with its own tween setting (like repeat or stagger), which Studio can't keep while it edits them. ${IN_CODE}`,
45053
+ "array-step-computed": `${STEP_LIST} whose value comes from code, which Studio can't keep while it edits them. ${IN_CODE}`,
45054
+ "array-step-relative": `${STEP_LIST} with a relative value (like "+=40"), which an edit would apply again. ${IN_CODE}`,
45055
+ "array-step-random": `${STEP_LIST} with a random() value, which an edit would roll again. ${IN_CODE}`
45056
+ };
45057
+ function gsapEditBlockMessage(reason, detail) {
45058
+ return (detail && GSAP_EDIT_DETAIL_COPY[detail]) ?? GSAP_EDIT_BLOCK_COPY[reason];
45059
+ }
45060
+ var GsapEditBlockedError = class extends Error {
45061
+ constructor(reason, detail) {
45062
+ super(gsapEditBlockMessage(reason, detail));
45063
+ this.reason = reason;
45064
+ this.detail = detail;
45065
+ this.name = "GsapEditBlockedError";
45066
+ }
45067
+ reason;
45068
+ detail;
45069
+ };
45070
+ function assertGsapEditPersisted(outcome) {
45071
+ if (outcome.status === "blocked") throw new GsapEditBlockedError(outcome.reason, outcome.detail);
45072
+ }
45073
+ async function saveMove(outcome, saveOnElement) {
45074
+ if (outcome.status === "element-offset") return saveOnElement();
45075
+ assertGsapEditPersisted(outcome);
45076
+ }
45077
+ function assertGsapAnimationDirectlyEditable(animation) {
45078
+ const editability = editabilityForProvenance(animation.provenance);
45079
+ if (editability === "unroll") throw new GsapEditBlockedError("unroll-required");
45080
+ if (editability === "source") {
45081
+ throw new GsapEditBlockedError("source-uneditable", "provenance-runtime-dynamic");
45082
+ }
45083
+ if (animation.hasUnresolvedKeyframes) {
45084
+ throw new GsapEditBlockedError("source-uneditable", "unresolved-keyframes");
45085
+ }
45086
+ if (animation.hasUnresolvedSelector) {
45087
+ throw new GsapEditBlockedError("source-uneditable", "unresolved-selector");
45088
+ }
45089
+ }
45090
+ function isGsapEditBlockedError(error) {
45091
+ return error instanceof GsapEditBlockedError;
45092
+ }
45093
+ function animationWritesAnyProperty(animation, properties) {
45094
+ return Object.keys(animation.properties ?? {}).some((property) => properties.has(property)) || Object.keys(animation.fromProperties ?? {}).some((property) => properties.has(property)) || !!animation.keyframes?.keyframes.some(
45095
+ (keyframe) => Object.keys(keyframe.properties).some((property) => properties.has(property))
45096
+ );
45097
+ }
45098
+ function directEditOutcomeForProperties(animations, properties) {
45099
+ try {
45100
+ for (const animation of animations) {
45101
+ if (animationWritesAnyProperty(animation, properties)) {
45102
+ assertGsapAnimationDirectlyEditable(animation);
45103
+ }
45104
+ }
45105
+ return { status: "persisted" };
45106
+ } catch (error) {
45107
+ if (isGsapEditBlockedError(error))
45108
+ return { status: "blocked", reason: error.reason, detail: error.detail };
45109
+ throw error;
45110
+ }
45111
+ }
45112
+
45113
+ // src/hooks/domEditPersistFailure.ts
45114
+ var DomEditPersistUnresolvableError = class extends Error {
45115
+ constructor(targetPath) {
45116
+ super(`Couldn't find this element in the source file (${targetPath})`);
45117
+ this.name = "DomEditPersistUnresolvableError";
45118
+ }
45119
+ };
45120
+ var DomEditPersistUnsafeValueError = class extends Error {
45121
+ alreadyToasted;
45122
+ constructor(message, options = {}) {
45123
+ super(message);
45124
+ this.name = "DomEditPersistUnsafeValueError";
45125
+ this.alreadyToasted = options.alreadyToasted ?? false;
45126
+ }
45127
+ };
45128
+ var DomEditPersistPreparedWriteError = class extends Error {
45129
+ alreadyToasted = true;
45130
+ constructor(targetPath) {
45131
+ super(`The edit saved but its follow-up write to ${targetPath} did not`);
45132
+ this.name = "DomEditPersistPreparedWriteError";
45133
+ }
45134
+ };
45135
+ function summarizeOperations(operations) {
45136
+ return operations.map((op) => `${op.type}:${op.property}`).join(", ");
45137
+ }
45138
+ function getTargetTuple(selection) {
45139
+ return {
45140
+ hfId: selection.hfId,
45141
+ id: selection.id,
45142
+ selector: selection.selector,
45143
+ selectorIndex: selection.selectorIndex,
45144
+ sourceFile: selection.sourceFile
45145
+ };
45146
+ }
45147
+ function getErrorDetail(error) {
45148
+ return error instanceof Error ? error.message : String(error);
45149
+ }
45150
+ function getSelectionLabel(selection) {
45151
+ return selection.label || selection.selector || selection.id || "this element";
45152
+ }
45153
+ function wasAlreadyToasted(error) {
45154
+ return (error instanceof DomEditPersistUnsafeValueError || error instanceof DomEditPersistPreparedWriteError || error instanceof StudioSaveHttpError) && error.alreadyToasted;
45155
+ }
45156
+ function reportDomEditPersistFailure(selection, operations, error, showToast) {
45157
+ const detail = getErrorDetail(error);
45158
+ console.warn("[Studio] DOM edit persist failed", {
45159
+ target: getTargetTuple(selection),
45160
+ operations: summarizeOperations(operations),
45161
+ error: detail
45162
+ });
45163
+ if (wasAlreadyToasted(error)) return;
45164
+ showToast(`Couldn't save "${getSelectionLabel(selection)}": ${detail}`, "error");
45165
+ }
45166
+ function warnDomEditPersistNoOp(selection, operations) {
45167
+ console.warn("[Studio] DOM edit persist no-op", {
45168
+ target: getTargetTuple(selection),
45169
+ operations: summarizeOperations(operations),
45170
+ detail: "Server matched the target but reported no change even though the client believed the value changed."
45171
+ });
45172
+ }
45173
+
45174
+ // src/hooks/useGsapInteractionFailureTelemetry.ts
45175
+ function failureToast(error, toast) {
45176
+ if (!toast || wasAlreadyToasted(error)) return null;
45177
+ return isGsapEditBlockedError(error) ? error.message : "Failed to save animated edit.";
45178
+ }
45179
+ function useGsapInteractionFailureTelemetry(activeCompPath, showToast) {
45180
+ return useCallback65(
45181
+ (error, selection, mutationType, label, toast) => {
45182
+ const report = isGsapEditBlockedError(error) ? trackStudioEditBlocked : trackStudioSaveFailure;
45183
+ report({
45184
+ source: "gsap_commit",
45185
+ error,
45186
+ filePath: selection?.sourceFile ?? activeCompPath ?? "index.html",
45187
+ mutationType,
45188
+ label,
45189
+ targetId: selection?.id,
45190
+ targetSelector: selection?.selector,
45191
+ targetSourceFile: selection?.sourceFile
45192
+ });
45193
+ const message = failureToast(error, toast !== false);
45194
+ if (message) showToast(message, "error");
45195
+ },
45196
+ [activeCompPath, showToast]
45197
+ );
45198
+ }
45199
+
45129
45200
  // src/hooks/gestureTransaction.ts
45130
45201
  var transactionCounter = 0;
45131
45202
  var transactionCommits = /* @__PURE__ */ new WeakSet();
@@ -45282,69 +45353,8 @@ function findSizeSetAnimation(animations, selector, element) {
45282
45353
  ) ?? null;
45283
45354
  }
45284
45355
 
45285
- // src/hooks/gsapEditOutcome.ts
45286
- import { editabilityForProvenance } from "@hyperframes/core/gsap-parser";
45287
- var GSAP_EDIT_BLOCK_COPY = {
45288
- "no-selector": "This layer needs a stable selector before Studio can save the edit.",
45289
- "unroll-required": "This motion comes from a helper or loop. Choose Unroll to edit it explicitly.",
45290
- "source-uneditable": "This animation is computed at runtime. Edit the animation in the Code tab.",
45291
- "keyframes-uneditable": "Studio can't add this edit to the animation's keyframes. Edit this animation in the Code tab.",
45292
- "mixed-files": "These layers are animated in different files. Move each file's layers separately."
45293
- };
45294
- var GsapEditBlockedError = class extends Error {
45295
- constructor(reason, detail) {
45296
- super(GSAP_EDIT_BLOCK_COPY[reason]);
45297
- this.reason = reason;
45298
- this.detail = detail;
45299
- this.name = "GsapEditBlockedError";
45300
- }
45301
- reason;
45302
- detail;
45303
- };
45304
- function assertGsapEditPersisted(outcome) {
45305
- if (outcome.status === "blocked") throw new GsapEditBlockedError(outcome.reason, outcome.detail);
45306
- }
45307
- async function saveMove(outcome, saveOnElement) {
45308
- if (outcome.status === "element-offset") return saveOnElement();
45309
- assertGsapEditPersisted(outcome);
45310
- }
45311
- function assertGsapAnimationDirectlyEditable(animation) {
45312
- const editability = editabilityForProvenance(animation.provenance);
45313
- if (editability === "unroll") throw new GsapEditBlockedError("unroll-required");
45314
- if (editability === "source") {
45315
- throw new GsapEditBlockedError("source-uneditable", "provenance-runtime-dynamic");
45316
- }
45317
- if (animation.hasUnresolvedKeyframes) {
45318
- throw new GsapEditBlockedError("source-uneditable", "unresolved-keyframes");
45319
- }
45320
- if (animation.hasUnresolvedSelector) {
45321
- throw new GsapEditBlockedError("source-uneditable", "unresolved-selector");
45322
- }
45323
- }
45324
- function isGsapEditBlockedError(error) {
45325
- return error instanceof GsapEditBlockedError;
45326
- }
45327
- function animationWritesAnyProperty(animation, properties) {
45328
- return Object.keys(animation.properties ?? {}).some((property) => properties.has(property)) || Object.keys(animation.fromProperties ?? {}).some((property) => properties.has(property)) || !!animation.keyframes?.keyframes.some(
45329
- (keyframe) => Object.keys(keyframe.properties).some((property) => properties.has(property))
45330
- );
45331
- }
45332
- function directEditOutcomeForProperties(animations, properties) {
45333
- try {
45334
- for (const animation of animations) {
45335
- if (animationWritesAnyProperty(animation, properties)) {
45336
- assertGsapAnimationDirectlyEditable(animation);
45337
- }
45338
- }
45339
- return { status: "persisted" };
45340
- } catch (error) {
45341
- if (isGsapEditBlockedError(error))
45342
- return { status: "blocked", reason: error.reason, detail: error.detail };
45343
- throw error;
45344
- }
45345
- }
45346
-
45347
45356
  // src/hooks/gsapDragCommit.ts
45357
+ import { isTweenConfigKey } from "@hyperframes/parsers/gsap-constants";
45348
45358
  function newTweenTarget(selection) {
45349
45359
  return writeTargetSelector(selection);
45350
45360
  }
@@ -45398,6 +45408,28 @@ async function materializeIfDynamic(anim, iframe, commitMutation, selection) {
45398
45408
  void selection;
45399
45409
  throw new GsapEditBlockedError("source-uneditable", "geometry-unresolved-source");
45400
45410
  }
45411
+ function stepBlock([key2, value]) {
45412
+ if (key2 === "delay") return "array-step-delay";
45413
+ if (/^on[A-Z]/.test(key2)) return "array-step-callback";
45414
+ if (isTweenConfigKey(key2)) return "array-step-config";
45415
+ if (typeof value === "number") return null;
45416
+ return STEP_VALUE_BLOCKS.find(([pattern]) => pattern.test(value))?.[1] ?? null;
45417
+ }
45418
+ var STEP_VALUE_BLOCKS = [
45419
+ [/^__raw:/, "array-step-computed"],
45420
+ [/random\(/, "array-step-random"],
45421
+ [/[-+*/]=/, "array-step-relative"]
45422
+ ];
45423
+ function stepListBlock(anim) {
45424
+ const data = anim.keyframes;
45425
+ if (data?.format !== "object-array") return null;
45426
+ const entries = data.keyframes.flatMap((kf) => Object.entries(kf.properties));
45427
+ return entries.map(stepBlock).find(Boolean) ?? null;
45428
+ }
45429
+ function refuseStepListRewrite(anim) {
45430
+ const step = stepListBlock(anim);
45431
+ if (step) throw new GsapEditBlockedError("keyframes-uneditable", step);
45432
+ }
45401
45433
  async function commitStaticGsapPosition(selection, studioOffset, gsapPos, selector, existingSet, callbacks) {
45402
45434
  const { newX, newY } = computeDraggedGsapPosition(selection.element, studioOffset, gsapPos);
45403
45435
  if (existingSet) {
@@ -45535,6 +45567,7 @@ async function commitWholePathOffset(selection, anim, studioOffset, gsapPos, ifr
45535
45567
  };
45536
45568
  let effectiveAnim = anim;
45537
45569
  if (anim.keyframes) {
45570
+ refuseStepListRewrite(anim);
45538
45571
  const newId = await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
45539
45572
  if (newId) effectiveAnim = { ...anim, id: newId };
45540
45573
  }
@@ -45577,53 +45610,6 @@ async function commitWholePathOffset(selection, anim, studioOffset, gsapPos, ifr
45577
45610
  );
45578
45611
  }
45579
45612
 
45580
- // src/hooks/gsapWholePropertyOffsetCommit.ts
45581
- async function commitWholePropertyOffset(selection, anim, newValues, currentPct, iframe, callbacks, label) {
45582
- let effectiveAnim = anim;
45583
- if (anim.keyframes) {
45584
- const newId = await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
45585
- if (newId) effectiveAnim = { ...anim, id: newId };
45586
- }
45587
- const ts = resolveTweenStart(effectiveAnim);
45588
- const td = resolveTweenDuration(effectiveAnim);
45589
- const keys = Object.keys(newValues);
45590
- const at = (props, key2) => typeof props[key2] === "number" ? props[key2] : PROPERTY_DEFAULTS[key2] ?? 0;
45591
- const kfs = effectiveAnim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(effectiveAnim)?.keyframes;
45592
- if (!kfs || kfs.length === 0) {
45593
- await callbacks.commitMutation(
45594
- selection,
45595
- { type: "update-properties", animationId: effectiveAnim.id, properties: newValues },
45596
- { label, softReload: true }
45597
- );
45598
- return;
45599
- }
45600
- const nearest = kfs.reduce(
45601
- (best, kf) => Math.abs(kf.percentage - currentPct) < Math.abs(best.percentage - currentPct) ? kf : best
45602
- );
45603
- const shifted = kfs.map((kf) => {
45604
- const properties = { ...kf.properties };
45605
- for (const key2 of keys) {
45606
- properties[key2] = roundTo3(
45607
- at(properties, key2) + (newValues[key2] - at(nearest.properties, key2))
45608
- );
45609
- }
45610
- return { percentage: kf.percentage, properties, ...kf.ease ? { ease: kf.ease } : {} };
45611
- });
45612
- await callbacks.commitMutation(
45613
- selection,
45614
- {
45615
- type: "replace-with-keyframes",
45616
- animationId: effectiveAnim.id,
45617
- targetSelector: effectiveAnim.targetSelector,
45618
- position: roundTo3(ts ?? 0),
45619
- duration: roundTo3(td || 1),
45620
- keyframes: shifted,
45621
- ...keyframeEases(effectiveAnim)
45622
- },
45623
- { label, softReload: true }
45624
- );
45625
- }
45626
-
45627
45613
  // src/components/editor/motionPathGeometry.ts
45628
45614
  function nodeCentre(n, home, pScale) {
45629
45615
  const grow2 = (to, now2, share) => to === void 0 ? 0 : (to - now2) * share;
@@ -45674,7 +45660,7 @@ function buildMotionPathGeometry(read2, base2 = { x: 0, y: 0 }) {
45674
45660
  if (!at) return;
45675
45661
  nodes.push({
45676
45662
  ...at,
45677
- ref: isArc ? { type: "waypoint", index: i } : { type: "keyframe", pct: kf.percentage }
45663
+ ref: isArc ? { type: "waypoint", index: i } : { type: "keyframe", pct: kf.percentage, ...kf.step == null ? {} : { step: kf.step } }
45678
45664
  });
45679
45665
  });
45680
45666
  if (nodes.length < 2) return null;
@@ -45939,6 +45925,386 @@ function MotionPathLine(props) {
45939
45925
  ] });
45940
45926
  }
45941
45927
 
45928
+ // src/utils/keyframeUsage.ts
45929
+ function keyframeUsageActions(mutations, semanticActions) {
45930
+ const actions = /* @__PURE__ */ new Set();
45931
+ for (const [index, mutation] of mutations.entries()) {
45932
+ const semanticAction = semanticActions?.[index];
45933
+ if (semanticAction) {
45934
+ actions.add(semanticAction);
45935
+ continue;
45936
+ }
45937
+ switch (mutation.type) {
45938
+ case "add-keyframe":
45939
+ case "add-with-keyframes":
45940
+ actions.add("add");
45941
+ break;
45942
+ case "convert-to-keyframes":
45943
+ actions.add("convert");
45944
+ break;
45945
+ case "remove-all-keyframes":
45946
+ actions.add("remove_all");
45947
+ break;
45948
+ }
45949
+ }
45950
+ return actions;
45951
+ }
45952
+ function trackKeyframeUsage(action, property) {
45953
+ trackStudioEvent("keyframe", property === void 0 ? { action } : { action, property });
45954
+ }
45955
+ function primaryKeyframeAction(actions) {
45956
+ return ["add", "convert", "remove_all", "reset"].find((action) => actions.has(action));
45957
+ }
45958
+ function changedMutationIndices(result, count, options) {
45959
+ if (!result.ok || result.changed !== true) return [];
45960
+ const changes = count === 1 ? [true] : result.mutationChanges;
45961
+ if (!Array.isArray(changes) || changes.length !== count || changes.some((value) => typeof value !== "boolean"))
45962
+ return [];
45963
+ return changes.flatMap(
45964
+ (changed, index) => changed && options?.[index]?.keyframeTelemetry !== false ? [index] : []
45965
+ );
45966
+ }
45967
+ function trackKeyframeCommit(mutations, result, options, memberOptions) {
45968
+ if (!result.ok || result.changed !== true || options.keyframeTelemetry === false) return;
45969
+ const indices = changedMutationIndices(result, mutations.length, memberOptions);
45970
+ const actions = keyframeUsageActions(
45971
+ indices.map((index) => mutations[index]),
45972
+ indices.map(
45973
+ (index) => memberOptions?.[index]?.keyframeAction ?? (mutations.length === 1 ? options.keyframeAction : void 0)
45974
+ )
45975
+ );
45976
+ const action = primaryKeyframeAction(actions);
45977
+ if (action) trackKeyframeUsage(action, action === "add" ? options.keyframeProperty : void 0);
45978
+ }
45979
+
45980
+ // src/hooks/gsapGestureOutcome.ts
45981
+ function observeGsapGesture(writer) {
45982
+ const edit = adoptingStudioPendingEdit();
45983
+ const join = (run) => edit ? edit.within(run) : run();
45984
+ let changed = false;
45985
+ let pendingResults = 0;
45986
+ const actions = /* @__PURE__ */ new Set();
45987
+ const observe = (calls, options) => {
45988
+ pendingResults += 1;
45989
+ return {
45990
+ ...options,
45991
+ ...edit && { pendingEdit: edit },
45992
+ keyframeTelemetry: false,
45993
+ onResult: (result) => {
45994
+ pendingResults -= 1;
45995
+ options.onResult?.(result);
45996
+ if (!result.ok || result.changed !== true) return;
45997
+ changed = true;
45998
+ edit?.markSaved();
45999
+ const members = changedMutationIndices(
46000
+ result,
46001
+ calls.length,
46002
+ calls.map((call) => call.options)
46003
+ ).map((index) => calls[index]);
46004
+ for (const action of keyframeUsageActions(
46005
+ members.map((call) => call.mutation),
46006
+ members.map((call) => call.options.keyframeAction)
46007
+ ))
46008
+ actions.add(action);
46009
+ }
46010
+ };
46011
+ };
46012
+ let commit2 = null;
46013
+ if (writer) {
46014
+ commit2 = (selection, mutation, options) => join(() => writer(selection, mutation, observe([{ selection, mutation, options }], options)));
46015
+ if (writer.batch) {
46016
+ const batch = writer.batch;
46017
+ commit2.batch = (calls, options) => join(() => batch(calls, observe(calls, options)));
46018
+ }
46019
+ }
46020
+ return {
46021
+ commit: commit2,
46022
+ drawKeepingUndone: (draw) => edit ? edit.drawKeepingUndone(draw) : draw(),
46023
+ recordDomResult: (result) => {
46024
+ changed ||= result?.changed === true;
46025
+ if (result?.changed) edit?.markSaved();
46026
+ },
46027
+ finish: (domChanged = false) => {
46028
+ if (pendingResults !== 0) return { ok: true, changed: false };
46029
+ const action = primaryKeyframeAction(actions);
46030
+ if (action) trackKeyframeUsage(action);
46031
+ return { ok: true, changed: changed || domChanged };
46032
+ }
46033
+ };
46034
+ }
46035
+
46036
+ // src/hooks/gsapTweenReach.ts
46037
+ function tweenReach(animation, el) {
46038
+ const doc = el?.ownerDocument;
46039
+ if (!el?.isConnected || !doc || animation.hasUnresolvedSelector) return "unknown";
46040
+ let hits;
46041
+ try {
46042
+ hits = [...doc.querySelectorAll(animation.targetSelector)];
46043
+ } catch {
46044
+ return "unknown";
46045
+ }
46046
+ if (!hits.includes(el)) return "elsewhere";
46047
+ return hits.length === 1 ? "own" : "shared";
46048
+ }
46049
+ function tweensForThisElement(selection, animations) {
46050
+ return animations.filter((a) => {
46051
+ const reach = tweenReach(a, selection.element);
46052
+ return reach === "own" || reach === "unknown";
46053
+ });
46054
+ }
46055
+
46056
+ // src/hooks/gsapValueAtPlayhead.ts
46057
+ var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
46058
+ var ARRAY_STEP_EASE = "none";
46059
+ var roundPct2 = (pct) => Math.round(pct * 1e3) / 1e3;
46060
+ var isLinear = (ease) => !ease || ease === "none" || ease === "linear";
46061
+ function refuse(reason) {
46062
+ return { ok: false, reason };
46063
+ }
46064
+ function normalize(edit) {
46065
+ const { anim } = edit;
46066
+ const data = anim.keyframes;
46067
+ if (data) {
46068
+ if (data.format === "simple-array") return { reason: "simple-array-keyframes" };
46069
+ const arrayStep = data.format === "object-array";
46070
+ const blocked = stepListBlock(anim);
46071
+ if (blocked) return { reason: blocked };
46072
+ return {
46073
+ runEase: data.ease ?? anim.ease,
46074
+ keyframes: data.keyframes.map((kf) => ({
46075
+ ...kf,
46076
+ properties: { ...kf.properties },
46077
+ ...arrayStep ? { ease: kf.ease ?? ARRAY_STEP_EASE } : {}
46078
+ })),
46079
+ ...data.easeEach && !arrayStep ? { easeEach: data.easeEach } : {}
46080
+ };
46081
+ }
46082
+ const easeEach = anim.ease ?? edit.parsedEase;
46083
+ if (!easeEach) return { reason: "unknown-ease" };
46084
+ const to = { percentage: 100, properties: { ...anim.properties } };
46085
+ if (anim.method === "to") return { keyframes: [to], easeEach };
46086
+ if (anim.method === "fromTo") {
46087
+ return { keyframes: [{ percentage: 0, properties: { ...anim.fromProperties } }, to], easeEach };
46088
+ }
46089
+ if (anim.method === "from") {
46090
+ const end = {};
46091
+ for (const prop of Object.keys(anim.properties)) {
46092
+ const value = edit.implicitEndValue(prop, "end");
46093
+ if (value == null) return { reason: "implicit-end-unknown" };
46094
+ end[prop] = value;
46095
+ }
46096
+ return {
46097
+ keyframes: [
46098
+ { percentage: 0, properties: { ...anim.properties } },
46099
+ { percentage: 100, properties: end }
46100
+ ],
46101
+ easeEach
46102
+ };
46103
+ }
46104
+ return { reason: "not-a-tween" };
46105
+ }
46106
+ function valueAt(keyframes, prop, side) {
46107
+ const carrying = keyframes.filter((kf2) => kf2.properties[prop] != null);
46108
+ const kf = side === "first" ? carrying[0] : carrying.at(-1);
46109
+ return kf ? { percentage: kf.percentage, value: kf.properties[prop] } : null;
46110
+ }
46111
+ var keyframeAt = (keyframes, percentage) => keyframes[nearestKeyframeIndex(keyframes, percentage)];
46112
+ function upsert(keyframes, percentage, properties, ease) {
46113
+ const hit = keyframeAt(keyframes, percentage);
46114
+ if (hit) {
46115
+ Object.assign(hit.properties, properties);
46116
+ return hit;
46117
+ }
46118
+ const added = { percentage, properties, ...ease ? { ease } : {} };
46119
+ keyframes.push(added);
46120
+ return added;
46121
+ }
46122
+ function heldEnds(norm, end, implicit, backfilled) {
46123
+ const props = new Set(norm.keyframes.flatMap((kf) => Object.keys(kf.properties)));
46124
+ const held = {};
46125
+ for (const prop of props) {
46126
+ const known = valueAt(norm.keyframes, prop, end === "start" ? "first" : "last");
46127
+ const value = known && (end === "end" || known.percentage <= 0) ? known.value : backfilled[prop] ?? implicit(prop, "start");
46128
+ if (value == null) return null;
46129
+ held[prop] = value;
46130
+ }
46131
+ return held;
46132
+ }
46133
+ function holdOmittedChannels(steps, implicit, backfilled) {
46134
+ const props = new Set(steps.flatMap((kf) => Object.keys(kf.properties)));
46135
+ for (const prop of props) {
46136
+ let held;
46137
+ for (const kf of steps) {
46138
+ if (kf.properties[prop] != null) held = kf.properties[prop];
46139
+ else {
46140
+ held ??= backfilled[prop] ?? implicit(prop, "start");
46141
+ if (held == null) return false;
46142
+ kf.properties[prop] = held;
46143
+ }
46144
+ }
46145
+ }
46146
+ return true;
46147
+ }
46148
+ function planValueAtPlayhead(edit) {
46149
+ const { anim, values } = edit;
46150
+ const start = resolveTweenStart(anim);
46151
+ const duration = resolveTweenDuration(anim);
46152
+ if (start == null || !(duration > 0)) return refuse("no-timing");
46153
+ const norm = normalize(edit);
46154
+ if ("reason" in norm) return refuse(norm.reason);
46155
+ const keyframes = norm.keyframes;
46156
+ const authored = keyframes.length;
46157
+ const steps = anim.keyframes?.format === "object-array" ? [...keyframes] : [];
46158
+ if ("scaleX" in values || "scaleY" in values) {
46159
+ for (const kf of keyframes) {
46160
+ const { scale, ...rest } = kf.properties;
46161
+ if (scale != null) kf.properties = { scaleX: scale, scaleY: scale, ...rest };
46162
+ }
46163
+ }
46164
+ const backfilled = {};
46165
+ const pauseStepsHoldThePrevious = new Set(
46166
+ steps.filter((kf) => Object.keys(kf.properties).length === 0)
46167
+ );
46168
+ for (const [prop, value] of Object.entries(edit.backfill ?? {})) {
46169
+ if (!(prop in values) || keyframes.some((kf) => kf.properties[prop] != null)) continue;
46170
+ if (edit.holdFromStart && !keyframes.some((kf) => kf.percentage <= 0))
46171
+ keyframes.unshift({ percentage: 0, properties: {} });
46172
+ for (const kf of keyframes) if (!pauseStepsHoldThePrevious.has(kf)) kf.properties[prop] = value;
46173
+ backfilled[prop] = value;
46174
+ }
46175
+ if (Object.keys(values).some((prop) => !keyframes.some((kf) => kf.properties[prop] != null)))
46176
+ return refuse("implicit-end-unknown");
46177
+ let position = start;
46178
+ let span = duration;
46179
+ const pct = "percentage" in edit.at ? edit.at.percentage : roundPct2((edit.at.time - start) / duration * 100);
46180
+ const eased = !isLinear(norm.runEase);
46181
+ if (pct >= -KEYFRAME_PCT_MATCH && pct <= 100 + KEYFRAME_PCT_MATCH) {
46182
+ const at = Math.min(100, Math.max(0, pct));
46183
+ const hit = keyframeAt(keyframes, at);
46184
+ const onKeyframe = "percentage" in edit.at || hit?.percentage === 0 || hit?.percentage === 100;
46185
+ if (eased && !(hit && onKeyframe)) return refuse("eased-keyframes");
46186
+ const next = [...keyframes].sort((a, b) => a.percentage - b.percentage).find((kf) => kf.percentage > at + KEYFRAME_PCT_MATCH);
46187
+ upsert(keyframes, at, { ...values }, next?.ease);
46188
+ } else {
46189
+ if (eased) return refuse("eased-keyframes");
46190
+ const time = edit.at.time;
46191
+ const before = time < start;
46192
+ const held = heldEnds(norm, before ? "start" : "end", edit.implicitEndValue, backfilled);
46193
+ if (!held) return refuse("implicit-end-unknown");
46194
+ position = before ? time : start;
46195
+ span = before ? start + duration - time : time - start;
46196
+ for (const kf of keyframes) {
46197
+ kf.percentage = roundPct2(
46198
+ (start + kf.percentage / 100 * duration - position) / span * 100
46199
+ );
46200
+ }
46201
+ const oldEdge = roundPct2(((before ? start : start + duration) - position) / span * 100);
46202
+ const linear = isLinear(norm.easeEach ?? PERCENTAGE_SEGMENT_EASE) ? void 0 : "none";
46203
+ const edge = upsert(keyframes, oldEdge, held, linear);
46204
+ if (before && linear) edge.ease ??= linear;
46205
+ upsert(keyframes, before ? 0 : 100, { ...held, ...values }, before ? void 0 : linear);
46206
+ }
46207
+ if (!holdOmittedChannels(steps, edit.implicitEndValue, backfilled))
46208
+ return refuse("implicit-end-unknown");
46209
+ keyframes.sort((a, b) => a.percentage - b.percentage);
46210
+ return {
46211
+ ok: true,
46212
+ mutation: {
46213
+ type: "replace-with-keyframes",
46214
+ animationId: anim.id,
46215
+ targetSelector: anim.targetSelector,
46216
+ position: roundTo3(position),
46217
+ duration: roundTo3(span),
46218
+ keyframes,
46219
+ ...norm.runEase ? { ease: norm.runEase } : {},
46220
+ ...norm.easeEach ? { easeEach: norm.easeEach } : {}
46221
+ },
46222
+ added: keyframes.length > authored
46223
+ };
46224
+ }
46225
+ function planValueEdit(selection, anim, values, iframe, { backfill, holdFromStart } = {}) {
46226
+ if (tweenReach(anim, selection.element) === "shared") return refuse("shared-tween");
46227
+ const { activeKeyframePct, currentTime } = usePlayerStore.getState();
46228
+ const tween = findParsedTween(iframe, selection.element, anim);
46229
+ const timed = withExactStepTimes(anim, tween);
46230
+ return planValueAtPlayhead({
46231
+ anim: withLiveTiming(timed, tween),
46232
+ at: activeKeyframePct != null ? { percentage: exactKeyframePct(anim, tween, activeKeyframePct) } : { time: currentTime },
46233
+ values,
46234
+ backfill,
46235
+ holdFromStart,
46236
+ implicitEndValue: parsedImplicitEndValue(tween),
46237
+ parsedEase: parsedTweenEase(iframe, tween)
46238
+ });
46239
+ }
46240
+ async function commitValueAtPlayhead(selection, anim, values, iframe, callbacks, options) {
46241
+ await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
46242
+ const { activeKeyframePct, setActiveKeyframePct } = usePlayerStore.getState();
46243
+ const tween = findParsedTween(iframe, selection.element, anim);
46244
+ const parkAt = activeKeyframePct == null ? null : exactKeyframePct(anim, tween, activeKeyframePct);
46245
+ const plan = planValueEdit(selection, anim, values, iframe, options);
46246
+ if (!plan.ok) return { status: "blocked", reason: "keyframes-uneditable", detail: plan.reason };
46247
+ await callbacks.commitMutation(selection, plan.mutation, {
46248
+ label: options.label,
46249
+ softReload: true,
46250
+ beforeReload: options.beforeReload,
46251
+ ...plan.added && { keyframeAction: "add" }
46252
+ });
46253
+ if (parkAt != null) {
46254
+ setActiveKeyframePct(null);
46255
+ parkPlayheadOnKeyframe(anim, parkAt);
46256
+ }
46257
+ return { status: "persisted" };
46258
+ }
46259
+
46260
+ // src/hooks/gsapWholePropertyOffsetCommit.ts
46261
+ async function commitWholePropertyOffset(selection, anim, newValues, currentPct, iframe, callbacks, label) {
46262
+ let effectiveAnim = anim;
46263
+ if (anim.keyframes) {
46264
+ refuseStepListRewrite(anim);
46265
+ const newId = await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
46266
+ if (newId) effectiveAnim = { ...anim, id: newId };
46267
+ }
46268
+ const ts = resolveTweenStart(effectiveAnim);
46269
+ const td = resolveTweenDuration(effectiveAnim);
46270
+ const keys = Object.keys(newValues);
46271
+ const at = (props, key2) => typeof props[key2] === "number" ? props[key2] : PROPERTY_DEFAULTS[key2] ?? 0;
46272
+ const kfs = effectiveAnim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(effectiveAnim)?.keyframes;
46273
+ if (!kfs || kfs.length === 0) {
46274
+ await callbacks.commitMutation(
46275
+ selection,
46276
+ { type: "update-properties", animationId: effectiveAnim.id, properties: newValues },
46277
+ { label, softReload: true }
46278
+ );
46279
+ return;
46280
+ }
46281
+ const nearest = kfs.reduce(
46282
+ (best, kf) => Math.abs(kf.percentage - currentPct) < Math.abs(best.percentage - currentPct) ? kf : best
46283
+ );
46284
+ const shifted = kfs.map((kf) => {
46285
+ const properties = { ...kf.properties };
46286
+ for (const key2 of keys) {
46287
+ properties[key2] = roundTo3(
46288
+ at(properties, key2) + (newValues[key2] - at(nearest.properties, key2))
46289
+ );
46290
+ }
46291
+ return { percentage: kf.percentage, properties, ...kf.ease ? { ease: kf.ease } : {} };
46292
+ });
46293
+ await callbacks.commitMutation(
46294
+ selection,
46295
+ {
46296
+ type: "replace-with-keyframes",
46297
+ animationId: effectiveAnim.id,
46298
+ targetSelector: effectiveAnim.targetSelector,
46299
+ position: roundTo3(ts ?? 0),
46300
+ duration: roundTo3(td || 1),
46301
+ keyframes: shifted,
46302
+ ...keyframeEases(effectiveAnim)
46303
+ },
46304
+ { label, softReload: true }
46305
+ );
46306
+ }
46307
+
45942
46308
  // src/components/editor/motionPathCommit.ts
45943
46309
  var NEW_PATH_DURATION = 1.5;
45944
46310
  function motionPathCommitOptions(label, method) {
@@ -45957,6 +46323,37 @@ function commitNode(ref, x, y, animationId, commit2) {
45957
46323
  motionPathCommitOptions(ref.type === "keyframe" ? "Move keyframe" : "Move waypoint", "drag")
45958
46324
  );
45959
46325
  }
46326
+ function nodeDropLabel(ref) {
46327
+ if (ref.type !== "keyframe") return "Move waypoint";
46328
+ return usePlayerStore.getState().autoKeyframeEnabled ? "Move keyframe" : "Move animation path";
46329
+ }
46330
+ function commitNodeDrop(drop) {
46331
+ const { ref, at, anim, selection, iframe, commitMutation } = drop;
46332
+ if (ref.type !== "keyframe" || !anim || !selection)
46333
+ return commitNode(ref, at.x, at.y, drop.animId, commitMutation);
46334
+ const writes = observeGsapGesture((_sel, mutation, options) => commitMutation(mutation, options));
46335
+ const callbacks = { commitMutation: writes.commit };
46336
+ let done;
46337
+ const store = usePlayerStore.getState();
46338
+ const step = ref.step == null ? void 0 : anim.keyframes?.keyframes[ref.step];
46339
+ const pct = step?.percentage ?? ref.pct;
46340
+ if (store.autoKeyframeEnabled) {
46341
+ const selected = store.activeKeyframePct;
46342
+ store.setActiveKeyframePct(pct);
46343
+ const live = readGsapPositionFromIframe(iframe, selectorFor(selection) ?? "");
46344
+ done = commitValueAtPlayhead(selection, anim, at, iframe, callbacks, {
46345
+ label: nodeDropLabel(ref),
46346
+ backfill: live ?? void 0
46347
+ }).then(assertGsapEditPersisted).catch((error) => {
46348
+ usePlayerStore.getState().setActiveKeyframePct(selected);
46349
+ throw error;
46350
+ });
46351
+ } else {
46352
+ const label = nodeDropLabel(ref);
46353
+ done = commitWholePropertyOffset(selection, anim, at, pct, iframe, callbacks, label);
46354
+ }
46355
+ return done.then(() => trackPreviewEditResult("motion_path", "drag", writes.finish()));
46356
+ }
45960
46357
  function commitAddWaypoint(animationId, index, x, y, commit2) {
45961
46358
  return commit2(
45962
46359
  { type: "add-motion-path-point", animationId, index, x, y },
@@ -46145,6 +46542,8 @@ function interpolatedKeyframePct(a, b, t2) {
46145
46542
  }
46146
46543
  var NODE_PX = 6;
46147
46544
  var DRAG_THRESHOLD_PX2 = 3;
46545
+ var noToast = () => {
46546
+ };
46148
46547
  var MotionPathOverlay = memo29(function MotionPathOverlay2({
46149
46548
  iframeRef,
46150
46549
  selection,
@@ -46158,6 +46557,11 @@ var MotionPathOverlay = memo29(function MotionPathOverlay2({
46158
46557
  handleGsapRemoveAllKeyframes,
46159
46558
  handleGsapMoveKeyframeToPlayhead
46160
46559
  } = useDomEditContext();
46560
+ const shell = useStudioShellContextOptional();
46561
+ const reportFailure = useGsapInteractionFailureTelemetry(
46562
+ shell?.activeCompPath ?? null,
46563
+ shell?.showToast ?? noToast
46564
+ );
46161
46565
  const { rect, geometry, geometryResolved, visibleInPreview, home, pScale } = useMotionPathData(
46162
46566
  iframeRef,
46163
46567
  selectorFor(selection)
@@ -46346,22 +46750,16 @@ var MotionPathOverlay = memo29(function MotionPathOverlay2({
46346
46750
  }
46347
46751
  if (x === Math.round(d.initX) && y === Math.round(d.initY)) return;
46348
46752
  const anim = d.ref.type === "keyframe" ? selectedGsapAnimations?.find((a) => a.id === animId) : void 0;
46349
- if (d.ref.type === "keyframe" && anim && selection && !usePlayerStore.getState().autoKeyframeEnabled) {
46350
- const writes = observeGsapGesture(
46351
- (_sel, mutation, options) => commitMutation(mutation, options)
46352
- );
46353
- void commitWholePropertyOffset(
46354
- selection,
46355
- anim,
46356
- { x, y },
46357
- d.ref.pct,
46358
- iframeRef.current,
46359
- { commitMutation: writes.commit },
46360
- "Move animation path"
46361
- ).then(() => trackPreviewEditResult("motion_path", "drag", writes.finish()));
46362
- } else {
46363
- void commitNode(d.ref, x, y, animId, commitMutation);
46364
- }
46753
+ const label = nodeDropLabel(d.ref);
46754
+ commitNodeDrop({
46755
+ ref: d.ref,
46756
+ at: { x, y },
46757
+ animId,
46758
+ anim,
46759
+ selection,
46760
+ iframe: iframeRef.current,
46761
+ commitMutation
46762
+ }).catch((error) => reportFailure(error, selection, "drag", label));
46365
46763
  if (d.ref.type === "keyframe" && anim) {
46366
46764
  parkPlayheadOnKeyframe(anim, d.ref.pct);
46367
46765
  }
@@ -46506,7 +46904,7 @@ var MotionPathOverlay = memo29(function MotionPathOverlay2({
46506
46904
  });
46507
46905
 
46508
46906
  // src/components/editor/SnapToolbar.tsx
46509
- import { memo as memo30, useCallback as useCallback65, useEffect as useEffect60, useRef as useRef76, useState as useState53 } from "react";
46907
+ import { memo as memo30, useCallback as useCallback66, useEffect as useEffect60, useRef as useRef76, useState as useState53 } from "react";
46510
46908
  import { MagnetStraight, GridFour, Path, Ruler, FrameCorners } from "@phosphor-icons/react";
46511
46909
  import { jsx as jsx106, jsxs as jsxs77 } from "react/jsx-runtime";
46512
46910
  var SnapToolbar = memo30(function SnapToolbar2() {
@@ -46521,24 +46919,24 @@ var SnapToolbar = memo30(function SnapToolbar2() {
46521
46919
  const gridSpacingAtFocus = useRef76(null);
46522
46920
  const currentGridSpacing = useRef76(prefs.gridSpacing);
46523
46921
  currentGridSpacing.current = prefs.gridSpacing;
46524
- const settleGridSpacing = useCallback65(() => {
46922
+ const settleGridSpacing = useCallback66(() => {
46525
46923
  const previous = gridSpacingAtFocus.current;
46526
46924
  gridSpacingAtFocus.current = null;
46527
46925
  if (previous !== null && previous !== currentGridSpacing.current)
46528
46926
  trackPreviewFeatureUsed("grid_spacing", "field");
46529
46927
  }, []);
46530
46928
  useEffect60(() => settleGridSpacing, [settleGridSpacing]);
46531
- const updatePrefs = useCallback65(
46929
+ const updatePrefs = useCallback66(
46532
46930
  (patch) => {
46533
46931
  actions.setSnapPrefs(patch);
46534
46932
  },
46535
46933
  [actions]
46536
46934
  );
46537
- const toggleSnap = useCallback65(() => {
46935
+ const toggleSnap = useCallback66(() => {
46538
46936
  updatePrefs({ snapEnabled: !prefs.snapEnabled });
46539
46937
  trackPreviewFeatureUsed("snapping", "button");
46540
46938
  }, [prefs.snapEnabled, updatePrefs]);
46541
- const toggleGrid = useCallback65(() => {
46939
+ const toggleGrid = useCallback66(() => {
46542
46940
  updatePrefs({ gridVisible: !prefs.gridVisible });
46543
46941
  trackPreviewFeatureUsed("grid", "button");
46544
46942
  }, [prefs.gridVisible, updatePrefs]);
@@ -46819,13 +47217,13 @@ function PreviewOverlays({
46819
47217
  const captionModelPresent = useCaptionStore((state) => state.model !== null);
46820
47218
  const captionDismissed = useCaptionStore((state) => state.dismissed);
46821
47219
  const captionSyncError = useCaptionStore((state) => state.syncError);
46822
- const exitCaptionMode = useCallback66(() => {
47220
+ const exitCaptionMode = useCallback67(() => {
46823
47221
  const store = useCaptionStore.getState();
46824
47222
  store.clearSelection();
46825
47223
  store.setDismissed(true);
46826
47224
  store.setEditMode(false);
46827
47225
  }, []);
46828
- const enterCaptionMode = useCallback66(() => {
47226
+ const enterCaptionMode = useCallback67(() => {
46829
47227
  const store = useCaptionStore.getState();
46830
47228
  store.setDismissed(false);
46831
47229
  store.setEditMode(true);
@@ -46938,10 +47336,10 @@ function PreviewOverlays({
46938
47336
  }
46939
47337
 
46940
47338
  // src/components/nle/useTimelineEditCallbacks.ts
46941
- import { useCallback as useCallback68, useMemo as useMemo45 } from "react";
47339
+ import { useCallback as useCallback69, useMemo as useMemo45 } from "react";
46942
47340
 
46943
47341
  // src/hooks/useGsapTweenCache.ts
46944
- import { useEffect as useEffect62, useMemo as useMemo44, useRef as useRef77, useState as useState55, useCallback as useCallback67 } from "react";
47342
+ import { useEffect as useEffect62, useMemo as useMemo44, useRef as useRef77, useState as useState55, useCallback as useCallback68 } from "react";
46945
47343
 
46946
47344
  // src/hooks/gsapRuntimeBridge.ts
46947
47345
  import { isXYPositionWrite, PROPERTY_GROUPS } from "@hyperframes/parsers/gsap-constants";
@@ -47050,204 +47448,6 @@ function readAllAnimatedProperties(iframe, selector, anim, group) {
47050
47448
  return result;
47051
47449
  }
47052
47450
 
47053
- // src/hooks/gsapTweenReach.ts
47054
- function tweenReach(animation, el) {
47055
- const doc = el?.ownerDocument;
47056
- if (!el?.isConnected || !doc || animation.hasUnresolvedSelector) return "unknown";
47057
- let hits;
47058
- try {
47059
- hits = [...doc.querySelectorAll(animation.targetSelector)];
47060
- } catch {
47061
- return "unknown";
47062
- }
47063
- if (!hits.includes(el)) return "elsewhere";
47064
- return hits.length === 1 ? "own" : "shared";
47065
- }
47066
- function tweensForThisElement(selection, animations) {
47067
- return animations.filter((a) => {
47068
- const reach = tweenReach(a, selection.element);
47069
- return reach === "own" || reach === "unknown";
47070
- });
47071
- }
47072
-
47073
- // src/hooks/gsapValueAtPlayhead.ts
47074
- var PERCENTAGE_SEGMENT_EASE = "power1.inOut";
47075
- var ARRAY_STEP_EASE = "none";
47076
- var roundPct2 = (pct) => Math.round(pct * 1e3) / 1e3;
47077
- var isLinear = (ease) => !ease || ease === "none" || ease === "linear";
47078
- function refuse(reason) {
47079
- return { ok: false, reason };
47080
- }
47081
- function normalize(edit) {
47082
- const { anim } = edit;
47083
- const data = anim.keyframes;
47084
- if (data) {
47085
- if (data.format === "simple-array") return { reason: "simple-array-keyframes" };
47086
- const arrayStep = data.format === "object-array";
47087
- return {
47088
- runEase: data.ease ?? anim.ease,
47089
- keyframes: data.keyframes.map((kf) => ({
47090
- ...kf,
47091
- properties: { ...kf.properties },
47092
- ...arrayStep ? { ease: kf.ease ?? ARRAY_STEP_EASE } : {}
47093
- })),
47094
- ...data.easeEach && !arrayStep ? { easeEach: data.easeEach } : {}
47095
- };
47096
- }
47097
- const easeEach = anim.ease ?? edit.parsedEase;
47098
- if (!easeEach) return { reason: "unknown-ease" };
47099
- const to = { percentage: 100, properties: { ...anim.properties } };
47100
- if (anim.method === "to") return { keyframes: [to], easeEach };
47101
- if (anim.method === "fromTo") {
47102
- return { keyframes: [{ percentage: 0, properties: { ...anim.fromProperties } }, to], easeEach };
47103
- }
47104
- if (anim.method === "from") {
47105
- const end = {};
47106
- for (const prop of Object.keys(anim.properties)) {
47107
- const value = edit.implicitEndValue(prop, "end");
47108
- if (value == null) return { reason: "implicit-end-unknown" };
47109
- end[prop] = value;
47110
- }
47111
- return {
47112
- keyframes: [
47113
- { percentage: 0, properties: { ...anim.properties } },
47114
- { percentage: 100, properties: end }
47115
- ],
47116
- easeEach
47117
- };
47118
- }
47119
- return { reason: "not-a-tween" };
47120
- }
47121
- function valueAt(keyframes, prop, side) {
47122
- const carrying = keyframes.filter((kf2) => kf2.properties[prop] != null);
47123
- const kf = side === "first" ? carrying[0] : carrying.at(-1);
47124
- return kf ? { percentage: kf.percentage, value: kf.properties[prop] } : null;
47125
- }
47126
- var keyframeAt = (keyframes, percentage) => keyframes.find((kf) => Math.abs(kf.percentage - percentage) <= KEYFRAME_PCT_MATCH);
47127
- function upsert(keyframes, percentage, properties, ease) {
47128
- const hit = keyframeAt(keyframes, percentage);
47129
- if (hit) {
47130
- Object.assign(hit.properties, properties);
47131
- return hit;
47132
- }
47133
- const added = { percentage, properties, ...ease ? { ease } : {} };
47134
- keyframes.push(added);
47135
- return added;
47136
- }
47137
- function heldEnds(norm, end, implicit, backfilled) {
47138
- const props = new Set(norm.keyframes.flatMap((kf) => Object.keys(kf.properties)));
47139
- const held = {};
47140
- for (const prop of props) {
47141
- const known = valueAt(norm.keyframes, prop, end === "start" ? "first" : "last");
47142
- const value = known && (end === "end" || known.percentage <= 0) ? known.value : backfilled[prop] ?? implicit(prop, "start");
47143
- if (value == null) return null;
47144
- held[prop] = value;
47145
- }
47146
- return held;
47147
- }
47148
- function planValueAtPlayhead(edit) {
47149
- const { anim, values } = edit;
47150
- const start = resolveTweenStart(anim);
47151
- const duration = resolveTweenDuration(anim);
47152
- if (start == null || !(duration > 0)) return refuse("no-timing");
47153
- const norm = normalize(edit);
47154
- if ("reason" in norm) return refuse(norm.reason);
47155
- const keyframes = norm.keyframes;
47156
- const authored = keyframes.length;
47157
- if ("scaleX" in values || "scaleY" in values) {
47158
- for (const kf of keyframes) {
47159
- const { scale, ...rest } = kf.properties;
47160
- if (scale != null) kf.properties = { scaleX: scale, scaleY: scale, ...rest };
47161
- }
47162
- }
47163
- const backfilled = {};
47164
- for (const [prop, value] of Object.entries(edit.backfill ?? {})) {
47165
- if (!(prop in values) || keyframes.some((kf) => kf.properties[prop] != null)) continue;
47166
- if (edit.holdFromStart && !keyframes.some((kf) => kf.percentage <= 0))
47167
- keyframes.unshift({ percentage: 0, properties: {} });
47168
- for (const kf of keyframes) kf.properties[prop] = value;
47169
- backfilled[prop] = value;
47170
- }
47171
- if (Object.keys(values).some((prop) => !keyframes.some((kf) => kf.properties[prop] != null)))
47172
- return refuse("implicit-end-unknown");
47173
- let position = start;
47174
- let span = duration;
47175
- const pct = "percentage" in edit.at ? edit.at.percentage : roundPct2((edit.at.time - start) / duration * 100);
47176
- const eased = !isLinear(norm.runEase);
47177
- if (pct >= -KEYFRAME_PCT_MATCH && pct <= 100 + KEYFRAME_PCT_MATCH) {
47178
- const at = Math.min(100, Math.max(0, pct));
47179
- const hit = keyframeAt(keyframes, at);
47180
- if (eased && hit?.percentage !== 0 && hit?.percentage !== 100) return refuse("eased-keyframes");
47181
- const next = [...keyframes].sort((a, b) => a.percentage - b.percentage).find((kf) => kf.percentage > at + KEYFRAME_PCT_MATCH);
47182
- upsert(keyframes, at, { ...values }, next?.ease);
47183
- } else {
47184
- if (eased) return refuse("eased-keyframes");
47185
- const time = edit.at.time;
47186
- const before = time < start;
47187
- const held = heldEnds(norm, before ? "start" : "end", edit.implicitEndValue, backfilled);
47188
- if (!held) return refuse("implicit-end-unknown");
47189
- position = before ? time : start;
47190
- span = before ? start + duration - time : time - start;
47191
- for (const kf of keyframes) {
47192
- kf.percentage = roundPct2(
47193
- (start + kf.percentage / 100 * duration - position) / span * 100
47194
- );
47195
- }
47196
- const oldEdge = roundPct2(((before ? start : start + duration) - position) / span * 100);
47197
- const linear = isLinear(norm.easeEach ?? PERCENTAGE_SEGMENT_EASE) ? void 0 : "none";
47198
- const edge = upsert(keyframes, oldEdge, held, linear);
47199
- if (before && linear) edge.ease ??= linear;
47200
- upsert(keyframes, before ? 0 : 100, { ...held, ...values }, before ? void 0 : linear);
47201
- }
47202
- keyframes.sort((a, b) => a.percentage - b.percentage);
47203
- return {
47204
- ok: true,
47205
- mutation: {
47206
- type: "replace-with-keyframes",
47207
- animationId: anim.id,
47208
- targetSelector: anim.targetSelector,
47209
- position: roundTo3(position),
47210
- duration: roundTo3(span),
47211
- keyframes,
47212
- ...norm.runEase ? { ease: norm.runEase } : {},
47213
- ...norm.easeEach ? { easeEach: norm.easeEach } : {}
47214
- },
47215
- added: keyframes.length > authored
47216
- };
47217
- }
47218
- function planValueEdit(selection, anim, values, iframe, { backfill, holdFromStart } = {}) {
47219
- if (tweenReach(anim, selection.element) === "shared") return refuse("shared-tween");
47220
- const { activeKeyframePct, currentTime } = usePlayerStore.getState();
47221
- const tween = findParsedTween(iframe, selection.element, anim);
47222
- const timed = withExactStepTimes(anim, tween);
47223
- return planValueAtPlayhead({
47224
- anim: withLiveTiming(timed, tween),
47225
- at: activeKeyframePct != null ? { percentage: activeKeyframePct } : { time: currentTime },
47226
- values,
47227
- backfill,
47228
- holdFromStart,
47229
- implicitEndValue: parsedImplicitEndValue(tween),
47230
- parsedEase: parsedTweenEase(iframe, tween)
47231
- });
47232
- }
47233
- async function commitValueAtPlayhead(selection, anim, values, iframe, callbacks, options) {
47234
- await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
47235
- const { activeKeyframePct, setActiveKeyframePct } = usePlayerStore.getState();
47236
- const plan = planValueEdit(selection, anim, values, iframe, options);
47237
- if (!plan.ok) return { status: "blocked", reason: "keyframes-uneditable", detail: plan.reason };
47238
- await callbacks.commitMutation(selection, plan.mutation, {
47239
- label: options.label,
47240
- softReload: true,
47241
- beforeReload: options.beforeReload,
47242
- ...plan.added && { keyframeAction: "add" }
47243
- });
47244
- if (activeKeyframePct != null) {
47245
- setActiveKeyframePct(null);
47246
- parkPlayheadOnKeyframe(anim, activeKeyframePct);
47247
- }
47248
- return { status: "persisted" };
47249
- }
47250
-
47251
47451
  // src/hooks/gsapDragPositionCommit.ts
47252
47452
  function buildTemporalArcKeyframes(anim, percentage, properties) {
47253
47453
  return [
@@ -47455,7 +47655,10 @@ async function planDrag(selection, offset, allAnimations, iframe, options) {
47455
47655
  if (!posAnim) {
47456
47656
  return { status: "blocked", reason: "source-uneditable", detail: "no-position-tween" };
47457
47657
  }
47458
- if (route === "whole-path") return { status: "persisted" };
47658
+ if (route === "whole-path") {
47659
+ const step = stepListBlock(posAnim);
47660
+ return step ? { status: "blocked", reason: "keyframes-uneditable", detail: step } : { status: "persisted" };
47661
+ }
47459
47662
  const gsapPos = readGsapPositionFromIframe(iframe, selector) ?? { x: 0, y: 0 };
47460
47663
  return gsapPositionFromDragOutcome(selection, posAnim, offset, gsapPos, iframe);
47461
47664
  }
@@ -47849,7 +48052,7 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
47849
48052
  }
47850
48053
  function useGsapCacheVersion() {
47851
48054
  const [version, setVersion] = useState55(0);
47852
- const bump = useCallback67(() => setVersion((v) => v + 1), []);
48055
+ const bump = useCallback68(() => setVersion((v) => v + 1), []);
47853
48056
  return { version, bump };
47854
48057
  }
47855
48058
  function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeRef) {
@@ -48037,7 +48240,7 @@ function useTimelineEditCallbacks({
48037
48240
  handleGsapRemoveAllKeyframes,
48038
48241
  buildDomSelectionForTimelineElement
48039
48242
  } = useDomEditActionsContext();
48040
- const resolveElementAnimations = useCallback68(
48243
+ const resolveElementAnimations = useCallback69(
48041
48244
  (elementKey3) => {
48042
48245
  const { gsapAnimations } = usePlayerStore.getState();
48043
48246
  const { sourceFile, domId } = splitTimelineElementKey(elementKey3);
@@ -48050,7 +48253,7 @@ function useTimelineEditCallbacks({
48050
48253
  },
48051
48254
  [activeCompPath]
48052
48255
  );
48053
- const resolveKeyframeTarget = useCallback68(
48256
+ const resolveKeyframeTarget = useCallback69(
48054
48257
  (elementKey3, target, animations = selectedGsapAnimations) => {
48055
48258
  const carriesIdentity = target.propertyGroup !== void 0 || target.tweenPercentage !== void 0 || target.animationId !== void 0;
48056
48259
  const keyframeCache = usePlayerStore.getState().keyframeCache;
@@ -48063,7 +48266,7 @@ function useTimelineEditCallbacks({
48063
48266
  },
48064
48267
  [selectedGsapAnimations]
48065
48268
  );
48066
- const removeKeyframeTarget = useCallback68(
48269
+ const removeKeyframeTarget = useCallback69(
48067
48270
  (animationId, percentage, selectionOverride) => {
48068
48271
  handleGsapRemoveKeyframe(animationId, percentage, void 0, selectionOverride);
48069
48272
  },
@@ -48276,7 +48479,7 @@ function useTimelineEditCallbacks({
48276
48479
  }
48277
48480
 
48278
48481
  // src/captions/components/CaptionTimeline.tsx
48279
- import { memo as memo32, useCallback as useCallback69 } from "react";
48482
+ import { memo as memo32, useCallback as useCallback70 } from "react";
48280
48483
  import { jsx as jsx109, jsxs as jsxs79 } from "react/jsx-runtime";
48281
48484
  var GROUP_COLORS = [
48282
48485
  "#3CE6AC",
@@ -48299,14 +48502,14 @@ var CaptionTimeline = memo32(function CaptionTimeline2({
48299
48502
  const selectedSegmentIds = useCaptionStore((s) => s.selectedSegmentIds);
48300
48503
  const selectSegment = useCaptionStore((s) => s.selectSegment);
48301
48504
  const currentTime = usePlayerStore((s) => s.currentTime);
48302
- const handleBlockClick = useCallback69(
48505
+ const handleBlockClick = useCallback70(
48303
48506
  (e, segId) => {
48304
48507
  e.stopPropagation();
48305
48508
  selectSegment(segId, e.shiftKey);
48306
48509
  },
48307
48510
  [selectSegment]
48308
48511
  );
48309
- const handleBlockKeyDown = useCallback69(
48512
+ const handleBlockKeyDown = useCallback70(
48310
48513
  (e, segId) => {
48311
48514
  if (e.key === "Enter" || e.key === " ") {
48312
48515
  e.preventDefault();
@@ -48316,7 +48519,7 @@ var CaptionTimeline = memo32(function CaptionTimeline2({
48316
48519
  },
48317
48520
  [selectSegment]
48318
48521
  );
48319
- const handleTrackClick = useCallback69(
48522
+ const handleTrackClick = useCallback70(
48320
48523
  (e) => {
48321
48524
  if (!onSeek) return;
48322
48525
  const rect = e.currentTarget.getBoundingClientRect();
@@ -48524,7 +48727,7 @@ function useTimelineSelectionPreviewSync({
48524
48727
  }
48525
48728
 
48526
48729
  // src/webmcp/StudioAgentTools.tsx
48527
- import { useCallback as useCallback70, useEffect as useEffect65, useMemo as useMemo47 } from "react";
48730
+ import { useCallback as useCallback71, useEffect as useEffect65, useMemo as useMemo47 } from "react";
48528
48731
 
48529
48732
  // src/webmcp/useStudioAgentTools.ts
48530
48733
  import { useEffect as useEffect64, useRef as useRef79 } from "react";
@@ -49816,7 +50019,7 @@ function StudioAgentTools() {
49816
50019
  studioEditLifecycle.activateProject(projectId);
49817
50020
  return () => studioEditLifecycle.reset();
49818
50021
  }, [projectId]);
49819
- const getSnapshot = useCallback70(() => {
50022
+ const getSnapshot = useCallback71(() => {
49820
50023
  const player = usePlayerStore.getState();
49821
50024
  return {
49822
50025
  projectId,
@@ -49931,7 +50134,7 @@ function StudioAgentTools() {
49931
50134
  // src/components/dock/Dock.tsx
49932
50135
  import {
49933
50136
  createContext as createContext6,
49934
- useCallback as useCallback73,
50137
+ useCallback as useCallback74,
49935
50138
  useContext as useContext11,
49936
50139
  useEffect as useEffect66,
49937
50140
  useRef as useRef80,
@@ -50020,7 +50223,7 @@ function installDockAccessibility(api, root) {
50020
50223
  }
50021
50224
 
50022
50225
  // src/components/dock/DockStripActions.tsx
50023
- import { useCallback as useCallback71, useSyncExternalStore as useSyncExternalStore6 } from "react";
50226
+ import { useCallback as useCallback72, useSyncExternalStore as useSyncExternalStore6 } from "react";
50024
50227
  import { CornersIn, CornersOut, DotsThree, X as X2 } from "@phosphor-icons/react";
50025
50228
 
50026
50229
  // src/components/dock/dockLayoutStore.ts
@@ -50087,7 +50290,7 @@ function DockStripActions({
50087
50290
  panels,
50088
50291
  isGroupActive
50089
50292
  }) {
50090
- const subscribe2 = useCallback71(
50293
+ const subscribe2 = useCallback72(
50091
50294
  (onChange) => {
50092
50295
  const subscription = containerApi.onDidMaximizedGroupChange(onChange);
50093
50296
  return () => subscription.dispose();
@@ -50145,7 +50348,7 @@ function DockStripActions({
50145
50348
  }
50146
50349
 
50147
50350
  // src/components/dock/DockTab.tsx
50148
- import { useCallback as useCallback72, useSyncExternalStore as useSyncExternalStore7 } from "react";
50351
+ import { useCallback as useCallback73, useSyncExternalStore as useSyncExternalStore7 } from "react";
50149
50352
  import {
50150
50353
  BracketsCurly,
50151
50354
  ChartBarHorizontal,
@@ -50175,7 +50378,7 @@ var TAB_ICONS = {
50175
50378
  slideshow: Presentation
50176
50379
  };
50177
50380
  function DockTab({ api }) {
50178
- const subscribe2 = useCallback72(
50381
+ const subscribe2 = useCallback73(
50179
50382
  (onChange) => {
50180
50383
  const subscription = api.onDidTitleChange(onChange);
50181
50384
  return () => subscription.dispose();
@@ -50400,7 +50603,7 @@ function useSlots() {
50400
50603
  function PanelSlot({ api }) {
50401
50604
  const { registerSlot } = useSlots();
50402
50605
  const id = api.id;
50403
- const ref = useCallback73(
50606
+ const ref = useCallback74(
50404
50607
  (element) => {
50405
50608
  if (isPanelId(id)) registerSlot(id, element);
50406
50609
  },
@@ -50479,7 +50682,7 @@ function Root({
50479
50682
  children
50480
50683
  }) {
50481
50684
  const [slots, setSlots] = useState56({});
50482
- const registerSlot = useCallback73((id, element) => {
50685
+ const registerSlot = useCallback74((id, element) => {
50483
50686
  setSlots((prev) => {
50484
50687
  if (prev[id] === (element ?? void 0)) return prev;
50485
50688
  const next = { ...prev };
@@ -50491,7 +50694,7 @@ function Root({
50491
50694
  const disposeRef = useRef80(() => {
50492
50695
  });
50493
50696
  useEffect66(() => () => disposeRef.current(), []);
50494
- const onReady = useCallback73(
50697
+ const onReady = useCallback74(
50495
50698
  ({ api }) => {
50496
50699
  disposeRef.current();
50497
50700
  const options = { panels, storageKey, dockWidth };
@@ -50693,7 +50896,7 @@ var EditorShell = memo33(function EditorShell2({
50693
50896
  const { domEditSelection, domEditGroupSelections } = useDomEditSelectionContext();
50694
50897
  const selectedElementId = usePlayerStore((state) => state.selectedElementId);
50695
50898
  const selectedElementIds = usePlayerStore((state) => state.selectedElementIds);
50696
- const reportTimelineSelectionNotFound = useCallback74(() => {
50899
+ const reportTimelineSelectionNotFound = useCallback75(() => {
50697
50900
  showToast("The selected clip is not available in the preview yet.", "info");
50698
50901
  }, [showToast]);
50699
50902
  useTimelineSelectionPreviewSync({
@@ -50801,10 +51004,10 @@ function EditorShellBody({
50801
51004
  splitShortcut
50802
51005
  }) {
50803
51006
  const { compositionStack, updateCompositionStack, containerRef } = useNLEContext();
50804
- const seekCaptionTime = useCallback74((time) => {
51007
+ const seekCaptionTime = useCallback75((time) => {
50805
51008
  usePlayerStore.getState().requestSeek(time);
50806
51009
  }, []);
50807
- const handleKeyDown = useCallback74(
51010
+ const handleKeyDown = useCallback75(
50808
51011
  (e) => {
50809
51012
  if (e.key === "Escape" && compositionStack.length > 1) {
50810
51013
  updateCompositionStack((prev) => prev.slice(0, -1));
@@ -51044,7 +51247,7 @@ var ImageThumbnail = memo34(function ImageThumbnail2({
51044
51247
  });
51045
51248
 
51046
51249
  // src/hooks/useRenderClipContent.ts
51047
- import { useCallback as useCallback75 } from "react";
51250
+ import { useCallback as useCallback76 } from "react";
51048
51251
  import { createElement } from "react";
51049
51252
 
51050
51253
  // src/player/components/TextClipContent.tsx
@@ -51223,7 +51426,7 @@ function useRenderClipContent({
51223
51426
  const sessionEpoch = usePlayerStore((s) => s.timelineSessionEpoch);
51224
51427
  const thumbnailRevisions = usePlayerStore((s) => s.thumbnailRevisions);
51225
51428
  const elements = usePlayerStore((s) => s.elements);
51226
- return useCallback75(
51429
+ return useCallback76(
51227
51430
  // Pre-existing clip-content dispatcher; reduced by extracting renderAudioClip.
51228
51431
  // fallow-ignore-next-line complexity
51229
51432
  (el, style, context = { priority: "visible", rich: false }) => {
@@ -51354,7 +51557,7 @@ function useThumbnailStill(url, projectId, { sessionEpoch = 0, priority = "visib
51354
51557
  }
51355
51558
 
51356
51559
  // src/components/editor/SourceEditor.tsx
51357
- import { useRef as useRef81, useCallback as useCallback76, useEffect as useEffect68, memo as memo35 } from "react";
51560
+ import { useRef as useRef81, useCallback as useCallback77, useEffect as useEffect68, memo as memo35 } from "react";
51358
51561
  import {
51359
51562
  EditorView as EditorView2,
51360
51563
  keymap,
@@ -51514,7 +51717,7 @@ var SourceEditor = memo35(function SourceEditor2({
51514
51717
  onChangeRef.current = onChange;
51515
51718
  const contentRef = useRef81(content);
51516
51719
  contentRef.current = content;
51517
- const mountEditor = useCallback76(
51720
+ const mountEditor = useCallback77(
51518
51721
  (node) => {
51519
51722
  if (editorRef.current) {
51520
51723
  editorRef.current.destroy();
@@ -51582,7 +51785,7 @@ import { memo as memo39, useMemo as useMemo65, useRef as useRef112, useState as
51582
51785
  import { Eye as Eye3, EyeSlash as EyeSlash2 } from "@phosphor-icons/react";
51583
51786
 
51584
51787
  // src/contexts/DesignPanelInputContext.tsx
51585
- import { useCallback as useCallback77, useContext as useContext12, useMemo as useMemo50 } from "react";
51788
+ import { useCallback as useCallback78, useContext as useContext12, useMemo as useMemo50 } from "react";
51586
51789
 
51587
51790
  // src/utils/designInputTracking.ts
51588
51791
  var COALESCE_WINDOW_MS = 600;
@@ -51633,7 +51836,7 @@ function DesignPanelInputProvider({
51633
51836
  }
51634
51837
  function useTrackDesignInput() {
51635
51838
  const { ui, section } = useContext12(DesignPanelInputContext);
51636
- return useCallback77(
51839
+ return useCallback78(
51637
51840
  (control, name) => trackDesignInput({ ui, section, control, name }),
51638
51841
  [ui, section]
51639
51842
  );
@@ -52238,13 +52441,13 @@ function buildElementInfoText(element, sourceLabel, gsapAnimations, previewIfram
52238
52441
  }
52239
52442
 
52240
52443
  // src/components/editor/propertyPanelPrimitives.tsx
52241
- import { useCallback as useCallback79, useEffect as useEffect71, useRef as useRef84, useState as useState60 } from "react";
52444
+ import { useCallback as useCallback80, useEffect as useEffect71, useRef as useRef84, useState as useState60 } from "react";
52242
52445
 
52243
52446
  // src/components/editor/propertyPanelCommitField.tsx
52244
52447
  import { useEffect as useEffect70, useRef as useRef83, useState as useState59 } from "react";
52245
52448
 
52246
52449
  // src/components/editor/useInspectorGestureTransaction.ts
52247
- import { useCallback as useCallback78, useEffect as useEffect69, useRef as useRef82, useState as useState58 } from "react";
52450
+ import { useCallback as useCallback79, useEffect as useEffect69, useRef as useRef82, useState as useState58 } from "react";
52248
52451
  function isPromiseCommit(result) {
52249
52452
  return Boolean(result && typeof result.then === "function");
52250
52453
  }
@@ -52278,13 +52481,13 @@ function useInspectorGestureTransaction({
52278
52481
  }
52279
52482
  previewRef.current = onPreview;
52280
52483
  commitRef.current = onCommit;
52281
- const begin = useCallback78(() => {
52484
+ const begin = useCallback79(() => {
52282
52485
  if (!activeRef.current) {
52283
52486
  generationRef.current += 1;
52284
52487
  activeRef.current = { before: sourceRef.current, latest: sourceRef.current };
52285
52488
  }
52286
52489
  }, []);
52287
- const preview = useCallback78((value) => {
52490
+ const preview = useCallback79((value) => {
52288
52491
  if (!activeRef.current) {
52289
52492
  generationRef.current += 1;
52290
52493
  activeRef.current = { before: sourceRef.current, latest: sourceRef.current };
@@ -52292,7 +52495,7 @@ function useInspectorGestureTransaction({
52292
52495
  activeRef.current.latest = value;
52293
52496
  previewRef.current(value);
52294
52497
  }, []);
52295
- const rollbackCommit = useCallback78((active, generation) => {
52498
+ const rollbackCommit = useCallback79((active, generation) => {
52296
52499
  if (generation !== generationRef.current) return;
52297
52500
  pendingRef.current = null;
52298
52501
  if (awaitingSourceAckRef.current?.generation === generation) {
@@ -52301,7 +52504,7 @@ function useInspectorGestureTransaction({
52301
52504
  sourceRef.current = active.before;
52302
52505
  previewRef.current(active.before);
52303
52506
  }, []);
52304
- const settle = useCallback78(() => {
52507
+ const settle = useCallback79(() => {
52305
52508
  const active = activeRef.current;
52306
52509
  activeRef.current = null;
52307
52510
  if (active && !Object.is(active.before, active.latest)) {
@@ -52327,7 +52530,7 @@ function useInspectorGestureTransaction({
52327
52530
  }
52328
52531
  }
52329
52532
  }, [rollbackCommit]);
52330
- const cancel = useCallback78(() => {
52533
+ const cancel = useCallback79(() => {
52331
52534
  generationRef.current += 1;
52332
52535
  const active = activeRef.current;
52333
52536
  activeRef.current = null;
@@ -52575,14 +52778,14 @@ function MetricField({
52575
52778
  }) {
52576
52779
  const track2 = useTrackDesignInput();
52577
52780
  const scrubRef = useRef84(null);
52578
- const commit2 = useCallback79(
52781
+ const commit2 = useCallback80(
52579
52782
  (nextValue) => {
52580
52783
  if (nextValue !== value) track2("metric", label);
52581
52784
  return onCommit(nextValue);
52582
52785
  },
52583
52786
  [label, onCommit, track2, value]
52584
52787
  );
52585
- const handleScrubPointerDown = useCallback79(
52788
+ const handleScrubPointerDown = useCallback80(
52586
52789
  (e) => {
52587
52790
  if (disabled || !scrub) return;
52588
52791
  const parsed = parseFloat(value);
@@ -52592,7 +52795,7 @@ function MetricField({
52592
52795
  },
52593
52796
  [disabled, scrub, value]
52594
52797
  );
52595
- const handleScrubPointerMove = useCallback79(
52798
+ const handleScrubPointerMove = useCallback80(
52596
52799
  (e) => {
52597
52800
  const state = scrubRef.current;
52598
52801
  if (!state) return;
@@ -52601,7 +52804,7 @@ function MetricField({
52601
52804
  },
52602
52805
  [commit2]
52603
52806
  );
52604
- const handleScrubPointerUp = useCallback79(() => {
52807
+ const handleScrubPointerUp = useCallback80(() => {
52605
52808
  scrubRef.current = null;
52606
52809
  }, []);
52607
52810
  const scrubProps = scrub && !disabled ? {
@@ -53297,7 +53500,7 @@ import {
53297
53500
  } from "@hyperframes/core/color-grading";
53298
53501
 
53299
53502
  // src/components/editor/propertyPanelColorGradingSlider.tsx
53300
- import { useCallback as useCallback80, useEffect as useEffect73, useRef as useRef85, useState as useState62 } from "react";
53503
+ import { useCallback as useCallback81, useEffect as useEffect73, useRef as useRef85, useState as useState62 } from "react";
53301
53504
  import { jsx as jsx124, jsxs as jsxs90 } from "react/jsx-runtime";
53302
53505
  var SLIDER_THUMB_SIZE = 10;
53303
53506
  var SLIDER_THUMB_RADIUS = SLIDER_THUMB_SIZE / 2;
@@ -53346,11 +53549,11 @@ function ColorGradingSliderControl({
53346
53549
  },
53347
53550
  []
53348
53551
  );
53349
- const clampDraft = useCallback80(
53552
+ const clampDraft = useCallback81(
53350
53553
  (nextValue) => clampNumber2(nextValue, min, max),
53351
53554
  [max, min]
53352
53555
  );
53353
- const setLocalDraft = useCallback80(
53556
+ const setLocalDraft = useCallback81(
53354
53557
  (nextValue) => {
53355
53558
  const clamped = clampDraft(nextValue);
53356
53559
  const source = valueRef.current;
@@ -53360,7 +53563,7 @@ function ColorGradingSliderControl({
53360
53563
  },
53361
53564
  [clampDraft, scale]
53362
53565
  );
53363
- const commitDraft = useCallback80(
53566
+ const commitDraft = useCallback81(
53364
53567
  (nextValue) => {
53365
53568
  const clamped = setLocalDraft(nextValue);
53366
53569
  if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
@@ -53372,7 +53575,7 @@ function ColorGradingSliderControl({
53372
53575
  },
53373
53576
  [label, onCommit, setLocalDraft, track2]
53374
53577
  );
53375
- const scheduleCommit = useCallback80(
53578
+ const scheduleCommit = useCallback81(
53376
53579
  (nextValue) => {
53377
53580
  const clamped = setLocalDraft(nextValue);
53378
53581
  if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
@@ -53384,7 +53587,7 @@ function ColorGradingSliderControl({
53384
53587
  );
53385
53588
  const draft = draftState?.source === value ? draftState.value : value;
53386
53589
  const inputValue = inputDraft?.source === value ? inputDraft.value : formatNumericInput(draft, scale);
53387
- const commitInputDraft = useCallback80(() => {
53590
+ const commitInputDraft = useCallback81(() => {
53388
53591
  const parsed = parseNumericInput(inputValue, scale);
53389
53592
  if (parsed === null) {
53390
53593
  interactionChangedRef.current = false;
@@ -53393,7 +53596,7 @@ function ColorGradingSliderControl({
53393
53596
  }
53394
53597
  commitDraft(parsed);
53395
53598
  }, [commitDraft, inputValue, scale]);
53396
- const nudge = useCallback80(
53599
+ const nudge = useCallback81(
53397
53600
  (direction) => {
53398
53601
  interactionChangedRef.current = true;
53399
53602
  commitDraft(draft + step * direction);
@@ -54014,7 +54217,7 @@ function ColorGradingControls({
54014
54217
  }
54015
54218
 
54016
54219
  // src/components/editor/useColorGradingController.ts
54017
- import { useCallback as useCallback82, useEffect as useEffect75, useMemo as useMemo52, useRef as useRef88, useState as useState65 } from "react";
54220
+ import { useCallback as useCallback83, useEffect as useEffect75, useMemo as useMemo52, useRef as useRef88, useState as useState65 } from "react";
54018
54221
  import {
54019
54222
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR3,
54020
54223
  hasHfColorGradingAuthoredValues,
@@ -54024,7 +54227,7 @@ import {
54024
54227
  } from "@hyperframes/core/color-grading";
54025
54228
 
54026
54229
  // src/components/editor/useColorGradingPreviews.ts
54027
- import { useCallback as useCallback81, useEffect as useEffect74, useRef as useRef87, useState as useState64 } from "react";
54230
+ import { useCallback as useCallback82, useEffect as useEffect74, useRef as useRef87, useState as useState64 } from "react";
54028
54231
  import {
54029
54232
  HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS,
54030
54233
  HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS,
@@ -54199,14 +54402,14 @@ function useColorGradingPreviews({
54199
54402
  request,
54200
54403
  target
54201
54404
  ]);
54202
- const stopAnimatedPreview = useCallback81(() => {
54405
+ const stopAnimatedPreview = useCallback82(() => {
54203
54406
  const session = animatedRef.current;
54204
54407
  if (!session) return;
54205
54408
  animatedRef.current = null;
54206
54409
  if (session.timer !== null) window.clearTimeout(session.timer);
54207
54410
  session.stopPlayback();
54208
54411
  }, []);
54209
- const previewColorGrading = useCallback81(
54412
+ const previewColorGrading = useCallback82(
54210
54413
  (next, options) => {
54211
54414
  stopAnimatedPreview();
54212
54415
  postColorGrading(next ?? gradingRef.current);
@@ -54253,7 +54456,7 @@ function useColorGradingPreviews({
54253
54456
  [gradingRef, postColorGrading, previewIframeRef, stopAnimatedPreview, target]
54254
54457
  );
54255
54458
  useEffect74(() => stopAnimatedPreview, [identityKey, stopAnimatedPreview]);
54256
- const requestPreviews = useCallback81(
54459
+ const requestPreviews = useCallback82(
54257
54460
  (kind, effects) => {
54258
54461
  setRequest((current) => ({
54259
54462
  kind,
@@ -54263,12 +54466,12 @@ function useColorGradingPreviews({
54263
54466
  },
54264
54467
  []
54265
54468
  );
54266
- const requestPresetPreviews = useCallback81(() => requestPreviews("presets"), [requestPreviews]);
54267
- const requestEffectPreviews = useCallback81(
54469
+ const requestPresetPreviews = useCallback82(() => requestPreviews("presets"), [requestPreviews]);
54470
+ const requestEffectPreviews = useCallback82(
54268
54471
  (effects) => requestPreviews("effects", effects),
54269
54472
  [requestPreviews]
54270
54473
  );
54271
- const captureGradedFrame = useCallback81(
54474
+ const captureGradedFrame = useCallback82(
54272
54475
  async ({
54273
54476
  grading: requestedGrading = gradingRef.current
54274
54477
  } = {}) => {
@@ -54433,7 +54636,7 @@ function useColorGradingController({
54433
54636
  }),
54434
54637
  [element.hfId, element.id, element.selector, element.selectorIndex]
54435
54638
  );
54436
- const refreshRuntimeStatus = useCallback82(() => {
54639
+ const refreshRuntimeStatus = useCallback83(() => {
54437
54640
  setRuntimeStatus(readRuntimeColorGradingStatus(previewIframeRef?.current, target));
54438
54641
  }, [previewIframeRef, target]);
54439
54642
  useEffect75(() => {
@@ -54462,11 +54665,11 @@ function useColorGradingController({
54462
54665
  });
54463
54666
  return () => controller.abort();
54464
54667
  }, [projectId, selectedAssetPath]);
54465
- const clearStatusTimers = useCallback82(() => {
54668
+ const clearStatusTimers = useCallback83(() => {
54466
54669
  for (const timer of statusTimersRef.current) clearTimeout(timer);
54467
54670
  statusTimersRef.current = [];
54468
54671
  }, []);
54469
- const scheduleRuntimeStatusRefresh = useCallback82(() => {
54672
+ const scheduleRuntimeStatusRefresh = useCallback83(() => {
54470
54673
  clearStatusTimers();
54471
54674
  statusTimersRef.current = RUNTIME_STATUS_REFRESH_DELAYS.map(
54472
54675
  (delay2) => window.setTimeout(refreshRuntimeStatus, delay2)
@@ -54475,7 +54678,7 @@ function useColorGradingController({
54475
54678
  useEffect75(() => {
54476
54679
  refreshRuntimeStatus();
54477
54680
  }, [refreshRuntimeStatus]);
54478
- const persistColorGradingValue = useCallback82(
54681
+ const persistColorGradingValue = useCallback83(
54479
54682
  (value, attemptedGrading, attemptIdentityKey, isLatestAttempt, setAttributeLive) => {
54480
54683
  const applySettled = (ok) => {
54481
54684
  if (identityKeyRef.current !== attemptIdentityKey) return;
@@ -54499,7 +54702,7 @@ function useColorGradingController({
54499
54702
  },
54500
54703
  []
54501
54704
  );
54502
- const flushPendingPersist = useCallback82(() => {
54705
+ const flushPendingPersist = useCallback83(() => {
54503
54706
  if (persistTimerRef.current) {
54504
54707
  clearTimeout(persistTimerRef.current);
54505
54708
  persistTimerRef.current = null;
@@ -54527,7 +54730,7 @@ function useColorGradingController({
54527
54730
  void flushPendingPersist();
54528
54731
  };
54529
54732
  }, [clearStatusTimers, flushPendingPersist]);
54530
- const postColorGrading = useCallback82(
54733
+ const postColorGrading = useCallback83(
54531
54734
  (nextGrading) => {
54532
54735
  postRuntimeControlMessage(previewIframeRef?.current?.contentWindow, "set-color-grading", {
54533
54736
  target,
@@ -54544,7 +54747,7 @@ function useColorGradingController({
54544
54747
  previewIframeRef,
54545
54748
  postColorGrading
54546
54749
  });
54547
- const postCompare = useCallback82(
54750
+ const postCompare = useCallback83(
54548
54751
  (enabled) => {
54549
54752
  postRuntimeControlMessage(
54550
54753
  previewIframeRef?.current?.contentWindow,
@@ -54596,7 +54799,7 @@ function useColorGradingController({
54596
54799
  },
54597
54800
  [postCompare]
54598
54801
  );
54599
- const commitColorGrading = useCallback82(
54802
+ const commitColorGrading = useCallback83(
54600
54803
  (nextGrading) => {
54601
54804
  latestGradingRef.current = nextGrading;
54602
54805
  setGrading(nextGrading);
@@ -54638,7 +54841,7 @@ function useColorGradingController({
54638
54841
  scheduleRuntimeStatusRefresh
54639
54842
  ]
54640
54843
  );
54641
- const commitCompare = useCallback82(
54844
+ const commitCompare = useCallback83(
54642
54845
  (enabled) => {
54643
54846
  const nextEnabled = enabled && isHfColorGradingActive(grading);
54644
54847
  setCompareEnabled(nextEnabled);
@@ -54648,7 +54851,7 @@ function useColorGradingController({
54648
54851
  },
54649
54852
  [grading, postColorGrading, postCompare, scheduleRuntimeStatusRefresh]
54650
54853
  );
54651
- const applyToScope = useCallback82(async () => {
54854
+ const applyToScope = useCallback83(async () => {
54652
54855
  if (!onApplyScope || applyBusy) return;
54653
54856
  setApplyBusy(true);
54654
54857
  try {
@@ -54897,7 +55100,7 @@ function ColorGradingSection({
54897
55100
  import { useEffect as useEffect81, useRef as useRef93, useState as useState74 } from "react";
54898
55101
 
54899
55102
  // src/components/editor/propertyPanelColor.tsx
54900
- import { useCallback as useCallback83, useEffect as useEffect76, useLayoutEffect as useLayoutEffect12, useRef as useRef89, useState as useState66 } from "react";
55103
+ import { useCallback as useCallback84, useEffect as useEffect76, useLayoutEffect as useLayoutEffect12, useRef as useRef89, useState as useState66 } from "react";
54901
55104
  import { createPortal as createPortal12 } from "react-dom";
54902
55105
  import { jsx as jsx127, jsxs as jsxs93 } from "react/jsx-runtime";
54903
55106
  var COLOR_PICKER_SIZE = { width: 292, height: 386 };
@@ -55032,13 +55235,13 @@ function ColorField({
55032
55235
  const saturationPercent = Math.round(hsv.saturation * 100);
55033
55236
  const brightnessPercent = Math.round(hsv.value * 100);
55034
55237
  const alphaPercent = Math.round(draftColor.alpha * 100);
55035
- const updateColorDraft = useCallback83((nextValue, source) => {
55238
+ const updateColorDraft = useCallback84((nextValue, source) => {
55036
55239
  const nextColor = parseCssColor(nextValue);
55037
55240
  if (!nextColor) return;
55038
55241
  setDraftColor(nextColor);
55039
55242
  if (source === "picker") setHexDraft(toHexColor(nextColor).toUpperCase());
55040
55243
  }, []);
55041
- const resolveColorGestureValue = useCallback83((nextValue) => {
55244
+ const resolveColorGestureValue = useCallback84((nextValue) => {
55042
55245
  const source = nextValue.startsWith("#") ? "hex" : "picker";
55043
55246
  if (source === "hex" && !/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(nextValue)) return null;
55044
55247
  const nextColor = parseCssColor(nextValue);
@@ -55051,7 +55254,7 @@ function ColorField({
55051
55254
  })
55052
55255
  };
55053
55256
  }, []);
55054
- const persistColorValue = useCallback83(
55257
+ const persistColorValue = useCallback84(
55055
55258
  (nextValue) => {
55056
55259
  if (nextValue !== value) track2("color", label);
55057
55260
  onCommit(nextValue);
@@ -55081,7 +55284,7 @@ function ColorField({
55081
55284
  setDraftColor(nextColor);
55082
55285
  setHexDraft(toHexColor(nextColor).toUpperCase());
55083
55286
  }, [value]);
55084
- const updatePanelPosition = useCallback83(() => {
55287
+ const updatePanelPosition = useCallback84(() => {
55085
55288
  const anchor2 = buttonRef.current?.getBoundingClientRect();
55086
55289
  if (!anchor2) return;
55087
55290
  const measured = panelRef.current?.getBoundingClientRect();
@@ -57227,7 +57430,7 @@ function GradientField({
57227
57430
  }
57228
57431
 
57229
57432
  // src/components/editor/BorderRadiusEditor.tsx
57230
- import { useCallback as useCallback84, useState as useState72 } from "react";
57433
+ import { useCallback as useCallback85, useState as useState72 } from "react";
57231
57434
  import { jsx as jsx135, jsxs as jsxs100 } from "react/jsx-runtime";
57232
57435
  var PREVIEW_W = 72;
57233
57436
  var PREVIEW_H = 52;
@@ -57254,7 +57457,7 @@ function BorderRadiusEditor({
57254
57457
  const sTR = scaleRadius(tr, maxVal);
57255
57458
  const sBR = scaleRadius(br, maxVal);
57256
57459
  const sBL = scaleRadius(bl, maxVal);
57257
- const handleCornerCommit = useCallback84(
57460
+ const handleCornerCommit = useCallback85(
57258
57461
  (corner, raw) => {
57259
57462
  const v = parseNumericValue(raw) ?? 0;
57260
57463
  if (linked) {
@@ -57265,7 +57468,7 @@ function BorderRadiusEditor({
57265
57468
  },
57266
57469
  [linked, onCommit]
57267
57470
  );
57268
- const handleToggleLinked = useCallback84(() => {
57471
+ const handleToggleLinked = useCallback85(() => {
57269
57472
  if (!linked && !uniform) {
57270
57473
  onCommit("all", tl);
57271
57474
  }
@@ -58304,11 +58507,11 @@ function TextSection({
58304
58507
  import { memo as memo38 } from "react";
58305
58508
 
58306
58509
  // src/components/editor/GsapAnimationList.tsx
58307
- import { useCallback as useCallback87, useState as useState76 } from "react";
58510
+ import { useCallback as useCallback88, useState as useState76 } from "react";
58308
58511
  import { useShallow } from "zustand/react/shallow";
58309
58512
 
58310
58513
  // src/components/editor/AnimationCard.tsx
58311
- import { memo as memo37, useCallback as useCallback86, useEffect as useEffect82, useMemo as useMemo57, useRef as useRef94, useState as useState75 } from "react";
58514
+ import { memo as memo37, useCallback as useCallback87, useEffect as useEffect82, useMemo as useMemo57, useRef as useRef94, useState as useState75 } from "react";
58312
58515
  import { SUPPORTED_EASES, SUPPORTED_PROPS } from "@hyperframes/core/gsap-constants";
58313
58516
 
58314
58517
  // src/components/editor/gsapAnimationHelpers.ts
@@ -58346,7 +58549,7 @@ function buildTweenSummary(animation) {
58346
58549
  }
58347
58550
 
58348
58551
  // src/components/editor/ArcPathControls.tsx
58349
- import { memo as memo36, useCallback as useCallback85 } from "react";
58552
+ import { memo as memo36, useCallback as useCallback86 } from "react";
58350
58553
 
58351
58554
  // src/components/editor/panelTokens.ts
58352
58555
  var P = {
@@ -58366,10 +58569,10 @@ var ArcPathControls = memo36(function ArcPathControls2({
58366
58569
  onToggleAutoRotate,
58367
58570
  disabled
58368
58571
  }) {
58369
- const handleToggle = useCallback85(() => {
58572
+ const handleToggle = useCallback86(() => {
58370
58573
  onToggle(!arcPath.enabled);
58371
58574
  }, [arcPath.enabled, onToggle]);
58372
- const handleAutoRotate = useCallback85(() => {
58575
+ const handleAutoRotate = useCallback86(() => {
58373
58576
  onToggleAutoRotate(!arcPath.autoRotate);
58374
58577
  }, [arcPath.autoRotate, onToggleAutoRotate]);
58375
58578
  if (segmentCount < 1) {
@@ -58867,7 +59070,7 @@ var AnimationCard = memo37(function AnimationCard2({
58867
59070
  () => SUPPORTED_PROPS.filter((p) => !usedFromProps.has(p) && !BOOLEAN_PROPS.has(p)),
58868
59071
  [usedFromProps]
58869
59072
  );
58870
- const commitProperty = useCallback86(
59073
+ const commitProperty = useCallback87(
58871
59074
  (prop, raw) => {
58872
59075
  const value = parseNumericOrString(raw);
58873
59076
  onUpdateProperty(animation.id, prop, value);
@@ -58875,13 +59078,13 @@ var AnimationCard = memo37(function AnimationCard2({
58875
59078
  },
58876
59079
  [animation.id, onUpdateProperty, onLivePreviewEnd]
58877
59080
  );
58878
- const scrubProperty = useCallback86(
59081
+ const scrubProperty = useCallback87(
58879
59082
  (prop, raw) => {
58880
59083
  onLivePreview?.(prop, parseNumericOrString(raw));
58881
59084
  },
58882
59085
  [onLivePreview]
58883
59086
  );
58884
- const commitFromProperty = useCallback86(
59087
+ const commitFromProperty = useCallback87(
58885
59088
  (prop, raw) => {
58886
59089
  const value = parseNumericOrString(raw);
58887
59090
  onUpdateFromProperty?.(animation.id, prop, value);
@@ -58889,7 +59092,7 @@ var AnimationCard = memo37(function AnimationCard2({
58889
59092
  },
58890
59093
  [animation.id, onUpdateFromProperty, onLivePreviewEnd]
58891
59094
  );
58892
- const commitDuration = useCallback86(
59095
+ const commitDuration = useCallback87(
58893
59096
  (raw) => {
58894
59097
  const num = Number(raw);
58895
59098
  if (Number.isFinite(num) && num >= 0)
@@ -58897,7 +59100,7 @@ var AnimationCard = memo37(function AnimationCard2({
58897
59100
  },
58898
59101
  [animation.id, onUpdateMeta]
58899
59102
  );
58900
- const commitPosition = useCallback86(
59103
+ const commitPosition = useCallback87(
58901
59104
  (raw) => {
58902
59105
  const num = Number(raw);
58903
59106
  if (Number.isFinite(num) && num >= 0)
@@ -59352,7 +59555,7 @@ function GsapAnimationList({
59352
59555
  timelineSessionEpoch,
59353
59556
  selectedElementId
59354
59557
  }) && animations.some((animation) => animation.id === focusedEaseSegment.animationId) ? focusedEaseSegment : null;
59355
- const consumeFocusedEaseSegment = useCallback87(() => {
59558
+ const consumeFocusedEaseSegment = useCallback88(() => {
59356
59559
  if (focusedHere) {
59357
59560
  usePlayerStore.getState().clearFocusedEaseSegment(focusedHere.nonce);
59358
59561
  }
@@ -62271,7 +62474,7 @@ import {
62271
62474
  } from "@hyperframes/core/audio-automation";
62272
62475
 
62273
62476
  // src/components/editor/propertyPanelFxSection.tsx
62274
- import { useCallback as useCallback90, useEffect as useEffect90, useMemo as useMemo59, useRef as useRef99, useState as useState86 } from "react";
62477
+ import { useCallback as useCallback91, useEffect as useEffect90, useMemo as useMemo59, useRef as useRef99, useState as useState86 } from "react";
62275
62478
 
62276
62479
  // src/components/editor/audioFxRevealTarget.ts
62277
62480
  import { parseAutomationTarget as parseAutomationTarget2 } from "@hyperframes/core/audio-automation";
@@ -62351,7 +62554,7 @@ import { DEFAULT_CARVE as DEFAULT_CARVE2 } from "@hyperframes/core/audio-carve";
62351
62554
  import { fxAutomationTarget as fxAutomationTarget3 } from "@hyperframes/core/audio-automation";
62352
62555
 
62353
62556
  // src/components/editor/propertyPanelFxControls.tsx
62354
- import { useCallback as useCallback88, useEffect as useEffect88, useRef as useRef98, useState as useState83 } from "react";
62557
+ import { useCallback as useCallback89, useEffect as useEffect88, useRef as useRef98, useState as useState83 } from "react";
62355
62558
  import { jsx as jsx160, jsxs as jsxs123 } from "react/jsx-runtime";
62356
62559
  function toSlider(p, value) {
62357
62560
  if (p.scale !== "log" || p.min <= 0) return value;
@@ -62410,7 +62613,7 @@ function FxParamRow({
62410
62613
  useEffect88(() => {
62411
62614
  setPending(null);
62412
62615
  }, [value]);
62413
- const handleNumber = useCallback88(
62616
+ const handleNumber = useCallback89(
62414
62617
  (raw) => {
62415
62618
  const p = param;
62416
62619
  const next = Math.min(p.max, Math.max(p.min, raw));
@@ -62421,7 +62624,7 @@ function FxParamRow({
62421
62624
  },
62422
62625
  [param, onChange]
62423
62626
  );
62424
- const commit2 = useCallback88(() => {
62627
+ const commit2 = useCallback89(() => {
62425
62628
  setDragging(false);
62426
62629
  if (!edited.current) return;
62427
62630
  edited.current = false;
@@ -62535,11 +62738,11 @@ function FxParams({
62535
62738
  onAutomate,
62536
62739
  onRemoveAutomation
62537
62740
  }) {
62538
- const set = useCallback88(
62741
+ const set = useCallback89(
62539
62742
  (key2, value) => onChange({ ...params, [key2]: value }),
62540
62743
  [params, onChange]
62541
62744
  );
62542
- const commit2 = useCallback88(
62745
+ const commit2 = useCallback89(
62543
62746
  (key2, value) => onCommit?.({ ...params, [key2]: value }),
62544
62747
  [params, onCommit]
62545
62748
  );
@@ -63071,7 +63274,7 @@ function CarveAnalysis({
63071
63274
  }
63072
63275
 
63073
63276
  // src/components/editor/propertyPanelFxEqModule.tsx
63074
- import { useCallback as useCallback89, useEffect as useEffect89, useState as useState84 } from "react";
63277
+ import { useCallback as useCallback90, useEffect as useEffect89, useState as useState84 } from "react";
63075
63278
  import {
63076
63279
  audioEqSummary,
63077
63280
  HF_AUDIO_EQ_RANGE_DB
@@ -63162,8 +63365,8 @@ function FxEqModule({
63162
63365
  onCommit,
63163
63366
  onRemove
63164
63367
  }) {
63165
- const preview = useCallback89((name, gain) => onPreview(name, gain), [onPreview]);
63166
- const commit2 = useCallback89((name, gain) => onCommit(name, gain), [onCommit]);
63368
+ const preview = useCallback90((name, gain) => onPreview(name, gain), [onPreview]);
63369
+ const commit2 = useCallback90((name, gain) => onCommit(name, gain), [onCommit]);
63167
63370
  return /* @__PURE__ */ jsxs125(
63168
63371
  "div",
63169
63372
  {
@@ -64247,14 +64450,14 @@ function FxSection({
64247
64450
  const [adding, setAdding] = useState86(false);
64248
64451
  const [picking, setPicking] = useState86(false);
64249
64452
  const [openNode, setOpenNode] = useState86(0);
64250
- const mutate = useCallback90(
64453
+ const mutate = useCallback91(
64251
64454
  (nodes, input, control = "button") => {
64252
64455
  onChainChange({ ...chain, nodes });
64253
64456
  if (input) trackInput(control, input);
64254
64457
  },
64255
64458
  [chain, onChainChange, trackInput]
64256
64459
  );
64257
- const previewNode = useCallback90(
64460
+ const previewNode = useCallback91(
64258
64461
  (index, params) => onChainPreview?.({
64259
64462
  ...chain,
64260
64463
  nodes: chain.nodes.map((n, i) => i === index ? { ...n, params } : n)
@@ -64266,7 +64469,7 @@ function FxSection({
64266
64469
  onChainPreview,
64267
64470
  onAuditionTransport
64268
64471
  );
64269
- const applyPreset = useCallback90(
64472
+ const applyPreset = useCallback91(
64270
64473
  (id) => {
64271
64474
  const next = applyPresetToChain(storedChain(), id, trackKind);
64272
64475
  if (!next) return;
@@ -64277,7 +64480,7 @@ function FxSection({
64277
64480
  },
64278
64481
  [storedChain, mutate, clearAudition, trackKind]
64279
64482
  );
64280
- const addJob = useCallback90(
64483
+ const addJob = useCallback91(
64281
64484
  (job) => {
64282
64485
  clearAudition();
64283
64486
  trackNodeAdded(job.type, "job", job.id, { trackKind });
@@ -64287,7 +64490,7 @@ function FxSection({
64287
64490
  },
64288
64491
  [chain, mutate, clearAudition, trackKind]
64289
64492
  );
64290
- const addEffect = useCallback90(
64493
+ const addEffect = useCallback91(
64291
64494
  (type) => {
64292
64495
  clearAudition();
64293
64496
  trackNodeAdded(type, "effect", null, { trackKind });
@@ -64297,11 +64500,11 @@ function FxSection({
64297
64500
  },
64298
64501
  [chain, mutate, clearAudition, trackKind]
64299
64502
  );
64300
- const updateNode = useCallback90(
64503
+ const updateNode = useCallback91(
64301
64504
  (index, patch) => mutate(chain.nodes.map((n, i) => i === index ? { ...n, ...patch } : n)),
64302
64505
  [chain.nodes, mutate]
64303
64506
  );
64304
- const setRunAmount = useCallback90(
64507
+ const setRunAmount = useCallback91(
64305
64508
  (items, amount, persist2 = true) => {
64306
64509
  const slots = new Set(items.map((item) => item.i));
64307
64510
  const next = {
@@ -64313,7 +64516,7 @@ function FxSection({
64313
64516
  },
64314
64517
  [chain, mutate, onChainPreview]
64315
64518
  );
64316
- const removeRun = useCallback90(
64519
+ const removeRun = useCallback91(
64317
64520
  (items, presetId) => {
64318
64521
  const ids = items.map(({ node }) => node.id).filter((id) => Boolean(id));
64319
64522
  if (ids.length > 0 || presetId) onRemoveNodesAutomation?.(ids, presetId);
@@ -64327,7 +64530,7 @@ function FxSection({
64327
64530
  },
64328
64531
  [chain.nodes, mutate, onRemoveNodesAutomation, trackKind]
64329
64532
  );
64330
- const removeNode = useCallback90(
64533
+ const removeNode = useCallback91(
64331
64534
  (index) => {
64332
64535
  const removed = chain.nodes[index];
64333
64536
  const removedId = removed?.id;
@@ -64393,7 +64596,7 @@ function FxSection({
64393
64596
  setPendingReveal(null);
64394
64597
  }
64395
64598
  }, [openNode, openEq, carveOpen, collapsedRuns, pendingReveal]);
64396
- const addEq = useCallback90(() => {
64599
+ const addEq = useCallback91(() => {
64397
64600
  clearAudition();
64398
64601
  const { chain: next, eqId } = addAudioEq(chain);
64399
64602
  trackNodeAdded("eq", "eq", null, { trackKind });
@@ -64401,15 +64604,15 @@ function FxSection({
64401
64604
  setOpenEq(eqId);
64402
64605
  setAdding(false);
64403
64606
  }, [chain, mutate, clearAudition, trackKind]);
64404
- const previewEqBand = useCallback90(
64607
+ const previewEqBand = useCallback91(
64405
64608
  (eqId, band, gain) => onChainPreview?.(setAudioEqBandGain(chain, eqId, band, gain)),
64406
64609
  [chain, onChainPreview]
64407
64610
  );
64408
- const commitEqBand = useCallback90(
64611
+ const commitEqBand = useCallback91(
64409
64612
  (eqId, band, gain) => mutate(setAudioEqBandGain(chain, eqId, band, gain).nodes, "eq-gain", "slider"),
64410
64613
  [chain, mutate]
64411
64614
  );
64412
- const removeEq = useCallback90(
64615
+ const removeEq = useCallback91(
64413
64616
  (eqId) => {
64414
64617
  const ids = chain.nodes.filter((node) => node.fromEq === eqId).map((node) => node.id).filter((id) => Boolean(id));
64415
64618
  if (ids.length > 0) onRemoveNodesAutomation?.(ids);
@@ -64417,7 +64620,7 @@ function FxSection({
64417
64620
  },
64418
64621
  [chain, mutate, onRemoveNodesAutomation]
64419
64622
  );
64420
- const moveNode = useCallback90(
64623
+ const moveNode = useCallback91(
64421
64624
  (index, delta) => {
64422
64625
  const target = index + delta;
64423
64626
  if (target < 0 || target >= chain.nodes.length) return;
@@ -64430,7 +64633,7 @@ function FxSection({
64430
64633
  },
64431
64634
  [chain.nodes, mutate, trackKind]
64432
64635
  );
64433
- const closeMenus = useCallback90(
64636
+ const closeMenus = useCallback91(
64434
64637
  (event) => {
64435
64638
  if (event.key !== "Escape" || !adding && !picking) return;
64436
64639
  event.stopPropagation();
@@ -70200,11 +70403,11 @@ var PropertyPanel = memo39(function PropertyPanel2(props) {
70200
70403
  });
70201
70404
 
70202
70405
  // src/components/editor/FileTree.tsx
70203
- import { memo as memo41, useState as useState103, useCallback as useCallback92, useMemo as useMemo67, useRef as useRef114 } from "react";
70406
+ import { memo as memo41, useState as useState103, useCallback as useCallback93, useMemo as useMemo67, useRef as useRef114 } from "react";
70204
70407
  import { Plus as Plus2, FolderSimplePlus as FolderSimplePlus2 } from "@phosphor-icons/react";
70205
70408
 
70206
70409
  // src/components/editor/FileTreeNodes.tsx
70207
- import { memo as memo40, useState as useState102, useCallback as useCallback91, useMemo as useMemo66, useRef as useRef113, useEffect as useEffect104 } from "react";
70410
+ import { memo as memo40, useState as useState102, useCallback as useCallback92, useMemo as useMemo66, useRef as useRef113, useEffect as useEffect104 } from "react";
70208
70411
  import {
70209
70412
  PencilSimple,
70210
70413
  Copy,
@@ -70706,7 +70909,7 @@ var TreeFolder = memo40(function TreeFolder2({
70706
70909
  lintFindingsByFile
70707
70910
  }) {
70708
70911
  const [isOpen, setIsOpen] = useState102(defaultOpen);
70709
- const toggle = useCallback91(() => setIsOpen((v) => !v), []);
70912
+ const toggle = useCallback92(() => setIsOpen((v) => !v), []);
70710
70913
  const children = useMemo66(() => sortChildren(node.children), [node.children]);
70711
70914
  const Chevron = isOpen ? ChevronDown : ChevronRight;
70712
70915
  const isDragOver = dragOverFolder === node.fullPath;
@@ -70846,7 +71049,7 @@ var FileTree = memo41(function FileTree2({
70846
71049
  const [dragOverFolder, setDragOverFolder] = useState103(null);
70847
71050
  const dragSourceRef = useRef114(null);
70848
71051
  const hasFileOps = !!(onCreateFile || onCreateFolder || onDeleteFile || onRenameFile || onDuplicateFile);
70849
- const handleContextMenu = useCallback92(
71052
+ const handleContextMenu = useCallback93(
70850
71053
  (e, path, isFolder) => {
70851
71054
  if (!hasFileOps) return;
70852
71055
  e.preventDefault();
@@ -70854,8 +71057,8 @@ var FileTree = memo41(function FileTree2({
70854
71057
  },
70855
71058
  [hasFileOps]
70856
71059
  );
70857
- const handleCloseContextMenu = useCallback92(() => setContextMenu(null), []);
70858
- const handleNewFile = useCallback92(
71060
+ const handleCloseContextMenu = useCallback93(() => setContextMenu(null), []);
71061
+ const handleNewFile = useCallback93(
70859
71062
  (parentPath) => {
70860
71063
  setInlineInput({
70861
71064
  parentPath,
@@ -70870,7 +71073,7 @@ var FileTree = memo41(function FileTree2({
70870
71073
  },
70871
71074
  [onCreateFile]
70872
71075
  );
70873
- const handleNewFolder = useCallback92(
71076
+ const handleNewFolder = useCallback93(
70874
71077
  (parentPath) => {
70875
71078
  setInlineInput({
70876
71079
  parentPath,
@@ -70885,7 +71088,7 @@ var FileTree = memo41(function FileTree2({
70885
71088
  },
70886
71089
  [onCreateFolder]
70887
71090
  );
70888
- const handleRename = useCallback92(
71091
+ const handleRename = useCallback93(
70889
71092
  (path) => {
70890
71093
  const name = path.includes("/") ? path.slice(path.lastIndexOf("/") + 1) : path;
70891
71094
  const parentPath = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
@@ -70906,13 +71109,13 @@ var FileTree = memo41(function FileTree2({
70906
71109
  },
70907
71110
  [onRenameFile]
70908
71111
  );
70909
- const handleDuplicate = useCallback92(
71112
+ const handleDuplicate = useCallback93(
70910
71113
  (path) => {
70911
71114
  onDuplicateFile?.(path);
70912
71115
  },
70913
71116
  [onDuplicateFile]
70914
71117
  );
70915
- const handleDelete2 = useCallback92(
71118
+ const handleDelete2 = useCallback93(
70916
71119
  (path) => {
70917
71120
  setDeleteTarget({
70918
71121
  path,
@@ -70923,24 +71126,24 @@ var FileTree = memo41(function FileTree2({
70923
71126
  },
70924
71127
  [contextMenu]
70925
71128
  );
70926
- const handleDeleteConfirm = useCallback92(() => {
71129
+ const handleDeleteConfirm = useCallback93(() => {
70927
71130
  if (deleteTarget) {
70928
71131
  onDeleteFile?.(deleteTarget.path);
70929
71132
  setDeleteTarget(null);
70930
71133
  }
70931
71134
  }, [deleteTarget, onDeleteFile]);
70932
- const handleDeleteCancel = useCallback92(() => {
71135
+ const handleDeleteCancel = useCallback93(() => {
70933
71136
  setDeleteTarget(null);
70934
71137
  }, []);
70935
- const handleDragStart = useCallback92((e, path) => {
71138
+ const handleDragStart = useCallback93((e, path) => {
70936
71139
  dragSourceRef.current = path;
70937
71140
  e.dataTransfer.effectAllowed = "move";
70938
71141
  e.dataTransfer.setData("text/plain", path);
70939
71142
  }, []);
70940
- const handleDragOver = useCallback92((_e, folderPath) => {
71143
+ const handleDragOver = useCallback93((_e, folderPath) => {
70941
71144
  setDragOverFolder(folderPath);
70942
71145
  }, []);
70943
- const handleDrop = useCallback92(
71146
+ const handleDrop = useCallback93(
70944
71147
  (e, folderPath) => {
70945
71148
  if (e.dataTransfer.files.length > 0 && !dragSourceRef.current) {
70946
71149
  e.preventDefault();
@@ -70963,10 +71166,10 @@ var FileTree = memo41(function FileTree2({
70963
71166
  },
70964
71167
  [onMoveFile, onImportFiles]
70965
71168
  );
70966
- const handleDragLeave = useCallback92(() => {
71169
+ const handleDragLeave = useCallback93(() => {
70967
71170
  setDragOverFolder(null);
70968
71171
  }, []);
70969
- const handleRootContextMenu = useCallback92(
71172
+ const handleRootContextMenu = useCallback93(
70970
71173
  (e) => {
70971
71174
  if (!hasFileOps) return;
70972
71175
  if (e.target === e.currentTarget) {
@@ -71181,7 +71384,7 @@ function useToolbarSession(session) {
71181
71384
  }
71182
71385
 
71183
71386
  // src/hooks/useRightPanelIntents.ts
71184
- import { useCallback as useCallback93, useEffect as useEffect105, useRef as useRef116 } from "react";
71387
+ import { useCallback as useCallback94, useEffect as useEffect105, useRef as useRef116 } from "react";
71185
71388
  function useSlideshowDockPanel(isSlideshowComposition) {
71186
71389
  const controller = useDockLayoutStore((state) => state.controller);
71187
71390
  const wasSlideshow = useRef116(false);
@@ -71203,7 +71406,7 @@ function useBlockParamsDismissal({
71203
71406
  }, [hasBlockParams, designVisible, onDismiss]);
71204
71407
  }
71205
71408
  function useDismissingTabSetter(setTab, dismissBlockParams) {
71206
- return useCallback93(
71409
+ return useCallback94(
71207
71410
  (tab) => {
71208
71411
  if (tab !== "block-params") dismissBlockParams();
71209
71412
  setTab(tab);
@@ -71229,7 +71432,7 @@ function useRightPanelIntent() {
71229
71432
  }
71230
71433
 
71231
71434
  // src/components/renders/useRenderQueue.ts
71232
- import { useState as useState105, useEffect as useEffect107, useCallback as useCallback95, useRef as useRef118, useMemo as useMemo69 } from "react";
71435
+ import { useState as useState105, useEffect as useEffect107, useCallback as useCallback96, useRef as useRef118, useMemo as useMemo69 } from "react";
71233
71436
 
71234
71437
  // src/components/renders/serverError.ts
71235
71438
  async function readServerError(res) {
@@ -71247,7 +71450,7 @@ async function readServerError(res) {
71247
71450
  }
71248
71451
 
71249
71452
  // src/components/renders/useFfmpegStatus.ts
71250
- import { useCallback as useCallback94, useEffect as useEffect106, useRef as useRef117, useState as useState104 } from "react";
71453
+ import { useCallback as useCallback95, useEffect as useEffect106, useRef as useRef117, useState as useState104 } from "react";
71251
71454
  function ffmpegInstallMessage(status) {
71252
71455
  const title = status?.title ?? "FFmpeg not found";
71253
71456
  const remedy = status?.command ?? status?.hint;
@@ -71285,7 +71488,7 @@ function useFfmpegStatus() {
71285
71488
  mounted.current = false;
71286
71489
  };
71287
71490
  }, []);
71288
- const run = useCallback94(async () => {
71491
+ const run = useCallback95(async () => {
71289
71492
  setChecking(true);
71290
71493
  const next = await probe();
71291
71494
  cached4 = next;
@@ -71297,7 +71500,7 @@ function useFfmpegStatus() {
71297
71500
  if (cached4 !== null) return;
71298
71501
  void run();
71299
71502
  }, [run]);
71300
- const recheck = useCallback94(() => {
71503
+ const recheck = useCallback95(() => {
71301
71504
  cached4 = null;
71302
71505
  void run();
71303
71506
  }, [run]);
@@ -71480,17 +71683,17 @@ function useRenderQueue(projectId, activeCompPathRef) {
71480
71683
  const activeJobRef = useRef118(null);
71481
71684
  const sessionJobs = useRef118(/* @__PURE__ */ new Map());
71482
71685
  const promptedJobIds = useRef118(/* @__PURE__ */ new Set());
71483
- const addSessionJob = useCallback95((job, settings) => {
71686
+ const addSessionJob = useCallback96((job, settings) => {
71484
71687
  sessionJobs.current.set(job.id, settings);
71485
71688
  setJobs((prev) => [...prev, job]);
71486
71689
  }, []);
71487
- const closeActiveEventSource = useCallback95((jobId) => {
71690
+ const closeActiveEventSource = useCallback96((jobId) => {
71488
71691
  if (jobId && activeJobRef.current !== jobId) return;
71489
71692
  eventSourceRef.current?.close();
71490
71693
  eventSourceRef.current = null;
71491
71694
  activeJobRef.current = null;
71492
71695
  }, []);
71493
- const loadRenders = useCallback95(async () => {
71696
+ const loadRenders = useCallback96(async () => {
71494
71697
  if (!projectId) return;
71495
71698
  try {
71496
71699
  const res = await studioApiFetch(buildProjectApiPath(projectId, `/renders`));
@@ -71524,7 +71727,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
71524
71727
  useEffect107(() => {
71525
71728
  loadRenders();
71526
71729
  }, [loadRenders]);
71527
- const startRender = useCallback95(
71730
+ const startRender = useCallback96(
71528
71731
  // fallow-ignore-next-line complexity
71529
71732
  async (opts = {}) => {
71530
71733
  if (!projectId) return;
@@ -71663,7 +71866,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
71663
71866
  },
71664
71867
  [projectId, activeCompPathRef, closeActiveEventSource, addSessionJob, ffmpeg, ffmpegMissing]
71665
71868
  );
71666
- const cancelRender = useCallback95(
71869
+ const cancelRender = useCallback96(
71667
71870
  async (jobId) => {
71668
71871
  setActionError(null);
71669
71872
  closeActiveEventSource(jobId);
@@ -71690,7 +71893,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
71690
71893
  },
71691
71894
  [closeActiveEventSource, loadRenders]
71692
71895
  );
71693
- const deleteRender = useCallback95(
71896
+ const deleteRender = useCallback96(
71694
71897
  async (jobId) => {
71695
71898
  setActionError(null);
71696
71899
  closeActiveEventSource(jobId);
@@ -71708,7 +71911,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
71708
71911
  },
71709
71912
  [closeActiveEventSource]
71710
71913
  );
71711
- const clearCompleted = useCallback95(() => {
71914
+ const clearCompleted = useCallback96(() => {
71712
71915
  setJobs((prev) => {
71713
71916
  const finished = prev.filter((j) => j.status !== "rendering");
71714
71917
  if (projectId && finished.length > 0) {
@@ -71719,7 +71922,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
71719
71922
  return prev.filter((j) => j.status === "rendering");
71720
71923
  });
71721
71924
  }, [projectId]);
71722
- const dismissActionError = useCallback95(() => setActionError(null), []);
71925
+ const dismissActionError = useCallback96(() => setActionError(null), []);
71723
71926
  useEffect107(() => {
71724
71927
  for (const job of jobs) {
71725
71928
  if (job.status === "rendering" || job.status === "cancelled") continue;
@@ -71798,7 +72001,7 @@ import { useRef as useRef120, useState as useState106 } from "react";
71798
72001
  import { XIcon, WarningIcon, CheckCircleIcon, CaretRightIcon } from "@phosphor-icons/react";
71799
72002
 
71800
72003
  // src/components/ui/useDialogBehavior.ts
71801
- import { useEffect as useEffect108, useCallback as useCallback96, useRef as useRef119 } from "react";
72004
+ import { useEffect as useEffect108, useCallback as useCallback97, useRef as useRef119 } from "react";
71802
72005
  var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
71803
72006
  function useDialogBehavior({
71804
72007
  open,
@@ -71811,7 +72014,7 @@ function useDialogBehavior({
71811
72014
  canCloseRef.current = canClose;
71812
72015
  const onCloseRef = useRef119(onClose);
71813
72016
  onCloseRef.current = onClose;
71814
- const requestClose = useCallback96(() => {
72017
+ const requestClose = useCallback97(() => {
71815
72018
  const guard = canCloseRef.current;
71816
72019
  if (guard && !guard()) return;
71817
72020
  onCloseRef.current();
@@ -72156,7 +72359,7 @@ function toastSurface(isError) {
72156
72359
  }
72157
72360
 
72158
72361
  // src/components/feedback/StudioFeedbackCard.tsx
72159
- import { memo as memo42, useState as useState108, useCallback as useCallback97, useRef as useRef122, useEffect as useEffect109 } from "react";
72362
+ import { memo as memo42, useState as useState108, useCallback as useCallback98, useRef as useRef122, useEffect as useEffect109 } from "react";
72160
72363
 
72161
72364
  // src/components/feedback/projectProvenance.ts
72162
72365
  var CONFIG_FILE = "hyperframes.json";
@@ -72243,13 +72446,13 @@ var StudioFeedbackCard = memo42(function StudioFeedbackCard2() {
72243
72446
  const timerRef = useRef122(null);
72244
72447
  const stateRef = useRef122({ request, rating, step });
72245
72448
  stateRef.current = { request, rating, step };
72246
- const clearTimer = useCallback97(() => {
72449
+ const clearTimer = useCallback98(() => {
72247
72450
  if (timerRef.current) {
72248
72451
  clearTimeout(timerRef.current);
72249
72452
  timerRef.current = null;
72250
72453
  }
72251
72454
  }, []);
72252
- const close = useCallback97(
72455
+ const close = useCallback98(
72253
72456
  (via) => {
72254
72457
  const { request: req, rating: picked, step: at } = stateRef.current;
72255
72458
  if (!req) return;
@@ -72296,7 +72499,7 @@ var StudioFeedbackCard = memo42(function StudioFeedbackCard2() {
72296
72499
  useEffect109(() => {
72297
72500
  if (step === "comment") inputRef.current?.focus();
72298
72501
  }, [step]);
72299
- const submit = useCallback97(
72502
+ const submit = useCallback98(
72300
72503
  (preset) => {
72301
72504
  const req = stateRef.current.request;
72302
72505
  if (!req) return;
@@ -72323,7 +72526,7 @@ var StudioFeedbackCard = memo42(function StudioFeedbackCard2() {
72323
72526
  },
72324
72527
  [rating, comment, followUp.id, close, clearTimer]
72325
72528
  );
72326
- const pickRating = useCallback97((n) => {
72529
+ const pickRating = useCallback98((n) => {
72327
72530
  setRating(n);
72328
72531
  setFollowUp(followUpFor(n));
72329
72532
  setStep("comment");
@@ -72859,7 +73062,7 @@ function CompositionMissingBanner({ path }) {
72859
73062
  }
72860
73063
 
72861
73064
  // src/captions/hooks/useCaptionSync.ts
72862
- import { useCallback as useCallback98, useRef as useRef124 } from "react";
73065
+ import { useCallback as useCallback99, useRef as useRef124 } from "react";
72863
73066
  function buildOverrides(model) {
72864
73067
  const entries = [];
72865
73068
  let globalWordIndex = 0;
@@ -72896,7 +73099,7 @@ function useCaptionSync(projectId) {
72896
73099
  const suppressSaveRef = useRef124(false);
72897
73100
  const pendingRef = useRef124(false);
72898
73101
  const editSeqRef = useRef124(0);
72899
- const save = useCallback98(() => {
73102
+ const save = useCallback99(() => {
72900
73103
  const state = useCaptionStore.getState();
72901
73104
  if (!state.model || !state.sourceFilePath) {
72902
73105
  pendingRef.current = false;
@@ -72962,7 +73165,7 @@ function useCaptionSync(projectId) {
72962
73165
  useCaptionStore.getState().setRetrySave(null);
72963
73166
  };
72964
73167
  });
72965
- const loadOverrides = useCallback98(async () => {
73168
+ const loadOverrides = useCallback99(async () => {
72966
73169
  const state = useCaptionStore.getState();
72967
73170
  if (!state.model || !state.sourceFilePath) return;
72968
73171
  const pid = projectIdRef.current;
@@ -73025,7 +73228,7 @@ function useCaptionSync(projectId) {
73025
73228
  }
73026
73229
 
73027
73230
  // src/hooks/usePersistentEditHistory.ts
73028
- import { useCallback as useCallback99, useEffect as useEffect111, useMemo as useMemo70, useRef as useRef125, useState as useState111 } from "react";
73231
+ import { useCallback as useCallback100, useEffect as useEffect111, useMemo as useMemo70, useRef as useRef125, useState as useState111 } from "react";
73029
73232
  var EMPTY3 = { entries: [], back: null, forward: null };
73030
73233
  var OWN_ENTRIES_KEPT = 100;
73031
73234
  function createOwnHistory() {
@@ -73159,7 +73362,7 @@ function usePersistentEditHistory({ projectId }) {
73159
73362
  const projectIdRef = useRef125(projectId);
73160
73363
  projectIdRef.current = projectId;
73161
73364
  const [own] = useState111(createOwnHistory);
73162
- const refresh2 = useCallback99(async () => {
73365
+ const refresh2 = useCallback100(async () => {
73163
73366
  if (!projectId) return;
73164
73367
  const seen = own.changes();
73165
73368
  const response = await studioApiFetch(historyUrl(projectId)).catch(() => null);
@@ -73176,7 +73379,7 @@ function usePersistentEditHistory({ projectId }) {
73176
73379
  own.clear();
73177
73380
  void refresh2().finally(() => setLoaded(true));
73178
73381
  }, [refresh2, own]);
73179
- const recordEdit = useCallback99(
73382
+ const recordEdit = useCallback100(
73180
73383
  async ({ label, coalesceKey, coalesceMs, files, created = [] }) => {
73181
73384
  if (!projectId) return;
73182
73385
  const paths = [...Object.keys(files), ...created];
@@ -73197,7 +73400,7 @@ function usePersistentEditHistory({ projectId }) {
73197
73400
  },
73198
73401
  [projectId, refresh2, own]
73199
73402
  );
73200
- const step = useCallback99(
73403
+ const step = useCallback100(
73201
73404
  async (direction, callbacks) => {
73202
73405
  if (!projectId) return { ok: false, reason: "empty" };
73203
73406
  const candidate = direction === "undo" && callbacks.claimedAfter !== void 0 ? own.claimedAfter(callbacks.claimedAfter) : null;
@@ -73245,12 +73448,12 @@ function usePersistentEditHistory({ projectId }) {
73245
73448
  },
73246
73449
  [projectId, view, refresh2, own]
73247
73450
  );
73248
- const noteOutsideChange = useCallback99(() => {
73451
+ const noteOutsideChange = useCallback100(() => {
73249
73452
  own.overtake();
73250
73453
  void refresh2();
73251
73454
  }, [own, refresh2]);
73252
- const undo = useCallback99((callbacks) => step("undo", callbacks), [step]);
73253
- const redo = useCallback99((callbacks) => step("redo", callbacks), [step]);
73455
+ const undo = useCallback100((callbacks) => step("undo", callbacks), [step]);
73456
+ const redo = useCallback100((callbacks) => step("redo", callbacks), [step]);
73254
73457
  const state = useMemo70(() => {
73255
73458
  const backAt = Math.max(view.back?.endedAt ?? 0, heldClaimRef.current?.at ?? 0);
73256
73459
  const redoAt = redoneAt(view);
@@ -73278,7 +73481,7 @@ function usePersistentEditHistory({ projectId }) {
73278
73481
  }
73279
73482
 
73280
73483
  // src/hooks/usePanelLayout.ts
73281
- import { useCallback as useCallback100, useEffect as useEffect112, useRef as useRef126 } from "react";
73484
+ import { useCallback as useCallback101, useEffect as useEffect112, useRef as useRef126 } from "react";
73282
73485
  function panelForTab(tab) {
73283
73486
  return tab === "block-params" ? "design" : tab;
73284
73487
  }
@@ -73313,13 +73516,13 @@ function usePanelLayout(initialState2) {
73313
73516
  const visibleRight = visiblePanelInZone("right", lastActive, visiblePanels);
73314
73517
  const rightPanelTab = tabForPanel(visibleRight);
73315
73518
  const rightCollapsed = visibleRight === null;
73316
- const setRightPanelTab = useCallback100((tab) => {
73519
+ const setRightPanelTab = useCallback101((tab) => {
73317
73520
  const store = useDockLayoutStore.getState();
73318
73521
  const shown2 = visiblePanelInZone("right", store.lastActive, store.visiblePanels);
73319
73522
  store.activatePanel(panelForTab(tab));
73320
73523
  if (tabForPanel(shown2) !== tab) trackStudioEvent("tab_switch", { panel: "right_panel", tab });
73321
73524
  }, []);
73322
- const setRightCollapsed = useCallback100((collapsed) => {
73525
+ const setRightCollapsed = useCallback101((collapsed) => {
73323
73526
  const store = useDockLayoutStore.getState();
73324
73527
  if (!collapsed && visiblePanelInZone("right", store.lastActive, store.visiblePanels) === null) {
73325
73528
  store.activatePanel("design");
@@ -73334,10 +73537,10 @@ function usePanelLayout(initialState2) {
73334
73537
  }
73335
73538
 
73336
73539
  // src/hooks/useFileManager.ts
73337
- import { useState as useState113, useCallback as useCallback104, useRef as useRef129 } from "react";
73540
+ import { useState as useState113, useCallback as useCallback105, useRef as useRef129 } from "react";
73338
73541
 
73339
73542
  // src/hooks/useFileTree.ts
73340
- import { useState as useState112, useCallback as useCallback101, useEffect as useEffect113, useMemo as useMemo71, useRef as useRef127 } from "react";
73543
+ import { useState as useState112, useCallback as useCallback102, useEffect as useEffect113, useMemo as useMemo71, useRef as useRef127 } from "react";
73341
73544
  var EMPTY_FILE_LIST = [];
73342
73545
  function useFileTree({ projectId, projectIdRef }) {
73343
73546
  const [fetched, setFetched] = useState112(null);
@@ -73374,7 +73577,7 @@ function useFileTree({ projectId, projectIdRef }) {
73374
73577
  }, [projectId]);
73375
73578
  const refreshRequestRef = useRef127(0);
73376
73579
  const refreshAbortRef = useRef127(null);
73377
- const refreshFileTree = useCallback101(async () => {
73580
+ const refreshFileTree = useCallback102(async () => {
73378
73581
  const pid = projectIdRef.current;
73379
73582
  if (!pid) return;
73380
73583
  refreshAbortRef.current?.abort();
@@ -73425,7 +73628,7 @@ function useFileTree({ projectId, projectIdRef }) {
73425
73628
  }
73426
73629
 
73427
73630
  // src/hooks/useEditorSave.ts
73428
- import { useCallback as useCallback102, useRef as useRef128 } from "react";
73631
+ import { useCallback as useCallback103, useRef as useRef128 } from "react";
73429
73632
  var FAILURE_BURST_MS = 5e3;
73430
73633
  function useEditorSave({
73431
73634
  editingPathRef,
@@ -73443,7 +73646,7 @@ function useEditorSave({
73443
73646
  const pendingCandidateRef = useRef128(null);
73444
73647
  const inFlightRef = useRef128(null);
73445
73648
  const inFlightCandidateRef = useRef128(null);
73446
- const reportFailure = useCallback102(
73649
+ const reportFailure = useCallback103(
73447
73650
  (path, error) => {
73448
73651
  const now2 = Date.now();
73449
73652
  const properties = buildStudioSaveFailureProperties({
@@ -73473,7 +73676,7 @@ function useEditorSave({
73473
73676
  },
73474
73677
  [showToast]
73475
73678
  );
73476
- const persistCandidate = useCallback102(
73679
+ const persistCandidate = useCallback103(
73477
73680
  (candidate) => {
73478
73681
  const task = saveProjectFilesWithHistory({
73479
73682
  projectId: candidate.projectId,
@@ -73504,7 +73707,7 @@ function useEditorSave({
73504
73707
  },
73505
73708
  [readProjectFile, recordEdit, reportFailure, setRefreshKey, writeProjectFile]
73506
73709
  );
73507
- const handleContentChange = useCallback102(
73710
+ const handleContentChange = useCallback103(
73508
73711
  (content) => {
73509
73712
  const pid = projectIdRef.current;
73510
73713
  if (!pid) return;
@@ -73520,7 +73723,7 @@ function useEditorSave({
73520
73723
  },
73521
73724
  [editingPathRef, projectIdRef, persistCandidate]
73522
73725
  );
73523
- const flushPendingSave = useCallback102(async () => {
73726
+ const flushPendingSave = useCallback103(async () => {
73524
73727
  if (saveRafRef.current != null) {
73525
73728
  cancelAnimationFrame(saveRafRef.current);
73526
73729
  saveRafRef.current = null;
@@ -73534,7 +73737,7 @@ function useEditorSave({
73534
73737
  }
73535
73738
  return await inFlightRef.current ?? { status: "clean" };
73536
73739
  }, [persistCandidate]);
73537
- const discardPendingSave = useCallback102(() => {
73740
+ const discardPendingSave = useCallback103(() => {
73538
73741
  if (saveRafRef.current != null) cancelAnimationFrame(saveRafRef.current);
73539
73742
  saveRafRef.current = null;
73540
73743
  pendingCandidateRef.current = null;
@@ -73549,7 +73752,7 @@ function useEditorSave({
73549
73752
  }
73550
73753
 
73551
73754
  // src/hooks/useProjectFileWriter.ts
73552
- import { useCallback as useCallback103, useMemo as useMemo72 } from "react";
73755
+ import { useCallback as useCallback104, useMemo as useMemo72 } from "react";
73553
73756
  async function fetchProjectFile(projectId, path, query) {
73554
73757
  if (!projectId) throw new Error("No active project");
73555
73758
  const response = await studioApiFetch(
@@ -73565,13 +73768,13 @@ function useProjectFileWriter({ projectId }) {
73565
73768
  [projectId]
73566
73769
  );
73567
73770
  const fileVersions = fileVersionScope.versions;
73568
- const observeProjectFileVersion = useCallback103(
73771
+ const observeProjectFileVersion = useCallback104(
73569
73772
  (path, version) => {
73570
73773
  fileVersions.set(path, version);
73571
73774
  },
73572
73775
  [fileVersions]
73573
73776
  );
73574
- const readProjectFile = useCallback103(
73777
+ const readProjectFile = useCallback104(
73575
73778
  async (path) => {
73576
73779
  const { content, version } = await fetchProjectFile(projectId, path, "");
73577
73780
  if (typeof content !== "string") throw new Error(`Missing file contents for ${path}`);
@@ -73580,7 +73783,7 @@ function useProjectFileWriter({ projectId }) {
73580
73783
  },
73581
73784
  [fileVersions, projectId]
73582
73785
  );
73583
- const readOptionalProjectFile = useCallback103(
73786
+ const readOptionalProjectFile = useCallback104(
73584
73787
  async (path) => {
73585
73788
  const { content, version } = await fetchProjectFile(projectId, path, "?optional=1");
73586
73789
  fileVersions.set(path, version);
@@ -73588,7 +73791,7 @@ function useProjectFileWriter({ projectId }) {
73588
73791
  },
73589
73792
  [fileVersions, projectId]
73590
73793
  );
73591
- const writeProjectFile = useCallback103(
73794
+ const writeProjectFile = useCallback104(
73592
73795
  async (path, content, expectedContent) => {
73593
73796
  if (!projectId) throw new Error("No active project");
73594
73797
  const writeProjectId = projectId;
@@ -73703,12 +73906,12 @@ function useFileManager({
73703
73906
  assets,
73704
73907
  fontAssets
73705
73908
  } = useFileTree({ projectId, projectIdRef });
73706
- const updateEditingFileContent = useCallback104((path, content) => {
73909
+ const updateEditingFileContent = useCallback105((path, content) => {
73707
73910
  if (editingPathRef.current === path) {
73708
73911
  setEditingFile({ path, content });
73709
73912
  }
73710
73913
  }, []);
73711
- const writeProjectFile = useCallback104(
73914
+ const writeProjectFile = useCallback105(
73712
73915
  async (path, content, expectedContent) => {
73713
73916
  const writeProjectId = projectIdRef.current;
73714
73917
  await writeProjectFileRaw(path, content, expectedContent);
@@ -73728,7 +73931,7 @@ function useFileManager({
73728
73931
  showToast
73729
73932
  });
73730
73933
  const { saveRafRef, handleContentChange } = editorSave;
73731
- const overwriteExternalConflict = useCallback104(
73934
+ const overwriteExternalConflict = useCallback105(
73732
73935
  async (conflict) => {
73733
73936
  await serializeStudioFileMutation(writeProjectFile, conflict.filePath, async () => {
73734
73937
  if (conflict.currentContent != null) {
@@ -73748,7 +73951,7 @@ function useFileManager({
73748
73951
  );
73749
73952
  const revealRequestIdRef = useRef129(0);
73750
73953
  const revealAbortRef = useRef129(null);
73751
- const handleFileSelect = useCallback104(
73954
+ const handleFileSelect = useCallback105(
73752
73955
  (path) => {
73753
73956
  const pid = projectIdRef.current;
73754
73957
  if (!pid) return;
@@ -73773,7 +73976,7 @@ function useFileManager({
73773
73976
  },
73774
73977
  [fileVersions, showToast]
73775
73978
  );
73776
- const openSourceForSelection = useCallback104(
73979
+ const openSourceForSelection = useCallback105(
73777
73980
  (sourceFile, target) => {
73778
73981
  const pid = projectIdRef.current;
73779
73982
  if (!pid || !sourceFile) return;
@@ -73805,7 +74008,7 @@ function useFileManager({
73805
74008
  },
73806
74009
  [editingFile?.content, fileVersions]
73807
74010
  );
73808
- const uploadProjectFiles = useCallback104(
74011
+ const uploadProjectFiles = useCallback105(
73809
74012
  async (files, dir) => {
73810
74013
  const pid = projectIdRef.current;
73811
74014
  const fileList = Array.from(files);
@@ -73848,7 +74051,7 @@ function useFileManager({
73848
74051
  },
73849
74052
  [refreshFileTree, setRefreshKey, showToast]
73850
74053
  );
73851
- const handleCreateFile = useCallback104(
74054
+ const handleCreateFile = useCallback105(
73852
74055
  async (path) => {
73853
74056
  const pid = projectIdRef.current;
73854
74057
  if (!pid) return;
@@ -73875,7 +74078,7 @@ function useFileManager({
73875
74078
  },
73876
74079
  [refreshFileTree, handleFileSelect, showToast]
73877
74080
  );
73878
- const handleCreateFolder = useCallback104(
74081
+ const handleCreateFolder = useCallback105(
73879
74082
  async (path) => {
73880
74083
  const pid = projectIdRef.current;
73881
74084
  if (!pid) return;
@@ -73897,7 +74100,7 @@ function useFileManager({
73897
74100
  },
73898
74101
  [refreshFileTree, showToast]
73899
74102
  );
73900
- const handleDeleteFile = useCallback104(
74103
+ const handleDeleteFile = useCallback105(
73901
74104
  async (path) => {
73902
74105
  const pid = projectIdRef.current;
73903
74106
  if (!pid) return;
@@ -73918,7 +74121,7 @@ function useFileManager({
73918
74121
  },
73919
74122
  [refreshFileTree, showToast]
73920
74123
  );
73921
- const handleRenameFile = useCallback104(
74124
+ const handleRenameFile = useCallback105(
73922
74125
  async (oldPath, newPath) => {
73923
74126
  const pid = projectIdRef.current;
73924
74127
  if (!pid) return;
@@ -73944,7 +74147,7 @@ function useFileManager({
73944
74147
  },
73945
74148
  [refreshFileTree, handleFileSelect, setRefreshKey, showToast]
73946
74149
  );
73947
- const handleDuplicateFile = useCallback104(
74150
+ const handleDuplicateFile = useCallback105(
73948
74151
  async (path) => {
73949
74152
  const pid = projectIdRef.current;
73950
74153
  if (!pid) return;
@@ -73966,13 +74169,13 @@ function useFileManager({
73966
74169
  [refreshFileTree, handleFileSelect, showToast]
73967
74170
  );
73968
74171
  const handleMoveFile = handleRenameFile;
73969
- const handleImportFiles = useCallback104(
74172
+ const handleImportFiles = useCallback105(
73970
74173
  async (files, dir) => {
73971
74174
  return uploadProjectFiles(Array.from(files), dir);
73972
74175
  },
73973
74176
  [uploadProjectFiles]
73974
74177
  );
73975
- const handleImportFonts = useCallback104(
74178
+ const handleImportFonts = useCallback105(
73976
74179
  async (files) => {
73977
74180
  const pid = projectIdRef.current;
73978
74181
  if (!pid) return [];
@@ -74041,7 +74244,7 @@ function useFileManager({
74041
74244
  }
74042
74245
 
74043
74246
  // src/hooks/usePreviewPersistence.ts
74044
- import { useCallback as useCallback105, useRef as useRef130, useState as useState114 } from "react";
74247
+ import { useCallback as useCallback106, useRef as useRef130, useState as useState114 } from "react";
74045
74248
 
74046
74249
  // src/utils/gsapUndoRestore.ts
74047
74250
  import { isCompositionTemplate as isCompositionTemplate2 } from "@hyperframes/parsers/hf-ids";
@@ -74423,31 +74626,31 @@ function usePreviewPersistence({
74423
74626
  }
74424
74627
  });
74425
74628
  }
74426
- const queueDomEditSave = useCallback105((save) => {
74629
+ const queueDomEditSave = useCallback106((save) => {
74427
74630
  return domEditSaveQueueRef.current?.enqueue(save) ?? save();
74428
74631
  }, []);
74429
- const drainPendingDomEditSaves = useCallback105(async () => {
74632
+ const drainPendingDomEditSaves = useCallback106(async () => {
74430
74633
  return drainStudioSaveQueues(flushStudioPendingEdits, async () => {
74431
74634
  return await domEditSaveQueueRef.current?.waitForIdle() ?? { status: "clean" };
74432
74635
  });
74433
74636
  }, []);
74434
- const waitForPendingDomEditSaves = useCallback105(async () => {
74637
+ const waitForPendingDomEditSaves = useCallback106(async () => {
74435
74638
  const result = await drainPendingDomEditSaves();
74436
74639
  if (result.status !== "clean") throw result.error;
74437
74640
  }, [drainPendingDomEditSaves]);
74438
- const settlePendingEdits = useCallback105(async () => {
74641
+ const settlePendingEdits = useCallback106(async () => {
74439
74642
  const queued = domEditSaveQueueRef.current?.waitForIdle();
74440
74643
  await flushStudioPendingEdits({ onlyCurrent: true });
74441
74644
  await queued;
74442
74645
  }, []);
74443
- const resetDomEditSaveQueueBreaker = useCallback105(() => {
74646
+ const resetDomEditSaveQueueBreaker = useCallback106(() => {
74444
74647
  domEditSaveQueueRef.current?.reset();
74445
74648
  setDomEditSaveQueuePaused(null);
74446
74649
  }, []);
74447
74650
  useMountEffect(() => () => {
74448
74651
  domEditSaveQueueRef.current?.destroy();
74449
74652
  });
74450
- const applyCurrentStudioManualEditsToPreview = useCallback105(
74653
+ const applyCurrentStudioManualEditsToPreview = useCallback106(
74451
74654
  (iframe = previewIframeRef.current) => {
74452
74655
  if (!iframe) return;
74453
74656
  if (!readIframeDocument(iframe)) return;
@@ -74455,14 +74658,14 @@ function usePreviewPersistence({
74455
74658
  },
74456
74659
  [previewIframeRef]
74457
74660
  );
74458
- const applyStudioManualEditsToPreview = useCallback105(
74661
+ const applyStudioManualEditsToPreview = useCallback106(
74459
74662
  async (iframe = previewIframeRef.current) => {
74460
74663
  applyCurrentStudioManualEditsToPreview(iframe);
74461
74664
  },
74462
74665
  [applyCurrentStudioManualEditsToPreview, previewIframeRef]
74463
74666
  );
74464
74667
  applyStudioManualEditsToPreviewRef.current = applyStudioManualEditsToPreview;
74465
- const syncHistoryPreviewAfterApply = useCallback105(
74668
+ const syncHistoryPreviewAfterApply = useCallback106(
74466
74669
  async (restore2) => {
74467
74670
  const projectId = usePlayerStore.getState().timelineProjectId;
74468
74671
  const nestedFiles = await settleNestedReads(
@@ -74489,7 +74692,7 @@ function usePreviewPersistence({
74489
74692
  },
74490
74693
  [previewIframeRef, activeCompPathRef, reloadPreview]
74491
74694
  );
74492
- const showHistoryRestoreNow = useCallback105(
74695
+ const showHistoryRestoreNow = useCallback106(
74493
74696
  (files) => {
74494
74697
  if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits()) return null;
74495
74698
  const iframe = previewIframeRef.current;
@@ -74524,11 +74727,11 @@ function usePreviewPersistence({
74524
74727
  }
74525
74728
 
74526
74729
  // src/hooks/usePreviewDocumentVersion.ts
74527
- import { useCallback as useCallback106, useEffect as useEffect114, useRef as useRef131, useState as useState115 } from "react";
74730
+ import { useCallback as useCallback107, useEffect as useEffect114, useRef as useRef131, useState as useState115 } from "react";
74528
74731
  function usePreviewDocumentVersion() {
74529
74732
  const [previewDocumentVersion, setPreviewDocumentVersion] = useState115(0);
74530
74733
  const refreshTimersRef = useRef131([]);
74531
- const refresh2 = useCallback106(() => {
74734
+ const refresh2 = useCallback107(() => {
74532
74735
  for (const id of refreshTimersRef.current) clearTimeout(id);
74533
74736
  refreshTimersRef.current = [];
74534
74737
  setPreviewDocumentVersion((v) => v + 1);
@@ -74547,10 +74750,10 @@ function usePreviewDocumentVersion() {
74547
74750
  }
74548
74751
 
74549
74752
  // src/hooks/useTimelineEditing.ts
74550
- import { useCallback as useCallback122, useRef as useRef139 } from "react";
74753
+ import { useCallback as useCallback123, useRef as useRef139 } from "react";
74551
74754
 
74552
74755
  // src/hooks/useRazorSplit.ts
74553
- import { useCallback as useCallback108, useRef as useRef133 } from "react";
74756
+ import { useCallback as useCallback109, useRef as useRef133 } from "react";
74554
74757
 
74555
74758
  // src/utils/razorSplitTransaction.ts
74556
74759
  function targetIdentity(path, target) {
@@ -74676,7 +74879,7 @@ function runAtomicCutTransaction(input) {
74676
74879
  }
74677
74880
 
74678
74881
  // src/hooks/useFreezeFrame.ts
74679
- import { useCallback as useCallback107, useRef as useRef132 } from "react";
74882
+ import { useCallback as useCallback108, useRef as useRef132 } from "react";
74680
74883
  function isFreezeFrameResponse(value) {
74681
74884
  if (typeof value !== "object" || value === null) return false;
74682
74885
  const body = value;
@@ -74727,7 +74930,7 @@ async function requestFreezeFrame(input) {
74727
74930
  function useFreezeFrame(options) {
74728
74931
  const optionsRef = useRef132(options);
74729
74932
  optionsRef.current = options;
74730
- return useCallback107(async (element, playhead) => {
74933
+ return useCallback108(async (element, playhead) => {
74731
74934
  const opts = optionsRef.current;
74732
74935
  if (opts.isRecordingRef?.current) {
74733
74936
  opts.showToast("Cannot edit timeline while recording", "error");
@@ -74769,7 +74972,7 @@ function useRazorSplit({
74769
74972
  }) {
74770
74973
  const projectIdRef = useRef133(projectId);
74771
74974
  projectIdRef.current = projectId;
74772
- const synchronize = useCallback108(() => {
74975
+ const synchronize = useCallback109(() => {
74773
74976
  let failure;
74774
74977
  try {
74775
74978
  forceReloadSdkSession?.();
@@ -74783,7 +74986,7 @@ function useRazorSplit({
74783
74986
  }
74784
74987
  if (failure) throw failure;
74785
74988
  }, [forceReloadSdkSession, reloadPreview]);
74786
- const runCut = useCallback108(
74989
+ const runCut = useCallback109(
74787
74990
  async (elements, splitTime, mode) => {
74788
74991
  const pid = projectIdRef.current;
74789
74992
  if (!pid || elements.length === 0) return;
@@ -74823,7 +75026,7 @@ function useRazorSplit({
74823
75026
  writeProjectFile
74824
75027
  ]
74825
75028
  );
74826
- const handleRazorSplit = useCallback108(
75029
+ const handleRazorSplit = useCallback109(
74827
75030
  async (element, splitTime) => {
74828
75031
  if (isRecordingRef?.current) {
74829
75032
  showToast("Cannot edit timeline while recording", "error");
@@ -74851,7 +75054,7 @@ function useRazorSplit({
74851
75054
  },
74852
75055
  [isRecordingRef, runCut, showToast]
74853
75056
  );
74854
- const handleRazorSplitAll = useCallback108(
75057
+ const handleRazorSplitAll = useCallback109(
74855
75058
  async (splitTime) => {
74856
75059
  if (isRecordingRef?.current) {
74857
75060
  showToast("Cannot edit timeline while recording", "error");
@@ -74886,7 +75089,7 @@ function useRazorSplit({
74886
75089
  }
74887
75090
 
74888
75091
  // src/hooks/useTimelineAssetDropOps.ts
74889
- import { useCallback as useCallback109 } from "react";
75092
+ import { useCallback as useCallback110 } from "react";
74890
75093
 
74891
75094
  // src/utils/timelineDropTrackInsert.ts
74892
75095
  function planDropTrackInsert(input) {
@@ -75049,7 +75252,7 @@ function useTimelineAssetDropOps({
75049
75252
  observeProjectFileVersion,
75050
75253
  checkEditable
75051
75254
  }) {
75052
- const dropAssetAt = useCallback109(
75255
+ const dropAssetAt = useCallback110(
75053
75256
  // fallow-ignore-next-line complexity
75054
75257
  async (assetPath, placement, durationOverride, gesture = { placed: [], onNewTrack: false }) => {
75055
75258
  if (isRecordingRef?.current) {
@@ -75158,13 +75361,13 @@ function useTimelineAssetDropOps({
75158
75361
  checkEditable
75159
75362
  ]
75160
75363
  );
75161
- const handleTimelineAssetDrop = useCallback109(
75364
+ const handleTimelineAssetDrop = useCallback110(
75162
75365
  async (assetPath, placement, durationOverride) => {
75163
75366
  await dropAssetAt(assetPath, placement, durationOverride);
75164
75367
  },
75165
75368
  [dropAssetAt]
75166
75369
  );
75167
- const handleTimelineFileDrop = useCallback109(
75370
+ const handleTimelineFileDrop = useCallback110(
75168
75371
  // fallow-ignore-next-line complexity
75169
75372
  async (files, placement) => {
75170
75373
  if (isRecordingRef?.current) {
@@ -75214,7 +75417,7 @@ function useTimelineAssetDropOps({
75214
75417
  showToast
75215
75418
  ]
75216
75419
  );
75217
- const handleTimelineCompositionDrop = useCallback109(
75420
+ const handleTimelineCompositionDrop = useCallback110(
75218
75421
  async (sourcePath, placement) => {
75219
75422
  if (isRecordingRef?.current) {
75220
75423
  showToast("Cannot edit timeline while recording", "error");
@@ -75265,10 +75468,10 @@ function useTimelineAssetDropOps({
75265
75468
  }
75266
75469
 
75267
75470
  // src/hooks/timelineAudioGroupVolume.ts
75268
- import { useCallback as useCallback111 } from "react";
75471
+ import { useCallback as useCallback112 } from "react";
75269
75472
 
75270
75473
  // src/hooks/timelineEditPermission.ts
75271
- import { useCallback as useCallback110 } from "react";
75474
+ import { useCallback as useCallback111 } from "react";
75272
75475
  function projectForTimelineSave(isRecording, projectId, showToast) {
75273
75476
  if (isRecording) {
75274
75477
  showToast("Cannot edit timeline while recording", "error");
@@ -75288,7 +75491,7 @@ function timelineEditRefusal(canEdit, targets) {
75288
75491
  return null;
75289
75492
  }
75290
75493
  function useTimelineEditRefusal(canEdit, showToast) {
75291
- return useCallback110(
75494
+ return useCallback111(
75292
75495
  (targets) => {
75293
75496
  const reason = timelineEditRefusal(canEdit, targets);
75294
75497
  if (reason !== null) showToast(reason, "error");
@@ -75299,7 +75502,7 @@ function useTimelineEditRefusal(canEdit, showToast) {
75299
75502
  }
75300
75503
  function useTimelineEditGate(canEdit, showToast) {
75301
75504
  const refuse2 = useTimelineEditRefusal(canEdit, showToast);
75302
- return useCallback110(
75505
+ return useCallback111(
75303
75506
  (targets) => refuse2(targets) === null,
75304
75507
  [refuse2]
75305
75508
  );
@@ -75528,7 +75731,7 @@ function useSetAudioGroupAttribute({
75528
75731
  isRecordingRef
75529
75732
  }) {
75530
75733
  const liveLanes = useLiveLanes(projectIdRef, activeCompPath);
75531
- const setLive = useCallback111(
75734
+ const setLive = useCallback112(
75532
75735
  (groupId, attr, value) => {
75533
75736
  const key2 = audioGroupAttributeLiveKey(groupId, attr);
75534
75737
  const target = previewIframeRef.current?.contentDocument?.getElementById(groupId);
@@ -75542,22 +75745,22 @@ function useSetAudioGroupAttribute({
75542
75745
  },
75543
75746
  [liveLanes, previewIframeRef, activeCompPath]
75544
75747
  );
75545
- const laneApply = useCallback111(
75748
+ const laneApply = useCallback112(
75546
75749
  (groupId, attr) => ({
75547
75750
  preview: (value) => patchLiveGroupAttribute(previewIframeRef.current, groupId, attr, value),
75548
75751
  store: (value) => syncStoredGroupAttribute(groupId, attr, value)
75549
75752
  }),
75550
75753
  [previewIframeRef]
75551
75754
  );
75552
- const claimLive = useCallback111(
75755
+ const claimLive = useCallback112(
75553
75756
  (groupId, attr, source) => liveLanes.claim(audioGroupAttributeLiveKey(groupId, attr), laneApply(groupId, attr), source),
75554
75757
  [liveLanes, laneApply]
75555
75758
  );
75556
- const revertLive = useCallback111(
75759
+ const revertLive = useCallback112(
75557
75760
  (groupId, attr) => liveLanes.revert(audioGroupAttributeLiveKey(groupId, attr), laneApply(groupId, attr)),
75558
75761
  [liveLanes, laneApply]
75559
75762
  );
75560
- const setQuiet = useCallback111(
75763
+ const setQuiet = useCallback112(
75561
75764
  async (groupId, attr, value, label) => {
75562
75765
  const project = projectIdRef.current;
75563
75766
  const pid = projectForTimelineSave(isRecordingRef?.current, project, showToast);
@@ -75611,7 +75814,7 @@ function useSetAudioGroupAttribute({
75611
75814
  }
75612
75815
 
75613
75816
  // src/hooks/timelineElementFxAttribute.ts
75614
- import { useCallback as useCallback112 } from "react";
75817
+ import { useCallback as useCallback113 } from "react";
75615
75818
  function patchLiveElementAttribute(iframe, element, attr, value, activeCompPath) {
75616
75819
  const target = findTimelineElementInIframe(iframe, element, activeCompPath);
75617
75820
  if (!target) return;
@@ -75671,7 +75874,7 @@ function useSetElementAttribute({
75671
75874
  isRecordingRef
75672
75875
  }) {
75673
75876
  const liveLanes = useLiveLanes(projectIdRef, activeCompPath);
75674
- const setLive = useCallback112(
75877
+ const setLive = useCallback113(
75675
75878
  (element, attr, value) => {
75676
75879
  const key2 = elementAttributeLiveKey(element, activeCompPath, attr);
75677
75880
  const target = findTimelineElementInIframe(previewIframeRef.current, element, activeCompPath);
@@ -75684,14 +75887,14 @@ function useSetElementAttribute({
75684
75887
  },
75685
75888
  [liveLanes, previewIframeRef, activeCompPath]
75686
75889
  );
75687
- const laneApply = useCallback112(
75890
+ const laneApply = useCallback113(
75688
75891
  (element, attr) => ({
75689
75892
  preview: (value) => patchLiveElementAttribute(previewIframeRef.current, element, attr, value, activeCompPath),
75690
75893
  store: (value) => syncStoredElementAttribute(element, attr, value)
75691
75894
  }),
75692
75895
  [previewIframeRef, activeCompPath]
75693
75896
  );
75694
- const claimLive = useCallback112(
75897
+ const claimLive = useCallback113(
75695
75898
  (element, attr) => liveLanes.claim(
75696
75899
  elementAttributeLiveKey(element, activeCompPath, attr),
75697
75900
  laneApply(element, attr),
@@ -75699,14 +75902,14 @@ function useSetElementAttribute({
75699
75902
  ),
75700
75903
  [liveLanes, laneApply, activeCompPath]
75701
75904
  );
75702
- const revertLive = useCallback112(
75905
+ const revertLive = useCallback113(
75703
75906
  (element, attr) => liveLanes.revert(
75704
75907
  elementAttributeLiveKey(element, activeCompPath, attr),
75705
75908
  laneApply(element, attr)
75706
75909
  ),
75707
75910
  [liveLanes, laneApply, activeCompPath]
75708
75911
  );
75709
- const setQuiet = useCallback112(
75912
+ const setQuiet = useCallback113(
75710
75913
  async (element, attr, value, label) => {
75711
75914
  const project = projectIdRef.current;
75712
75915
  const pid = projectForTimelineSave(isRecordingRef?.current, project, showToast);
@@ -75758,7 +75961,7 @@ function useSetElementAttribute({
75758
75961
  }
75759
75962
 
75760
75963
  // src/hooks/timelineElementsAttribute.ts
75761
- import { useCallback as useCallback113 } from "react";
75964
+ import { useCallback as useCallback114 } from "react";
75762
75965
  function editsByFile(edits, activeCompPath) {
75763
75966
  const byFile = /* @__PURE__ */ new Map();
75764
75967
  for (const { element, value } of edits) {
@@ -75787,7 +75990,7 @@ function useSetElementsAttribute({
75787
75990
  pendingTimelineEditPathRef,
75788
75991
  isRecordingRef
75789
75992
  }) {
75790
- return useCallback113(
75993
+ return useCallback114(
75791
75994
  async (edits, attr, label) => {
75792
75995
  const projectId = projectForTimelineSave(
75793
75996
  isRecordingRef?.current,
@@ -75838,7 +76041,7 @@ function useSetElementsAttribute({
75838
76041
  }
75839
76042
 
75840
76043
  // src/hooks/useTimelineDeleteOps.ts
75841
- import { useCallback as useCallback114, useRef as useRef135 } from "react";
76044
+ import { useCallback as useCallback115, useRef as useRef135 } from "react";
75842
76045
  function applyRippleShifts(survivors, changes) {
75843
76046
  if (!changes) return survivors;
75844
76047
  const newStartByElement = new Map(changes.map((c) => [c.element, c.start]));
@@ -75866,7 +76069,7 @@ function useTimelineDeleteOps({
75866
76069
  handleTimelineGroupMove
75867
76070
  }) {
75868
76071
  const rippleNoticeShownRef = useRef135(false);
75869
- const handleTimelineElementsDelete = useCallback114(
76072
+ const handleTimelineElementsDelete = useCallback115(
75870
76073
  // fallow-ignore-next-line complexity
75871
76074
  async (selection, alsoUnlink = []) => {
75872
76075
  if (isRecordingRef?.current) {
@@ -76002,7 +76205,7 @@ function useTimelineDeleteOps({
76002
76205
  handleTimelineGroupMove
76003
76206
  ]
76004
76207
  );
76005
- const handleTimelineElementDelete = useCallback114(
76208
+ const handleTimelineElementDelete = useCallback115(
76006
76209
  async (element) => {
76007
76210
  await handleTimelineElementsDelete([element]);
76008
76211
  },
@@ -76012,13 +76215,13 @@ function useTimelineDeleteOps({
76012
76215
  }
76013
76216
 
76014
76217
  // src/hooks/useTimelineEditGuard.ts
76015
- import { useCallback as useCallback115, useRef as useRef136 } from "react";
76218
+ import { useCallback as useCallback116, useRef as useRef136 } from "react";
76016
76219
  function useTimelineEditGuard(canEdit, showToast) {
76017
76220
  const refuseEdit = useTimelineEditRefusal(canEdit, showToast);
76018
76221
  const refuseEditRef = useRef136(refuseEdit);
76019
76222
  refuseEditRef.current = refuseEdit;
76020
76223
  const guardedRef = useRef136(/* @__PURE__ */ new WeakMap());
76021
- const guard = useCallback115(
76224
+ const guard = useCallback116(
76022
76225
  (resolveTargets, fn, onRefused) => {
76023
76226
  const key2 = fn;
76024
76227
  const cached5 = guardedRef.current.get(key2);
@@ -76045,7 +76248,7 @@ function useTimelineEditGuard(canEdit, showToast) {
76045
76248
  }
76046
76249
 
76047
76250
  // src/hooks/useTimelineLinkEditing.ts
76048
- import { useCallback as useCallback116 } from "react";
76251
+ import { useCallback as useCallback117 } from "react";
76049
76252
 
76050
76253
  // src/hooks/timelineLinkEditPlan.ts
76051
76254
  var keyOf15 = (el) => el.key ?? el.id;
@@ -76172,7 +76375,7 @@ function useTimelineLinkEditing({
76172
76375
  forceReloadSdkSession,
76173
76376
  handleTimelineElementsDelete
76174
76377
  }) {
76175
- const handleLinkEdit = useCallback116(
76378
+ const handleLinkEdit = useCallback117(
76176
76379
  async (edit) => {
76177
76380
  if (isRecordingRef?.current) {
76178
76381
  showToast("Cannot edit timeline while recording", "error");
@@ -76227,7 +76430,7 @@ function useTimelineLinkEditing({
76227
76430
  writeProjectFile
76228
76431
  ]
76229
76432
  );
76230
- const handleLinkedElementsDelete = useCallback116(
76433
+ const handleLinkedElementsDelete = useCallback117(
76231
76434
  (selection) => {
76232
76435
  if (isLinkedSelectionOn()) return handleTimelineElementsDelete(withLinkPartners(selection));
76233
76436
  const removed = new Set(selection.map(keyOf16));
@@ -76238,11 +76441,11 @@ function useTimelineLinkEditing({
76238
76441
  },
76239
76442
  [handleTimelineElementsDelete]
76240
76443
  );
76241
- const handleLinkedElementDelete = useCallback116(
76444
+ const handleLinkedElementDelete = useCallback117(
76242
76445
  (element) => handleLinkedElementsDelete([element]),
76243
76446
  [handleLinkedElementsDelete]
76244
76447
  );
76245
- const handleDeleteElementOnly = useCallback116(
76448
+ const handleDeleteElementOnly = useCallback117(
76246
76449
  (element) => {
76247
76450
  const orphans = clipsToUnlink([element], usePlayerStore.getState().elements).filter(
76248
76451
  (el) => keyOf16(el) !== keyOf16(element)
@@ -76260,10 +76463,10 @@ function useTimelineLinkEditing({
76260
76463
  }
76261
76464
 
76262
76465
  // src/hooks/useTrackPendingTimelineEdit.ts
76263
- import { useCallback as useCallback117, useRef as useRef137 } from "react";
76466
+ import { useCallback as useCallback118, useRef as useRef137 } from "react";
76264
76467
  function useTrackPendingTimelineEdit() {
76265
76468
  const wrappedRef = useRef137(/* @__PURE__ */ new WeakMap());
76266
- return useCallback117((fn) => {
76469
+ return useCallback118((fn) => {
76267
76470
  const key2 = fn;
76268
76471
  const cached5 = wrappedRef.current.get(key2);
76269
76472
  if (cached5) return cached5;
@@ -76274,11 +76477,11 @@ function useTrackPendingTimelineEdit() {
76274
76477
  }
76275
76478
 
76276
76479
  // src/hooks/timelineAudioGroupCreate.ts
76277
- import { useCallback as useCallback119 } from "react";
76480
+ import { useCallback as useCallback120 } from "react";
76278
76481
  import { HF_AUDIO_GROUP_ATTR as HF_AUDIO_GROUP_ATTR5, HF_AUDIO_GROUP_TAG as HF_AUDIO_GROUP_TAG6 } from "@hyperframes/core/audio-groups";
76279
76482
 
76280
76483
  // src/hooks/timelineTrackVisibility.ts
76281
- import { useCallback as useCallback118 } from "react";
76484
+ import { useCallback as useCallback119 } from "react";
76282
76485
  function getTimelineElementTargetPath(element, activeCompPath) {
76283
76486
  return element.sourceFile || activeCompPath || "index.html";
76284
76487
  }
@@ -76429,7 +76632,7 @@ function useTimelineTrackVisibilityEditing({
76429
76632
  forceReloadSdkSession
76430
76633
  }) {
76431
76634
  const timelineElements = usePlayerStore((state) => state.elements);
76432
- return useCallback118(
76635
+ return useCallback119(
76433
76636
  async (track2, hidden, displayNumber) => {
76434
76637
  if (isRecordingRef?.current) {
76435
76638
  showToast("Cannot edit timeline while recording", "error");
@@ -76483,7 +76686,7 @@ function useTimelineElementVisibilityEditing({
76483
76686
  forceReloadSdkSession
76484
76687
  }) {
76485
76688
  const timelineElements = usePlayerStore((state) => state.elements);
76486
- return useCallback118(
76689
+ return useCallback119(
76487
76690
  async (elementKey3, hidden) => {
76488
76691
  if (isRecordingRef?.current) {
76489
76692
  showToast("Cannot edit timeline while recording", "error");
@@ -76658,7 +76861,7 @@ function useAudioGroupCarveAssignment({
76658
76861
  checkEditable
76659
76862
  }) {
76660
76863
  const timelineElements = usePlayerStore((state) => state.elements);
76661
- return useCallback119(
76864
+ return useCallback120(
76662
76865
  async (clipIds, groupId, groupLabel2) => {
76663
76866
  if (isRecordingRef?.current) {
76664
76867
  showToast("Cannot edit timeline while recording", "error");
@@ -76713,7 +76916,7 @@ function useAudioGroupCarveAssignment({
76713
76916
  }
76714
76917
 
76715
76918
  // src/hooks/useTimelineGroupEditing.ts
76716
- import { useCallback as useCallback120 } from "react";
76919
+ import { useCallback as useCallback121 } from "react";
76717
76920
  function targetPathFor(element, activeCompPath) {
76718
76921
  return element.sourceFile || activeCompPath || "index.html";
76719
76922
  }
@@ -76752,7 +76955,7 @@ function useTimelineGroupEditing({
76752
76955
  showToast,
76753
76956
  writeProjectFile
76754
76957
  }) {
76755
- const enqueueGroupOperation = useCallback120(
76958
+ const enqueueGroupOperation = useCallback121(
76756
76959
  (label, operation) => {
76757
76960
  if (isRecordingRef?.current) {
76758
76961
  showToast("Cannot edit timeline while recording", "error");
@@ -76771,7 +76974,7 @@ function useTimelineGroupEditing({
76771
76974
  },
76772
76975
  [editQueueRef, isRecordingRef, projectIdRef, showToast]
76773
76976
  );
76774
- const persistServerBatch = useCallback120(
76977
+ const persistServerBatch = useCallback121(
76775
76978
  async (projectId, label, batchChanges, coalesceKey, coalesceMs) => {
76776
76979
  await persistTimelineBatchEdit({
76777
76980
  projectId,
@@ -76794,7 +76997,7 @@ function useTimelineGroupEditing({
76794
76997
  writeProjectFile
76795
76998
  ]
76796
76999
  );
76797
- const trySdkBatchPersist = useCallback120(
77000
+ const trySdkBatchPersist = useCallback121(
76798
77001
  async (input) => {
76799
77002
  const sharedPath = allChangesSharePath(input.changes, activeCompPath);
76800
77003
  const canUseSdk = !input.needsExtension && sharedPath !== null && input.eligible && input.sdkChanges.every((change) => change !== null);
@@ -76830,7 +77033,7 @@ function useTimelineGroupEditing({
76830
77033
  writeProjectFile
76831
77034
  ]
76832
77035
  );
76833
- const handleTimelineGroupMove = useCallback120(
77036
+ const handleTimelineGroupMove = useCallback121(
76834
77037
  (changes, options) => {
76835
77038
  if (changes.length === 0) return Promise.resolve();
76836
77039
  for (const change of changes) {
@@ -76935,7 +77138,7 @@ function useTimelineGroupEditing({
76935
77138
  writeProjectFile
76936
77139
  ]
76937
77140
  );
76938
- const handleTimelineGroupResize = useCallback120(
77141
+ const handleTimelineGroupResize = useCallback121(
76939
77142
  (changes, options) => {
76940
77143
  if (changes.length === 0) return Promise.resolve();
76941
77144
  for (const change of changes) {
@@ -77043,11 +77246,11 @@ function useTimelineGroupEditing({
77043
77246
  }
77044
77247
 
77045
77248
  // src/hooks/useBlockedTimelineEditToast.ts
77046
- import { useCallback as useCallback121, useRef as useRef138 } from "react";
77249
+ import { useCallback as useCallback122, useRef as useRef138 } from "react";
77047
77250
  var BLOCKED_TOAST_INTERVAL_MS = 1500;
77048
77251
  function useBlockedTimelineEditToast(showToast) {
77049
77252
  const lastAtRef = useRef138(0);
77050
- return useCallback121(
77253
+ return useCallback122(
77051
77254
  (_element, intent) => {
77052
77255
  const now2 = Date.now();
77053
77256
  if (now2 - lastAtRef.current < BLOCKED_TOAST_INTERVAL_MS) return;
@@ -77088,7 +77291,7 @@ function useTimelineEditing({
77088
77291
  const track2 = useTrackPendingTimelineEdit();
77089
77292
  const checkEditable = useTimelineEditGate(canEdit, showToast);
77090
77293
  const guard = useTimelineEditGuard(canEdit, showToast);
77091
- const enqueueEdit = useCallback122(
77294
+ const enqueueEdit = useCallback123(
77092
77295
  (element, label, buildPatches, coalesceKey) => {
77093
77296
  if (isRecordingRef?.current) {
77094
77297
  showToast("Cannot edit timeline while recording", "error");
@@ -77142,7 +77345,7 @@ function useTimelineEditing({
77142
77345
  showToast,
77143
77346
  writeProjectFile
77144
77347
  });
77145
- const handleTimelineElementMove = useCallback122(
77348
+ const handleTimelineElementMove = useCallback123(
77146
77349
  // fallow-ignore-next-line complexity
77147
77350
  (element, updates) => {
77148
77351
  const commitMove = () => {
@@ -77248,7 +77451,7 @@ function useTimelineEditing({
77248
77451
  invalidateGsapCache
77249
77452
  ]
77250
77453
  );
77251
- const handleTimelineElementResize = useCallback122(
77454
+ const handleTimelineElementResize = useCallback123(
77252
77455
  // fallow-ignore-next-line complexity
77253
77456
  (element, updates) => {
77254
77457
  const authoredStart = toAuthoredStart(element, updates.start);
@@ -77549,7 +77752,7 @@ function useTimelineEditing({
77549
77752
  }
77550
77753
 
77551
77754
  // src/hooks/timelineMoveAdapter.ts
77552
- import { useCallback as useCallback123 } from "react";
77755
+ import { useCallback as useCallback124 } from "react";
77553
77756
  function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps, coalesceMs) {
77554
77757
  return deps.handleTimelineGroupMove(
77555
77758
  edits.map(({ element, updates }) => ({
@@ -77566,7 +77769,7 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
77566
77769
  );
77567
77770
  }
77568
77771
  function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
77569
- return useCallback123(
77772
+ return useCallback124(
77570
77773
  async (edits, coalesceKey, operation = "timing", coalesceMs) => {
77571
77774
  const deps = { handleTimelineGroupMove };
77572
77775
  await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps, coalesceMs);
@@ -77579,7 +77782,7 @@ function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
77579
77782
  import { useCallback as useCallback148 } from "react";
77580
77783
 
77581
77784
  // src/hooks/useAskAgentModal.ts
77582
- import { useState as useState117, useCallback as useCallback124, useRef as useRef140, useEffect as useEffect115 } from "react";
77785
+ import { useState as useState117, useCallback as useCallback125, useRef as useRef140, useEffect as useEffect115 } from "react";
77583
77786
  function useAskAgentModal({
77584
77787
  activeCompPath,
77585
77788
  projectDir,
@@ -77596,7 +77799,7 @@ function useAskAgentModal({
77596
77799
  const [copiedAgentPrompt, setCopiedAgentPrompt] = useState117(false);
77597
77800
  const [agentModalOpen, setAgentModalOpen] = useState117(false);
77598
77801
  const copiedAgentTimerRef = useRef140(null);
77599
- const preloadAgentPromptSnippet = useCallback124(
77802
+ const preloadAgentPromptSnippet = useCallback125(
77600
77803
  async (selection) => {
77601
77804
  const pid = projectIdRef.current;
77602
77805
  if (!pid) return;
@@ -77618,7 +77821,7 @@ function useAskAgentModal({
77618
77821
  },
77619
77822
  [activeCompPath, domEditSelectionRef, projectIdRef]
77620
77823
  );
77621
- const handleAskAgent = useCallback124(() => {
77824
+ const handleAskAgent = useCallback125(() => {
77622
77825
  if (!domEditSelection) return;
77623
77826
  setAgentPromptTagSnippet(void 0);
77624
77827
  setAgentPromptSelectionContext(void 0);
@@ -77626,7 +77829,7 @@ function useAskAgentModal({
77626
77829
  void preloadAgentPromptSnippet(domEditSelection);
77627
77830
  setAgentModalOpen(true);
77628
77831
  }, [domEditSelection, preloadAgentPromptSnippet]);
77629
- const handleAgentModalSubmit = useCallback124(
77832
+ const handleAgentModalSubmit = useCallback125(
77630
77833
  async (userInstruction) => {
77631
77834
  if (!domEditSelection) return;
77632
77835
  const targetPath = domEditSelection.sourceFile || activeCompPath || "index.html";
@@ -77691,7 +77894,7 @@ function useAskAgentModal({
77691
77894
  }
77692
77895
 
77693
77896
  // src/hooks/useDomSelection.ts
77694
- import { useState as useState118, useCallback as useCallback125, useRef as useRef141, useEffect as useEffect117 } from "react";
77897
+ import { useState as useState118, useCallback as useCallback126, useRef as useRef141, useEffect as useEffect117 } from "react";
77695
77898
 
77696
77899
  // src/utils/domEditHelpers.ts
77697
77900
  function domEditSelectionsTargetSame(a, b) {
@@ -77930,7 +78133,7 @@ function useDomSelection({
77930
78133
  domEditGroupSelectionsRef.current = domEditGroupSelections;
77931
78134
  domEditHoverSelectionRef.current = domEditHoverSelection;
77932
78135
  activeGroupElementRef.current = activeGroupElement;
77933
- const announceTimelineSelection2 = useCallback125(
78136
+ const announceTimelineSelection2 = useCallback126(
77934
78137
  (group, primary, replaceSet) => announceTimelineSelection(
77935
78138
  {
77936
78139
  timelineElements,
@@ -77949,7 +78152,7 @@ function useDomSelection({
77949
78152
  timelineElements
77950
78153
  ]
77951
78154
  );
77952
- const applyDomSelection = useCallback125(
78155
+ const applyDomSelection = useCallback126(
77953
78156
  // fallow-ignore-next-line complexity
77954
78157
  (selection, options) => {
77955
78158
  if (!selection) {
@@ -78006,10 +78209,10 @@ function useDomSelection({
78006
78209
  },
78007
78210
  [announceTimelineSelection2, setRightCollapsed, setRightPanelTab]
78008
78211
  );
78009
- const clearDomSelection = useCallback125(() => {
78212
+ const clearDomSelection = useCallback126(() => {
78010
78213
  applyDomSelection(null, { revealPanel: false });
78011
78214
  }, [applyDomSelection]);
78012
- const setActiveGroupElement = useCallback125(
78215
+ const setActiveGroupElement = useCallback126(
78013
78216
  (el) => {
78014
78217
  if (activeGroupElementRef.current === el) return;
78015
78218
  activeGroupElementRef.current = el;
@@ -78018,7 +78221,7 @@ function useDomSelection({
78018
78221
  },
78019
78222
  [applyDomSelection]
78020
78223
  );
78021
- const buildDomSelectionFromTarget = useCallback125(
78224
+ const buildDomSelectionFromTarget = useCallback126(
78022
78225
  (target, options) => {
78023
78226
  return resolveDomEditSelection(target, {
78024
78227
  activeCompositionPath: activeCompPath,
@@ -78032,7 +78235,7 @@ function useDomSelection({
78032
78235
  },
78033
78236
  [activeCompPath, isMasterView, projectId]
78034
78237
  );
78035
- const resolveDomSelectionFromPreviewPoint = useCallback125(
78238
+ const resolveDomSelectionFromPreviewPoint = useCallback126(
78036
78239
  // fallow-ignore-next-line complexity
78037
78240
  async (clientX, clientY, options) => {
78038
78241
  const iframe = previewIframeRef.current;
@@ -78057,7 +78260,7 @@ function useDomSelection({
78057
78260
  },
78058
78261
  [activeCompPath, buildDomSelectionFromTarget, captionEditMode, previewIframeRef]
78059
78262
  );
78060
- const resolveAllDomSelectionsFromPreviewPoint = useCallback125(
78263
+ const resolveAllDomSelectionsFromPreviewPoint = useCallback126(
78061
78264
  // fallow-ignore-next-line complexity
78062
78265
  async (clientX, clientY) => {
78063
78266
  const iframe = previewIframeRef.current;
@@ -78076,12 +78279,12 @@ function useDomSelection({
78076
78279
  },
78077
78280
  [activeCompPath, buildDomSelectionFromTarget, captionEditMode, previewIframeRef]
78078
78281
  );
78079
- const updateDomEditHoverSelection = useCallback125((selection) => {
78282
+ const updateDomEditHoverSelection = useCallback126((selection) => {
78080
78283
  if (domEditSelectionsTargetSame(domEditHoverSelectionRef.current, selection)) return;
78081
78284
  domEditHoverSelectionRef.current = selection;
78082
78285
  setDomEditHoverSelection(selection);
78083
78286
  }, []);
78084
- const buildDomSelectionForTimelineElement = useCallback125(
78287
+ const buildDomSelectionForTimelineElement = useCallback126(
78085
78288
  // fallow-ignore-next-line complexity
78086
78289
  async (element) => {
78087
78290
  const iframe = previewIframeRef.current;
@@ -78104,7 +78307,7 @@ function useDomSelection({
78104
78307
  },
78105
78308
  [activeCompPath, buildDomSelectionFromTarget, compIdToSrc, isMasterView, previewIframeRef]
78106
78309
  );
78107
- const handleTimelineElementSelect = useCallback125(
78310
+ const handleTimelineElementSelect = useCallback126(
78108
78311
  async (element) => {
78109
78312
  const seq = ++timelineSelectSeqRef.current;
78110
78313
  if (!element) {
@@ -78123,7 +78326,7 @@ function useDomSelection({
78123
78326
  );
78124
78327
  const refreshDomEditGroupSelectionsFromPreviewRef = useRef141(async () => {
78125
78328
  });
78126
- const refreshDomEditSelectionFromPreview = useCallback125(
78329
+ const refreshDomEditSelectionFromPreview = useCallback126(
78127
78330
  // fallow-ignore-next-line complexity
78128
78331
  async (selection) => {
78129
78332
  const iframe = previewIframeRef.current;
@@ -78158,7 +78361,7 @@ function useDomSelection({
78158
78361
  },
78159
78362
  [activeCompPath, applyDomSelection, buildDomSelectionFromTarget, previewIframeRef]
78160
78363
  );
78161
- const refreshDomEditGroupSelectionsFromPreview = useCallback125(
78364
+ const refreshDomEditGroupSelectionsFromPreview = useCallback126(
78162
78365
  // fallow-ignore-next-line complexity
78163
78366
  async (selections) => {
78164
78367
  const iframe = previewIframeRef.current;
@@ -78217,7 +78420,7 @@ function useDomSelection({
78217
78420
  applyDomSelection(null, { revealPanel: false });
78218
78421
  }, [applyDomSelection, captionEditMode]);
78219
78422
  useStudioTestHooks({ previewIframeRef, buildDomSelectionFromTarget, applyDomSelection });
78220
- const applyMarqueeSelection = useCallback125(
78423
+ const applyMarqueeSelection = useCallback126(
78221
78424
  // fallow-ignore-next-line complexity
78222
78425
  (selections, additive) => {
78223
78426
  logSelect("marquee", { hits: selections.length, additive });
@@ -78283,7 +78486,7 @@ function useDomSelection({
78283
78486
  }
78284
78487
 
78285
78488
  // src/hooks/usePreviewInteraction.ts
78286
- import { useCallback as useCallback126, useRef as useRef142 } from "react";
78489
+ import { useCallback as useCallback127, useRef as useRef142 } from "react";
78287
78490
  var CYCLE_RADIUS_PX = 6;
78288
78491
  var CYCLE_WINDOW_MS = 600;
78289
78492
  var DOUBLE_CLICK_MS = 400;
@@ -78302,7 +78505,7 @@ function usePreviewInteraction({
78302
78505
  }) {
78303
78506
  const cycleRef = useRef142(null);
78304
78507
  const lastDownRef = useRef142(null);
78305
- const pausePreviewPlayback = useCallback126(() => {
78508
+ const pausePreviewPlayback = useCallback127(() => {
78306
78509
  const pausedTime = pauseStudioPreviewPlayback(previewIframeRef.current);
78307
78510
  const playerStore = usePlayerStore.getState();
78308
78511
  playerStore.setIsPlaying(false);
@@ -78311,7 +78514,7 @@ function usePreviewInteraction({
78311
78514
  liveTime.notify(pausedTime);
78312
78515
  }
78313
78516
  }, [previewIframeRef]);
78314
- const handlePreviewCanvasMouseDown = useCallback126(
78517
+ const handlePreviewCanvasMouseDown = useCallback127(
78315
78518
  // fallow-ignore-next-line complexity
78316
78519
  async (e, options) => {
78317
78520
  if (captionEditMode || compositionLoading) return;
@@ -78414,7 +78617,7 @@ function usePreviewInteraction({
78414
78617
  setActiveGroupElement
78415
78618
  ]
78416
78619
  );
78417
- const handlePreviewCanvasPointerMove = useCallback126(
78620
+ const handlePreviewCanvasPointerMove = useCallback127(
78418
78621
  // fallow-ignore-next-line complexity
78419
78622
  async (e, options) => {
78420
78623
  if (captionEditMode || compositionLoading) {
@@ -78435,10 +78638,10 @@ function usePreviewInteraction({
78435
78638
  updateDomEditHoverSelection
78436
78639
  ]
78437
78640
  );
78438
- const handlePreviewCanvasPointerLeave = useCallback126(() => {
78641
+ const handlePreviewCanvasPointerLeave = useCallback127(() => {
78439
78642
  updateDomEditHoverSelection(null);
78440
78643
  }, [updateDomEditHoverSelection]);
78441
- const handleBlockedDomMove = useCallback126(
78644
+ const handleBlockedDomMove = useCallback127(
78442
78645
  (selection, reason) => {
78443
78646
  showToast(
78444
78647
  reason ?? selection.capabilities.reasonIfDisabled ?? "This element can't be adjusted directly from the preview.",
@@ -78447,7 +78650,7 @@ function usePreviewInteraction({
78447
78650
  },
78448
78651
  [showToast]
78449
78652
  );
78450
- const handleDomManualDragStart = useCallback126(() => {
78653
+ const handleDomManualDragStart = useCallback127(() => {
78451
78654
  pausePreviewPlayback();
78452
78655
  }, [pausePreviewPlayback]);
78453
78656
  return {
@@ -78460,7 +78663,7 @@ function usePreviewInteraction({
78460
78663
  }
78461
78664
 
78462
78665
  // src/hooks/useDomEditCommits.ts
78463
- import { useCallback as useCallback132 } from "react";
78666
+ import { useCallback as useCallback133 } from "react";
78464
78667
  import { findUnsafeDomPatchValues as findUnsafeDomPatchValues3 } from "@hyperframes/core/studio-api/finite-mutation";
78465
78668
 
78466
78669
  // src/utils/studioFontHelpers.ts
@@ -78513,69 +78716,8 @@ function importedFontFaceCssFor(asset, sourceFile) {
78513
78716
  return importedFontFaceCss(asset, toRelativeProjectAssetPath(sourceFile, asset.path));
78514
78717
  }
78515
78718
 
78516
- // src/hooks/domEditPersistFailure.ts
78517
- var DomEditPersistUnresolvableError = class extends Error {
78518
- constructor(targetPath) {
78519
- super(`Couldn't find this element in the source file (${targetPath})`);
78520
- this.name = "DomEditPersistUnresolvableError";
78521
- }
78522
- };
78523
- var DomEditPersistUnsafeValueError = class extends Error {
78524
- alreadyToasted;
78525
- constructor(message, options = {}) {
78526
- super(message);
78527
- this.name = "DomEditPersistUnsafeValueError";
78528
- this.alreadyToasted = options.alreadyToasted ?? false;
78529
- }
78530
- };
78531
- var DomEditPersistPreparedWriteError = class extends Error {
78532
- alreadyToasted = true;
78533
- constructor(targetPath) {
78534
- super(`The edit saved but its follow-up write to ${targetPath} did not`);
78535
- this.name = "DomEditPersistPreparedWriteError";
78536
- }
78537
- };
78538
- function summarizeOperations(operations) {
78539
- return operations.map((op) => `${op.type}:${op.property}`).join(", ");
78540
- }
78541
- function getTargetTuple(selection) {
78542
- return {
78543
- hfId: selection.hfId,
78544
- id: selection.id,
78545
- selector: selection.selector,
78546
- selectorIndex: selection.selectorIndex,
78547
- sourceFile: selection.sourceFile
78548
- };
78549
- }
78550
- function getErrorDetail(error) {
78551
- return error instanceof Error ? error.message : String(error);
78552
- }
78553
- function getSelectionLabel(selection) {
78554
- return selection.label || selection.selector || selection.id || "this element";
78555
- }
78556
- function wasAlreadyToasted(error) {
78557
- return (error instanceof DomEditPersistUnsafeValueError || error instanceof DomEditPersistPreparedWriteError || error instanceof StudioSaveHttpError) && error.alreadyToasted;
78558
- }
78559
- function reportDomEditPersistFailure(selection, operations, error, showToast) {
78560
- const detail = getErrorDetail(error);
78561
- console.warn("[Studio] DOM edit persist failed", {
78562
- target: getTargetTuple(selection),
78563
- operations: summarizeOperations(operations),
78564
- error: detail
78565
- });
78566
- if (wasAlreadyToasted(error)) return;
78567
- showToast(`Couldn't save "${getSelectionLabel(selection)}": ${detail}`, "error");
78568
- }
78569
- function warnDomEditPersistNoOp(selection, operations) {
78570
- console.warn("[Studio] DOM edit persist no-op", {
78571
- target: getTargetTuple(selection),
78572
- operations: summarizeOperations(operations),
78573
- detail: "Server matched the target but reported no change even though the client believed the value changed."
78574
- });
78575
- }
78576
-
78577
78719
  // src/hooks/useDomEditPersist.ts
78578
- import { useCallback as useCallback127, useRef as useRef143 } from "react";
78720
+ import { useCallback as useCallback128, useRef as useRef143 } from "react";
78579
78721
  import { findUnsafeDomPatchValues as findUnsafeDomPatchValues2 } from "@hyperframes/core/studio-api/finite-mutation";
78580
78722
 
78581
78723
  // src/hooks/gsapScriptCommitHelpers.ts
@@ -78800,7 +78942,7 @@ function useDomEditPersist({
78800
78942
  onTrySdkPersist
78801
78943
  }) {
78802
78944
  const reportedUnresolvableRef = useRef143(/* @__PURE__ */ new Set());
78803
- const performPersistDomEditOperations = useCallback127(
78945
+ const performPersistDomEditOperations = useCallback128(
78804
78946
  // fallow-ignore-next-line complexity
78805
78947
  async (selection, operations, options, expectedProjectId) => {
78806
78948
  if (projectIdRef.current !== expectedProjectId) {
@@ -78925,7 +79067,7 @@ function useDomEditPersist({
78925
79067
  previewIframeRef
78926
79068
  ]
78927
79069
  );
78928
- const persistDomEditOperations = useCallback127(
79070
+ const persistDomEditOperations = useCallback128(
78929
79071
  (selection, operations, options) => {
78930
79072
  const expectedProjectId = projectIdRef.current;
78931
79073
  if (!expectedProjectId) return Promise.reject(new Error("No active project"));
@@ -78940,13 +79082,13 @@ function useDomEditPersist({
78940
79082
  }
78941
79083
 
78942
79084
  // src/hooks/useDomEditPositionPatchCommit.ts
78943
- import { useCallback as useCallback128 } from "react";
79085
+ import { useCallback as useCallback129 } from "react";
78944
79086
  function useDomEditPositionPatchCommit({
78945
79087
  activeCompPath,
78946
79088
  persistDomEditOperations,
78947
79089
  showToast
78948
79090
  }) {
78949
- return useCallback128(
79091
+ return useCallback129(
78950
79092
  (selection, patches, options) => {
78951
79093
  return persistDomEditOperations(selection, patches, {
78952
79094
  label: options.label,
@@ -78977,7 +79119,7 @@ function useDomEditPositionPatchCommit({
78977
79119
  }
78978
79120
 
78979
79121
  // src/hooks/useDomEditTextCommits.ts
78980
- import { useCallback as useCallback130, useRef as useRef145 } from "react";
79122
+ import { useCallback as useCallback131, useRef as useRef145 } from "react";
78981
79123
 
78982
79124
  // src/hooks/refreshTimelineRowText.ts
78983
79125
  function refreshTimelineRowText(el) {
@@ -79129,7 +79271,7 @@ async function commitDomStyles(context, selection, stylesInEitherCase) {
79129
79271
  }
79130
79272
 
79131
79273
  // src/hooks/useDomEditAttributeCommits.ts
79132
- import { useCallback as useCallback129, useRef as useRef144 } from "react";
79274
+ import { useCallback as useCallback130, useRef as useRef144 } from "react";
79133
79275
  import { HF_AUDIO_GROUP_ATTR as HF_AUDIO_GROUP_ATTR6, HF_AUDIO_GROUP_TAG as HF_AUDIO_GROUP_TAG8 } from "@hyperframes/core/audio-groups";
79134
79276
  function resolveFullAttrName(attr, prefixData) {
79135
79277
  return prefixData && !attr.startsWith("data-") ? `data-${attr}` : attr;
@@ -79181,7 +79323,7 @@ function useDomEditAttributeCommits({
79181
79323
  persistDomEditOperations
79182
79324
  }) {
79183
79325
  const domAttributeCommitVersionRef = useRef144(/* @__PURE__ */ new Map());
79184
- const commitDataAttribute = useCallback129(
79326
+ const commitDataAttribute = useCallback130(
79185
79327
  async (attr, value, options) => {
79186
79328
  if (!domEditSelection) return;
79187
79329
  const iframe = previewIframeRef.current;
@@ -79241,7 +79383,7 @@ function useDomEditAttributeCommits({
79241
79383
  previewIframeRef
79242
79384
  ]
79243
79385
  );
79244
- const commitAttributeOps = useCallback129(
79386
+ const commitAttributeOps = useCallback130(
79245
79387
  async (selection, ops, options) => {
79246
79388
  const iframe = previewIframeRef.current;
79247
79389
  const entries = ops.map((op) => ({
@@ -79311,7 +79453,7 @@ function useDomEditAttributeCommits({
79311
79453
  previewIframeRef
79312
79454
  ]
79313
79455
  );
79314
- const handleDomAttributesCommit = useCallback129(
79456
+ const handleDomAttributesCommit = useCallback130(
79315
79457
  async (selection, attrs) => {
79316
79458
  await commitAttributeOps(
79317
79459
  selection,
@@ -79330,7 +79472,7 @@ function useDomEditAttributeCommits({
79330
79472
  },
79331
79473
  [commitAttributeOps]
79332
79474
  );
79333
- const handleDomAttributeBatchCommit = useCallback129(
79475
+ const handleDomAttributeBatchCommit = useCallback130(
79334
79476
  (selection, ops, options) => commitAttributeOps(selection, ops, {
79335
79477
  label: options.label,
79336
79478
  coalescePrefix: "attr-batch",
@@ -79341,7 +79483,7 @@ function useDomEditAttributeCommits({
79341
79483
  }),
79342
79484
  [commitAttributeOps]
79343
79485
  );
79344
- const handleDomAttributeCommit = useCallback129(
79486
+ const handleDomAttributeCommit = useCallback130(
79345
79487
  async (attr, value) => {
79346
79488
  await commitDataAttribute(attr, value, {
79347
79489
  label: `Edit ${attr.replace(/-/g, " ")}`,
@@ -79352,7 +79494,7 @@ function useDomEditAttributeCommits({
79352
79494
  },
79353
79495
  [commitDataAttribute]
79354
79496
  );
79355
- const handleDomAttributeLiveCommit = useCallback129(
79497
+ const handleDomAttributeLiveCommit = useCallback130(
79356
79498
  async (attr, value, onSettled, live) => {
79357
79499
  await commitDataAttribute(attr, value, {
79358
79500
  label: `Edit ${attr.replace(/^(data-)?/, "").replace(/-/g, " ")}`,
@@ -79364,7 +79506,7 @@ function useDomEditAttributeCommits({
79364
79506
  },
79365
79507
  [commitDataAttribute]
79366
79508
  );
79367
- const handleDomAttributeQuietCommit = useCallback129(
79509
+ const handleDomAttributeQuietCommit = useCallback130(
79368
79510
  async (attr, value) => {
79369
79511
  await commitDataAttribute(attr, value, {
79370
79512
  label: `Edit ${attr.replace(/^(data-)?/, "").replace(/-/g, " ")}`,
@@ -79375,7 +79517,7 @@ function useDomEditAttributeCommits({
79375
79517
  },
79376
79518
  [commitDataAttribute]
79377
79519
  );
79378
- const handleDomHtmlAttributeCommit = useCallback129(
79520
+ const handleDomHtmlAttributeCommit = useCallback130(
79379
79521
  async (attr, value) => {
79380
79522
  if (!domEditSelection) return;
79381
79523
  const iframe = previewIframeRef.current;
@@ -79492,7 +79634,7 @@ function useDomEditTextCommits({
79492
79634
  refreshDomEditSelectionFromPreview,
79493
79635
  persistDomEditOperations
79494
79636
  });
79495
- const handleDomStyleCommitForSelection = useCallback130(
79637
+ const handleDomStyleCommitForSelection = useCallback131(
79496
79638
  (selection, propertyOrStylesAsOnePatch, value = "") => commitDomStyles(
79497
79639
  {
79498
79640
  activeCompPath,
@@ -79515,11 +79657,11 @@ function useDomEditTextCommits({
79515
79657
  previewIframeRef
79516
79658
  ]
79517
79659
  );
79518
- const handleDomStyleCommit = useCallback130(
79660
+ const handleDomStyleCommit = useCallback131(
79519
79661
  (property, value) => domEditSelection ? handleDomStyleCommitForSelection(domEditSelection, property, value) : Promise.resolve(domEditCommitDeclined("no-selection")),
79520
79662
  [domEditSelection, handleDomStyleCommitForSelection]
79521
79663
  );
79522
- const handleDomTextCommitForSelection = useCallback130(
79664
+ const handleDomTextCommitForSelection = useCallback131(
79523
79665
  async (selection, value, fieldKey) => {
79524
79666
  if (!isTextEditableSelection(selection)) {
79525
79667
  return domEditCommitDeclined("not-text-editable");
@@ -79581,11 +79723,11 @@ function useDomEditTextCommits({
79581
79723
  showToast
79582
79724
  ]
79583
79725
  );
79584
- const handleDomTextCommit = useCallback130(
79726
+ const handleDomTextCommit = useCallback131(
79585
79727
  (value, fieldKey) => domEditSelection ? handleDomTextCommitForSelection(domEditSelection, value, fieldKey) : Promise.resolve(domEditCommitDeclined("no-selection")),
79586
79728
  [domEditSelection, handleDomTextCommitForSelection]
79587
79729
  );
79588
- const handleDomRichTextCommit = useCallback130(
79730
+ const handleDomRichTextCommit = useCallback131(
79589
79731
  async ({ element, html: html2, previousHtml }) => {
79590
79732
  const putBack = () => {
79591
79733
  if (element.isConnected && element.innerHTML === html2) element.innerHTML = previousHtml;
@@ -79659,7 +79801,7 @@ function useDomEditTextCommits({
79659
79801
  showToast
79660
79802
  ]
79661
79803
  );
79662
- const commitDomTextFields = useCallback130(
79804
+ const commitDomTextFields = useCallback131(
79663
79805
  async (selection, nextTextFields, options) => {
79664
79806
  const isLatestTextCommit = bumpDomEditCommitMapVersion(
79665
79807
  domTextCommitVersionRef.current,
@@ -79724,7 +79866,7 @@ function useDomEditTextCommits({
79724
79866
  showToast
79725
79867
  ]
79726
79868
  );
79727
- const handleDomTextFieldStyleCommit = useCallback130(
79869
+ const handleDomTextFieldStyleCommit = useCallback131(
79728
79870
  async (fieldKey, property, value) => {
79729
79871
  if (!domEditSelection) return;
79730
79872
  const field = domEditSelection.textFields.find((entry2) => entry2.key === fieldKey);
@@ -79765,7 +79907,7 @@ function useDomEditTextCommits({
79765
79907
  previewIframeRef
79766
79908
  ]
79767
79909
  );
79768
- const handleDomAddTextField = useCallback130(
79910
+ const handleDomAddTextField = useCallback131(
79769
79911
  async (afterFieldKey) => {
79770
79912
  if (!domEditSelection) return null;
79771
79913
  if (!domEditSelection.textFields.some((field) => field.source === "child")) return null;
@@ -79785,7 +79927,7 @@ function useDomEditTextCommits({
79785
79927
  },
79786
79928
  [commitDomTextFields, domEditSelection]
79787
79929
  );
79788
- const handleDomRemoveTextField = useCallback130(
79930
+ const handleDomRemoveTextField = useCallback131(
79789
79931
  async (fieldKey) => {
79790
79932
  if (!domEditSelection) return;
79791
79933
  const field = domEditSelection.textFields.find((entry2) => entry2.key === fieldKey);
@@ -79819,7 +79961,7 @@ function useDomEditTextCommits({
79819
79961
  }
79820
79962
 
79821
79963
  // src/hooks/useDomGeometryCommits.ts
79822
- import { useCallback as useCallback131 } from "react";
79964
+ import { useCallback as useCallback132 } from "react";
79823
79965
 
79824
79966
  // src/components/editor/elementPositionOffset.ts
79825
79967
  var layoutBox = (el) => ({
@@ -79962,7 +80104,7 @@ function useDomGeometryCommits({
79962
80104
  commitPositionPatchToHtml,
79963
80105
  readOnlyPreview
79964
80106
  }) {
79965
- const stageElementPositionOffset = useCallback131(
80107
+ const stageElementPositionOffset = useCallback132(
79966
80108
  (selection, next, plainTranslate, coalesceKey) => stageElementOffset(
79967
80109
  { commitPositionPatchToHtml, showToast, readOnlyPreview },
79968
80110
  selection,
@@ -79972,7 +80114,7 @@ function useDomGeometryCommits({
79972
80114
  ),
79973
80115
  [commitPositionPatchToHtml, readOnlyPreview, showToast]
79974
80116
  );
79975
- const handleDomBoxSizeCommit = useCallback131(
80117
+ const handleDomBoxSizeCommit = useCallback132(
79976
80118
  (selection, next, offset, restore2, undoKey) => {
79977
80119
  if (readOnlyPreview) return Promise.resolve(void 0);
79978
80120
  const element = selection.element;
@@ -79997,11 +80139,11 @@ function useDomGeometryCommits({
79997
80139
  },
79998
80140
  [commitPositionPatchToHtml, readOnlyPreview]
79999
80141
  );
80000
- const handleDomRotationCommit = useCallback131(
80142
+ const handleDomRotationCommit = useCallback132(
80001
80143
  (selection, next) => savePlainRotation({ commitPositionPatchToHtml, readOnlyPreview }, selection, next),
80002
80144
  [commitPositionPatchToHtml, readOnlyPreview]
80003
80145
  );
80004
- const handleDomManualEditsReset = useCallback131(
80146
+ const handleDomManualEditsReset = useCallback132(
80005
80147
  (selection) => {
80006
80148
  const element = selection.element;
80007
80149
  const beforeOffset = captureStudioPathOffset(element);
@@ -80060,7 +80202,7 @@ function useDomEditCommits({
80060
80202
  onReorderShadow,
80061
80203
  readOnlyPreview
80062
80204
  }) {
80063
- const resolveImportedFontAsset = useCallback132(
80205
+ const resolveImportedFontAsset = useCallback133(
80064
80206
  (fontFamilyValue) => {
80065
80207
  const family = primaryFontFamilyValue(fontFamilyValue);
80066
80208
  if (!family) return null;
@@ -80092,7 +80234,7 @@ function useDomEditCommits({
80092
80234
  forceReloadSdkSession,
80093
80235
  onTrySdkPersist
80094
80236
  });
80095
- const commitDomEditPatchBatches = useCallback132(
80237
+ const commitDomEditPatchBatches = useCallback133(
80096
80238
  (batches, options) => {
80097
80239
  const expectedProjectId = projectIdRef.current;
80098
80240
  if (!expectedProjectId) return Promise.reject(new Error("No active project"));
@@ -80258,7 +80400,7 @@ function useDomEditCommits({
80258
80400
  }
80259
80401
 
80260
80402
  // src/hooks/useGroupCommits.ts
80261
- import { useCallback as useCallback133 } from "react";
80403
+ import { useCallback as useCallback134 } from "react";
80262
80404
  function resolveAuthoredTrack(selection, timelineElements) {
80263
80405
  const id = findMatchingTimelineElementId(selection, timelineElements);
80264
80406
  if (!id) return void 0;
@@ -80337,7 +80479,7 @@ async function commitStructuralMutation(pid, targetPath, route, body, label, dep
80337
80479
  }
80338
80480
  function useGroupCommits(params) {
80339
80481
  const { activeCompPath, showToast, projectIdRef, timelineElements } = params;
80340
- const groupSelection = useCallback133(
80482
+ const groupSelection = useCallback134(
80341
80483
  async (members) => {
80342
80484
  const pid = projectIdRef.current;
80343
80485
  if (!pid || members.length === 0) return null;
@@ -80366,7 +80508,7 @@ function useGroupCommits(params) {
80366
80508
  },
80367
80509
  [activeCompPath, projectIdRef, showToast, params, timelineElements]
80368
80510
  );
80369
- const ungroupSelection = useCallback133(
80511
+ const ungroupSelection = useCallback134(
80370
80512
  async (group) => {
80371
80513
  const pid = projectIdRef.current;
80372
80514
  if (!pid) return;
@@ -80391,7 +80533,7 @@ function useGroupCommits(params) {
80391
80533
  }
80392
80534
 
80393
80535
  // src/hooks/useGsapScriptCommits.ts
80394
- import { useCallback as useCallback139, useMemo as useMemo73, useRef as useRef147 } from "react";
80536
+ import { useCallback as useCallback140, useMemo as useMemo73, useRef as useRef147 } from "react";
80395
80537
  import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
80396
80538
 
80397
80539
  // src/hooks/gsapRuntimePatch.ts
@@ -80591,7 +80733,7 @@ function gsapWriteFile(selection, activeCompPath) {
80591
80733
  }
80592
80734
 
80593
80735
  // src/hooks/useGsapAnimationOps.ts
80594
- import { useCallback as useCallback134 } from "react";
80736
+ import { useCallback as useCallback135 } from "react";
80595
80737
  function useGsapAnimationOps({
80596
80738
  projectIdRef,
80597
80739
  activeCompPath,
@@ -80602,7 +80744,7 @@ function useGsapAnimationOps({
80602
80744
  sdkDeps,
80603
80745
  writeProjectFile
80604
80746
  }) {
80605
- const updateGsapMeta = useCallback134(
80747
+ const updateGsapMeta = useCallback135(
80606
80748
  async (selection, animationId, updates) => {
80607
80749
  if (sdkSession && sdkDeps) {
80608
80750
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -80623,7 +80765,7 @@ function useGsapAnimationOps({
80623
80765
  },
80624
80766
  [commitMutationSafely, activeCompPath, sdkSession, sdkDeps]
80625
80767
  );
80626
- const deleteGsapAnimation = useCallback134(
80768
+ const deleteGsapAnimation = useCallback135(
80627
80769
  async (selection, animationId) => {
80628
80770
  if (sdkSession && sdkDeps) {
80629
80771
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -80644,7 +80786,7 @@ function useGsapAnimationOps({
80644
80786
  },
80645
80787
  [commitMutationSafely, activeCompPath, sdkSession, sdkDeps]
80646
80788
  );
80647
- const deleteAllForSelector = useCallback134(
80789
+ const deleteAllForSelector = useCallback135(
80648
80790
  async (selection, targetSelector) => {
80649
80791
  if (sdkSession && sdkDeps) {
80650
80792
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -80665,7 +80807,7 @@ function useGsapAnimationOps({
80665
80807
  },
80666
80808
  [commitMutation, activeCompPath, sdkSession, sdkDeps]
80667
80809
  );
80668
- const addGsapAnimation = useCallback134(
80810
+ const addGsapAnimation = useCallback135(
80669
80811
  // fallow-ignore-next-line complexity
80670
80812
  async (selection, method, _currentTime) => {
80671
80813
  const address = ensureElementAddressable(selection);
@@ -80740,7 +80882,7 @@ function useGsapAnimationOps({
80740
80882
  writeProjectFile
80741
80883
  ]
80742
80884
  );
80743
- const addWithKeyframes = useCallback134(
80885
+ const addWithKeyframes = useCallback135(
80744
80886
  async (selection, targetSelector, position, duration, keyframes, ease, label = "Add animation with keyframes") => {
80745
80887
  if (sdkSession && sdkDeps) {
80746
80888
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -80772,7 +80914,7 @@ function useGsapAnimationOps({
80772
80914
  },
80773
80915
  [commitMutation, activeCompPath, sdkSession, sdkDeps]
80774
80916
  );
80775
- const replaceWithKeyframes = useCallback134(
80917
+ const replaceWithKeyframes = useCallback135(
80776
80918
  async (selection, animationId, targetSelector, position, duration, keyframes, ease, label = "Replace animation with keyframes") => {
80777
80919
  if (sdkSession && sdkDeps) {
80778
80920
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -80817,9 +80959,9 @@ function useGsapAnimationOps({
80817
80959
  }
80818
80960
 
80819
80961
  // src/hooks/useGsapArcPathOps.ts
80820
- import { useCallback as useCallback135 } from "react";
80962
+ import { useCallback as useCallback136 } from "react";
80821
80963
  function useGsapArcPathOps(commitMutationSafely) {
80822
- const setArcPath = useCallback135(
80964
+ const setArcPath = useCallback136(
80823
80965
  (selection, animationId, config) => {
80824
80966
  commitMutationSafely(
80825
80967
  selection,
@@ -80829,7 +80971,7 @@ function useGsapArcPathOps(commitMutationSafely) {
80829
80971
  },
80830
80972
  [commitMutationSafely]
80831
80973
  );
80832
- const updateArcSegment = useCallback135(
80974
+ const updateArcSegment = useCallback136(
80833
80975
  (selection, animationId, segmentIndex, update) => {
80834
80976
  commitMutationSafely(
80835
80977
  selection,
@@ -80839,7 +80981,7 @@ function useGsapArcPathOps(commitMutationSafely) {
80839
80981
  },
80840
80982
  [commitMutationSafely]
80841
80983
  );
80842
- const removeArcPath = useCallback135(
80984
+ const removeArcPath = useCallback136(
80843
80985
  (selection, animationId) => {
80844
80986
  commitMutationSafely(
80845
80987
  selection,
@@ -80853,7 +80995,7 @@ function useGsapArcPathOps(commitMutationSafely) {
80853
80995
  }
80854
80996
 
80855
80997
  // src/hooks/useGsapKeyframeOps.ts
80856
- import { useCallback as useCallback136 } from "react";
80998
+ import { useCallback as useCallback137 } from "react";
80857
80999
 
80858
81000
  // src/utils/optimisticUpdate.ts
80859
81001
  async function executeOptimistic(options) {
@@ -80954,7 +81096,7 @@ function useGsapKeyframeOps({
80954
81096
  sdkSession,
80955
81097
  sdkDeps
80956
81098
  }) {
80957
- const addKeyframe = useCallback136(
81099
+ const addKeyframe = useCallback137(
80958
81100
  (selection, animationId, percentage, property, value) => {
80959
81101
  const sourceFile = selection.sourceFile || activeCompPath || "index.html";
80960
81102
  const mutation = {
@@ -81019,7 +81161,7 @@ function useGsapKeyframeOps({
81019
81161
  },
81020
81162
  [activeCompPath, commitMutation, trackGsapSaveFailure, sdkSession, sdkDeps]
81021
81163
  );
81022
- const addKeyframeBatch = useCallback136(
81164
+ const addKeyframeBatch = useCallback137(
81023
81165
  async (selection, animationId, percentage, properties, commitOverrides) => {
81024
81166
  if (sdkSession && sdkDeps) {
81025
81167
  const sourceFile = selection.sourceFile || activeCompPath || "index.html";
@@ -81050,7 +81192,7 @@ function useGsapKeyframeOps({
81050
81192
  },
81051
81193
  [commitMutation, activeCompPath, sdkSession, sdkDeps]
81052
81194
  );
81053
- const removeKeyframe = useCallback136(
81195
+ const removeKeyframe = useCallback137(
81054
81196
  (selection, animationId, percentage, commitOverrides) => {
81055
81197
  const sourceFile = selection.sourceFile || activeCompPath || "index.html";
81056
81198
  const mutation = { type: "remove-keyframe", animationId, percentage };
@@ -81090,7 +81232,7 @@ function useGsapKeyframeOps({
81090
81232
  },
81091
81233
  [activeCompPath, commitMutation, trackGsapSaveFailure, sdkSession, sdkDeps]
81092
81234
  );
81093
- const moveKeyframe = useCallback136(
81235
+ const moveKeyframe = useCallback137(
81094
81236
  async (selection, animationId, fromPercentage, toPercentage) => {
81095
81237
  const mutation = { type: "move-keyframe", animationId, fromPercentage, toPercentage };
81096
81238
  try {
@@ -81110,7 +81252,7 @@ function useGsapKeyframeOps({
81110
81252
  },
81111
81253
  [commitMutation, trackGsapSaveFailure]
81112
81254
  );
81113
- const resizeKeyframedTween = useCallback136(
81255
+ const resizeKeyframedTween = useCallback137(
81114
81256
  async (selection, animationId, position, duration, pctRemap) => {
81115
81257
  const mutation = {
81116
81258
  type: "resize-keyframed-tween",
@@ -81136,7 +81278,7 @@ function useGsapKeyframeOps({
81136
81278
  },
81137
81279
  [commitMutation, trackGsapSaveFailure]
81138
81280
  );
81139
- const convertToKeyframes = useCallback136(
81281
+ const convertToKeyframes = useCallback137(
81140
81282
  async (selection, animationId, resolvedFromValues, duration, commitOverrides = { softReload: true }) => {
81141
81283
  if (sdkSession && sdkDeps) {
81142
81284
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
@@ -81164,7 +81306,7 @@ function useGsapKeyframeOps({
81164
81306
  },
81165
81307
  [commitMutation, activeCompPath, sdkSession, sdkDeps]
81166
81308
  );
81167
- const removeAllKeyframes = useCallback136(
81309
+ const removeAllKeyframes = useCallback137(
81168
81310
  async (selection, animationId, action = "remove_all", telemetry = true) => {
81169
81311
  const targetPath = selection.sourceFile || activeCompPath || "index.html";
81170
81312
  const clearCache = () => {
@@ -81208,7 +81350,7 @@ function useGsapKeyframeOps({
81208
81350
  },
81209
81351
  [commitMutationSafely, activeCompPath, sdkSession, sdkDeps]
81210
81352
  );
81211
- const commitKeyframeAtTime = useCallback136(
81353
+ const commitKeyframeAtTime = useCallback137(
81212
81354
  (selection, absoluteTime, animations, properties) => commitKeyframeAtTimeImpl(selection, absoluteTime, animations, properties, commitMutation),
81213
81355
  [commitMutation]
81214
81356
  );
@@ -81225,7 +81367,7 @@ function useGsapKeyframeOps({
81225
81367
  }
81226
81368
 
81227
81369
  // src/hooks/useGsapPropertyDebounce.ts
81228
- import { useCallback as useCallback137, useEffect as useEffect118, useRef as useRef146 } from "react";
81370
+ import { useCallback as useCallback138, useEffect as useEffect118, useRef as useRef146 } from "react";
81229
81371
  import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
81230
81372
  var DEBOUNCE_MS = 150;
81231
81373
  function mergeTweenProperties(sdkSession, animationId, edited, kind) {
@@ -81245,7 +81387,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81245
81387
  const debounceTimerRef = useRef146(null);
81246
81388
  const sdkRef = useRef146(sdk);
81247
81389
  sdkRef.current = sdk;
81248
- const flushPendingPropertyEdit = useCallback137(async () => {
81390
+ const flushPendingPropertyEdit = useCallback138(async () => {
81249
81391
  const pending2 = pendingPropertyEditRef.current;
81250
81392
  if (!pending2) return;
81251
81393
  pendingPropertyEditRef.current = null;
@@ -81285,7 +81427,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81285
81427
  sdkRef.current?.onFlushError?.(error, selection, mutation, label);
81286
81428
  }
81287
81429
  }, [commitMutationSafely]);
81288
- const updateGsapProperty = useCallback137(
81430
+ const updateGsapProperty = useCallback138(
81289
81431
  (selection, animationId, property, value) => {
81290
81432
  pendingPropertyEditRef.current = { selection, animationId, property, value };
81291
81433
  if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
@@ -81301,7 +81443,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81301
81443
  void flushPendingPropertyEdit();
81302
81444
  };
81303
81445
  }, [flushPendingPropertyEdit]);
81304
- const addGsapProperty = useCallback137(
81446
+ const addGsapProperty = useCallback138(
81305
81447
  // fallow-ignore-next-line complexity
81306
81448
  async (selection, animationId, property) => {
81307
81449
  let defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
@@ -81345,7 +81487,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81345
81487
  },
81346
81488
  [commitMutationSafely]
81347
81489
  );
81348
- const removeProperty = useCallback137(
81490
+ const removeProperty = useCallback138(
81349
81491
  async (selection, animationId, property, from) => {
81350
81492
  const { sdkSession, sdkDeps, activeCompPath } = sdkRef.current ?? {};
81351
81493
  if (sdkSession && sdkDeps) {
@@ -81381,11 +81523,11 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81381
81523
  },
81382
81524
  [commitMutationSafely]
81383
81525
  );
81384
- const removeGsapProperty = useCallback137(
81526
+ const removeGsapProperty = useCallback138(
81385
81527
  (selection, animationId, property) => removeProperty(selection, animationId, property, false),
81386
81528
  [removeProperty]
81387
81529
  );
81388
- const updateGsapFromProperty = useCallback137(
81530
+ const updateGsapFromProperty = useCallback138(
81389
81531
  async (selection, animationId, property, value) => {
81390
81532
  const { sdkSession, sdkDeps, activeCompPath } = sdkRef.current ?? {};
81391
81533
  if (sdkSession && sdkDeps) {
@@ -81424,7 +81566,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81424
81566
  },
81425
81567
  [commitMutationSafely]
81426
81568
  );
81427
- const addGsapFromProperty = useCallback137(
81569
+ const addGsapFromProperty = useCallback138(
81428
81570
  async (selection, animationId, property) => {
81429
81571
  const defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
81430
81572
  const { sdkSession, sdkDeps, activeCompPath } = sdkRef.current ?? {};
@@ -81458,7 +81600,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81458
81600
  },
81459
81601
  [commitMutationSafely]
81460
81602
  );
81461
- const removeGsapFromProperty = useCallback137(
81603
+ const removeGsapFromProperty = useCallback138(
81462
81604
  (selection, animationId, property) => removeProperty(selection, animationId, property, true),
81463
81605
  [removeProperty]
81464
81606
  );
@@ -81473,12 +81615,12 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
81473
81615
  }
81474
81616
 
81475
81617
  // src/hooks/useSafeGsapCommitMutation.ts
81476
- import { useCallback as useCallback138 } from "react";
81618
+ import { useCallback as useCallback139 } from "react";
81477
81619
  function getGsapMutationType(mutation) {
81478
81620
  return typeof mutation.type === "string" ? mutation.type : "gsap";
81479
81621
  }
81480
81622
  function useGsapSaveFailureTelemetry(activeCompPath) {
81481
- return useCallback138(
81623
+ return useCallback139(
81482
81624
  (error, selection, mutation, label) => {
81483
81625
  trackStudioSaveFailure({
81484
81626
  source: "gsap_commit",
@@ -81495,7 +81637,7 @@ function useGsapSaveFailureTelemetry(activeCompPath) {
81495
81637
  );
81496
81638
  }
81497
81639
  function useSafeGsapCommitMutation(commitMutation, trackGsapSaveFailure, showToast) {
81498
- return useCallback138(
81640
+ return useCallback139(
81499
81641
  (selection, mutation, options) => (
81500
81642
  // Return the chain so awaiting consumers (gesture commit, enable-keyframes)
81501
81643
  // run their post-actions AFTER the server save settles, not immediately.
@@ -81669,7 +81811,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81669
81811
  const activeCompPathRef = useRef147(activeCompPath);
81670
81812
  activeCompPathRef.current = activeCompPath;
81671
81813
  const serializerRef = useRef147(createKeyedSerializer());
81672
- const recordMutationEdit = useCallback139(async (targetPath, result, options) => {
81814
+ const recordMutationEdit = useCallback140(async (targetPath, result, options) => {
81673
81815
  if (result.before == null || result.after == null) return;
81674
81816
  await editHistory.recordEdit({
81675
81817
  label: options.label,
@@ -81678,7 +81820,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81678
81820
  files: { [targetPath]: { before: result.before, after: result.after } }
81679
81821
  });
81680
81822
  }, [editHistory]);
81681
- const finalizeSuccessfulMutation = useCallback139(async (projectId, compositionPath, selection, mutation, targetPath, result, options) => {
81823
+ const finalizeSuccessfulMutation = useCallback140(async (projectId, compositionPath, selection, mutation, targetPath, result, options) => {
81682
81824
  if (projectIdRef.current !== projectId) return;
81683
81825
  const previewIsActive = isActiveCommitTarget(
81684
81826
  projectIdRef,
@@ -81715,7 +81857,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81715
81857
  nestedFiles
81716
81858
  });
81717
81859
  }, [projectIdRef, previewIframeRef, reloadPreview, onCacheInvalidate, onFileContentChanged, forceReloadSdkSession, recordMutationEdit]);
81718
- const runCommit = useCallback139(async (pid, compositionPath, targetPath, selection, mutation, options) => {
81860
+ const runCommit = useCallback140(async (pid, compositionPath, targetPath, selection, mutation, options) => {
81719
81861
  const result = await runMutationRequest(
81720
81862
  [mutation],
81721
81863
  options,
@@ -81727,7 +81869,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81727
81869
  options.onResult?.(result);
81728
81870
  await finalizeSuccessfulMutation(pid, compositionPath, selection, mutation, targetPath, result, options);
81729
81871
  }, [showToast, finalizeSuccessfulMutation]);
81730
- const runBatchCommit = useCallback139(async (pid, compositionPath, targetPath, calls, options) => {
81872
+ const runBatchCommit = useCallback140(async (pid, compositionPath, targetPath, calls, options) => {
81731
81873
  const first = calls[0];
81732
81874
  const last = calls.at(-1);
81733
81875
  if (!first || !last) return;
@@ -81784,7 +81926,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81784
81926
  return commit2;
81785
81927
  }, [runCommit, runBatchCommit, activeCompPath, activeProjectId, writeProjectFile]);
81786
81928
  const trackGsapSaveFailure = useGsapSaveFailureTelemetry(activeCompPath);
81787
- const handleGsapSaveFailure = useCallback139(
81929
+ const handleGsapSaveFailure = useCallback140(
81788
81930
  (error, selection, mutation, label) => {
81789
81931
  trackGsapSaveFailure(error, selection, mutation, label);
81790
81932
  if (!isStudioSaveErrorAlreadyToasted(error)) {
@@ -81794,7 +81936,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81794
81936
  [showToast, trackGsapSaveFailure]
81795
81937
  );
81796
81938
  const commitMutationSafely = useSafeGsapCommitMutation(commitMutation, handleGsapSaveFailure);
81797
- const sdkRefresh = useCallback139(
81939
+ const sdkRefresh = useCallback140(
81798
81940
  (after) => {
81799
81941
  const script = extractGsapScriptText(after);
81800
81942
  if (script) {
@@ -81806,7 +81948,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
81806
81948
  },
81807
81949
  [previewIframeRef, reloadPreview, onCacheInvalidate]
81808
81950
  );
81809
- const readProjectFileContent2 = useCallback139(
81951
+ const readProjectFileContent2 = useCallback140(
81810
81952
  (path) => {
81811
81953
  if (!activeProjectId) throw new Error("No active project");
81812
81954
  return readProjectFileContent(activeProjectId, path);
@@ -81959,7 +82101,7 @@ function useDomEditPreviewSync({
81959
82101
  }
81960
82102
 
81961
82103
  // src/hooks/useGsapAnimationFetchFallback.ts
81962
- import { useCallback as useCallback140 } from "react";
82104
+ import { useCallback as useCallback141 } from "react";
81963
82105
  var FETCH_ERROR_RETRIES = 1;
81964
82106
  var FETCH_ERROR_DELAY_MS = 120;
81965
82107
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -81987,7 +82129,7 @@ async function fetchElementAnimationsWithRetry(projectId, gsapSourceFile, target
81987
82129
  }
81988
82130
  }
81989
82131
  function useGsapAnimationFetchFallback(projectId) {
81990
- return useCallback140(
82132
+ return useCallback141(
81991
82133
  (selection, options) => async () => {
81992
82134
  if (!projectId) return [];
81993
82135
  const target = { id: selection.id ?? null, selector: selection.selector ?? null };
@@ -82004,33 +82146,6 @@ function useGsapAnimationFetchFallback(projectId) {
82004
82146
  );
82005
82147
  }
82006
82148
 
82007
- // src/hooks/useGsapInteractionFailureTelemetry.ts
82008
- import { useCallback as useCallback141 } from "react";
82009
- function failureToast(error, toast) {
82010
- if (!toast || wasAlreadyToasted(error)) return null;
82011
- return isGsapEditBlockedError(error) ? error.message : "Failed to save animated edit.";
82012
- }
82013
- function useGsapInteractionFailureTelemetry(activeCompPath, showToast) {
82014
- return useCallback141(
82015
- (error, selection, mutationType, label, toast) => {
82016
- const report = isGsapEditBlockedError(error) ? trackStudioEditBlocked : trackStudioSaveFailure;
82017
- report({
82018
- source: "gsap_commit",
82019
- error,
82020
- filePath: selection?.sourceFile ?? activeCompPath ?? "index.html",
82021
- mutationType,
82022
- label,
82023
- targetId: selection?.id,
82024
- targetSelector: selection?.selector,
82025
- targetSourceFile: selection?.sourceFile
82026
- });
82027
- const message = failureToast(error, toast !== false);
82028
- if (message) showToast(message, "error");
82029
- },
82030
- [activeCompPath, showToast]
82031
- );
82032
- }
82033
-
82034
82149
  // src/hooks/useGsapSelectionHandlers.ts
82035
82150
  import { useCallback as useCallback142, useRef as useRef149 } from "react";
82036
82151
  function useGsapSelectionHandlers({
@@ -83818,7 +83933,7 @@ var MANUAL_FLAGS = [
83818
83933
  function refusal(preflight, check) {
83819
83934
  const outcome = preflight?.[check];
83820
83935
  if (!outcome) return "";
83821
- return outcome.status === "blocked" ? GSAP_EDIT_BLOCK_COPY[outcome.reason] : null;
83936
+ return outcome.status === "blocked" ? gsapEditBlockMessage(outcome.reason, outcome.detail) : null;
83822
83937
  }
83823
83938
  function narrowCapabilities(selection, preflight) {
83824
83939
  const resolved2 = resolvedOf(selection);