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:
@@ -112,11 +112,32 @@ async function patchElementBatch(projectId: string, batch: DomEditPatchBatch) {
|
||||
return {
|
||||
sourceFile: batch.sourceFile,
|
||||
changed: result.changed === true,
|
||||
// Skip-reload safety: the persist is only provably in sync with the live
|
||||
// DOM when the server confirmed EVERY patch target matched. A missing /
|
||||
// short matched[] is treated as unknown (false) so the caller falls back
|
||||
// to reloading rather than silently diverging from disk.
|
||||
allMatched:
|
||||
Array.isArray(result.matched) &&
|
||||
result.matched.length === batch.patches.length &&
|
||||
result.matched.every(Boolean),
|
||||
before,
|
||||
after: typeof result.content === "string" ? result.content : before,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A batch is reload-skippable only when it is style-only: every operation is an
|
||||
* `inline-style` write. The z-reorder commit applies those exact styles to the
|
||||
* live iframe DOM synchronously, so persisting them adds nothing the preview
|
||||
* doesn't already show. Any other op type (attribute / text-content / …) can
|
||||
* have server-side semantics the live DOM hasn't mirrored — reload for those.
|
||||
*/
|
||||
function batchesAreInlineStyleOnly(batches: DomEditPatchBatch[]): boolean {
|
||||
return batches.every((batch) =>
|
||||
batch.patches.every((patch) => patch.operations.every((op) => op.type === "inline-style")),
|
||||
);
|
||||
}
|
||||
|
||||
export interface UseDomEditCommitsParams {
|
||||
activeCompPath: string | null;
|
||||
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
||||
@@ -390,7 +411,18 @@ export function useDomEditCommits({
|
||||
files,
|
||||
});
|
||||
forceReloadSdkSession?.();
|
||||
reloadPreview();
|
||||
// A z-only reorder already applied its inline styles to the live iframe
|
||||
// DOM (and the store) synchronously, so remounting the iframe here only
|
||||
// produces a visible blink. Skip the reload when the caller asked for it
|
||||
// AND the persist is provably in sync: style-only ops, every target
|
||||
// matched. Any unmatched patch means the live DOM now shows state disk
|
||||
// doesn't hold — reload so the preview reconverges. (The SSE/file-watcher
|
||||
// reload is independently suppressed by domEditSaveTimestampRef above.)
|
||||
const skipSafe =
|
||||
options.skipReload === true &&
|
||||
batchesAreInlineStyleOnly(batches) &&
|
||||
results.every((result) => result.allMatched);
|
||||
if (!skipSafe) reloadPreview();
|
||||
}).catch((error) => {
|
||||
const alreadyToasted =
|
||||
(error instanceof StudioSaveHttpError ||
|
||||
|
||||
Reference in New Issue
Block a user