Skip to content

Commit 439257a

Browse files
committed
refactor(studio): delete the move writer no Studio path calls
1 parent 0db5b0a commit 439257a

12 files changed

Lines changed: 9 additions & 371 deletions

‎.fallowrc.jsonc‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@
6262
"packages/studio/src/components/nle/TimelineEditorNotice.tsx",
6363
// Zoom hook extracted for downstream razor-blade PRs (#1330, #1331).
6464
"packages/studio/src/player/components/useTimelineZoom.ts",
65-
// Cached O(1) GSAP target lookup, replacing O(n²) inline checks.
66-
// Consumers migrate in a follow-up once useDomGeometryCommits adopts it.
67-
"packages/studio/src/hooks/gsapTargetCache.ts",
6865
// Preview helper consumed dynamically from the studio iframe bridge.
6966
"packages/studio/src/hooks/gsapRuntimePreview.ts",
7067
],
@@ -370,13 +367,6 @@
370367
"file": "packages/studio/src/player/lib/timelineDOM.ts",
371368
"exports": ["*"],
372369
},
373-
// gsapTargetCache: cached O(1) GSAP target lookup, consumed by
374-
// useDomEditCommits and intended to replace the local copy in
375-
// useDomGeometryCommits once callers migrate.
376-
{
377-
"file": "packages/studio/src/hooks/gsapTargetCache.ts",
378-
"exports": ["isElementGsapTargeted"],
379-
},
380370
// Re-exports from useDomEditCommits: barrel-style re-exports
381371
// consumed by downstream studio code.
382372
{

‎packages/studio/src/components/editor/anchoredResizeReleaseShift.test.ts‎

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
// @vitest-environment jsdom
22
import { afterEach, describe, expect, it } from "vitest";
3-
import { applyStudioBoxSize, applyStudioPathOffset, readStudioBoxSize } from "./manualEditsDom";
4-
import { reapplyPositionEditsAfterSeek } from "./manualEditsSeekReapply";
5-
import { buildBoxSizePatches, buildPathOffsetPatches } from "./manualEditsDomPatches";
6-
import { createManualOffsetDragMember, applyManualOffsetDragCommit } from "./manualOffsetDrag";
73
import { computeNextResizeAnchor } from "./domEditResizeLocal";
8-
import type { PatchOperation } from "../../utils/sourcePatcher";
9-
import { splitTopLevelWhitespace } from "./manualEditsStyleHelpers";
104

115
/**
126
* Center-anchored corner resize (CapCut model): the element scales about its
@@ -38,40 +32,6 @@ afterEach(() => {
3832
document.body.innerHTML = "";
3933
});
4034

41-
/** Apply a built PatchOperation[] to a live element, mirroring sourcePatcher's
42-
* inline-style / attribute application — i.e. what the persisted source carries
43-
* when it is re-parsed into the DOM on the next preview load. */
44-
function applyPatchesToElement(el: HTMLElement, ops: PatchOperation[]): void {
45-
for (const op of ops) {
46-
if (op.type === "inline-style") {
47-
if (op.value === null) el.style.removeProperty(op.property);
48-
else el.style.setProperty(op.property, op.value);
49-
} else if (op.type === "attribute") {
50-
if (op.value === null) el.removeAttribute(op.property);
51-
else el.setAttribute(op.property, op.value);
52-
}
53-
}
54-
}
55-
56-
/** Net translate applied to an element, resolving the studio offset var()
57-
* expression to its px value so we compare the actually-rendered translation. */
58-
function resolvedTranslatePx(el: HTMLElement): { x: number; y: number } {
59-
const raw = el.style.getPropertyValue("translate").trim();
60-
if (!raw || raw === "none") return { x: 0, y: 0 };
61-
const vx = Number.parseFloat(el.style.getPropertyValue("--hf-studio-offset-x")) || 0;
62-
const vy = Number.parseFloat(el.style.getPropertyValue("--hf-studio-offset-y")) || 0;
63-
const parts = splitTopLevelWhitespace(raw);
64-
const parseAxis = (part: string, varVal: number): number => {
65-
if (part && part.includes("--hf-studio-offset")) return varVal;
66-
const n = Number.parseFloat(part);
67-
return Number.isFinite(n) ? n : 0;
68-
};
69-
return {
70-
x: parseAxis(parts[0] ?? "", vx),
71-
y: parseAxis(parts[1] ?? "", vy),
72-
};
73-
}
74-
7535
describe("center-anchored corner resize — no shift after release", () => {
7636
it("the per-frame center anchor converges (does NOT oscillate) — the release-shift root cause", () => {
7737
// Model the pointermove anchor loop that pins the element's CENTER. The physical
@@ -157,48 +117,4 @@ describe("center-anchored corner resize — no shift after release", () => {
157117
}
158118
}
159119
});
160-
161-
it("net translate after persist+reload equals the committed anchor offset (non-GSAP)", () => {
162-
// The committed offset flows through the real apply → persist → reload chain
163-
// unchanged (this hop was proved clean; the shift is upstream in the anchor
164-
// loop above, not in persistence).
165-
const el = document.createElement("div");
166-
el.style.setProperty("width", "200px");
167-
el.style.setProperty("height", "100px");
168-
document.body.appendChild(el);
169-
170-
const anchorDx = -30;
171-
const anchorDy = -18;
172-
const finalSize = { width: 240, height: 130 };
173-
174-
applyStudioBoxSize(el, finalSize);
175-
const memberResult = createManualOffsetDragMember({
176-
key: "k",
177-
selection: { element: el } as never,
178-
element: el,
179-
rect: { left: 0, top: 0, width: 240, height: 130, editScaleX: 1, editScaleY: 1 },
180-
});
181-
expect(memberResult.ok).toBe(true);
182-
if (!memberResult.ok) return;
183-
184-
const finalOffset = applyManualOffsetDragCommit(memberResult.member, anchorDx, anchorDy);
185-
186-
applyStudioBoxSize(el, finalSize);
187-
const patches = buildBoxSizePatches(el);
188-
applyStudioPathOffset(el, finalOffset);
189-
patches.push(...buildPathOffsetPatches(el));
190-
191-
expect(resolvedTranslatePx(el)).toEqual({ x: anchorDx, y: anchorDy });
192-
193-
// Persist → fresh element re-parsed from source → reload re-stamp.
194-
const reloaded = document.createElement("div");
195-
reloaded.style.setProperty("width", "200px");
196-
reloaded.style.setProperty("height", "100px");
197-
document.body.appendChild(reloaded);
198-
applyPatchesToElement(reloaded, patches);
199-
reapplyPositionEditsAfterSeek(reloaded.ownerDocument);
200-
201-
expect(resolvedTranslatePx(reloaded)).toEqual({ x: anchorDx, y: anchorDy });
202-
expect(readStudioBoxSize(reloaded)).toEqual(finalSize);
203-
});
204120
});

‎packages/studio/src/components/editor/manualEditsDom.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,9 @@ function stripGsapTranslateFromTransform(element: HTMLElement): void {
253253
// — as the non-GSAP path does — composes ON TOP of GSAP's transform, and the
254254
// subsequent strip/reapply math compounds into a runaway matrix that flings the
255255
// element off-canvas. So for GSAP-animated elements we keep `translate: none`
256-
// and push the offset straight into GSAP's x/y via gsap.set; the var() offset is
257-
// still persisted (buildPathOffsetPatches), and GSAP re-reads it at init on
258-
// reload. Returns true when handled as GSAP (caller must skip the CSS path).
256+
// and push the offset straight into GSAP's x/y via gsap.set; the var() offset an
257+
// older Studio saved stays in the file, and GSAP re-reads it at init on reload.
258+
// Returns true when handled as GSAP (caller must skip the CSS path).
259259
// fallow-ignore-next-line complexity
260260
function applyStudioPathOffsetViaGsap(
261261
element: HTMLElement,

‎packages/studio/src/components/editor/manualEditsDomPatches.test.ts‎

Lines changed: 2 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,14 @@ import {
4242
STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR,
4343
} from "./studioMotionTypes";
4444
import {
45-
buildPathOffsetPatches,
4645
buildClearPathOffsetPatches,
4746
buildBoxSizePatches,
4847
buildClearBoxSizePatches,
4948
buildClearRotationPatches,
5049
buildMotionPatches,
5150
buildClearMotionPatches,
5251
} from "./manualEditsDomPatches";
53-
import { applyStudioBoxSize, applyStudioPathOffset } from "./manualEditsDom";
52+
import { applyStudioBoxSize } from "./manualEditsDom";
5453

5554
/* ── helpers ── */
5655

@@ -71,39 +70,7 @@ function assertClearCoversKeys(buildOps: PatchOperation[], clearOps: PatchOperat
7170

7271
/* ── Path offset ─────────────────────────────────────────────────────────── */
7372

74-
describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
75-
function populatedPathEl(): HTMLElement {
76-
const e = div();
77-
e.style.setProperty(STUDIO_OFFSET_X_PROP, "10px");
78-
e.style.setProperty(STUDIO_OFFSET_Y_PROP, "20px");
79-
e.style.setProperty("translate", "10px 20px");
80-
e.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, "5px 10px");
81-
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "3px");
82-
e.style.setProperty("display", "flex");
83-
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "block");
84-
return e;
85-
}
86-
87-
it("populated: captures offset styles, attrs, display, and transform-display marker in declaration order", () => {
88-
const ops = buildPathOffsetPatches(populatedPathEl());
89-
expect(ops).toEqual([
90-
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: "10px" },
91-
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: "20px" },
92-
{ type: "inline-style", property: "translate", value: "10px 20px" },
93-
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
94-
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: "5px 10px" },
95-
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: "3px" },
96-
{ type: "inline-style", property: "display", value: "flex" },
97-
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "block" },
98-
]);
99-
});
100-
101-
it("empty: bare element yields only the path-offset marker", () => {
102-
expect(buildPathOffsetPatches(div())).toEqual([
103-
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
104-
]);
105-
});
106-
73+
describe("buildClearPathOffsetPatches", () => {
10774
it("clear: restores translate from STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR and display from STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR", () => {
10875
const e = div();
10976
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "5px");
@@ -127,11 +94,6 @@ describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
12794
const ops = buildClearPathOffsetPatches(e);
12895
expect(ops.find((o) => o.property === "translate")?.value).toBeNull();
12996
});
130-
131-
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
132-
const e = populatedPathEl();
133-
assertClearCoversKeys(buildPathOffsetPatches(e), buildClearPathOffsetPatches(e));
134-
});
13597
});
13698

