diff --git a/packages/aws-lambda/src/handler.ts b/packages/aws-lambda/src/handler.ts index 3a41b58fb..92018f4af 100644 --- a/packages/aws-lambda/src/handler.ts +++ b/packages/aws-lambda/src/handler.ts @@ -18,6 +18,7 @@ import { S3Client } from "@aws-sdk/client-s3"; import { assemble, type AssembleResult, + type ChunkRenderer, type ChunkResult, type DistributedRenderConfig, listPlanV2ArtifactsForTarget, @@ -77,7 +78,7 @@ export interface HandlerDeps { primitives?: { plan: typeof plan; planV2WithPublisher?: typeof planV2WithPublisher; - renderChunk: typeof renderChunk; + renderChunk: ChunkRenderer; assemble: typeof assemble; }; /** Override the per-invocation `/tmp` workdir root (defaults to Lambda's `/tmp`). */ diff --git a/packages/engine/src/services/videoFrameInjector.test.ts b/packages/engine/src/services/videoFrameInjector.test.ts index b61ab4901..eb2098ac4 100644 --- a/packages/engine/src/services/videoFrameInjector.test.ts +++ b/packages/engine/src/services/videoFrameInjector.test.ts @@ -183,6 +183,16 @@ describe("createVideoFrameInjector cache hygiene against page-side skips", () => return `data:image/png;base64,fake-${framePath}`; } + function makeGpuInjector() { + const evaluate = vi.fn(async () => undefined); + const page = { evaluate } as unknown as Page; + const hook = createVideoFrameInjector( + fakeTable({ videoId: "facet", framePath: "/f", frameIndex: 3 }), + { frameSrcResolver: inlineResolver }, + ); + return { evaluate, page, hook }; + } + beforeEach(() => { injectVideoFramesBatchMock.mockReset(); syncVideoFrameVisibilityMock.mockReset(); @@ -256,18 +266,33 @@ describe("createVideoFrameInjector cache hygiene against page-side skips", () => expect(injectVideoFramesBatchMock).toHaveBeenCalledTimes(1); }); + it("reinjects frame zero when a whole-chunk retry uses a fresh hook and page", async () => { + const table = fakeTable({ videoId: "hero", framePath: "/frame-0", frameIndex: 0 }); + const beginFramePage = { evaluate: async () => undefined } as unknown as Page; + const screenshotPage = { evaluate: async () => undefined } as unknown as Page; + const beginFrameHook = createVideoFrameInjector(table, { + frameSrcResolver: inlineResolver, + }); + const screenshotRetryHook = createVideoFrameInjector(table, { + frameSrcResolver: inlineResolver, + }); + + injectVideoFramesBatchMock.mockResolvedValue(["hero"]); + await beginFrameHook!(beginFramePage, 0); + await screenshotRetryHook!(screenshotPage, 0); + + expect(injectVideoFramesBatchMock).toHaveBeenCalledTimes(2); + expect(injectVideoFramesBatchMock.mock.calls[0]?.[0]).toBe(beginFramePage); + expect(injectVideoFramesBatchMock.mock.calls[1]?.[0]).toBe(screenshotPage); + }); + // Regression: WebGL/WebGPU compositions that sample a