Skip to content

Commit 05ce6ff

Browse files
feat(studio): s7.5 — delete shadow scaffolding; keep cutover flag (dark launch)
Removes the SDK shadow telemetry: STUDIO_SDK_SHADOW_ENABLED, sdkShadow.ts + sdkShadowGsapFidelity/GsapKeyframe/Numeric and their tests, the runShadow* call-sites across the GSAP/timeline hooks, and the onDomEditPersisted shadow callback in useDomEditSession. Moves patchOpsToSdkEditOps into sdkCutover.ts. KEEPS STUDIO_SDK_CUTOVER_ENABLED as a dark-launch kill-switch — default false, enable per-environment via VITE_STUDIO_SDK_CUTOVER_ENABLED=true. shouldUseSdkCutover stays flag-gated. The stack can merge with zero behavior change; cutover is validated by flipping the flag, not by removing it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent fc897cc commit 05ce6ff

15 files changed

Lines changed: 54 additions & 2110 deletions

packages/studio/src/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ export function StudioApp() {
189189
pendingTimelineEditPathRef,
190190
uploadProjectFiles: fileManager.uploadProjectFiles,
191191
isRecordingRef: isGestureRecordingRef,
192-
sdkSession: sdkHandle.session,
193192
});
194193
const {
195194
activeBlockParams,
@@ -305,7 +304,6 @@ export function StudioApp() {
305304
openSourceForSelection: fileManager.openSourceForSelection,
306305
selectSidebarTab: selectSidebarTabStable,
307306
getSidebarTab: getSidebarTabStable,
308-
sdkSession: sdkHandle.session,
309307
});
310308
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
311309
clearDomSelectionRef.current = domEditSession.clearDomSelection;

