fix: address review feedback — observability, dedup, query-strip, catalog

Review items addressed:

1. Mirror video-failure warning in beginFrame path (was screenshot-only)
2. Fix resolveProjectRelativeSrc escape-fallback to use query-stripped
   cleanSrc instead of raw src for the normalize/strip arm
3. Export prepareFlattenedInnerRoot from @hyperframes/core/compiler and
   consume in the producer instead of duplicating the implementation
4. Use typed Window cast instead of (window as any) for __hfForceTimelineRebind
5. Regenerate docs/public/catalog-index.json with all 6 map blocks
6. Restore Maps nav group in docs.json (catalog generator had merged
   them into Data)
This commit is contained in:
Miguel Ángel
2026-05-18 01:41:15 -04:00
parent cf2addfb92
commit 0f1c64dcae
7 changed files with 125 additions and 33 deletions
+2 -2
View File
@@ -368,7 +368,7 @@ function parseHostVariableValues(host: Element): Record<string, unknown> {
return parsed as Record<string, unknown>;
}
const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
export const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-composition-id",
"data-composition-file",
"data-start",
@@ -381,7 +381,7 @@ const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-hf-authored-end",
];
function prepareFlattenedInnerRoot(innerRoot: Element): Element {
export function prepareFlattenedInnerRoot(innerRoot: Element): Element {
const prepared = innerRoot.cloneNode(true) as Element;
const authoredRootId = prepared.getAttribute("id")?.trim();
for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) {
+6 -1
View File
@@ -15,7 +15,12 @@ export {
export { compileHtml, type MediaDurationProber } from "./htmlCompiler";
// HTML bundler (Node.js — requires fs, linkedom, esbuild)
export { bundleToSingleHtml, type BundleOptions } from "./htmlBundler";
export {
bundleToSingleHtml,
type BundleOptions,
prepareFlattenedInnerRoot,
FLATTENED_INNER_ROOT_STRIP_ATTRS,
} from "./htmlBundler";
export {
RUNTIME_BOOTSTRAP_ATTR,
+1 -1
View File
@@ -964,7 +964,7 @@ export function initSandboxRuntimeModular(): void {
return true;
};
(window as any).__hfForceTimelineRebind = () => {
(window as Window & { __hfForceTimelineRebind?: () => void }).__hfForceTimelineRebind = () => {
childrenBound = false;
bindRootTimelineIfAvailable();
};
+15 -1
View File
@@ -682,11 +682,25 @@ export async function initializeSession(session: CaptureSession): Promise<void>
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
// Same readyState contract as the screenshot path above (>= 2 / HAVE_CURRENT_DATA).
await pollVideosReady(
const bfVideosReady = await pollVideosReady(
page,
session.options.skipReadinessVideoIds ?? [],
session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout,
);
if (!bfVideosReady) {
const failedVideos = await page.evaluate((skipIdList: readonly string[]) => {
const skip = new Set(skipIdList);
return Array.from(document.querySelectorAll("video"))
.filter((v) => !skip.has(v.id))
.filter((v) => (v as HTMLVideoElement).readyState < 2 && !(v as HTMLVideoElement).error)
.map((v) => (v as HTMLVideoElement).src || v.getAttribute("src") || "(no src)")
.join(", ");
}, session.options.skipReadinessVideoIds ?? []);
console.warn(
`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. ` +
`Continuing render — affected videos will appear as blank/black frames.`,
);
}
// Font check (no rAF dependency — uses fonts.ready API directly)
await page.evaluate(`document.fonts?.ready`);
@@ -530,7 +530,7 @@ export function resolveProjectRelativeSrc(
// then strip any remaining leading `..` segments. Stripping `..` from the
// raw input would leave dangling siblings (`assets/../../assets/foo`
// would become `assets/assets/foo` instead of `assets/foo`).
const normalized = posix.normalize(src.replace(/\\/g, "/"));
const normalized = posix.normalize(cleanSrc.replace(/\\/g, "/"));
const stripped = normalized.replace(/^(\.\.\/)+/, "");
if (stripped && stripped !== src && !stripped.startsWith("..")) {
if (compiledDir) candidates.push(join(compiledDir, stripped));
+5 -27
View File
@@ -21,7 +21,10 @@ import {
type ResolvedDuration,
type UnresolvedElement,
} from "@hyperframes/core";
import { inlineSubCompositions as inlineSubCompositionsShared } from "@hyperframes/core/compiler";
import {
inlineSubCompositions as inlineSubCompositionsShared,
prepareFlattenedInnerRoot,
} from "@hyperframes/core/compiler";
import { extractMediaMetadata, extractAudioMetadata } from "../utils/ffprobe.js";
import { isPathInside, toExternalAssetKey } from "../utils/paths.js";
import {
@@ -559,19 +562,6 @@ function inlineSubCompositions(
if (!hosts.length) return html;
const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-composition-id",
"data-composition-file",
"data-start",
"data-duration",
"data-end",
"data-track-index",
"data-track",
"data-composition-src",
"data-hf-authored-duration",
"data-hf-authored-end",
];
const result = inlineSubCompositionsShared(
document as unknown as Document,
hosts as unknown as Element[],
@@ -587,19 +577,7 @@ function inlineSubCompositions(
return compHtml;
},
parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document,
flattenInnerRoot: (innerRoot: Element): Element => {
const prepared = innerRoot.cloneNode(true) as Element;
const authoredRootId = prepared.getAttribute("id")?.trim();
for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) {
prepared.removeAttribute(attrName);
}
if (authoredRootId) {
prepared.removeAttribute("id");
prepared.setAttribute("data-hf-authored-id", authoredRootId);
}
prepared.setAttribute("data-hf-inner-root", "true");
return prepared;
},
flattenInnerRoot: prepareFlattenedInnerRoot as (innerRoot: Element) => Element,
scriptErrorLabel: "[Compiler] Composition script failed",
},
);