mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): match the write receipt in dev, so an edit stops reloading the preview (#3206)
## What Editing anything in the canvas on the dev server reloaded the preview iframe. It no longer does. ## Why The write receipt exists to prevent exactly this: Studio marks its own writes so the file-watcher echo can be told apart from somebody editing the file underneath it. The receipt is matched on the file's current bytes as well as its path, so `consumeFileWriteReceipt(absPath, expectedVersion)` takes a version. The dev plugin called it with the path alone. `expectedVersion` was `undefined`, the version comparison never matched, and so every Studio write looked external and reloaded the preview. The CLI server — which is what ships — has always passed the version, so this is dev-server only. ## How The plugin reads the file and passes its version, the same way `studioServer.ts` does, and treats a deletion (no readable bytes) as unmatched. ## Test plan Driven on the dev server against a real composition, with `hf-reload-debug` on: - Before: a drag logged `file-change` with a full external path, then `reload`, then `refreshPlayer`, and the iframe navigated — one reload per edit. - After: the same drag logs `file-change` carrying the write token, then `suppressed: own write token`. Iframe reloads are zero across drag, resize and an inline text edit. - Full studio suite (3727), format and lint green. Found while chasing a flash after every canvas edit. The other half of that flash was Vite's own HMR full-reloading the page, fixed separately in #3163; with both in, the canvas stops flashing.
This commit is contained in:
@@ -68,11 +68,11 @@ function devProjectApi(): Plugin {
|
||||
createStudioApi: (adapter: ReturnType<typeof createViteAdapter>) => {
|
||||
fetch: (req: Request) => Promise<Response>;
|
||||
};
|
||||
consumeFileWriteReceipt?: (path: string) => {
|
||||
path: string;
|
||||
version: string;
|
||||
writeToken: string;
|
||||
} | null;
|
||||
consumeFileWriteReceipt?: (
|
||||
path: string,
|
||||
expectedVersion: string,
|
||||
) => { path: string; version: string; writeToken: string } | null;
|
||||
fileContentVersion?: (content: string) => string;
|
||||
} | null = null;
|
||||
const getApi = async () => {
|
||||
if (!_api) {
|
||||
@@ -177,7 +177,20 @@ function devProjectApi(): Plugin {
|
||||
)
|
||||
return;
|
||||
console.log(`[Studio] File changed: ${filePath}`);
|
||||
const receipt = _studioServerModule?.consumeFileWriteReceipt?.(filePath) ?? null;
|
||||
// The receipt is matched on the file's current bytes, not just its path,
|
||||
// so a write is only recognised as ours when the version agrees. Calling
|
||||
// this without the version could never match, which left every Studio
|
||||
// write looking external and reloaded the preview on each edit.
|
||||
let version: string | null = null;
|
||||
try {
|
||||
version =
|
||||
_studioServerModule?.fileContentVersion?.(readFileSync(filePath, "utf-8")) ?? null;
|
||||
} catch {
|
||||
// A deletion has no current bytes to match a write receipt against.
|
||||
}
|
||||
const receipt = version
|
||||
? (_studioServerModule?.consumeFileWriteReceipt?.(filePath, version) ?? null)
|
||||
: null;
|
||||
server.ws.send({
|
||||
type: "custom",
|
||||
event: "hf:file-change",
|
||||
|
||||
Reference in New Issue
Block a user