mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
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:
@@ -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",
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user