13799
/* ── Box size ────────────────────────────────────────────────────────────── */
@@ -286,52 +248,6 @@ describe("buildBoxSizePatches / buildClearBoxSizePatches", () => {
286248
});
287249
});
288250

289-
/* ── Combined box-size + path-offset (anchored-corner resize) ──────────────── */
290-
291-
describe("anchored-corner combined patch: [...buildBoxSizePatches, ...buildPathOffsetPatches]", () => {
292-
// NW/NE/SW resize commits size AND anchor offset in ONE persist. The two
293-
// builders read the same already-mutated element and are concatenated; this
294-
// is only safe if their {type,property} keys are disjoint (no builder
295-
// overwrites the other's op when the source patcher applies them in order).
296-
it("concatenation of both builders emits disjoint {type,property} keys (no collision)", () => {
297-
const e = div();
298-
applyStudioBoxSize(e, { width: 300, height: 200 });
299-
applyStudioPathOffset(e, { x: 10, y: 20 });
300-
301-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
302-
const keys = combined.map(opKey);
303-
expect(new Set(keys).size, `duplicate {type,property} key in combined patch: ${keys}`).toBe(
304-
keys.length,
305-
);
306-
});
307-
308-
it("combined patch carries BOTH markers so a soft-reload re-hydrates size and offset together", () => {
309-
const e = div();
310-
applyStudioBoxSize(e, { width: 300, height: 200 });
311-
applyStudioPathOffset(e, { x: 10, y: 20 });
312-
313-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
314-
const has = (property: string) =>
315-
combined.some((op) => op.type === "attribute" && op.property === property);
316-
expect(has(STUDIO_BOX_SIZE_ATTR)).toBe(true);
317-
expect(has(STUDIO_PATH_OFFSET_ATTR)).toBe(true);
318-
});
319-
320-
it("order is size-first: every box-size op precedes every path-offset op", () => {
321-
const e = div();
322-
applyStudioBoxSize(e, { width: 300, height: 200 });
323-
applyStudioPathOffset(e, { x: 10, y: 20 });
324-
325-
const boxKeys = new Set(buildBoxSizePatches(e).map(opKey));
326-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
327-
const lastBoxIdx = combined.reduce((acc, op, i) => (boxKeys.has(opKey(op)) ? i : acc), -1);
328-
const firstOffsetIdx = combined.findIndex(
329-
(op) => op.type === "attribute" && op.property === STUDIO_PATH_OFFSET_ATTR,
330-
);
331-
expect(firstOffsetIdx).toBeGreaterThan(lastBoxIdx);
332-
});
333-
});
334-
335251
/* ── Rotation ────────────────────────────────────────────────────────────── */
336252

