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
+95
View File
@@ -544,6 +544,22 @@
"href": "/catalog/components/shimmer-sweep", "href": "/catalog/components/shimmer-sweep",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/shimmer-sweep.png" "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/shimmer-sweep.png"
}, },
{
"name": "spain-map",
"type": "block",
"title": "Spain Map",
"description": "Animated Spain choropleth by autonomous community with staggered reveals and gradient legend — D3 conic conformal projection",
"tags": [
"data",
"map",
"geography",
"spain",
"europe",
"choropleth"
],
"href": "/catalog/blocks/spain-map",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/spain-map.png"
},
{ {
"name": "spotify-card", "name": "spotify-card",
"type": "block", "type": "block",
@@ -777,6 +793,70 @@
"href": "/catalog/blocks/ui-3d-reveal", "href": "/catalog/blocks/ui-3d-reveal",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ui-3d-reveal.png" "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/ui-3d-reveal.png"
}, },
{
"name": "us-map",
"type": "block",
"title": "US Map",
"description": "Animated US choropleth map with staggered state reveals, value labels, and gradient legend — pure inline SVG with GSAP",
"tags": [
"data",
"map",
"geography",
"usa",
"choropleth"
],
"href": "/catalog/blocks/us-map",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map.png"
},
{
"name": "us-map-bubble",
"type": "block",
"title": "US Bubble Map",
"description": "Animated US bubble map with proportional city markers, value callouts, and connection lines — composable with us-map",
"tags": [
"data",
"map",
"geography",
"usa",
"bubble",
"cities"
],
"href": "/catalog/blocks/us-map-bubble",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-bubble.png"
},
{
"name": "us-map-flow",
"type": "block",
"title": "US Flow Map",
"description": "Animated connection arcs between US cities over a base map — composable origin-destination flow visualization",
"tags": [
"data",
"map",
"geography",
"usa",
"flow",
"connections",
"arcs"
],
"href": "/catalog/blocks/us-map-flow",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-flow.png"
},
{
"name": "us-map-hex",
"type": "block",
"title": "US Hex Grid Map",
"description": "Animated hexagonal tile grid map — each state as an equal-weight hex with data fill and abbreviation label",
"tags": [
"data",
"map",
"geography",
"usa",
"hexgrid",
"tilegrid"
],
"href": "/catalog/blocks/us-map-hex",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-hex.png"
},
{ {
"name": "vfx-iphone-device", "name": "vfx-iphone-device",
"type": "block", "type": "block",
@@ -911,6 +991,21 @@
"href": "/catalog/blocks/whip-pan", "href": "/catalog/blocks/whip-pan",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/whip-pan.png" "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/whip-pan.png"
}, },
{
"name": "world-map",
"type": "block",
"title": "World Map",
"description": "Animated world choropleth with country-by-country reveal, tooltip labels, and rotating globe inset — D3 Natural Earth projection",
"tags": [
"data",
"map",
"geography",
"world",
"choropleth"
],
"href": "/catalog/blocks/world-map",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/world-map.png"
},
{ {
"name": "x-post", "name": "x-post",
"type": "block", "type": "block",
+2 -2
View File
@@ -368,7 +368,7 @@ function parseHostVariableValues(host: Element): Record<string, unknown> {
return parsed as 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-id",
"data-composition-file", "data-composition-file",
"data-start", "data-start",
@@ -381,7 +381,7 @@ const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-hf-authored-end", "data-hf-authored-end",
]; ];
function prepareFlattenedInnerRoot(innerRoot: Element): Element { export function prepareFlattenedInnerRoot(innerRoot: Element): Element {
const prepared = innerRoot.cloneNode(true) as Element; const prepared = innerRoot.cloneNode(true) as Element;
const authoredRootId = prepared.getAttribute("id")?.trim(); const authoredRootId = prepared.getAttribute("id")?.trim();
for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) { for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) {
+6 -1
View File
@@ -15,7 +15,12 @@ export {
export { compileHtml, type MediaDurationProber } from "./htmlCompiler"; export { compileHtml, type MediaDurationProber } from "./htmlCompiler";
// HTML bundler (Node.js — requires fs, linkedom, esbuild) // 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 { export {
RUNTIME_BOOTSTRAP_ATTR, RUNTIME_BOOTSTRAP_ATTR,
+1 -1
View File
@@ -964,7 +964,7 @@ export function initSandboxRuntimeModular(): void {
return true; return true;
}; };
(window as any).__hfForceTimelineRebind = () => { (window as Window & { __hfForceTimelineRebind?: () => void }).__hfForceTimelineRebind = () => {
childrenBound = false; childrenBound = false;
bindRootTimelineIfAvailable(); bindRootTimelineIfAvailable();
}; };
+15 -1
View File
@@ -682,11 +682,25 @@ export async function initializeSession(session: CaptureSession): Promise<void>
await applyVideoMetadataHints(page, session.options.videoMetadataHints); await applyVideoMetadataHints(page, session.options.videoMetadataHints);
// Same readyState contract as the screenshot path above (>= 2 / HAVE_CURRENT_DATA). // Same readyState contract as the screenshot path above (>= 2 / HAVE_CURRENT_DATA).
await pollVideosReady( const bfVideosReady = await pollVideosReady(
page, page,
session.options.skipReadinessVideoIds ?? [], session.options.skipReadinessVideoIds ?? [],
session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout, 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) // Font check (no rAF dependency — uses fonts.ready API directly)
await page.evaluate(`document.fonts?.ready`); await page.evaluate(`document.fonts?.ready`);
@@ -530,7 +530,7 @@ export function resolveProjectRelativeSrc(
// then strip any remaining leading `..` segments. Stripping `..` from the // then strip any remaining leading `..` segments. Stripping `..` from the
// raw input would leave dangling siblings (`assets/../../assets/foo` // raw input would leave dangling siblings (`assets/../../assets/foo`
// would become `assets/assets/foo` instead of `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(/^(\.\.\/)+/, ""); const stripped = normalized.replace(/^(\.\.\/)+/, "");
if (stripped && stripped !== src && !stripped.startsWith("..")) { if (stripped && stripped !== src && !stripped.startsWith("..")) {
if (compiledDir) candidates.push(join(compiledDir, stripped)); if (compiledDir) candidates.push(join(compiledDir, stripped));
+5 -27
View File
@@ -21,7 +21,10 @@ import {
type ResolvedDuration, type ResolvedDuration,
type UnresolvedElement, type UnresolvedElement,
} from "@hyperframes/core"; } 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 { extractMediaMetadata, extractAudioMetadata } from "../utils/ffprobe.js";
import { isPathInside, toExternalAssetKey } from "../utils/paths.js"; import { isPathInside, toExternalAssetKey } from "../utils/paths.js";
import { import {
@@ -559,19 +562,6 @@ function inlineSubCompositions(
if (!hosts.length) return html; 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( const result = inlineSubCompositionsShared(
document as unknown as Document, document as unknown as Document,
hosts as unknown as Element[], hosts as unknown as Element[],
@@ -587,19 +577,7 @@ function inlineSubCompositions(
return compHtml; return compHtml;
}, },
parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document, parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document,
flattenInnerRoot: (innerRoot: Element): Element => { flattenInnerRoot: prepareFlattenedInnerRoot as (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;
},
scriptErrorLabel: "[Compiler] Composition script failed", scriptErrorLabel: "[Compiler] Composition script failed",
}, },
); );