From 224caf8f49e38a87674dc8d45344812239318d34 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Mon, 24 Aug 2026 09:56:41 -0700 Subject: [PATCH 1/7] feat(player): report runtime data application --- packages/core/src/runtime/init.ts | 10 +++++++++- packages/core/src/runtime/runtimeData.test.ts | 17 +++++++++++++++++ packages/core/src/runtime/runtimeData.ts | 14 ++++++++++++-- packages/core/src/runtime/types.ts | 7 +++++++ .../player/src/runtime-message-handler.test.ts | 16 ++++++++++++++++ packages/player/src/runtime-message-handler.ts | 7 +++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index 831fccce11..b6d3a80582 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -62,7 +62,12 @@ import { shouldAttemptPeriodicTimelineBind } from "./timelineRebindPolicy"; import { installStudioCustomEase } from "./customEase"; import { parseNumeric } from "./startExpression"; import { parseStrictFiniteTimingNumber } from "./playbackRate"; -import { clearRuntimeData, setRuntimeData, setRuntimeDataErrorReporter } from "./runtimeData"; +import { + clearRuntimeData, + setRuntimeData, + setRuntimeDataAppliedReporter, + setRuntimeDataErrorReporter, +} from "./runtimeData"; const AUTHORED_DURATION_ATTR = "data-hf-authored-duration"; const AUTHORED_END_ATTR = "data-hf-authored-end"; @@ -136,6 +141,9 @@ export function initSandboxRuntimeModular(): void { message: error instanceof Error ? error.message : String(error), }); }); + setRuntimeDataAppliedReporter((channel) => { + postRuntimeMessage({ source: "hf-preview", type: "runtime-data-applied", channel }); + }); // SDK moveElement edits must render even when no usable GSAP timeline ever // binds (CSS/WAAPI-animated or fully static compositions) — apply at init. // This runs at DOMContentLoaded, after inline composition scripts have diff --git a/packages/core/src/runtime/runtimeData.test.ts b/packages/core/src/runtime/runtimeData.test.ts index 60efc255ee..ac3d949e82 100644 --- a/packages/core/src/runtime/runtimeData.test.ts +++ b/packages/core/src/runtime/runtimeData.test.ts @@ -4,6 +4,7 @@ import { registerRuntimeDataHandler, resetRuntimeDataForTests, setRuntimeData, + setRuntimeDataAppliedReporter, setRuntimeDataErrorReporter, } from "./runtimeData"; @@ -50,4 +51,20 @@ describe("runtime data registry", () => { expect(() => setRuntimeData("captions", {})).not.toThrow(); expect(reporter).toHaveBeenCalledWith("captions", expect.any(Error)); }); + + it("reports asynchronous completion and rejection", async () => { + const applied = vi.fn(); + const failed = vi.fn(); + setRuntimeDataAppliedReporter(applied); + setRuntimeDataErrorReporter(failed); + registerRuntimeDataHandler("captions", async (payload) => { + await Promise.resolve(); + if (payload === "bad") throw new Error("async attach failed"); + }); + + setRuntimeData("captions", "good"); + await vi.waitFor(() => expect(applied).toHaveBeenCalledWith("captions")); + setRuntimeData("captions", "bad"); + await vi.waitFor(() => expect(failed).toHaveBeenCalledWith("captions", expect.any(Error))); + }); }); diff --git a/packages/core/src/runtime/runtimeData.ts b/packages/core/src/runtime/runtimeData.ts index d088bd8be7..9fb0f9cac0 100644 --- a/packages/core/src/runtime/runtimeData.ts +++ b/packages/core/src/runtime/runtimeData.ts @@ -1,9 +1,11 @@ -export type RuntimeDataHandler = (payload: unknown) => void; +export type RuntimeDataHandler = (payload: unknown) => void | Promise; export type RuntimeDataErrorReporter = (channel: string, error: unknown) => void; +export type RuntimeDataAppliedReporter = (channel: string) => void; const retained = new Map(); const handlers = new Map(); let reportError: RuntimeDataErrorReporter = () => undefined; +let reportApplied: RuntimeDataAppliedReporter = () => undefined; function validChannel(channel: string): boolean { return /^[a-z][a-z0-9-]{0,63}$/.test(channel); @@ -13,7 +15,10 @@ function deliver(channel: string, payload: unknown): void { const handler = handlers.get(channel); if (!handler) return; try { - handler(payload); + void Promise.resolve(handler(payload)).then( + () => reportApplied(channel), + (error) => reportError(channel, error), + ); } catch (error) { reportError(channel, error); } @@ -23,6 +28,10 @@ export function setRuntimeDataErrorReporter(reporter: RuntimeDataErrorReporter): reportError = reporter; } +export function setRuntimeDataAppliedReporter(reporter: RuntimeDataAppliedReporter): void { + reportApplied = reporter; +} + export function setRuntimeData(channel: string, payload: unknown): void { if (!validChannel(channel)) return; retained.set(channel, payload); @@ -52,4 +61,5 @@ export function resetRuntimeDataForTests(): void { retained.clear(); handlers.clear(); reportError = () => undefined; + reportApplied = () => undefined; } diff --git a/packages/core/src/runtime/types.ts b/packages/core/src/runtime/types.ts index 5e98822fa5..9c3afc9bd7 100644 --- a/packages/core/src/runtime/types.ts +++ b/packages/core/src/runtime/types.ts @@ -178,6 +178,12 @@ export type RuntimeDataErrorMessage = { message: string; }; +export type RuntimeDataAppliedMessage = { + source: "hf-preview"; + type: "runtime-data-applied"; + channel: string; +}; + /** * Analytics events emitted by the runtime. * @@ -229,6 +235,7 @@ export type RuntimeOutboundMessage = | RuntimeMediaAutoplayBlockedMessage | RuntimeReadyMessage | RuntimeDataErrorMessage + | RuntimeDataAppliedMessage | RuntimeAnalyticsMessage | RuntimePerformanceMessage | RuntimeGroupLevelsMessage; diff --git a/packages/player/src/runtime-message-handler.test.ts b/packages/player/src/runtime-message-handler.test.ts index f463c5f65c..84aa73b297 100644 --- a/packages/player/src/runtime-message-handler.test.ts +++ b/packages/player/src/runtime-message-handler.test.ts @@ -92,6 +92,22 @@ describe("handleRuntimeMessage runtime data errors", () => { expect.objectContaining({ type: "runtimedataerror" }), ); }); + + it("surfaces successful channel application", () => { + const frameWindow = {} as Window; + const callbacks = makeCallbacks(); + handleRuntimeMessage( + { + source: frameWindow, + data: { source: "hf-preview", type: "runtime-data-applied", channel: "captions" }, + } as MessageEvent, + frameWindow, + callbacks, + ); + expect(callbacks.dispatchEvent).toHaveBeenCalledWith( + expect.objectContaining({ type: "runtimedataapplied" }), + ); + }); }); describe("handleRuntimeMessage media autoplay fallback", () => { diff --git a/packages/player/src/runtime-message-handler.ts b/packages/player/src/runtime-message-handler.ts index 47d1335f68..4fd991b08c 100644 --- a/packages/player/src/runtime-message-handler.ts +++ b/packages/player/src/runtime-message-handler.ts @@ -101,6 +101,13 @@ export function handleRuntimeMessage( return; } + if (data["type"] === "runtime-data-applied") { + callbacks.dispatchEvent( + new CustomEvent("runtimedataapplied", { detail: { channel: data["channel"] } }), + ); + return; + } + if (data["type"] === "state") { callbacks.setPlaybackState( applyRuntimeStateMessage( From 0dced8834cfc1a476d445d8b083bff63f7c3e460 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Mon, 24 Aug 2026 13:45:46 -0700 Subject: [PATCH 2/7] fix(player): fail closed on runtime data delivery --- packages/player/README.md | 6 ++- .../player/src/hyperframes-player.test.ts | 47 +++++++++++++++++++ packages/player/src/hyperframes-player.ts | 39 +++++++++++++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/packages/player/README.md b/packages/player/README.md index 0627e4e074..782da8c7d6 100644 --- a/packages/player/README.md +++ b/packages/player/README.md @@ -134,7 +134,11 @@ player.iframeElement; // HTMLIFrameElement (read-only) ## Advanced: iframe access -The composition runs inside a sandboxed `