fix(studio): serialize GSAP script commits per file (shadow request race) (#1512)

Rapid GSAP edits (ease/duration/keyframe/property) fired overlapping
read-modify-write POSTs to one script file — coalesceKey only dedupes edit
history, not requests. The gsap_fidelity shadow then diffed an op against
whichever POST's scriptText resolved, which could predate that op → false
"expected null, actual power2.out" mismatches. Server persists correctly; a
pure client request-pairing race.

Adds createKeyedSerializer (per-key promise chain, rejection-safe, self-
cleaning). commitMutation now serializes every GSAP-script commit per target
file by default (key `gsap-file:<path>`) — covering all op types and all
animations, not just one meta family — so same-file POSTs can't interleave.
Distinct files run concurrently; an explicit serializeKey still overrides.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-06-16 12:36:33 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent cc055f318d
commit 4ee57d5505
5 changed files with 151 additions and 3 deletions
@@ -40,10 +40,14 @@ export function useGsapAnimationOps({
animationId,
properties: { duration: updates.duration, ease: updates.ease, position: updates.position },
};
// coalesceKey groups rapid meta edits into one history entry. Request
// serialization is now handled per-file at the commitMutation chokepoint
// (useGsapScriptCommits), so no per-op serializeKey is needed here.
const metaKey = `gsap:${animationId}:meta`;
commitMutationSafely(
selection,
{ type: "update-meta", animationId, updates },
{ label: "Edit GSAP animation", coalesceKey: `gsap:${animationId}:meta`, shadowGsapOp },
{ label: "Edit GSAP animation", coalesceKey: metaKey, shadowGsapOp },
);
if (sdkSession) runShadowGsapTween(sdkSession, shadowGsapOp);
},