feat(sdk,core): ws-3 prerequisites — acorn keyframe-collapse foundation + removeAllKeyframes (#1499)

* feat(sdk,core): ws-3 prerequisites — acorn keyframe-collapse foundation + removeAllKeyframes

P1: gsapWriter.parity.test.ts — recast-vs-acorn parity harness (reparse-equivalence).
P2: move pure keyframe-conversion transforms (resolveConversionProps, cssIdentityValue)
    to recast-free gsapSerialize.ts so the acorn/SDK path can share them.
P3: MagicString splice primitives in gsapWriterAcorn.ts (buildVarsObjectCode, overwriteVarsArg).
P4: reference vertical slice — removeAllKeyframesFromScript ported to acorn writer +
    removeAllKeyframes SDK op (types/mutate/can) + Studio cutover (useGsapKeyframeOps),
    replacing the server-authoritative ponytail stub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(sdk,core): ws-3 — convertToKeyframes acorn port + SDK op + Studio cutover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>

* feat(sdk,core): ws-3 — materializeKeyframes + splitIntoPropertyGroups acorn ports + SDK ops

- acorn: buildKeyframeObjectCode, materializeKeyframesFromScript, addAnimationWithKeyframesToScript
- acorn: splitIntoPropertyGroupsFromScript with filterGroupKeyframes/filterGroupProperties helpers
- parity tests: materialize (2 positive + 1 no-op) and split (2 positive + 2 no-op) suites
- SDK types: materializeKeyframes + splitIntoPropertyGroups EditOp variants
- mutate.ts: handlers + can() gates for both new ops
- mutate.gsap.test.ts: 6 new tests (53 total passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>

* feat(sdk,core): ws-3 — splitAnimationsInScript acorn port + SDK op

- acorn: updateAnimationSelectorInScript, insertInheritedStateSetInScript helpers
- acorn: splitAnimationsInScript exported (parity with recast version)
- parity: 4 new fixtures (3 cases + no-op) — 23 total parity tests
- SDK types: splitAnimations EditOp variant
- mutate.ts: handleSplitAnimations + can() gate
- mutate.gsap.test.ts: 3 new tests (56 total passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
This commit is contained in:
Vance Ingalls
2026-06-17 16:51:28 -07:00
committed by GitHub
co-authored by Claude Sonnet 4.6 Miguel Ángel
parent ceb815c318
commit a746db6017
9 changed files with 1397 additions and 75 deletions
@@ -6,6 +6,8 @@ import { executeOptimistic } from "../utils/optimisticUpdate";
import {
sdkGsapKeyframePersist,
sdkGsapRemoveKeyframePersist,
sdkGsapRemoveAllKeyframesPersist,
sdkGsapConvertToKeyframesPersist,
type CutoverDeps,
} from "../utils/sdkCutover";
import type { KeyframeCacheEntry } from "../player/store/playerStore";
@@ -191,31 +193,52 @@ export function useGsapKeyframeOps({
);
const convertToKeyframes = useCallback(
(
async (
selection: DomEditSelection,
animationId: string,
resolvedFromValues?: Record<string, number | string>,
) => {
// ponytail: no SDK equivalent; convertToKeyframes stays server-authoritative (T6f scope)
if (sdkSession && sdkDeps) {
const targetPath = selection.sourceFile || activeCompPath || "index.html";
const handled = await sdkGsapConvertToKeyframesPersist(
targetPath,
animationId,
resolvedFromValues,
sdkSession,
sdkDeps,
{ label: "Convert to keyframes" },
);
if (handled) return;
}
return commitMutation(
selection,
{ type: "convert-to-keyframes", animationId, resolvedFromValues },
{ label: "Convert to keyframes" },
);
},
[commitMutation],
[commitMutation, activeCompPath, sdkSession, sdkDeps],
);
const removeAllKeyframes = useCallback(
(selection: DomEditSelection, animationId: string) => {
// ponytail: no SDK equivalent for remove-all-keyframes; stays server-authoritative
async (selection: DomEditSelection, animationId: string) => {
if (sdkSession && sdkDeps) {
const targetPath = selection.sourceFile || activeCompPath || "index.html";
const handled = await sdkGsapRemoveAllKeyframesPersist(
targetPath,
animationId,
sdkSession,
sdkDeps,
{ label: "Remove all keyframes" },
);
if (handled) return;
}
commitMutationSafely(
selection,
{ type: "remove-all-keyframes", animationId },
{ label: "Remove all keyframes", softReload: true },
);
},
[commitMutationSafely],
[commitMutationSafely, activeCompPath, sdkSession, sdkDeps],
);
const commitKeyframeAtTime = useCallback(