mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): flashless z-order commits and visible-overlap stepping
Two legibility fixes for the canvas z-order menu, from user feel-testing: - z-only commits no longer remount the preview iframe. The commit hook already applies the inline z (+ injected position) to the live elements and updates the store synchronously; the post-commit reloadPreview() was a redundant full remount that read as a canvas 'blink' on every action. commitDomEditPatchBatches gains skipReload, engaged only when provably safe: every op is an inline-style patch AND the server reports every patch matched — anything else falls back to the reload so the preview reconverges with disk. The file-watcher's own reload stays suppressed by the existing domEditSaveTimestampRef window, so the skip is real. - Bring Forward / Send Backward step over the next VISIBLY overlapping sibling. The nearest z-neighbor in a composition is often invisible at the current frame (runtime hides time-inactive clips with inline visibility/display; GSAP parks elements at opacity 0), so the step crossed something the user couldn't see — 'enabled but nothing happens'. The forward/backward set now filters on element-level computed visibility (display/visibility/opacity, injectable for tests); enable/disable shares the resolver so the menu is honest: actions disable when no visible neighbor exists. Front/back keep the full painting family. - The neighbor that was stepped over gets a 600ms accent flash, drawn in the studio overlay layer (never in the iframe DOM), so the action shows its work.
This commit is contained in:
@@ -265,7 +265,7 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
it("persists an N-element reorder with one batch POST, one undo entry, and one reload", async () => {
|
||||
it("persists an N-element reorder with one batch POST, one undo entry, and NO iframe reload", async () => {
|
||||
const original =
|
||||
'<div id="a" style="z-index: 1"></div><div id="b" style="z-index: 2"></div><div id="c" style="z-index: 3"></div>';
|
||||
const after =
|
||||
@@ -338,6 +338,41 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
||||
coalesceKey: "z-reorder:test",
|
||||
files: { "index.html": { before: original, after } },
|
||||
});
|
||||
// FIX: a z-only reorder must NOT remount the preview iframe ("the blink").
|
||||
// The live DOM + store already hold the final state and the server matched
|
||||
// every style-only patch, so the reload is provably redundant.
|
||||
expect(rendered.reloadPreview).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
rendered.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to reloading when the server response omits matched[]", async () => {
|
||||
// Without a matched[] confirmation the persist can't be proven in sync with
|
||||
// the live DOM — the skip-reload path must not engage.
|
||||
const original = '<div id="a" style="z-index: 1"></div>';
|
||||
const after = '<div id="a" style="z-index: 2"></div>';
|
||||
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("/api/projects/p1/files/")) return jsonResponse({ content: original });
|
||||
if (url.includes("/file-mutations/patch-elements-batch/")) {
|
||||
return jsonResponse({ ok: true, changed: true, content: after });
|
||||
}
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
const { iframe, element } = createPreviewElement();
|
||||
element.id = "a";
|
||||
const rendered = renderDomEditCommits(createSelection(element), iframe);
|
||||
|
||||
try {
|
||||
await act(async () => {
|
||||
await rendered.hook.handleDomZIndexReorderCommit([
|
||||
{ element, zIndex: 2, id: "a", sourceFile: "index.html" },
|
||||
]);
|
||||
});
|
||||
|
||||
expect(rendered.recordEdit).toHaveBeenCalledTimes(1);
|
||||
expect(rendered.reloadPreview).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
rendered.cleanup();
|
||||
@@ -347,7 +382,9 @@ describe("useDomEditCommits z-index reorder persistence", () => {
|
||||
it("warns and reports telemetry for unmatched batch patches without throwing", async () => {
|
||||
// The server reports per-patch matched[]: #b was not found in the source.
|
||||
// The matched subset persisted, so the commit must complete (no rollback of
|
||||
// applied state) while surfacing the partial failure.
|
||||
// applied state) while surfacing the partial failure. An unmatched target
|
||||
// also means the live DOM shows z-order the disk lacks, so the skip-reload
|
||||
// path must NOT engage — the reload reconverges the preview with disk.
|
||||
const original = '<div id="a" style="z-index: 1"></div>';
|
||||
const after = '<div id="a" style="z-index: 2"></div>';
|
||||
const fetchMock = vi.fn(async (input: Parameters<typeof fetch>[0]): Promise<Response> => {
|
||||
|
||||
Reference in New Issue
Block a user