style: apply oxfmt baseline formatting across all source files (#25)

## Summary
- Run `oxfmt .` across the entire codebase to establish formatted baseline
- 299 files changed — mechanical formatting only, no logic changes
- Double quotes, semicolons, 2-space indent, trailing commas, 100 print width

Part 3/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `pnpm format:check` — all 426 files pass
- [x] `pnpm -r typecheck` — all packages pass
- [x] `pnpm build` — all packages build
- [x] All 348 tests pass
This commit is contained in:
Vance Ingalls
2026-03-23 17:15:14 -07:00
committed by GitHub
parent 323ff8f860
commit 20be2ea1c2
299 changed files with 27750 additions and 16792 deletions
@@ -21,7 +21,11 @@ function createLottieWebAnim(opts?: { totalFrames?: number; frameRate?: number }
};
}
function createDotLottiePlayer(opts?: { totalFrames?: number; frameRate?: number; duration?: number }) {
function createDotLottiePlayer(opts?: {
totalFrames?: number;
frameRate?: number;
duration?: number;
}) {
return {
play: vi.fn(),
pause: vi.fn(),
+5 -1
View File
@@ -156,7 +156,11 @@ export function createLottieAdapter(): RuntimeDeterministicAdapter {
// ── Type guards ────────────────────────────────────────────────────────────────
function isLottieWebAnimation(anim: unknown): anim is LottieWebAnimation {
return typeof anim === "object" && anim !== null && typeof (anim as LottieWebAnimation).goToAndStop === "function";
return (
typeof anim === "object" &&
anim !== null &&
typeof (anim as LottieWebAnimation).goToAndStop === "function"
);
}
function isDotLottiePlayer(anim: unknown): anim is DotLottiePlayer {
@@ -54,7 +54,9 @@ describe("waapi adapter", () => {
it("handles animation that throws on pause", () => {
const mockAnim = {
pause: vi.fn(() => { throw new Error("invalid state"); }),
pause: vi.fn(() => {
throw new Error("invalid state");
}),
currentTime: 0,
};
(document as any).getAnimations = vi.fn(() => [mockAnim]);
+11 -7
View File
@@ -86,18 +86,22 @@ describe("installRuntimeControlBridge", () => {
it("ignores messages from wrong source", () => {
const deps = createMockDeps();
const handler = installRuntimeControlBridge(deps);
handler(new MessageEvent("message", {
data: { source: "other", type: "control", action: "play" },
}));
handler(
new MessageEvent("message", {
data: { source: "other", type: "control", action: "play" },
}),
);
expect(deps.onPlay).not.toHaveBeenCalled();
});
it("ignores messages with wrong type", () => {
const deps = createMockDeps();
const handler = installRuntimeControlBridge(deps);
handler(new MessageEvent("message", {
data: { source: "hf-parent", type: "state", action: "play" },
}));
handler(
new MessageEvent("message", {
data: { source: "hf-parent", type: "state", action: "play" },
}),
);
expect(deps.onPlay).not.toHaveBeenCalled();
});
@@ -112,7 +116,7 @@ describe("installRuntimeControlBridge", () => {
const deps = createMockDeps();
const handler = installRuntimeControlBridge(deps);
expect(() =>
handler(makeControlMessage("flash-elements", { selectors: [".test"], duration: 500 }))
handler(makeControlMessage("flash-elements", { selectors: [".test"], duration: 500 })),
).not.toThrow();
});
});
@@ -44,9 +44,7 @@ describe("loadExternalCompositions", () => {
</body></html>
`;
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(compositionHtml, { status: 200 })
);
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(compositionHtml, { status: 200 }));
await loadExternalCompositions({ ...defaultParams });
@@ -68,9 +66,7 @@ describe("loadExternalCompositions", () => {
</body></html>
`;
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(compositionHtml, { status: 200 })
);
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(compositionHtml, { status: 200 }));
const injectedStyles: HTMLStyleElement[] = [];
await loadExternalCompositions({
@@ -102,7 +98,7 @@ describe("loadExternalCompositions", () => {
hostCompositionSrc: "https://example.com/broken.html",
errorMessage: "Network error",
}),
})
}),
);
});
@@ -111,9 +107,7 @@ describe("loadExternalCompositions", () => {
host.setAttribute("data-composition-src", "https://example.com/404.html");
document.body.appendChild(host);
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("Not Found", { status: 404 })
);
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("Not Found", { status: 404 }));
const onDiagnostic = vi.fn();
await loadExternalCompositions({
@@ -124,7 +118,7 @@ describe("loadExternalCompositions", () => {
expect(onDiagnostic).toHaveBeenCalledWith(
expect.objectContaining({
code: "external_composition_load_failed",
})
}),
);
});
@@ -165,9 +159,7 @@ describe("loadExternalCompositions", () => {
document.body.appendChild(host);
const compositionHtml = `<html><body><p>New</p></body></html>`;
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(compositionHtml, { status: 200 })
);
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(compositionHtml, { status: 200 }));
await loadExternalCompositions({ ...defaultParams });
expect(host.querySelector("span")).toBeNull();
@@ -186,9 +178,7 @@ describe("loadExternalCompositions", () => {
</body></html>
`;
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(compositionHtml, { status: 200 })
);
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(compositionHtml, { status: 200 }));
const injectedScripts: HTMLScriptElement[] = [];
await loadExternalCompositions({
+15 -6
View File
@@ -88,10 +88,13 @@ async function mountCompositionContent(params: {
}): Promise<void> {
let innerRoot: Element | null = null;
if (params.hostCompositionId) {
const candidateRoots = Array.from(params.sourceNode.querySelectorAll<Element>("[data-composition-id]"));
const candidateRoots = Array.from(
params.sourceNode.querySelectorAll<Element>("[data-composition-id]"),
);
innerRoot =
candidateRoots.find((candidate) => candidate.getAttribute("data-composition-id") === params.hostCompositionId) ??
null;
candidateRoots.find(
(candidate) => candidate.getAttribute("data-composition-id") === params.hostCompositionId,
) ?? null;
}
const contentNode = innerRoot ?? params.sourceNode;
@@ -188,7 +191,9 @@ async function mountCompositionContent(params: {
}
}
export async function loadExternalCompositions(params: LoadExternalCompositionsParams): Promise<void> {
export async function loadExternalCompositions(
params: LoadExternalCompositionsParams,
): Promise<void> {
const hosts = Array.from(document.querySelectorAll("[data-composition-src]"));
if (hosts.length === 0) return;
@@ -207,7 +212,9 @@ export async function loadExternalCompositions(params: LoadExternalCompositionsP
const hostCompositionId = host.getAttribute("data-composition-id");
const localTemplate =
hostCompositionId != null
? document.querySelector<HTMLTemplateElement>(`template#${CSS.escape(hostCompositionId)}-template`)
? document.querySelector<HTMLTemplateElement>(
`template#${CSS.escape(hostCompositionId)}-template`,
)
: null;
if (localTemplate) {
await mountCompositionContent({
@@ -234,7 +241,9 @@ export async function loadExternalCompositions(params: LoadExternalCompositionsP
const doc = parser.parseFromString(html, "text/html");
const template =
(hostCompositionId
? doc.querySelector<HTMLTemplateElement>(`template#${CSS.escape(hostCompositionId)}-template`)
? doc.querySelector<HTMLTemplateElement>(
`template#${CSS.escape(hostCompositionId)}-template`,
)
: null) ?? doc.querySelector<HTMLTemplateElement>("template");
const sourceNode = template ? template.content : doc.body;
await mountCompositionContent({
+142 -39
View File
@@ -36,7 +36,11 @@ export function initSandboxRuntimeModular(): void {
const registerRuntimeCleanup = (callback: () => void) => {
runtimeCleanupCallbacks.push(callback);
};
const postRuntimeDiagnosticOnce = (code: string, details: Record<string, RuntimeJson>, dedupeKey?: string) => {
const postRuntimeDiagnosticOnce = (
code: string,
details: Record<string, RuntimeJson>,
dedupeKey?: string,
) => {
const key = dedupeKey ?? `${code}:${JSON.stringify(details)}`;
if (postedDiagnosticKeys.has(key)) {
return;
@@ -157,7 +161,10 @@ export function initSandboxRuntimeModular(): void {
category: string;
} => {
const message = rawMessage.toLowerCase();
if (message.includes("cannot read properties of null") || message.includes("cannot set properties of null")) {
if (
message.includes("cannot read properties of null") ||
message.includes("cannot set properties of null")
) {
return { code: "runtime_null_dom_access", category: "dom-null-access" };
}
if (message.includes("failed to execute 'queryselector'")) {
@@ -185,10 +192,13 @@ export function initSandboxRuntimeModular(): void {
if (explicitRoot instanceof HTMLElement) {
return explicitRoot;
}
const compositionNodes = Array.from(document.querySelectorAll("[data-composition-id]")) as HTMLElement[];
const compositionNodes = Array.from(
document.querySelectorAll("[data-composition-id]"),
) as HTMLElement[];
if (compositionNodes.length === 0) return null;
return (
compositionNodes.find((node) => !node.parentElement?.closest("[data-composition-id]")) ?? compositionNodes[0]
compositionNodes.find((node) => !node.parentElement?.closest("[data-composition-id]")) ??
compositionNodes[0]
);
};
@@ -278,12 +288,18 @@ export function initSandboxRuntimeModular(): void {
el.style.position = "absolute";
}
const hasExplicitVerticalAnchor =
Boolean(el.style.top) || Boolean(el.style.bottom) || computed.top !== "auto" || computed.bottom !== "auto";
Boolean(el.style.top) ||
Boolean(el.style.bottom) ||
computed.top !== "auto" ||
computed.bottom !== "auto";
if (!hasExplicitVerticalAnchor) {
el.style.top = "0";
}
const hasExplicitHorizontalAnchor =
Boolean(el.style.left) || Boolean(el.style.right) || computed.left !== "auto" || computed.right !== "auto";
Boolean(el.style.left) ||
Boolean(el.style.right) ||
computed.left !== "auto" ||
computed.right !== "auto";
if (!hasExplicitHorizontalAnchor) {
el.style.left = "0";
}
@@ -312,14 +328,20 @@ export function initSandboxRuntimeModular(): void {
const resolveStartForElement = (element: Element, fallback = 0): number => {
const resolver = createRuntimeStartTimeResolver({
timelineRegistry: (window.__timelines ?? {}) as Record<string, RuntimeTimelineLike | undefined>,
timelineRegistry: (window.__timelines ?? {}) as Record<
string,
RuntimeTimelineLike | undefined
>,
});
return resolver.resolveStartForElement(element, fallback);
};
const resolveDurationForElement = (element: Element): number | null => {
const resolver = createRuntimeStartTimeResolver({
timelineRegistry: (window.__timelines ?? {}) as Record<string, RuntimeTimelineLike | undefined>,
timelineRegistry: (window.__timelines ?? {}) as Record<
string,
RuntimeTimelineLike | undefined
>,
});
return resolver.resolveDurationForElement(element);
};
@@ -399,13 +421,20 @@ export function initSandboxRuntimeModular(): void {
if (!isUsableTimelineDuration(mediaDurationFloorSeconds)) {
return MIN_VALID_TIMELINE_DURATION_SECONDS;
}
return Math.max(MIN_VALID_TIMELINE_DURATION_SECONDS, mediaDurationFloorSeconds * TIMELINE_FLOOR_COVERAGE_RATIO);
return Math.max(
MIN_VALID_TIMELINE_DURATION_SECONDS,
mediaDurationFloorSeconds * TIMELINE_FLOOR_COVERAGE_RATIO,
);
};
const getSafeTimelineDurationSeconds = (timeline: RuntimeTimelineLike | null, fallback = 0): number => {
const getSafeTimelineDurationSeconds = (
timeline: RuntimeTimelineLike | null,
fallback = 0,
): number => {
const timelineDuration = getTimelineDurationSeconds(timeline);
const mediaFloor = resolveMediaDurationFloorSeconds();
const fallbackDuration = Number.isFinite(fallback) && fallback > MIN_VALID_TIMELINE_DURATION_SECONDS ? fallback : 0;
const fallbackDuration =
Number.isFinite(fallback) && fallback > MIN_VALID_TIMELINE_DURATION_SECONDS ? fallback : 0;
let safeDuration = 0;
// Timeline is the source of truth for authored composition duration.
if (isUsableTimelineDuration(timelineDuration)) {
@@ -423,20 +452,30 @@ export function initSandboxRuntimeModular(): void {
const timelines = (window.__timelines ?? {}) as Record<string, RuntimeTimelineLike | undefined>;
const startResolver = createRuntimeStartTimeResolver({ timelineRegistry: timelines });
const mediaDurationFloorSeconds = resolveMediaDurationFloorSeconds();
const minCandidateDurationSeconds = resolveMinCandidateDurationSeconds(mediaDurationFloorSeconds);
const minCandidateDurationSeconds =
resolveMinCandidateDurationSeconds(mediaDurationFloorSeconds);
const resolveCompositionStartSeconds = (compositionId: string): number => {
const node = document.querySelector(`[data-composition-id="${CSS.escape(compositionId)}"]`) as Element | null;
const node = document.querySelector(
`[data-composition-id="${CSS.escape(compositionId)}"]`,
) as Element | null;
if (!node) return 0;
return startResolver.resolveStartForElement(node, 0);
};
const createCompositeTimelineFromCandidates = (
candidates: Array<{ compositionId: string; timeline: RuntimeTimelineLike; durationSeconds: number }>,
candidates: Array<{
compositionId: string;
timeline: RuntimeTimelineLike;
durationSeconds: number;
}>,
): RuntimeTimelineLike | null => {
const gsapApi = window.gsap;
if (!gsapApi || typeof gsapApi.timeline !== "function") return null;
const compositeTimeline = gsapApi.timeline({ paused: true }) as RuntimeTimelineLike;
for (const candidate of candidates) {
compositeTimeline.add(candidate.timeline, resolveCompositionStartSeconds(candidate.compositionId));
compositeTimeline.add(
candidate.timeline,
resolveCompositionStartSeconds(candidate.compositionId),
);
}
return compositeTimeline;
};
@@ -469,7 +508,11 @@ export function initSandboxRuntimeModular(): void {
};
const addMissingChildCandidatesToRootTimeline = (
rootTimeline: RuntimeTimelineLike,
candidates: Array<{ compositionId: string; timeline: RuntimeTimelineLike; durationSeconds: number }>,
candidates: Array<{
compositionId: string;
timeline: RuntimeTimelineLike;
durationSeconds: number;
}>,
): string[] => {
const rootWithChildren = rootTimeline as RuntimeTimelineLike & {
getChildren?: (...args: unknown[]) => unknown[];
@@ -509,7 +552,11 @@ export function initSandboxRuntimeModular(): void {
if (!rootCompositionNode) return [];
const seen = new Set<string>();
const childNodes = Array.from(rootCompositionNode.querySelectorAll("[data-composition-id]"));
const candidates: Array<{ compositionId: string; timeline: RuntimeTimelineLike; durationSeconds: number }> = [];
const candidates: Array<{
compositionId: string;
timeline: RuntimeTimelineLike;
durationSeconds: number;
}> = [];
for (const childNode of childNodes) {
const childId = childNode.getAttribute("data-composition-id");
if (!childId || childId === rootCompositionId) continue;
@@ -517,7 +564,10 @@ export function initSandboxRuntimeModular(): void {
seen.add(childId);
const candidateTimeline = timelines[childId] ?? null;
if (!candidateTimeline) continue;
if (typeof candidateTimeline.play !== "function" || typeof candidateTimeline.pause !== "function") {
if (
typeof candidateTimeline.play !== "function" ||
typeof candidateTimeline.pause !== "function"
) {
continue;
}
const candidateDuration = getTimelineDurationSeconds(candidateTimeline);
@@ -531,7 +581,11 @@ export function initSandboxRuntimeModular(): void {
};
const rootChildCandidates = collectRootChildCandidates();
const ensureChildCandidatesActive = (
candidates: Array<{ compositionId: string; timeline: RuntimeTimelineLike; durationSeconds: number }>,
candidates: Array<{
compositionId: string;
timeline: RuntimeTimelineLike;
durationSeconds: number;
}>,
): void => {
for (const candidate of candidates) {
const timelineWithPaused = candidate.timeline as RuntimeTimelineLike & {
@@ -554,7 +608,12 @@ export function initSandboxRuntimeModular(): void {
? addMissingChildCandidatesToRootTimeline(rootTimeline, rootChildCandidates)
: [];
// Mark children as bound so the polling loop stops re-resolving
if (rootChildCandidates.length > 0 || !document.querySelector("[data-composition-id]:not([data-composition-id='" + rootCompositionId + "'])")) {
if (
rootChildCandidates.length > 0 ||
!document.querySelector(
"[data-composition-id]:not([data-composition-id='" + rootCompositionId + "'])",
)
) {
childrenBound = true;
}
@@ -564,7 +623,9 @@ export function initSandboxRuntimeModular(): void {
try {
const currentTime = rootTimeline.time();
rootTimeline.seek(currentTime, false); // false = don't suppress events
} catch { /* ignore */ }
} catch {
/* ignore */
}
}
const rootDurationSeconds = getTimelineDurationSeconds(rootTimeline);
if (!isUsableTimelineDuration(rootDurationSeconds) && rootChildCandidates.length > 0) {
@@ -592,7 +653,10 @@ export function initSandboxRuntimeModular(): void {
},
};
}
const durationFloorTimeline = createDurationFloorTimeline(mediaDurationFloorSeconds ?? 0, rootTimeline);
const durationFloorTimeline = createDurationFloorTimeline(
mediaDurationFloorSeconds ?? 0,
rootTimeline,
);
const durationFloorSeconds = getTimelineDurationSeconds(durationFloorTimeline);
if (durationFloorTimeline && isUsableTimelineDuration(durationFloorSeconds)) {
return {
@@ -616,7 +680,10 @@ export function initSandboxRuntimeModular(): void {
}
}
if (!isUsableTimelineDuration(rootDurationSeconds) && rootChildCandidates.length === 0) {
const durationFloorTimeline = createDurationFloorTimeline(mediaDurationFloorSeconds ?? 0, rootTimeline);
const durationFloorTimeline = createDurationFloorTimeline(
mediaDurationFloorSeconds ?? 0,
rootTimeline,
);
const durationFloorSeconds = getTimelineDurationSeconds(durationFloorTimeline);
if (durationFloorTimeline && isUsableTimelineDuration(durationFloorSeconds)) {
return {
@@ -750,9 +817,15 @@ export function initSandboxRuntimeModular(): void {
const declaredHeight = Number(rootNode.getAttribute("data-height"));
const computedStyle = window.getComputedStyle(rootNode);
const hasDeclaredDimensions =
Number.isFinite(declaredWidth) && declaredWidth > 0 && Number.isFinite(declaredHeight) && declaredHeight > 0;
Number.isFinite(declaredWidth) &&
declaredWidth > 0 &&
Number.isFinite(declaredHeight) &&
declaredHeight > 0;
const looksCollapsed =
rect.width <= 0 || rect.height <= 0 || rootNode.clientWidth <= 0 || rootNode.clientHeight <= 0;
rect.width <= 0 ||
rect.height <= 0 ||
rootNode.clientWidth <= 0 ||
rootNode.clientHeight <= 0;
if (!hasDeclaredDimensions || !looksCollapsed) {
return;
}
@@ -811,7 +884,10 @@ export function initSandboxRuntimeModular(): void {
});
};
runtimeUnhandledRejectionListener = (event: PromiseRejectionEvent) => {
const normalized = normalizeDiagnosticMessage(event.reason).slice(0, MAX_DIAGNOSTIC_MESSAGE_LENGTH);
const normalized = normalizeDiagnosticMessage(event.reason).slice(
0,
MAX_DIAGNOSTIC_MESSAGE_LENGTH,
);
if (!normalized) {
return;
}
@@ -831,22 +907,31 @@ export function initSandboxRuntimeModular(): void {
};
const installAssetFailureDiagnostics = () => {
const assetNodes = Array.from(document.querySelectorAll("img, video, audio, source, link[rel='stylesheet']"));
const assetNodes = Array.from(
document.querySelectorAll("img, video, audio, source, link[rel='stylesheet']"),
);
for (const node of assetNodes) {
const onError = () => {
if (!(node instanceof Element)) {
return;
}
const tagName = node.tagName.toLowerCase();
const assetUrl = node.getAttribute("src") ?? node.getAttribute("href") ?? node.getAttribute("poster") ?? null;
const diagnosticCode = tagName === "link" ? "runtime_stylesheet_load_failed" : "runtime_asset_load_failed";
const assetUrl =
node.getAttribute("src") ??
node.getAttribute("href") ??
node.getAttribute("poster") ??
null;
const diagnosticCode =
tagName === "link" ? "runtime_stylesheet_load_failed" : "runtime_asset_load_failed";
postRuntimeDiagnosticOnce(
diagnosticCode,
{
tagName,
assetUrl,
currentSrc:
node instanceof HTMLImageElement || node instanceof HTMLMediaElement ? node.currentSrc || null : null,
node instanceof HTMLImageElement || node instanceof HTMLMediaElement
? node.currentSrc || null
: null,
readyState: node instanceof HTMLMediaElement ? node.readyState : null,
networkState: node instanceof HTMLMediaElement ? node.networkState : null,
},
@@ -890,7 +975,10 @@ export function initSandboxRuntimeModular(): void {
});
};
const rebindTimelineFromResolution = (resolution: TimelineResolution, reason: "loop_guard" | "manual"): boolean => {
const rebindTimelineFromResolution = (
resolution: TimelineResolution,
reason: "loop_guard" | "manual",
): boolean => {
if (!resolution.timeline) return false;
const previousTimeline = state.capturedTimeline;
if (previousTimeline && previousTimeline === resolution.timeline) {
@@ -940,7 +1028,9 @@ export function initSandboxRuntimeModular(): void {
metadataRebindDebounceTimerId = null;
const resolution = resolveRootTimelineFromDocument();
if (!resolution.timeline) return;
const hasResolvedMediaFloor = isUsableTimelineDuration(resolution.mediaDurationFloorSeconds ?? null);
const hasResolvedMediaFloor = isUsableTimelineDuration(
resolution.mediaDurationFloorSeconds ?? null,
);
if (!hasResolvedMediaFloor) return;
if (!state.capturedTimeline) {
if (bindRootTimelineIfAvailable()) {
@@ -951,7 +1041,8 @@ export function initSandboxRuntimeModular(): void {
}
if (metadataRebindApplied) return;
const currentDuration = getTimelineDurationSeconds(state.capturedTimeline);
const nextDuration = resolution.selectedDurationSeconds ?? getTimelineDurationSeconds(resolution.timeline);
const nextDuration =
resolution.selectedDurationSeconds ?? getTimelineDurationSeconds(resolution.timeline);
const isBetterCandidate =
isUsableTimelineDuration(nextDuration) &&
(!isUsableTimelineDuration(currentDuration) ||
@@ -1005,7 +1096,8 @@ export function initSandboxRuntimeModular(): void {
playing: state.isPlaying,
playbackRate: state.playbackRate,
});
const rootCompId = document.querySelector("[data-composition-id]")?.getAttribute("data-composition-id") ?? null;
const rootCompId =
document.querySelector("[data-composition-id]")?.getAttribute("data-composition-id") ?? null;
const visibilityNodes = Array.from(document.querySelectorAll("[data-start]"));
for (const rawNode of visibilityNodes) {
if (!(rawNode instanceof HTMLElement)) continue;
@@ -1038,7 +1130,9 @@ export function initSandboxRuntimeModular(): void {
if (compDur > 0) computedEnd = start + compDur;
}
}
const isVisibleNow = state.currentTime >= start && (Number.isFinite(computedEnd) ? state.currentTime < computedEnd : true);
const isVisibleNow =
state.currentTime >= start &&
(Number.isFinite(computedEnd) ? state.currentTime < computedEnd : true);
rawNode.style.visibility = isVisibleNow ? "visible" : "hidden";
}
};
@@ -1203,12 +1297,19 @@ export function initSandboxRuntimeModular(): void {
initRuntimeAnalytics(postRuntimeMessage as (payload: unknown) => void);
emitAnalyticsEvent("composition_loaded", {
duration: player.getDuration(),
compositionId: document.querySelector("[data-composition-id]")?.getAttribute("data-composition-id") ?? null,
compositionId:
document.querySelector("[data-composition-id]")?.getAttribute("data-composition-id") ?? null,
});
state.controlBridgeHandler = installRuntimeControlBridge({
onPlay: () => { player.play(); emitAnalyticsEvent("composition_played", { time: player.getTime() }); },
onPause: () => { player.pause(); emitAnalyticsEvent("composition_paused", { time: player.getTime() }); },
onPlay: () => {
player.play();
emitAnalyticsEvent("composition_played", { time: player.getTime() });
},
onPause: () => {
player.pause();
emitAnalyticsEvent("composition_paused", { time: player.getTime() });
},
onSeek: (frame, _seekMode) => {
const time = Math.max(0, frame) / state.canonicalFps;
player.seek(time);
@@ -1280,7 +1381,9 @@ export function initSandboxRuntimeModular(): void {
state.isPlaying &&
state.capturedTimeline != null &&
Math.max(0, state.currentTime || 0) < PLAY_REBIND_HOLD_SECONDS;
const timelineBoundThisTick = shouldHoldRebindDuringEarlyPlay ? false : bindRootTimelineIfAvailable();
const timelineBoundThisTick = shouldHoldRebindDuringEarlyPlay
? false
: bindRootTimelineIfAvailable();
if (state.capturedTimeline && !player._timeline) {
player._timeline = state.capturedTimeline;
}
+17 -8
View File
@@ -7,15 +7,17 @@ export type RuntimeMediaClip = {
volume: number | null;
};
export function refreshRuntimeMediaCache(params?: { resolveStartSeconds?: (element: Element) => number }): {
export function refreshRuntimeMediaCache(params?: {
resolveStartSeconds?: (element: Element) => number;
}): {
timedMediaEls: Array<HTMLVideoElement | HTMLAudioElement>;
mediaClips: RuntimeMediaClip[];
videoClips: RuntimeMediaClip[];
maxMediaEnd: number;
} {
const mediaEls = Array.from(document.querySelectorAll("video[data-start], audio[data-start]")) as Array<
HTMLVideoElement | HTMLAudioElement
>;
const mediaEls = Array.from(
document.querySelectorAll("video[data-start], audio[data-start]"),
) as Array<HTMLVideoElement | HTMLAudioElement>;
const mediaClips: RuntimeMediaClip[] = [];
const videoClips: RuntimeMediaClip[] = [];
let maxMediaEnd = 0;
@@ -24,12 +26,18 @@ export function refreshRuntimeMediaCache(params?: { resolveStartSeconds?: (eleme
? params.resolveStartSeconds(el)
: Number.parseFloat(el.dataset.start ?? "0");
if (!Number.isFinite(start)) continue;
const mediaStart = Number.parseFloat(el.dataset.playbackStart ?? el.dataset.mediaStart ?? "0") || 0;
const mediaStart =
Number.parseFloat(el.dataset.playbackStart ?? el.dataset.mediaStart ?? "0") || 0;
let duration = Number.parseFloat(el.dataset.duration ?? "");
if ((!Number.isFinite(duration) || duration <= 0) && Number.isFinite(el.duration) && el.duration > 0) {
if (
(!Number.isFinite(duration) || duration <= 0) &&
Number.isFinite(el.duration) &&
el.duration > 0
) {
duration = Math.max(0, el.duration - mediaStart);
}
const end = Number.isFinite(duration) && duration > 0 ? start + duration : Number.POSITIVE_INFINITY;
const end =
Number.isFinite(duration) && duration > 0 ? start + duration : Number.POSITIVE_INFINITY;
const volumeRaw = Number.parseFloat(el.dataset.volume ?? "");
const clip: RuntimeMediaClip = {
el,
@@ -56,7 +64,8 @@ export function syncRuntimeMedia(params: {
const { el } = clip;
if (!el.isConnected) continue;
const relTime = params.timeSeconds - clip.start + clip.mediaStart;
const isActive = params.timeSeconds >= clip.start && params.timeSeconds < clip.end && relTime >= 0;
const isActive =
params.timeSeconds >= clip.start && params.timeSeconds < clip.end && relTime >= 0;
if (isActive) {
if (clip.volume != null) el.volume = clip.volume;
try {
+6 -6
View File
@@ -8,7 +8,7 @@ function createMockPostMessage() {
describe("createPickerModule", () => {
afterEach(() => {
document.body.innerHTML = "";
document.head.querySelectorAll("style").forEach(s => s.remove());
document.head.querySelectorAll("style").forEach((s) => s.remove());
document.body.classList.remove("__hf-pick-active");
});
@@ -33,15 +33,15 @@ describe("createPickerModule", () => {
const picker = createPickerModule({ postMessage: createMockPostMessage() });
picker.enablePickMode();
const styles = document.head.querySelectorAll("style");
const hasPickStyle = Array.from(styles).some(s =>
s.textContent?.includes("__hf-pick-highlight")
const hasPickStyle = Array.from(styles).some((s) =>
s.textContent?.includes("__hf-pick-highlight"),
);
expect(hasPickStyle).toBe(true);
picker.disablePickMode();
const stylesAfter = document.head.querySelectorAll("style");
const hasPickStyleAfter = Array.from(stylesAfter).some(s =>
s.textContent?.includes("__hf-pick-highlight")
const hasPickStyleAfter = Array.from(stylesAfter).some((s) =>
s.textContent?.includes("__hf-pick-highlight"),
);
expect(hasPickStyleAfter).toBe(false);
});
@@ -137,7 +137,7 @@ describe("createPickerModule", () => {
expect.objectContaining({
source: "hf-preview",
type: "pick-mode-cancelled",
})
}),
);
});
+18 -5
View File
@@ -77,7 +77,8 @@ export function createPickerModule(deps: PickerModuleDeps): PickerModule {
const trimLabel = (value: string, maxChars: number) =>
value.length > maxChars ? `${value.slice(0, maxChars - 1)}` : value;
if (tag === "h1" || tag === "h2" || tag === "h3") return "Heading";
if (tag === "p" || tag === "span" || tag === "div") return text.length > 0 ? trimLabel(text, 56) : "Text";
if (tag === "p" || tag === "span" || tag === "div")
return text.length > 0 ? trimLabel(text, 56) : "Text";
if (tag === "img") return "Image";
if (tag === "video") return "Video";
if (tag === "audio") return "Audio";
@@ -132,7 +133,11 @@ export function createPickerModule(deps: PickerModuleDeps): PickerModule {
};
}
function getPickInfosFromPoint(clientX: number, clientY: number, limit?: number): RuntimePickerElementInfo[] {
function getPickInfosFromPoint(
clientX: number,
clientY: number,
limit?: number,
): RuntimePickerElementInfo[] {
return getPickCandidatesFromPoint(clientX, clientY, limit).map(extractElementInfo);
}
@@ -217,7 +222,9 @@ export function createPickerModule(deps: PickerModuleDeps): PickerModule {
getHovered: () => pickLastHoveredInfo,
getSelected: () => pickLastSelectedInfo,
getCandidatesAtPoint: (clientX, clientY, limit) =>
Number.isFinite(clientX) && Number.isFinite(clientY) ? getPickInfosFromPoint(clientX, clientY, limit) : [],
Number.isFinite(clientX) && Number.isFinite(clientY)
? getPickInfosFromPoint(clientX, clientY, limit)
: [],
pickAtPoint: (clientX, clientY, index) => {
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) return null;
const infos = getPickInfosFromPoint(clientX, clientY, 8);
@@ -240,12 +247,18 @@ export function createPickerModule(deps: PickerModuleDeps): PickerModule {
const idx = Math.max(0, Math.min(infos.length - 1, Math.floor(Number(rawIndex))));
const info = infos[idx];
if (!info) continue;
const duplicate = selected.some((item) => item.selector === info.selector && item.tagName === info.tagName);
const duplicate = selected.some(
(item) => item.selector === info.selector && item.tagName === info.tagName,
);
if (!duplicate) selected.push(info);
}
if (!selected.length) return [];
setLastSelectedInfo(selected[0] ?? null);
deps.postMessage({ source: "hf-preview", type: "element-picked-many", elementInfos: selected });
deps.postMessage({
source: "hf-preview",
type: "element-picked-many",
elementInfos: selected,
});
disablePickMode();
return selected;
},
+21 -7
View File
@@ -5,14 +5,24 @@ import type { RuntimeTimelineLike } from "./types";
function createMockTimeline(opts?: { time?: number; duration?: number }): RuntimeTimelineLike {
const state = { time: opts?.time ?? 0, duration: opts?.duration ?? 10, paused: false };
return {
play: vi.fn(() => { state.paused = false; }),
pause: vi.fn(() => { state.paused = true; }),
seek: vi.fn((t: number) => { state.time = t; }),
totalTime: vi.fn((t: number) => { state.time = t; }),
play: vi.fn(() => {
state.paused = false;
}),
pause: vi.fn(() => {
state.paused = true;
}),
seek: vi.fn((t: number) => {
state.time = t;
}),
totalTime: vi.fn((t: number) => {
state.time = t;
}),
time: vi.fn(() => state.time),
duration: vi.fn(() => state.duration),
add: vi.fn(),
paused: vi.fn((p?: boolean) => { if (p !== undefined) state.paused = p; }),
paused: vi.fn((p?: boolean) => {
if (p !== undefined) state.paused = p;
}),
timeScale: vi.fn(),
set: vi.fn(),
};
@@ -25,9 +35,13 @@ function createMockDeps(timeline?: RuntimeTimelineLike | null) {
getTimeline: vi.fn(() => timeline ?? null),
setTimeline: vi.fn(),
getIsPlaying: vi.fn(() => isPlaying),
setIsPlaying: vi.fn((v: boolean) => { isPlaying = v; }),
setIsPlaying: vi.fn((v: boolean) => {
isPlaying = v;
}),
getPlaybackRate: vi.fn(() => playbackRate),
setPlaybackRate: vi.fn((v: number) => { playbackRate = v; }),
setPlaybackRate: vi.fn((v: number) => {
playbackRate = v;
}),
getCanonicalFps: vi.fn(() => 30),
onSyncMedia: vi.fn(),
onStatePost: vi.fn(),
+9 -2
View File
@@ -40,7 +40,10 @@ export function createRuntimePlayer(deps: PlayerDeps): RuntimePlayer {
play: () => {
const timeline = deps.getTimeline();
if (!timeline || deps.getIsPlaying()) return;
const safeDuration = Math.max(0, Number(deps.getSafeDuration?.() ?? timeline.duration() ?? 0) || 0);
const safeDuration = Math.max(
0,
Number(deps.getSafeDuration?.() ?? timeline.duration() ?? 0) || 0,
);
if (safeDuration > 0) {
const currentTime = Math.max(0, Number(timeline.time()) || 0);
if (currentTime >= safeDuration) {
@@ -87,7 +90,11 @@ export function createRuntimePlayer(deps: PlayerDeps): RuntimePlayer {
renderSeek: (timeSeconds: number) => {
const timeline = deps.getTimeline();
if (!timeline) return;
const quantized = seekTimelineDeterministically(timeline, timeSeconds, deps.getCanonicalFps());
const quantized = seekTimelineDeterministically(
timeline,
timeSeconds,
deps.getCanonicalFps(),
);
deps.onDeterministicSeek(quantized);
deps.setIsPlaying(false);
deps.onSyncMedia(quantized, false);
@@ -7,8 +7,7 @@ beforeAll(() => {
(globalThis as any).CSS = {};
}
if (typeof CSS.escape !== "function") {
CSS.escape = (value: string) =>
value.replace(/([^\w-])/g, "\\$1");
CSS.escape = (value: string) => value.replace(/([^\w-])/g, "\\$1");
}
});
@@ -191,7 +190,16 @@ describe("createRuntimeStartTimeResolver", () => {
el.setAttribute("data-composition-id", "comp-1");
document.body.appendChild(el);
const mockTimeline = { duration: () => 12, time: () => 0, play: () => {}, pause: () => {}, seek: () => {}, add: () => {}, paused: () => {}, set: () => {} };
const mockTimeline = {
duration: () => 12,
time: () => 0,
play: () => {},
pause: () => {},
seek: () => {},
add: () => {},
paused: () => {},
set: () => {},
};
const resolver = createRuntimeStartTimeResolver({
timelineRegistry: { "comp-1": mockTimeline as any },
});
@@ -204,7 +212,16 @@ describe("createRuntimeStartTimeResolver", () => {
el.setAttribute("data-duration", "5");
document.body.appendChild(el);
const mockTimeline = { duration: () => 12, time: () => 0, play: () => {}, pause: () => {}, seek: () => {}, add: () => {}, paused: () => {}, set: () => {} };
const mockTimeline = {
duration: () => 12,
time: () => 0,
play: () => {},
pause: () => {},
seek: () => {},
add: () => {},
paused: () => {},
set: () => {},
};
const resolver = createRuntimeStartTimeResolver({
timelineRegistry: { "comp-1": mockTimeline as any },
});
+4 -1
View File
@@ -49,7 +49,10 @@ export function createRuntimeStartTimeResolver(params: {
const findReferenceTarget = (refId: string): Element | null => {
const byId = document.getElementById(refId);
if (byId) return byId;
return (document.querySelector(`[data-composition-id="${CSS.escape(refId)}"]`) as Element | null) ?? null;
return (
(document.querySelector(`[data-composition-id="${CSS.escape(refId)}"]`) as Element | null) ??
null
);
};
const resolveDurationForElement = (element: Element): number | null => {
+25 -4
View File
@@ -194,7 +194,10 @@ describe("collectRuntimeTimelinePayload", () => {
clip.setAttribute("data-duration", "5000");
root.appendChild(clip);
const result = collectRuntimeTimelinePayload({ canonicalFps: 30, maxTimelineDurationSeconds: 60 });
const result = collectRuntimeTimelinePayload({
canonicalFps: 30,
maxTimelineDurationSeconds: 60,
});
expect(result.durationInFrames).toBeLessThanOrEqual(60 * 30);
});
@@ -263,13 +266,31 @@ describe("collectRuntimeTimelinePayload", () => {
root.appendChild(comp);
(window as any).__timelines = {
"main": { duration: () => 15, time: () => 0, play: () => {}, pause: () => {}, seek: () => {}, add: () => {}, paused: () => {}, set: () => {} },
"scene-1": { duration: () => 8, time: () => 0, play: () => {}, pause: () => {}, seek: () => {}, add: () => {}, paused: () => {}, set: () => {} },
main: {
duration: () => 15,
time: () => 0,
play: () => {},
pause: () => {},
seek: () => {},
add: () => {},
paused: () => {},
set: () => {},
},
"scene-1": {
duration: () => 8,
time: () => 0,
play: () => {},
pause: () => {},
seek: () => {},
add: () => {},
paused: () => {},
set: () => {},
},
};
const result = collectRuntimeTimelinePayload(defaultParams);
// scene-1 should get duration 8 from timeline registry
const sceneClip = result.clips.find(c => c.compositionId === "scene-1");
const sceneClip = result.clips.find((c) => c.compositionId === "scene-1");
expect(sceneClip).toBeDefined();
expect(sceneClip?.duration).toBe(8);
});
+46 -16
View File
@@ -1,4 +1,9 @@
import type { RuntimeTimelineClip, RuntimeTimelineMessage, RuntimeTimelineScene, RuntimeTimelineLike } from "./types";
import type {
RuntimeTimelineClip,
RuntimeTimelineMessage,
RuntimeTimelineScene,
RuntimeTimelineLike,
} from "./types";
import { createRuntimeStartTimeResolver } from "./startResolver";
function parseNum(value: string | null | undefined): number | null {
@@ -50,22 +55,26 @@ export function collectRuntimeTimelinePayload(params: {
return null;
}
};
const resolveMediaElementDurationSeconds = (mediaEl: HTMLVideoElement | HTMLAudioElement): number | null => {
const resolveMediaElementDurationSeconds = (
mediaEl: HTMLVideoElement | HTMLAudioElement,
): number | null => {
const declaredDuration = parseNum(mediaEl.getAttribute("data-duration"));
if (declaredDuration != null && declaredDuration > 0) {
return declaredDuration;
}
const playbackStart =
parseNum(mediaEl.getAttribute("data-playback-start")) ?? parseNum(mediaEl.getAttribute("data-media-start")) ?? 0;
parseNum(mediaEl.getAttribute("data-playback-start")) ??
parseNum(mediaEl.getAttribute("data-media-start")) ??
0;
if (Number.isFinite(mediaEl.duration) && mediaEl.duration > playbackStart) {
return Math.max(0, mediaEl.duration - playbackStart);
}
return null;
};
const resolveMediaWindowEndSeconds = (): number | null => {
const mediaNodes = Array.from(document.querySelectorAll("video[data-start], audio[data-start]")) as Array<
HTMLVideoElement | HTMLAudioElement
>;
const mediaNodes = Array.from(
document.querySelectorAll("video[data-start], audio[data-start]"),
) as Array<HTMLVideoElement | HTMLAudioElement>;
if (mediaNodes.length === 0) return null;
let maxWindowEndSeconds = 0;
for (const mediaNode of mediaNodes) {
@@ -137,11 +146,15 @@ export function collectRuntimeTimelinePayload(params: {
? rootDurationFromTimeline
: null;
const attrDurationCandidate =
typeof rootDurationFromAttr === "number" && Number.isFinite(rootDurationFromAttr) && rootDurationFromAttr > 0
typeof rootDurationFromAttr === "number" &&
Number.isFinite(rootDurationFromAttr) &&
rootDurationFromAttr > 0
? rootDurationFromAttr
: null;
const mediaWindowDurationCandidate =
typeof mediaWindowDuration === "number" && Number.isFinite(mediaWindowDuration) && mediaWindowDuration > 0
typeof mediaWindowDuration === "number" &&
Number.isFinite(mediaWindowDuration) &&
mediaWindowDuration > 0
? mediaWindowDuration
: null;
const timelineLooksLoopInflated =
@@ -156,8 +169,11 @@ export function collectRuntimeTimelinePayload(params: {
? mediaWindowDurationCandidate
: (timelineDurationCandidate ?? mediaWindowDurationCandidate));
const rootCompositionDuration =
preferredRootDuration != null ? Math.min(preferredRootDuration, params.maxTimelineDurationSeconds) : null;
const rootCompositionEnd = rootCompositionDuration != null ? rootCompositionStart + rootCompositionDuration : null;
preferredRootDuration != null
? Math.min(preferredRootDuration, params.maxTimelineDurationSeconds)
: null;
const rootCompositionEnd =
rootCompositionDuration != null ? rootCompositionStart + rootCompositionDuration : null;
const timelineWindowEnd =
rootCompositionEnd ??
(typeof mediaWindowEnd === "number" && Number.isFinite(mediaWindowEnd) && mediaWindowEnd > 0
@@ -177,17 +193,27 @@ export function collectRuntimeTimelinePayload(params: {
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (node === root) continue;
if (["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"].includes(node.tagName)) continue;
if (["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"].includes(node.tagName))
continue;
const compositionContext = resolveNearestCompositionContext(node, root);
const start = startResolver.resolveStartForElement(node, compositionContext.inheritedStart ?? 0);
const start = startResolver.resolveStartForElement(
node,
compositionContext.inheritedStart ?? 0,
);
const nodeCompositionId = node.getAttribute("data-composition-id");
let duration = parseNum(node.getAttribute("data-duration"));
if ((duration == null || duration <= 0) && nodeCompositionId && nodeCompositionId !== rootCompositionId) {
if (
(duration == null || duration <= 0) &&
nodeCompositionId &&
nodeCompositionId !== rootCompositionId
) {
duration = resolveTimelineDurationSeconds(nodeCompositionId);
}
if ((duration == null || duration <= 0) && node instanceof HTMLMediaElement) {
const mediaStart =
parseNum(node.getAttribute("data-playback-start")) ?? parseNum(node.getAttribute("data-media-start")) ?? 0;
parseNum(node.getAttribute("data-playback-start")) ??
parseNum(node.getAttribute("data-media-start")) ??
0;
if (Number.isFinite(node.duration) && node.duration > 0) {
duration = Math.max(0, node.duration - mediaStart);
}
@@ -228,7 +254,10 @@ export function collectRuntimeTimelinePayload(params: {
start,
duration,
track:
Number.parseInt(node.getAttribute("data-track-index") ?? node.getAttribute("data-track") ?? String(i), 10) || 0,
Number.parseInt(
node.getAttribute("data-track-index") ?? node.getAttribute("data-track") ?? String(i),
10,
) || 0,
kind,
tagName: tag,
compositionId: node.getAttribute("data-composition-id"),
@@ -250,7 +279,8 @@ export function collectRuntimeTimelinePayload(params: {
const start = startResolver.resolveStartForElement(compositionNode, 0);
const durationFromAttr = parseNum(compositionNode.getAttribute("data-duration"));
const durationFromTimeline = resolveTimelineDurationSeconds(compositionId);
const duration = durationFromAttr && durationFromAttr > 0 ? durationFromAttr : durationFromTimeline;
const duration =
durationFromAttr && durationFromAttr > 0 ? durationFromAttr : durationFromTimeline;
if (duration == null || duration <= 0) continue;
const clampedDuration = clampDurationToRootWindow(start, duration);
if (clampedDuration <= 0) continue;
+7 -1
View File
@@ -1,4 +1,10 @@
export type RuntimeJson = string | number | boolean | null | RuntimeJson[] | { [key: string]: RuntimeJson };
export type RuntimeJson =
| string
| number
| boolean
| null
| RuntimeJson[]
| { [key: string]: RuntimeJson };
export type RuntimeBridgeControlAction =
| "play"