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>) => {
|
createStudioApi: (adapter: ReturnType<typeof createViteAdapter>) => {
|
||||||
fetch: (req: Request) => Promise<Response>;
|
fetch: (req: Request) => Promise<Response>;
|
||||||
};
|
};
|
||||||
consumeFileWriteReceipt?: (path: string) => {
|
consumeFileWriteReceipt?: (
|
||||||
path: string;
|
path: string,
|
||||||
version: string;
|
expectedVersion: string,
|
||||||
writeToken: string;
|
) => { path: string; version: string; writeToken: string } | null;
|
||||||
} | null;
|
fileContentVersion?: (content: string) => string;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
const getApi = async () => {
|
const getApi = async () => {
|
||||||
if (!_api) {
|
if (!_api) {
|
||||||
@@ -177,7 +177,20 @@ function devProjectApi(): Plugin {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
console.log(`[Studio] File changed: ${filePath}`);
|
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({
|
server.ws.send({
|
||||||
type: "custom",
|
type: "custom",
|
||||||
event: "hf:file-change",
|
event: "hf:file-change",
|
||||||
|
|||||||
Reference in New Issue
Block a user