From e185c116ba9c3eb3aa2de83bf38d66db569a7c6f Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 1 Oct 2026 11:48:59 -0700 Subject: [PATCH] fix(studio): a text edit's gesture mark draws nothing, so an undo under it reverts --- .../src/hooks/useInlineTextEdit.test.tsx | 27 +++++++++++++++++++ .../studio/src/hooks/useInlineTextEdit.ts | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/studio/src/hooks/useInlineTextEdit.test.tsx b/packages/studio/src/hooks/useInlineTextEdit.test.tsx index 8f4548b140..7544469ecc 100644 --- a/packages/studio/src/hooks/useInlineTextEdit.test.tsx +++ b/packages/studio/src/hooks/useInlineTextEdit.test.tsx @@ -5,6 +5,7 @@ import { createRoot } from "react-dom/client"; import { afterEach, describe, expect, it, vi } from "vitest"; import { PreviewReadOnlyProvider } from "../components/editor/previewReadOnlyContext"; import { useInlineTextEdit, type InlineTextEditControls } from "./useInlineTextEdit"; +import { applyUndoRestoreToPreview } from "../utils/gsapUndoRestore"; (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -643,3 +644,29 @@ describe("useInlineTextEdit with styled runs", () => { act(() => root.unmount()); }); }); + +describe("an undo that lands while a text edit is open", () => { + it("marks the edit as drawing nothing, so the undo reverts the move and keeps the edit", () => { + const wrap = (body: string) => `${body}`; + const moved = `

Title

`; + document.body.innerHTML = moved; + const element = document.getElementById("t")!; + const { controls, root } = mount(); + act(() => { + controls().start(element); + }); + const contentWindow = { __player: { seek: vi.fn() }, __hfStudioManualEditsApply: vi.fn() }; + const iframe = { contentWindow, contentDocument: document } as unknown as HTMLIFrameElement; + const files = { + "index.html": { previous: wrap(moved), restored: wrap(`

Title

`) }, + }; + + expect(element.getAttribute("data-hf-studio-manual-edit-gesture")).toMatch(/:edit$/); + expect(applyUndoRestoreToPreview(iframe, "index.html", files, 0, vi.fn())).toBe("soft"); + + expect(element.style.getPropertyValue("translate")).toBe(""); + expect(element.getAttribute("contenteditable")).toBe("true"); + expect(element.style.getPropertyValue("outline")).not.toBe(""); + act(() => root.unmount()); + }); +}); diff --git a/packages/studio/src/hooks/useInlineTextEdit.ts b/packages/studio/src/hooks/useInlineTextEdit.ts index 2c9b0ba63b..27706f6647 100644 --- a/packages/studio/src/hooks/useInlineTextEdit.ts +++ b/packages/studio/src/hooks/useInlineTextEdit.ts @@ -141,7 +141,7 @@ export function useInlineTextEdit({ original: element.innerHTML, outline: element.style.outline, outlineOffset: element.style.outlineOffset, - gesture: beginStudioManualEditGesture(element), + gesture: beginStudioManualEditGesture(element, "edit"), }; // Drawn on the element itself, not in Studio's overlay above it. This is // the only mark that says the caret is in the TEXT rather than the