Merge pull request #1826 from heygen-com/fix/studio-recent-issues

fix(studio): resolve timeline keyframe, click-selection, and nested-video sync regressions
This commit is contained in:
Miguel Ángel
2026-07-01 22:01:41 -07:00
committed by GitHub
34 changed files with 1483 additions and 88 deletions
+22 -2
View File
@@ -1,6 +1,14 @@
import { describe, expect, it } from "vitest";
import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest";
import { shouldWatchProjectFile } from "./fileWatcher.js";
const mockWatcher = new EventEmitter() as EventEmitter & { close: () => void };
mockWatcher.close = vi.fn();
vi.mock("node:fs", () => ({
watch: vi.fn(() => mockWatcher),
}));
const { shouldWatchProjectFile, createProjectWatcher } = await import("./fileWatcher.js");
describe("shouldWatchProjectFile", () => {
it("watches files that can affect the project signature", () => {
@@ -17,3 +25,15 @@ describe("shouldWatchProjectFile", () => {
expect(shouldWatchProjectFile(".hyperframes/cache.json")).toBe(false);
});
});
describe("createProjectWatcher", () => {
// Regression: fs.watch can fail asynchronously (e.g. EMFILE from exhausted
// OS watch handles) via an 'error' event, not a thrown exception. An
// EventEmitter 'error' with no listener crashes the whole process — this
// must degrade gracefully instead, per the sibling synchronous-failure path.
it("does not crash the process when the underlying watcher emits 'error'", () => {
createProjectWatcher("/fake/project/dir");
expect(() => mockWatcher.emit("error", new Error("EMFILE"))).not.toThrow();
expect(mockWatcher.close).toHaveBeenCalled();
});
});
+9
View File
@@ -47,6 +47,15 @@ export function createProjectWatcher(projectDir: string): ProjectWatcher {
}
}, DEBOUNCE_MS);
});
// fs.watch can fail asynchronously too (e.g. EMFILE from exhausted OS watch
// handles) — that surfaces as an 'error' event, not a thrown exception. An
// EventEmitter 'error' with no listener crashes the whole process, so this
// listener is required for the same "degrade gracefully" the catch below
// already promises for the synchronous failure mode.
watcher.on("error", () => {
watcher?.close();
watcher = null;
});
} catch {
// fs.watch may fail on some platforms — degrade gracefully (no auto-refresh)
}