Skip to content

Commit 2ca04a7

Browse files
vanceingallsclaude
andcommitted
fix(sdk): moveElement writes data-x/data-y, not left/top CSS
HF elements use data-x/data-y for positioning (read by htmlParser.ts, emitted by hyperframes generator). CSS left/top is not the runtime convention. Adds inverse round-trip test for prior position restore. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b6cdfff commit 2ca04a7

2 files changed

Lines changed: 33 additions & 9 deletions

File tree

packages/sdk/src/engine/mutate.test.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,19 +330,30 @@ describe("setCompositionMetadata", () => {
330330
// ─── moveElement ─────────────────────────────────────────────────────────────
331331

332332
describe("moveElement", () => {
333-
it("sets left and top as inline styles", () => {
333+
it("sets data-x and data-y attributes (HF positioning convention)", () => {
334334
const parsed = fresh();
335335
const result = applyOp(parsed, {
336336
type: "moveElement",
337337
target: "hf-title",
338338
x: 100,
339339
y: 200,
340340
});
341-
expect(result.forward.some((p) => p.path.endsWith("/left"))).toBe(true);
342-
expect(result.forward.some((p) => p.path.endsWith("/top"))).toBe(true);
343341
const el = parsed.document.querySelector('[data-hf-id="hf-title"]');
344-
expect(el?.getAttribute("style")).toContain("left: 100px");
345-
expect(el?.getAttribute("style")).toContain("top: 200px");
342+
expect(el?.getAttribute("data-x")).toBe("100");
343+
expect(el?.getAttribute("data-y")).toBe("200");
344+
expect(result.forward.some((p) => p.path.endsWith("/data-x"))).toBe(true);
345+
expect(result.forward.some((p) => p.path.endsWith("/data-y"))).toBe(true);
346+
});
347+
348+
it("inverse restores prior data-x/data-y", () => {
349+
const parsed = fresh();
350+
const el = parsed.document.querySelector('[data-hf-id="hf-title"]') as Element;
351+
el.setAttribute("data-x", "50");
352+
el.setAttribute("data-y", "75");
353+
const result = applyOp(parsed, { type: "moveElement", target: "hf-title", x: 100, y: 200 });
354+
applyPatchesToDocument(parsed, result.inverse);
355+
expect(el.getAttribute("data-x")).toBe("50");
356+
expect(el.getAttribute("data-y")).toBe("75");
346357
});
347358
});
348359

packages/sdk/src/engine/mutate.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ export function applyOp(parsed: ParsedDocument, op: EditOp): MutationResult {
9191
case "setHold":
9292
return handleSetHold(parsed, targets(op.target), op.hold);
9393
case "moveElement":
94-
return handleSetStyle(parsed, targets(op.target), {
95-
left: `${op.x}px`,
96-
top: `${op.y}px`,
97-
});
94+
return handleMoveElement(parsed, targets(op.target), op.x, op.y);
9895
case "removeElement":
9996
return handleRemoveElement(parsed, targets(op.target));
10097
case "setCompositionMetadata":
@@ -147,6 +144,22 @@ function handleSetStyle(
147144
return result;
148145
}
149146

147+
function handleMoveElement(
148+
parsed: ParsedDocument,
149+
ids: HfId[],
150+
x: number,
151+
y: number,
152+
): MutationResult {
153+
// HF elements are positioned via data-x / data-y (parsed by htmlParser.ts,
154+
// emitted by hyperframes generator). CSS left/top is not the convention.
155+
const rx = handleSetAttribute(parsed, ids, "data-x", String(x));
156+
const ry = handleSetAttribute(parsed, ids, "data-y", String(y));
157+
return {
158+
forward: [...rx.forward, ...ry.forward],
159+
inverse: [...ry.inverse, ...rx.inverse],
160+
};
161+
}
162+
150163
function handleSetText(parsed: ParsedDocument, ids: HfId[], value: string): MutationResult {
151164
const result: MutationResult = { forward: [], inverse: [] };
152165
for (const id of ids) {

0 commit comments

Comments
 (0)