Skip to content

Commit 2586d97

Browse files
feat(studio): s7.5 — make sdk cutover always-on, delete shadow scaffolding
SDK cutover is now unconditionally enabled. Removes STUDIO_SDK_SHADOW_ENABLED and STUDIO_SDK_CUTOVER_ENABLED feature flags, deletes sdkShadow.ts and its tests, moves patchOpsToSdkEditOps into sdkCutover.ts, and removes the onDomEditPersisted shadow callback from useDomEditSession. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent 5239366 commit 2586d97

7 files changed

Lines changed: 68 additions & 1176 deletions

File tree

packages/studio/src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ export function StudioApp() {
306306
openSourceForSelection: fileManager.openSourceForSelection,
307307
selectSidebarTab: selectSidebarTabStable,
308308
getSidebarTab: getSidebarTabStable,
309-
sdkSession,
310309
});
311310
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
312311
clearDomSelectionRef.current = domEditSession.clearDomSelection;

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

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,4 @@ export const STUDIO_GSAP_DRAG_INTERCEPT_ENABLED = resolveStudioBooleanEnvFlag(
9494

9595
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
9696

97-
// Stage 7 Step 3b: shadow dispatch parity mode — dispatches ops to the SDK
98-
// session alongside the server patch path and logs mismatches via telemetry.
99-
// Default false in production; enable via VITE_STUDIO_SDK_SHADOW_ENABLED=true.
100-
export const STUDIO_SDK_SHADOW_ENABLED = resolveStudioBooleanEnvFlag(
101-
env,
102-
["VITE_STUDIO_SDK_SHADOW_ENABLED"],
103-
false,
104-
);
105-
106-
// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
107-
// instead of the server patch-element API. Default false; enable via
108-
// VITE_STUDIO_SDK_CUTOVER_ENABLED=true. Requires SDK session to be open.
109-
export const STUDIO_SDK_CUTOVER_ENABLED = resolveStudioBooleanEnvFlag(
110-
env,
111-
["VITE_STUDIO_SDK_CUTOVER_ENABLED"],
112-
false,
113-
);
114-
11597
export const STUDIO_MANUAL_EDITING_DISABLED_TITLE = "Manual editing is temporarily disabled";

packages/studio/src/hooks/useDomEditSession.ts

Lines changed: 0 additions & 9 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;
@@ -234,10 +229,6 @@ export function useDomEditSession({
234229
clearDomSelection,
235230
refreshDomEditSelectionFromPreview,
236231
buildDomSelectionFromTarget,
237-
onDomEditPersisted: sdkSession
238-
? (sel, ops) => runShadowDispatch(sdkSession, sel, ops)
239-
: undefined,
240-
onElementDeleted: sdkSession ? (sel) => runShadowDelete(sdkSession, sel.hfId) : undefined,
241232
});
242233

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

packages/studio/src/utils/sdkCutover.test.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import { shouldUseSdkCutover, sdkCutoverPersist } from "./sdkCutover";
33
import type { PatchOperation } from "./sourcePatcher";
44
import type { MutableRefObject } from "react";
55

6-
vi.mock("../components/editor/manualEditingAvailability", () => ({
7-
STUDIO_SDK_CUTOVER_ENABLED: true,
8-
}));
96
vi.mock("./studioTelemetry", () => ({
107
trackStudioEvent: vi.fn(),
118
}));
@@ -23,17 +20,13 @@ const attrOp = (property: string, value: string): PatchOperation => ({
2320
});
2421

2522
describe("shouldUseSdkCutover", () => {
26-
it("returns false when flag disabled", () => {
27-
expect(shouldUseSdkCutover(false, true, "hf-abc", [styleOp("color", "red")])).toBe(false);
28-
});
29-
3023
it("returns false when no session", () => {
31-
expect(shouldUseSdkCutover(true, false, "hf-abc", [styleOp("color", "red")])).toBe(false);
24+
expect(shouldUseSdkCutover(false, "hf-abc", [styleOp("color", "red")])).toBe(false);
3225
});
3326

3427
it("returns false when no hfId", () => {
35-
expect(shouldUseSdkCutover(true, true, null, [styleOp("color", "red")])).toBe(false);
36-
expect(shouldUseSdkCutover(true, true, undefined, [styleOp("color", "red")])).toBe(false);
28+
expect(shouldUseSdkCutover(true, null, [styleOp("color", "red")])).toBe(false);
29+
expect(shouldUseSdkCutover(true, undefined, [styleOp("color", "red")])).toBe(false);
3730
});
3831

3932
it("returns false when ops include non-inline-style types", () => {
@@ -43,11 +36,34 @@ describe("shouldUseSdkCutover", () => {
4336
});
4437

4538
it("returns false when ops empty", () => {
46-
expect(shouldUseSdkCutover(true, true, "hf-abc", [])).toBe(false);
39+
expect(shouldUseSdkCutover(true, "hf-abc", [])).toBe(false);
40+
});
41+
42+
it("returns true for inline-style ops", () => {
43+
expect(shouldUseSdkCutover(true, "hf-abc", [styleOp("color", "red")])).toBe(true);
44+
});
45+
46+
it("returns true for text-content ops", () => {
47+
expect(shouldUseSdkCutover(true, "hf-abc", [textOp("hello")])).toBe(true);
48+
});
49+
50+
it("returns true for attribute ops", () => {
51+
expect(shouldUseSdkCutover(true, "hf-abc", [attrOp("data-x", "10")])).toBe(true);
4752
});
4853

49-
it("returns true when all conditions met", () => {
50-
expect(shouldUseSdkCutover(true, true, "hf-abc", [styleOp("color", "red")])).toBe(true);
54+
it("returns true for html-attribute ops", () => {
55+
expect(shouldUseSdkCutover(true, "hf-abc", [htmlAttrOp("class", "foo")])).toBe(true);
56+
});
57+
58+
it("returns true when ops mix all supported types", () => {
59+
expect(
60+
shouldUseSdkCutover(true, "hf-abc", [
61+
styleOp("color", "red"),
62+
textOp("hello"),
63+
attrOp("x", "1"),
64+
htmlAttrOp("class", "foo"),
65+
]),
66+
).toBe(true);
5167
});
5268
});
5369

packages/studio/src/utils/sdkCutover.ts

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { MutableRefObject } from "react";
22
import type { Composition } from "@hyperframes/sdk";
3+
import type { EditOp } from "@hyperframes/sdk";
34
import type { DomEditSelection } from "../components/editor/domEditing";
45
import type { EditHistoryKind } from "./editHistory";
56
import type { PatchOperation } from "./sourcePatcher";
6-
import { STUDIO_SDK_CUTOVER_ENABLED } from "../components/editor/manualEditingAvailability";
7-
import { patchOpsToSdkEditOps } from "./sdkShadow";
87
import { trackStudioEvent } from "./studioTelemetry";
98

109
const CUTOVER_OP_TYPES = new Set<PatchOperation["type"]>([
@@ -14,19 +13,48 @@ const CUTOVER_OP_TYPES = new Set<PatchOperation["type"]>([
1413
"html-attribute",
1514
]);
1615

16+
/**
17+
* Map Studio PatchOperations for a given hf-id to SDK EditOps.
18+
*
19+
* Multiple inline-style ops are coalesced into a single setStyle (SDK batches
20+
* style changes naturally). One SDK op is emitted per non-style op.
21+
*/
22+
function patchOpsToSdkEditOps(hfId: string, ops: PatchOperation[]): EditOp[] {
23+
const result: EditOp[] = [];
24+
const styles: Record<string, string | null> = {};
25+
let hasStyles = false;
26+
27+
for (const op of ops) {
28+
if (op.type === "inline-style") {
29+
styles[op.property] = op.value;
30+
hasStyles = true;
31+
} else if (op.type === "text-content") {
32+
result.push({ type: "setText", target: hfId, value: op.value ?? "" });
33+
} else if (op.type === "attribute") {
34+
result.push({
35+
type: "setAttribute",
36+
target: hfId,
37+
name: op.property.startsWith("data-") ? op.property : `data-${op.property}`,
38+
value: op.value,
39+
});
40+
} else if (op.type === "html-attribute") {
41+
result.push({ type: "setAttribute", target: hfId, name: op.property, value: op.value });
42+
}
43+
}
44+
45+
if (hasStyles) {
46+
result.unshift({ type: "setStyle", target: hfId, styles });
47+
}
48+
49+
return result;
50+
}
51+
1752
export function shouldUseSdkCutover(
18-
flagEnabled: boolean,
1953
hasSession: boolean,
2054
hfId: string | null | undefined,
2155
ops: PatchOperation[],
2256
): boolean {
23-
return (
24-
flagEnabled &&
25-
hasSession &&
26-
!!hfId &&
27-
ops.length > 0 &&
28-
ops.every((o) => CUTOVER_OP_TYPES.has(o.type))
29-
);
57+
return hasSession && !!hfId && ops.length > 0 && ops.every((o) => CUTOVER_OP_TYPES.has(o.type));
3058
}
3159

3260
interface CutoverDeps {
@@ -57,8 +85,7 @@ export async function sdkCutoverPersist(
5785
deps: CutoverDeps,
5886
options?: CutoverOptions,
5987
): Promise<boolean> {
60-
if (!shouldUseSdkCutover(STUDIO_SDK_CUTOVER_ENABLED, !!sdkSession, selection.hfId, ops))
61-
return false;
88+
if (!shouldUseSdkCutover(!!sdkSession, selection.hfId, ops)) return false;
6289
if (!sdkSession) return false;
6390
const hfId = selection.hfId;
6491
if (!hfId) return false;

0 commit comments

Comments
 (0)