packages/studio/src/components/editor/manualEditingAvailability.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,6 @@ export const STUDIO_GSAP_DRAG_INTERCEPT_ENABLED = resolveStudioBooleanEnvFlag(
104104

105105
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
106106

107-
// Stage 7 Step 3b: shadow dispatch parity mode — dispatches ops to the SDK
108-
// session alongside the server patch path and logs mismatches via telemetry.
109-
// Default on: server stays authoritative (no user-visible change), so we want
110-
// the sdk_shadow_dispatch parity signal from all traffic. Disable via
111-
// VITE_STUDIO_SDK_SHADOW_ENABLED=false.
112-
export const STUDIO_SDK_SHADOW_ENABLED = resolveStudioBooleanEnvFlag(
113-
env,
114-
["VITE_STUDIO_SDK_SHADOW_ENABLED"],
115-
true,
116-
);
117-
118107
// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
119108
// instead of the server patch-element API. Default false; enable via
120109
// VITE_STUDIO_SDK_CUTOVER_ENABLED=true. Requires SDK session to be open.

packages/studio/src/hooks/gsapScriptCommitTypes.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import type { ParsedGsap } from "@hyperframes/core/gsap-parser";
2-
import type { Composition } from "@hyperframes/sdk";
32
import type { DomEditSelection } from "../components/editor/domEditingTypes";
43
import type { EditHistoryKind } from "../utils/editHistory";
5-
import type { ShadowGsapOp } from "../utils/sdkShadow";
6-
import type { ShadowKeyframeOp } from "../utils/sdkShadowGsapKeyframe";
74

85
export interface MutationResult {
96
ok: boolean;
@@ -28,10 +25,6 @@ export interface CommitMutationOptions {
2825
* (and under distinct keys) run concurrently as before.
2926
*/
3027
serializeKey?: string;
31-
/** Stage 7 Step 3b: typed SDK equivalent of this mutation for value-fidelity shadow. */
32-
shadowGsapOp?: ShadowGsapOp;
33-
/** Typed SDK equivalent of a keyframe mutation for keyframe value-fidelity shadow (gsap_keyframe). */
34-
shadowKeyframeOp?: ShadowKeyframeOp;
3528
}
3629

3730
export type CommitMutation = (
@@ -70,6 +63,4 @@ export interface GsapScriptCommitsParams {
7063
onCacheInvalidate: () => void;
7164
onFileContentChanged?: (path: string, content: string) => void;
7265
showToast: (message: string, tone?: "error" | "info") => void;
73-
/** Stage 7 Step 3b: SDK session for shadow GSAP dispatch (server stays authoritative). */
74-
sdkSession?: Composition | null;
7566
}

packages/studio/src/hooks/useDomEditSession.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Composition } from "@hyperframes/sdk";
21
import type { TimelineElement } from "../player";
32
import type { ImportedFontAsset } from "../components/editor/fontAssets";
43
import type { EditHistoryKind } from "../utils/editHistory";
@@ -9,7 +8,6 @@ import { useAskAgentModal } from "./useAskAgentModal";
98
import { useDomSelection } from "./useDomSelection";
109
import { usePreviewInteraction } from "./usePreviewInteraction";
1110
import { useDomEditCommits } from "./useDomEditCommits";
12-
import { runShadowDispatch, runShadowDelete } from "../utils/sdkShadow";
1311
import { useGsapScriptCommits } from "./useGsapScriptCommits";
1412
import { useGsapCacheVersion } from "./useGsapTweenCache";
1513
import { useDomEditWiring } from "./useDomEditWiring";
@@ -60,8 +58,6 @@ export interface UseDomEditSessionParams {
6058
openSourceForSelection?: (sourceFile: string, target: PatchTarget) => void;
6159
selectSidebarTab?: (tab: SidebarTab) => void;
6260
getSidebarTab?: () => SidebarTab;
63-
/** Stage 7 Step 3b: SDK session for shadow dispatch parity tracking. */
64-
sdkSession?: Composition | null;
6561
}
6662

6763
// ── Hook ──
@@ -100,7 +96,6 @@ export function useDomEditSession({
10096
openSourceForSelection,
10197
selectSidebarTab,
10298
getSidebarTab,
103-
sdkSession,
10499
}: UseDomEditSessionParams) {
105100
void _setRefreshKey;
106101
void _readProjectFile;
@@ -194,7 +189,6 @@ export function useDomEditSession({
194189
onCacheInvalidate: bumpGsapCache,
195190
onFileContentChanged: updateEditingFileContent,
196191
showToast,
197-
sdkSession,
198192
});
199193

200194
// ── DOM commit handlers ──
@@ -234,10 +228,6 @@ export function useDomEditSession({
234228
clearDomSelection,
235229
refreshDomEditSelectionFromPreview,
236230
buildDomSelectionFromTarget,
237-
onDomEditPersisted: sdkSession
238-
? (sel, ops) => runShadowDispatch(sdkSession, sel, ops)
239-
: undefined,
240-
onElementDeleted: sdkSession ? (sel) => runShadowDelete(sdkSession, sel.hfId) : undefined,
241231
});
242232

243233
// ── Wiring: selection sync, GSAP cache, preview sync, selection handlers ──

packages/studio/src/hooks/useGsapAnimationOps.ts

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { useCallback } from "react";
2-
import type { Composition } from "@hyperframes/sdk";
32
import type { DomEditSelection } from "../components/editor/domEditingTypes";
43
import { roundTo3 } from "../utils/rounding";
5-
import { runShadowGsapTween, type ShadowGsapOp } from "../utils/sdkShadow";
64
import {
75
assignGsapTargetAutoIdIfNeeded,
86
ensureElementAddressable,
@@ -15,8 +13,6 @@ interface GsapAnimationOpsParams {
1513
commitMutation: CommitMutation;
1614
commitMutationSafely: SafeGsapCommitMutation;
1715
showToast: (message: string, tone?: "error" | "info") => void;
18-
/** Stage 7 Step 3b: SDK session for shadow GSAP dispatch (server stays authoritative). */
19-
sdkSession?: Composition | null;
2016
}
2117

2218
export function useGsapAnimationOps({
@@ -25,46 +21,35 @@ export function useGsapAnimationOps({
2521
commitMutation,
2622
commitMutationSafely,
2723
showToast,
28-
sdkSession,
2924
}: GsapAnimationOpsParams) {
3025
const updateGsapMeta = useCallback(
3126
(
3227
selection: DomEditSelection,
3328
animationId: string,
3429
updates: { duration?: number; ease?: string; position?: number },
3530
) => {
36-
// Shadow op (server animationId shares the SDK id-space): existence via
37-
// runShadowGsapTween (live session) + value fidelity via the chokepoint.
38-
const shadowGsapOp: ShadowGsapOp = {
39-
kind: "set",
40-
animationId,
41-
properties: { duration: updates.duration, ease: updates.ease, position: updates.position },
42-
};
4331
// coalesceKey groups rapid meta edits into one history entry. Request
4432
// serialization is now handled per-file at the commitMutation chokepoint
4533
// (useGsapScriptCommits), so no per-op serializeKey is needed here.
4634
const metaKey = `gsap:${animationId}:meta`;
4735
commitMutationSafely(
4836
selection,
4937
{ type: "update-meta", animationId, updates },
50-
{ label: "Edit GSAP animation", coalesceKey: metaKey, shadowGsapOp },
38+
{ label: "Edit GSAP animation", coalesceKey: metaKey },
5139
);
52-
if (sdkSession) runShadowGsapTween(sdkSession, shadowGsapOp);
5340
},
54-
[commitMutationSafely, sdkSession],
41+
[commitMutationSafely],
5542
);
5643

5744
const deleteGsapAnimation = useCallback(
5845
(selection: DomEditSelection, animationId: string) => {
59-
const shadowGsapOp: ShadowGsapOp = { kind: "remove", animationId };
6046
commitMutationSafely(
6147
selection,
6248
{ type: "delete", animationId, stripStudioEdits: true },
63-
{ label: "Delete GSAP animation", shadowGsapOp },
49+
{ label: "Delete GSAP animation" },
6450
);
65-
if (sdkSession) runShadowGsapTween(sdkSession, shadowGsapOp);
6651
},
67-
[commitMutationSafely, sdkSession],
52+
[commitMutationSafely],
6853
);
6954

7055
const deleteAllForSelector = useCallback(
@@ -78,8 +63,6 @@ export function useGsapAnimationOps({
7863
[commitMutation],
7964
);
8065

81-
// Pre-existing complexity (auto-id assignment + per-method defaults); this PR
82-
// adds only a guarded shadow-op construction at the tail.
8366
const addGsapAnimation = useCallback(
8467
// fallow-ignore-next-line complexity
8568
async (
@@ -114,26 +97,6 @@ export function useGsapAnimationOps({
11497
fromTo: { x: 0, y: 0, opacity: 1 },
11598
};
11699

117-
// Shadow op (server stays authoritative). "set" has no SDK method, so it
118-
// is not shadowed; otherwise: existence via runShadowGsapTween (live) +
119-
// value fidelity via the chokepoint (shadowGsapOp in options).
120-
const shadowGsapOp: ShadowGsapOp | undefined =
121-
selection.hfId && method !== "set"
122-
? {
123-
kind: "add",
124-
target: selection.hfId,
125-
tween: {
126-
method,
127-
position,
128-
duration,
129-
ease: "power2.out",
130-
...(method === "fromTo"
131-
? { fromProperties: { opacity: 0 }, toProperties: toDefaults[method] }
132-
: { properties: toDefaults[method] ?? { opacity: 1 } }),
133-
},
134-
}
135-
: undefined;
136-
137100
await commitMutation(
138101
selection,
139102
{
@@ -146,12 +109,10 @@ export function useGsapAnimationOps({
146109
properties: toDefaults[method] ?? { opacity: 1 },
147110
fromProperties: method === "fromTo" ? { opacity: 0 } : undefined,
148111
},
149-
{ label: `Add GSAP ${method} animation`, shadowGsapOp },
112+
{ label: `Add GSAP ${method} animation` },
150113
);
151-
152-
if (sdkSession && shadowGsapOp) runShadowGsapTween(sdkSession, shadowGsapOp);
153114
},
154-
[activeCompPath, commitMutation, projectIdRef, showToast, sdkSession],
115+
[activeCompPath, commitMutation, projectIdRef, showToast],
155116
);
156117

157118
return {

packages/studio/src/hooks/useGsapKeyframeOps.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useCallback } from "react";
22
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
3-
import type { ShadowKeyframeOp } from "../utils/sdkShadowGsapKeyframe";
43
import type { DomEditSelection } from "../components/editor/domEditingTypes";
54
import { executeOptimistic } from "../utils/optimisticUpdate";
65
import type { KeyframeCacheEntry } from "../player/store/playerStore";
@@ -59,13 +58,6 @@ export function useGsapKeyframeOps({
5958
percentage,
6059
properties: { [property]: value },
6160
};
62-
// Shadow op (gsap_keyframe): SDK equivalent diffed via the commit chokepoint.
63-
const shadowKeyframeOp: ShadowKeyframeOp = {
64-
kind: "add",
65-
animationId,
66-
percentage,
67-
properties: { [property]: value },
68-
};
6961
void executeOptimisticKeyframeCacheUpdate({
7062
sourceFile,
7163
elementId: selection.id,
@@ -79,7 +71,6 @@ export function useGsapKeyframeOps({
7971
commitMutation(selection, mutation, {
8072
label: `Add keyframe at ${percentage}%`,
8173
softReload: true,
82-
shadowKeyframeOp,
8374
}),
8475
}).catch((error) => {
8576
trackGsapSaveFailure(error, selection, mutation, `Add keyframe at ${percentage}%`);
@@ -95,16 +86,10 @@ export function useGsapKeyframeOps({
9586
percentage: number,
9687
properties: Record<string, number | string>,
9788
) => {
98-
const shadowKeyframeOp: ShadowKeyframeOp = {
99-
kind: "add",
100-
animationId,
101-
percentage,
102-
properties,
103-
};
10489
return commitMutation(
10590
selection,
10691
{ type: "add-keyframe", animationId, percentage, properties },
107-
{ label: `Add keyframe at ${percentage}%`, softReload: true, shadowKeyframeOp },
92+
{ label: `Add keyframe at ${percentage}%`, softReload: true },
10893
);
10994
},
11095
[commitMutation],
@@ -114,10 +99,6 @@ export function useGsapKeyframeOps({
11499
(selection: DomEditSelection, animationId: string, percentage: number) => {
115100
const sourceFile = selection.sourceFile || activeCompPath || "index.html";
116101
const mutation = { type: "remove-keyframe", animationId, percentage };
117-
// Shadow op (gsap_keyframe): SDK has no %-based removeGsapKeyframe on main,
118-
// so the runner resolves percentage → keyframeIndex against the pre-op
119-
// script and no-ops on ambiguity (duplicate-percentage keyframes).
120-
const shadowKeyframeOp: ShadowKeyframeOp = { kind: "remove", animationId, percentage };
121102
void executeOptimisticKeyframeCacheUpdate({
122103
sourceFile,
123104
elementId: selection.id,
@@ -131,7 +112,6 @@ export function useGsapKeyframeOps({
131112
commitMutation(selection, mutation, {
132113
label: `Remove keyframe at ${percentage}%`,
133114
softReload: true,
134-
shadowKeyframeOp,
135115
}),
136116
}).catch((error) => {
137117
trackGsapSaveFailure(error, selection, mutation, `Remove keyframe at ${percentage}%`);

packages/studio/src/hooks/useGsapScriptCommits.ts

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { useCallback, useRef } from "react";
22
import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
33
import type { DomEditSelection } from "../components/editor/domEditingTypes";
44
import { applySoftReload } from "../utils/gsapSoftReload";
5-
import { resolveGsapFidelityArgs, runShadowGsapFidelity } from "../utils/sdkShadowGsapFidelity";
6-
import { runShadowGsapKeyframeFidelity } from "../utils/sdkShadowGsapKeyframe";
75
import { updateKeyframeCacheFromParsed } from "./gsapKeyframeCacheHelpers";
86
import { createKeyedSerializer } from "./serializeByKey";
97
import {
@@ -46,15 +44,12 @@ async function mutateGsapScript(
4644

4745
// oxfmt-ignore
4846
// fallow-ignore-next-line complexity
49-
export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast, sdkSession }: GsapScriptCommitsParams) {
47+
export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast }: GsapScriptCommitsParams) {
5048
// Serializer for per-key commits (options.serializeKey). Keyed by
5149
// `gsap:${animationId}:meta`, it chains a meta commit onto the prior one for
52-
// the same animationId so their POSTs can't interleave — which is what made
53-
// the shadow fidelity diff pair an op with a stale server result and report
54-
// false ease mismatches. Held in a ref so the chain survives re-renders.
50+
// the same animationId so their POSTs can't interleave. Held in a ref so the
51+
// chain survives re-renders.
5552
const serializerRef = useRef(createKeyedSerializer());
56-
// Pre-existing complexity (server mutate + history + reload branches); this PR
57-
// adds only a guarded shadow-fidelity dispatch.
5853
// fallow-ignore-next-line complexity
5954
const runCommit = useCallback(async (selection: DomEditSelection, mutation: Record<string, unknown>, options: CommitMutationOptions) => {
6055
const pid = projectIdRef.current;
@@ -76,28 +71,6 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
7671
}
7772
if (result.changed === false) return;
7873
domEditSaveTimestampRef.current = Date.now();
79-
// Shadow value fidelity: diff the SDK's GSAP writer output against the
80-
// server's, from the same pre-op file. Fire-and-forget; server authoritative.
81-
// Meta-level ops carry shadowGsapOp (add / update-meta / delete via
82-
// useGsapAnimationOps); keyframe ops carry shadowKeyframeOp (add/remove via
83-
// useGsapKeyframeOps, handled by the gsap_keyframe block below). Per-property
84-
// handlers (useGsapPropertyDebounce) don't synthesize one yet — deferred follow-up.
85-
// scriptText is null when the composition has no GSAP script; nothing to diff.
86-
const fidelityArgs = resolveGsapFidelityArgs(
87-
sdkSession,
88-
options.shadowGsapOp,
89-
result.before,
90-
result.scriptText,
91-
);
92-
if (fidelityArgs) {
93-
void runShadowGsapFidelity(fidelityArgs.before, fidelityArgs.op, fidelityArgs.serverScript);
94-
}
95-
// Keyframe value fidelity (gsap_keyframe): same serialize-diff approach, but
96-
// the SDK has no keyframe reader so there is no live-existence path — the diff
97-
// is the only signal. Guarded on a live session + both scripts to diff.
98-
if (sdkSession && options.shadowKeyframeOp && result.before != null && result.scriptText != null) {
99-
void runShadowGsapKeyframeFidelity(result.before, options.shadowKeyframeOp, result.scriptText);
100-
}
10174
if (result.before != null && result.after != null) {
10275
await editHistory.recordEdit({ label: options.label, kind: "manual", coalesceKey: options.coalesceKey, files: { [targetPath]: { before: result.before, after: result.after } } });
10376
}
@@ -111,12 +84,10 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
11184
reloadPreview();
11285
}
11386
onCacheInvalidate();
114-
}, [projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast, sdkSession]);
87+
}, [projectIdRef, activeCompPath, previewIframeRef, editHistory, domEditSaveTimestampRef, reloadPreview, onCacheInvalidate, onFileContentChanged, showToast]);
11588
// Every GSAP-script commit is a read-modify-write of one file. Overlapping
116-
// commits to the SAME file (any op type, any animation) interleave server-side
117-
// and make the shadow fidelity diff pair an op with a stale server result —
118-
// the false ease/value mismatches this serializer exists to prevent. So
119-
// serialize per target file by default; an explicit serializeKey overrides.
89+
// commits to the SAME file (any op type, any animation) interleave server-side,
90+
// so serialize per target file by default; an explicit serializeKey overrides.
12091
const commitMutation = useCallback(
12192
(selection: DomEditSelection, mutation: Record<string, unknown>, options: CommitMutationOptions) => {
12293
const file = selection.sourceFile || activeCompPath || "index.html";
@@ -128,7 +99,7 @@ export function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIfra
12899
const trackGsapSaveFailure = useGsapSaveFailureTelemetry(activeCompPath);
129100
const commitMutationSafely = useSafeGsapCommitMutation(commitMutation, trackGsapSaveFailure, showToast);
130101
const propertyOps = useGsapPropertyDebounce(commitMutationSafely);
131-
const animationOps = useGsapAnimationOps({ projectIdRef, activeCompPath, commitMutation, commitMutationSafely, showToast, sdkSession });
102+
const animationOps = useGsapAnimationOps({ projectIdRef, activeCompPath, commitMutation, commitMutationSafely, showToast });
132103
const keyframeOps = useGsapKeyframeOps({ activeCompPath, commitMutation, commitMutationSafely, trackGsapSaveFailure });
133104
const arcPathOps = useGsapArcPathOps(commitMutationSafely);
134105
return { commitMutation, ...propertyOps, ...animationOps, ...keyframeOps, ...arcPathOps };

0 commit comments

Comments
 (0)