mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(sdk,studio): populate animationIds; shadow GSAP update/delete + value fidelity (#1474)
* feat(sdk,studio): populate animationIds; shadow GSAP update/delete Closes the GSAP shadow gaps. The server's animationId was assumed to live in a separate id-space — it does not: the studio-api read path (T6e) and the SDK both derive tween ids as targetSelector-method-position from the same acorn parser, so server ids are dispatchable in the SDK as-is. SDK: populate ElementSnapshot.animationIds (was a hardcoded stub) from parseGsapScriptAcornForWrite().located, resolving each tween's targetSelector to element hf-ids. Makes the snapshot truthful and enables real GSAP parity. Studio: shadow deleteGsapAnimation (removeGsapTween) and updateGsapMeta (setGsapTween) using the server animationId directly. GSAP add/remove parity now verifies via animationIds (present after add, gone after remove). set is existence-only — the SDK still has no per-tween property reader (value fidelity would need serialize()-script round-trip diffing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): GSAP value fidelity via serialize round-trip diff Closes the last shadow gap: GSAP value fidelity. Existence parity confirmed a tween was created/removed but not that its values (duration/ease/position/ properties) matched the server, since the SDK has no per-tween property reader. runShadowGsapFidelity opens a fresh SDK doc from the server's pre-op file (result.before), applies the same typed op, serializes, and structurally diffs the SDK's GSAP script against the server's resulting script (result.scriptText). Both are re-parsed via parseGsapScriptAcorn, so formatting/whitespace never produces false positives — only real value drift does. gsapFidelityMismatches reports per-field drift and tween presence/absence. Wired at the commitMutation chokepoint (the only place with the server's before+after scripts); handlers pass the typed ShadowGsapOp via CommitMutationOptions.shadowGsapOp. Emits sdk_shadow_dispatch op:gsap_fidelity. Complements the existing live existence shadow (op:gsap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): address shadow code-review findings - gsapFidelityMismatches: canonical comparison (sort property keys, numeric- coerce position/duration/values). Server (addAnimationToScript) and SDK (gsapWriterAcorn) are different writers; non-canonical compare flagged key-order / number-vs-string differences as false value drift. - document.ts buildAnimationIdMap: memoize the acorn parse by script text (single-entry). getElements() invalidates on every dispatch, so shadow's frequent dispatches were re-parsing the full GSAP AST each rebuild. Selector resolution still runs per-call (depends on live DOM). - runShadowGsapFidelity: early-bail when serverScript/beforeHtml is empty — skip the costly openComposition. - useSafeGsapCommitMutation: import the shared CommitMutationOptions/ CommitMutation instead of a stale local duplicate (was missing shadowGsapOp). - align extractGsapScript marker set across sdkShadow.ts and document.ts (gsap || __timelines || ScrollTrigger) so both pick the same script. Tests: +2 canonical-compare cases (key-order, number-vs-string → no drift). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): fallow gate for #1474 (fidelity diff + test clones) - suppress moderate CRAP on gsapFidelityMismatches and the runShadowGsapTween parity arrow (comparison/parity functions are inherently branchy) - suppress two pre-existing test clones in session.test.ts surfaced by the added animationIds tests (TestPreviewAdapter stub, selectionchange setup) Rebased onto the updated #1473 (no-persist shadow session); inherits the persist-race fix and prior fallow suppressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(studio): extract GSAP fidelity to its own module (file-size gate) sdkShadow.ts hit 602 lines (CI File size check: max 600). Move the GSAP value-fidelity diff (gsapFidelityMismatches, runShadowGsapFidelity, and their private helpers) into sdkShadowGsapFidelity.ts; re-export from sdkShadow.ts so the import surface is unchanged. sdkShadow.ts now 430 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): address #1474 review feedback - CodeQL js/bad-tag-filter: the GSAP <script> extraction + test regexes now match </script\s*> (whitespace-before-close variant). 3 alerts resolved. - Wiring (Miguel): extract resolveGsapFidelityArgs — a pure, narrowing gate for the commitMutation chokepoint (no non-null assertions) — and unit-test the fire/skip conditions (session, op, before, scriptText). Replaces the inline guard so the wiring decision is covered without rendering the hook. - Property-handler scope (Rames): comment at the chokepoint documenting that only meta-level ops (add/update-meta/delete) carry shadowGsapOp today; per-property and keyframe handlers are a deliberate follow-up. Also why scriptText can be null. - Test coverage (Rames): multi-tween-per-element and shared-selector cross-element animationIds cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): CodeQL js/bad-tag-filter — match </script[^>]*> close tags `</script\s*>` still tripped CodeQL on attribute-junk closes like `</script foo>` (HTML5 ignores junk before `>`). Widen the close-tag match to `</script[^>]*>` in the GSAP-script extraction and the test regexes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5f6ced116d
commit
7593aac5ef
@@ -1,8 +1,8 @@
|
||||
import { useCallback } from "react";
|
||||
import type { Composition, GsapTweenSpec } from "@hyperframes/sdk";
|
||||
import type { Composition } from "@hyperframes/sdk";
|
||||
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
||||
import { roundTo3 } from "../utils/rounding";
|
||||
import { runShadowGsapTween } from "../utils/sdkShadow";
|
||||
import { runShadowGsapTween, type ShadowGsapOp } from "../utils/sdkShadow";
|
||||
import {
|
||||
assignGsapTargetAutoIdIfNeeded,
|
||||
ensureElementAddressable,
|
||||
@@ -33,27 +33,34 @@ export function useGsapAnimationOps({
|
||||
animationId: string,
|
||||
updates: { duration?: number; ease?: string; position?: number },
|
||||
) => {
|
||||
// Shadow op (server animationId shares the SDK id-space): existence via
|
||||
// runShadowGsapTween (live session) + value fidelity via the chokepoint.
|
||||
const shadowGsapOp: ShadowGsapOp = {
|
||||
kind: "set",
|
||||
animationId,
|
||||
properties: { duration: updates.duration, ease: updates.ease, position: updates.position },
|
||||
};
|
||||
commitMutationSafely(
|
||||
selection,
|
||||
{ type: "update-meta", animationId, updates },
|
||||
{
|
||||
label: "Edit GSAP animation",
|
||||
coalesceKey: `gsap:${animationId}:meta`,
|
||||
},
|
||||
{ label: "Edit GSAP animation", coalesceKey: `gsap:${animationId}:meta`, shadowGsapOp },
|
||||
);
|
||||
if (sdkSession) runShadowGsapTween(sdkSession, shadowGsapOp);
|
||||
},
|
||||
[commitMutationSafely],
|
||||
[commitMutationSafely, sdkSession],
|
||||
);
|
||||
|
||||
const deleteGsapAnimation = useCallback(
|
||||
(selection: DomEditSelection, animationId: string) => {
|
||||
const shadowGsapOp: ShadowGsapOp = { kind: "remove", animationId };
|
||||
commitMutationSafely(
|
||||
selection,
|
||||
{ type: "delete", animationId, stripStudioEdits: true },
|
||||
{ label: "Delete GSAP animation" },
|
||||
{ label: "Delete GSAP animation", shadowGsapOp },
|
||||
);
|
||||
if (sdkSession) runShadowGsapTween(sdkSession, shadowGsapOp);
|
||||
},
|
||||
[commitMutationSafely],
|
||||
[commitMutationSafely, sdkSession],
|
||||
);
|
||||
|
||||
const deleteAllForSelector = useCallback(
|
||||
@@ -103,6 +110,26 @@ export function useGsapAnimationOps({
|
||||
fromTo: { x: 0, y: 0, opacity: 1 },
|
||||
};
|
||||
|
||||
// Shadow op (server stays authoritative). "set" has no SDK method, so it
|
||||
// is not shadowed; otherwise: existence via runShadowGsapTween (live) +
|
||||
// value fidelity via the chokepoint (shadowGsapOp in options).
|
||||
const shadowGsapOp: ShadowGsapOp | undefined =
|
||||
selection.hfId && method !== "set"
|
||||
? {
|
||||
kind: "add",
|
||||
target: selection.hfId,
|
||||
tween: {
|
||||
method,
|
||||
position,
|
||||
duration,
|
||||
ease: "power2.out",
|
||||
...(method === "fromTo"
|
||||
? { fromProperties: { opacity: 0 }, toProperties: toDefaults[method] }
|
||||
: { properties: toDefaults[method] ?? { opacity: 1 } }),
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
await commitMutation(
|
||||
selection,
|
||||
{
|
||||
@@ -115,25 +142,10 @@ export function useGsapAnimationOps({
|
||||
properties: toDefaults[method] ?? { opacity: 1 },
|
||||
fromProperties: method === "fromTo" ? { opacity: 0 } : undefined,
|
||||
},
|
||||
{ label: `Add GSAP ${method} animation` },
|
||||
{ label: `Add GSAP ${method} animation`, shadowGsapOp },
|
||||
);
|
||||
|
||||
// Shadow: dispatch the equivalent addGsapTween to the SDK (server stays
|
||||
// authoritative). "set" has no SDK method, so it is not shadowed.
|
||||
// ponytail: only add is shadowed — delete/update key on the server's
|
||||
// animationId, which doesn't resolve in the SDK's independent id-space.
|
||||
if (sdkSession && selection.hfId && method !== "set") {
|
||||
const tween: GsapTweenSpec = {
|
||||
method,
|
||||
position,
|
||||
duration,
|
||||
ease: "power2.out",
|
||||
...(method === "fromTo"
|
||||
? { fromProperties: { opacity: 0 }, toProperties: toDefaults[method] }
|
||||
: { properties: toDefaults[method] ?? { opacity: 1 } }),
|
||||
};
|
||||
runShadowGsapTween(sdkSession, { kind: "add", target: selection.hfId, tween });
|
||||
}
|
||||
if (sdkSession && shadowGsapOp) runShadowGsapTween(sdkSession, shadowGsapOp);
|
||||
},
|
||||
[activeCompPath, commitMutation, projectIdRef, showToast, sdkSession],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user