337253
describe("buildClearRotationPatches", () => {

‎packages/studio/src/components/editor/manualEditsDomPatches.ts‎

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -72,36 +72,6 @@ function appendTransformDisplayOps(element: HTMLElement, ops: PatchOperation[]):
7272

7373
/* ── Path offset patches ─────────────────────────────────────────── */
7474

75-
export function buildPathOffsetPatches(element: HTMLElement): PatchOperation[] {
76-
const ops: PatchOperation[] = [];
77-
collectInlineStyleOps(element, [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP], ops);
78-
// When GSAP owns the element's transform, the live inline translate is kept
79-
// at "none" (the offset lives in GSAP's cache — see applyStudioPathOffset).
80-
// Persist the var() expression in that case, so a reload re-folds the offset.
81-
const inlineTranslate = element.style.getPropertyValue("translate");
82-
const hasOffsetVars =
83-
element.style.getPropertyValue(STUDIO_OFFSET_X_PROP) ||
84-
element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP);
85-
const translateValue =
86-
inlineTranslate && inlineTranslate !== "none"
87-
? inlineTranslate
88-
: hasOffsetVars
89-
? `var(${STUDIO_OFFSET_X_PROP}, 0px) var(${STUDIO_OFFSET_Y_PROP}, 0px)`
90-
: null;
91-
if (translateValue) {
92-
ops.push({ type: "inline-style", property: "translate", value: translateValue });
93-
}
94-
ops.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" });
95-
collectAttributeOps(
96-
element,
97-
[STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR],
98-
ops,
99-
);
100-
collectInlineStyleOps(element, ["display"], ops);
101-
collectAttributeOps(element, [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR], ops);
102-
return ops;
103-
}
104-
10575
export function buildClearPathOffsetPatches(element: HTMLElement): PatchOperation[] {
10676
const originalInlineTranslate = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
10777
const ops: PatchOperation[] = [

‎packages/studio/src/components/editor/persistSeam.integration.test.ts‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import {
1414
buildDomEditStylePatchOperation,
1515
buildDomEditTextPatchOperation,
1616
} from "./domEditingLayers";
17-
import { buildPathOffsetPatches } from "./manualEditsDomPatches";
18-
import { STUDIO_OFFSET_X_PROP, STUDIO_PATH_OFFSET_ATTR } from "./manualEditsTypes";
1917
import { makeSelection } from "../../hooks/domSelectionTestHarness";
2018
import { buildTextFieldChildOperations } from "../../hooks/domEditTextFieldCommitOps";
2119

@@ -126,22 +124,6 @@ describe("persist seam source mutation", () => {
126124
expect(findByHfId(html, "qa-image").getAttribute("style")).toContain("opacity: 0.4");
127125
});
128126

129-
it("persists detached jsdom path offset operations", () => {
130-
const element = document.createElement("div");
131-
element.style.setProperty(STUDIO_OFFSET_X_PROP, "24px");
132-
133-
const html = patchAndExpectChange(
134-
indexHtml,
135-
clientTarget({ id: "qa-shape", hfId: "qa-shape", tagName: "div" }),
136-
buildPathOffsetPatches(element),
137-
);
138-
const shape = findByHfId(html, "qa-shape");
139-
140-
expect(shape.getAttribute("style")).toContain(`${STUDIO_OFFSET_X_PROP}: 24px`);
141-
expect(shape.getAttribute("style")).toContain("translate: var(--hf-studio-offset-x, 0px)");
142-
expect(shape.getAttribute(STUDIO_PATH_OFFSET_ATTR)).toBe("true");
143-
});
144-
145127
it("persists timeline data-start attribute operation", () => {
146128
const html = patchAndExpectChange(
147129
indexHtml,

‎packages/studio/src/hooks/gsapTargetCache.ts‎

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)