mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
fix(studio): save retries, mutation queue circuit breaker, save_failure diagnostics (#1366)
* fix(studio): save retries, mutation queue circuit breaker, save_failure diagnostics Save failures could silently drop user work: code-editor saves fired a single PUT with no retry, DOM-edit failures drained the whole queue against a failing server, and several failure paths only logged to the console. - Retry code-editor saves with exponential backoff instead of dropping the edit on the first failed PUT. - Circuit breaker on the DOM-edit save queue: a failing server pauses the queue with a user-visible error state instead of burning every queued mutation against it. - save_failure events now carry error_message, status_code, and source on every emission path; style/attribute DOM-edit failures that previously only logged to the console now emit telemetry too. - Route unawaited commitMutation call sites (GSAP drag, property scrubbing, undo/redo, text fields) through a safe wrapper that reports failures via telemetry instead of unhandledrejection. Follow-ups (deferred): version/ETag conflict guard on file PUTs, offline save queue. * fix(studio): narrow save retry changes for fallow
This commit is contained in:
@@ -21,8 +21,6 @@ import {
|
||||
useGsapAnimationsForElement,
|
||||
useGsapCacheVersion,
|
||||
usePopulateKeyframeCacheForFile,
|
||||
fetchParsedAnimations,
|
||||
getAnimationsForElement,
|
||||
} from "./useGsapTweenCache";
|
||||
import {
|
||||
tryGsapDragIntercept,
|
||||
@@ -30,6 +28,8 @@ import {
|
||||
tryGsapRotationIntercept,
|
||||
} from "./gsapRuntimeBridge";
|
||||
import { useAnimatedPropertyCommit } from "./useAnimatedPropertyCommit";
|
||||
import { useGsapAnimationFetchFallback } from "./useGsapAnimationFetchFallback";
|
||||
import { useGsapInteractionFailureTelemetry } from "./useGsapInteractionFailureTelemetry";
|
||||
import { useGsapSelectionHandlers } from "./useGsapSelectionHandlers";
|
||||
|
||||
// ── Types ──
|
||||
@@ -326,6 +326,10 @@ export function useDomEditSession({
|
||||
buildDomSelectionFromTarget,
|
||||
});
|
||||
|
||||
const trackGsapInteractionFailure = useGsapInteractionFailureTelemetry(activeCompPath, showToast);
|
||||
|
||||
const makeFetchFallback = useGsapAnimationFetchFallback(projectId, gsapSourceFile);
|
||||
|
||||
// GSAP-aware: intercept offset/resize/rotation to commit via script mutation when animated.
|
||||
const handleGsapAwarePathOffsetCommit = useCallback(
|
||||
async (selection: DomEditSelection, next: { x: number; y: number }) => {
|
||||
@@ -335,22 +339,20 @@ export function useDomEditSession({
|
||||
throw new Error(GSAP_CSS_FALLBACK_BLOCKED_MESSAGE);
|
||||
}
|
||||
if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
|
||||
const handled = await tryGsapDragIntercept(
|
||||
selection,
|
||||
next,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
async () => {
|
||||
const pid = projectId;
|
||||
if (!pid) return [];
|
||||
const parsed = await fetchParsedAnimations(pid, gsapSourceFile);
|
||||
if (!parsed) return [];
|
||||
const target = { id: selection.id ?? null, selector: selection.selector ?? null };
|
||||
return getAnimationsForElement(parsed.animations, target);
|
||||
},
|
||||
);
|
||||
if (handled) return;
|
||||
try {
|
||||
const handled = await tryGsapDragIntercept(
|
||||
selection,
|
||||
next,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
makeFetchFallback(selection),
|
||||
);
|
||||
if (handled) return;
|
||||
} catch (error) {
|
||||
trackGsapInteractionFailure(error, selection, "drag", "Move animated layer");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return handleDomPathOffsetCommit(selection, next);
|
||||
},
|
||||
@@ -359,38 +361,29 @@ export function useDomEditSession({
|
||||
selectedGsapAnimations,
|
||||
gsapCommitMutation,
|
||||
previewIframeRef,
|
||||
projectId,
|
||||
gsapSourceFile,
|
||||
makeFetchFallback,
|
||||
trackGsapInteractionFailure,
|
||||
showToast,
|
||||
],
|
||||
);
|
||||
|
||||
const makeFetchFallback = useCallback(
|
||||
(selection: DomEditSelection) => async () => {
|
||||
const pid = projectId;
|
||||
if (!pid) return [];
|
||||
const parsed = await fetchParsedAnimations(pid, gsapSourceFile);
|
||||
if (!parsed) return [];
|
||||
return getAnimationsForElement(parsed.animations, {
|
||||
id: selection.id ?? null,
|
||||
selector: selection.selector ?? null,
|
||||
});
|
||||
},
|
||||
[projectId, gsapSourceFile],
|
||||
);
|
||||
|
||||
const handleGsapAwareBoxSizeCommit = useCallback(
|
||||
async (selection: DomEditSelection, next: { width: number; height: number }) => {
|
||||
if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
|
||||
const handled = await tryGsapResizeIntercept(
|
||||
selection,
|
||||
next,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
makeFetchFallback(selection),
|
||||
);
|
||||
if (handled) return;
|
||||
try {
|
||||
const handled = await tryGsapResizeIntercept(
|
||||
selection,
|
||||
next,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
makeFetchFallback(selection),
|
||||
);
|
||||
if (handled) return;
|
||||
} catch (error) {
|
||||
trackGsapInteractionFailure(error, selection, "resize", "Resize animated layer");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return handleDomBoxSizeCommit(selection, next);
|
||||
},
|
||||
@@ -400,21 +393,27 @@ export function useDomEditSession({
|
||||
gsapCommitMutation,
|
||||
previewIframeRef,
|
||||
makeFetchFallback,
|
||||
trackGsapInteractionFailure,
|
||||
],
|
||||
);
|
||||
|
||||
const handleGsapAwareRotationCommit = useCallback(
|
||||
async (selection: DomEditSelection, next: { angle: number }) => {
|
||||
if (STUDIO_GSAP_DRAG_INTERCEPT_ENABLED && gsapCommitMutation) {
|
||||
const handled = await tryGsapRotationIntercept(
|
||||
selection,
|
||||
next.angle,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
makeFetchFallback(selection),
|
||||
);
|
||||
if (handled) return;
|
||||
try {
|
||||
const handled = await tryGsapRotationIntercept(
|
||||
selection,
|
||||
next.angle,
|
||||
selectedGsapAnimations,
|
||||
previewIframeRef.current,
|
||||
gsapCommitMutation,
|
||||
makeFetchFallback(selection),
|
||||
);
|
||||
if (handled) return;
|
||||
} catch (error) {
|
||||
trackGsapInteractionFailure(error, selection, "rotation", "Rotate animated layer");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return handleDomRotationCommit(selection, next);
|
||||
},
|
||||
@@ -424,6 +423,7 @@ export function useDomEditSession({
|
||||
gsapCommitMutation,
|
||||
previewIframeRef,
|
||||
makeFetchFallback,
|
||||
trackGsapInteractionFailure,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user