fix(cli): localize remote assets before validate so it matches render (#2001)

validate served the composition over a loopback origin and let headless
Chrome fetch remote <img crossorigin>/@font-face assets cross-origin, while
the render pipeline downloads them to disk first. Buckets whose CORS
allowlist omits the loopback origin then failed the CORS-mode request with a
false net::ERR_FAILED that never occurs in the real render, pushing authors
(and agent pipelines) to delete crossorigin — which disables WebGL
color-grading/shaders for that asset.

Reuse producer's localizeRemote{Media,Image,FontFace}Sources in validate,
downloading into a temp dir served as an extra static-server asset root
(project dir untouched, cleaned up after). validate now matches render.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Xuanru Li
2026-07-06 17:20:18 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 89e36da13d
commit 906c8d04f8
5 changed files with 130 additions and 13 deletions
@@ -9,6 +9,25 @@ import {
} from "./validate.js";
import type { ProjectLintResult } from "../utils/lintProject.js";
// validateInBrowser lazy-loads the producer localize helpers via loadProducer;
// mock it so these unit tests never resolve @hyperframes/producer's built dist.
vi.mock("../utils/producer.js", () => ({
loadProducer: vi.fn(async () => ({
localizeRemoteMediaSources: vi.fn(async (html: string) => ({
html,
remoteMediaAssets: new Map(),
})),
localizeRemoteImageSources: vi.fn(async (html: string) => ({
html,
remoteMediaAssets: new Map(),
})),
localizeRemoteFontFaces: vi.fn(async (html: string) => ({
html,
remoteMediaAssets: new Map(),
})),
})),
}));
// Regression for the validate audio-duration-probe timeout: a slow-loading
// media element's duration was snapshotted once, at a fixed point in time,
// and any element still mid-load was permanently misreported as unreadable.