Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/cli/src/commands/render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe("renderLocal browser GPU config", () => {
quality: "standard",
format: "mp4",
gpu: false,
browserGpu: false,
browserGpuMode: "software",
hdrMode: "auto",
quiet: true,
entryFile: "compositions/intro.html",
Expand All @@ -181,7 +181,7 @@ describe("renderLocal browser GPU config", () => {
quality: "standard",
format: "mp4",
gpu: false,
browserGpu: false,
browserGpuMode: "software",
hdrMode: "auto",
quiet: true,
});
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/runtime/adapters/animejs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuntimeDeterministicAdapter } from "../types";
import { swallow } from "../diagnostics";

/**
* anime.js adapter for HyperFrames
Expand Down Expand Up @@ -61,8 +62,9 @@ export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
}
}
(window as AnimeWindow).__hfAnime = existing;
} catch {
} catch (err) {
// ignore discovery failures
swallow("runtime.adapters.animejs.site1", err);
}
},

Expand All @@ -76,8 +78,9 @@ export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
if (typeof instance.seek === "function") {
instance.seek(timeMs);
}
} catch {
} catch (err) {
// ignore per-instance failures — keep going for other instances
swallow("runtime.adapters.animejs.site2", err);
}
}
},
Expand All @@ -91,8 +94,9 @@ export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
if (typeof instance.pause === "function") {
instance.pause();
}
} catch {
} catch (err) {
// ignore
swallow("runtime.adapters.animejs.site3", err);
}
}
},
Expand All @@ -106,8 +110,9 @@ export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
if (typeof instance.play === "function") {
instance.play();
}
} catch {
} catch (err) {
// ignore
swallow("runtime.adapters.animejs.site4", err);
}
}
},
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/runtime/adapters/css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuntimeDeterministicAdapter } from "../types";
import { swallow } from "../diagnostics";

export function createCssAdapter(params?: {
resolveStartSeconds?: (element: Element) => number;
Expand All @@ -22,13 +23,15 @@ export function createCssAdapter(params?: {
for (const animation of animations) {
try {
animation.currentTime = timeMs;
} catch {
} catch (err) {
// ignore animations that reject currentTime writes
swallow("runtime.adapters.css.site1", err);
}
try {
animation.pause();
} catch {
} catch (err) {
// infinite unresolved animations can throw on pause before currentTime sticks
swallow("runtime.adapters.css.site2", err);
}
}
};
Expand All @@ -37,8 +40,9 @@ export function createCssAdapter(params?: {
for (const animation of animations) {
try {
animation.play();
} catch {
} catch (err) {
// ignore animation edge-cases
swallow("runtime.adapters.css.site3", err);
}
}
};
Expand All @@ -47,8 +51,9 @@ export function createCssAdapter(params?: {
for (const animation of animations) {
try {
animation.pause();
} catch {
} catch (err) {
// ignore animation edge-cases
swallow("runtime.adapters.css.site4", err);
}
}
};
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/runtime/adapters/lottie.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuntimeDeterministicAdapter } from "../types";
import { swallow } from "../diagnostics";
export { isLottieAnimationLoaded } from "./lottieReadiness";

/**
Expand Down Expand Up @@ -71,8 +72,9 @@ export function createLottieAdapter(): RuntimeDeterministicAdapter {
(window as LottieWindow).__hfLottie = existing;
}
}
} catch {
} catch (err) {
// ignore discovery failures
swallow("runtime.adapters.lottie.site1", err);
}
},

Expand Down Expand Up @@ -107,8 +109,9 @@ export function createLottieAdapter(): RuntimeDeterministicAdapter {
anim.seek(percentage);
}
}
} catch {
} catch (err) {
// ignore per-animation failures — keep going for other instances
swallow("runtime.adapters.lottie.site2", err);
}
}
},
Expand All @@ -124,8 +127,9 @@ export function createLottieAdapter(): RuntimeDeterministicAdapter {
} else if (isDotLottiePlayer(anim)) {
anim.pause();
}
} catch {
} catch (err) {
// ignore
swallow("runtime.adapters.lottie.site3", err);
}
}
},
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/runtime/adapters/three.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuntimeDeterministicAdapter } from "../types";
import { swallow } from "../diagnostics";

export function createThreeAdapter(): RuntimeDeterministicAdapter {
let forcedTime: number | null = null;
Expand All @@ -13,8 +14,9 @@ export function createThreeAdapter(): RuntimeDeterministicAdapter {
window.__hfThreeTime = forcedTime;
try {
window.dispatchEvent(new CustomEvent("hf-seek", { detail: { time: forcedTime } }));
} catch {
} catch (err) {
// ignore custom event failures
swallow("runtime.adapters.three.site1", err);
}
},
pause: () => {
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/runtime/adapters/waapi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RuntimeDeterministicAdapter } from "../types";
import { swallow } from "../diagnostics";

export function createWaapiAdapter(): RuntimeDeterministicAdapter {
return {
Expand All @@ -10,13 +11,15 @@ export function createWaapiAdapter(): RuntimeDeterministicAdapter {
for (const animation of document.getAnimations()) {
try {
animation.currentTime = timeMs;
} catch {
} catch (err) {
// ignore animations that reject currentTime writes
swallow("runtime.adapters.waapi.site1", err);
}
try {
animation.pause();
} catch {
} catch (err) {
// infinite unresolved animations can throw here until currentTime resolves
swallow("runtime.adapters.waapi.site2", err);
}
}
},
Expand All @@ -25,8 +28,9 @@ export function createWaapiAdapter(): RuntimeDeterministicAdapter {
for (const animation of document.getAnimations()) {
try {
animation.pause();
} catch {
} catch (err) {
// ignore animation edge-cases
swallow("runtime.adapters.waapi.site3", err);
}
}
},
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/runtime/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { swallow } from "./diagnostics";
/**
* Runtime analytics & performance telemetry — vendor-agnostic event emission.
*
Expand Down Expand Up @@ -75,8 +76,9 @@ export function emitAnalyticsEvent(
event,
properties: properties ?? {},
});
} catch {
} catch (err) {
// Never let analytics failures affect the runtime
swallow("runtime.analytics.site1", err);
}
}

Expand Down Expand Up @@ -107,8 +109,9 @@ export function emitPerformanceMetric(
if (typeof performance !== "undefined" && typeof performance.mark === "function") {
performance.mark(name, { detail: { value, tags: tags ?? {} } });
}
} catch {
} catch (err) {
// performance API unavailable or rejected — keep going
swallow("runtime.analytics.site2", err);
}

if (!_postMessage) return;
Expand All @@ -120,7 +123,8 @@ export function emitPerformanceMetric(
value,
tags: tags ?? {},
});
} catch {
} catch (err) {
// Never let telemetry failures affect the runtime
swallow("runtime.analytics.site3", err);
}
}
9 changes: 6 additions & 3 deletions packages/core/src/runtime/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { swallow } from "./diagnostics";
import type { RuntimeBridgeControlMessage, RuntimeOutboundMessage } from "./types";

type BridgeDeps = {
Expand All @@ -15,8 +16,9 @@ type BridgeDeps = {
export function postRuntimeMessage(payload: RuntimeOutboundMessage): void {
try {
window.parent.postMessage(payload, "*");
} catch {
// Ignore cross-frame posting failures.
} catch (err) {
// Cross-frame posting can throw if the parent is gone or origin-isolated.
swallow("bridge.postMessage", err);
}
}

Expand Down Expand Up @@ -104,8 +106,9 @@ function flashElements(selectors: string[], duration: number): void {
el.classList.add("__hf-flash");
setTimeout(() => el.classList.remove("__hf-flash"), duration);
});
} catch {
} catch (err) {
// Invalid selector — skip
swallow("bridge.flashElements.querySelector", err);
}
}
}
77 changes: 77 additions & 0 deletions packages/core/src/runtime/diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @vitest-environment happy-dom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { swallow } from "./diagnostics";

interface HFTestWindow {
__hfDebug?: boolean;
__HYPERFRAMES_DEBUG?: boolean;
__hf?: {
onSwallowed?: (e: { label: string; error: unknown }) => void;
};
}

describe("swallow", () => {
const w = window as unknown as HFTestWindow;
const originalDebug = console.debug;

beforeEach(() => {
delete w.__hfDebug;
delete w.__HYPERFRAMES_DEBUG;
delete w.__hf;
console.debug = vi.fn();
});

afterEach(() => {
console.debug = originalDebug;
delete w.__hfDebug;
delete w.__HYPERFRAMES_DEBUG;
delete w.__hf;
});

it("is silent by default — no console output, no handler call", () => {
swallow("test.silent", new Error("boom"));
expect(console.debug).not.toHaveBeenCalled();
});

it("logs to console.debug when window.__hfDebug is true", () => {
w.__hfDebug = true;
const err = new Error("boom");
swallow("test.debug", err);
expect(console.debug).toHaveBeenCalledWith("[hyperframes] test.debug swallowed:", err);
});

it("also honors window.__HYPERFRAMES_DEBUG (legacy flag)", () => {
w.__HYPERFRAMES_DEBUG = true;
swallow("test.legacy", "string-error");
expect(console.debug).toHaveBeenCalledWith(
"[hyperframes] test.legacy swallowed:",
"string-error",
);
});

it("dispatches to window.__hf.onSwallowed when installed", () => {
const handler = vi.fn();
w.__hf = { onSwallowed: handler };
const err = new Error("from handler");
swallow("test.handler", err);
expect(handler).toHaveBeenCalledWith({ label: "test.handler", error: err });
});

it("does not propagate errors from the user-installed handler", () => {
w.__hf = {
onSwallowed: () => {
throw new Error("handler exploded");
},
};
expect(() => swallow("test.handler-throws", new Error("real"))).not.toThrow();
});

it("can run with both handler AND debug flag set", () => {
w.__hfDebug = true;
const handler = vi.fn();
w.__hf = { onSwallowed: handler };
swallow("test.both", "err");
expect(handler).toHaveBeenCalled();
expect(console.debug).toHaveBeenCalled();
});
});
Loading
Loading