mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
0c33b2dc7ab711ebd9c124ce44535d36d47da748
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e8f871bc8 | fix(video): hold final frame through composition | ||
|
|
cea3458016 |
refactor(cli): single-source-of-truth pass over the check branch
Every duplicated decision gets one owner: rectToBbox lives in checkTypes (was verbatim in pipeline and browser layers); the audit seek tuning is one exported AUDIT_SEEK_OPTIONS consumed by check and the deprecated inspect path; zoom padding/scale defaults export from the capture module instead of re-literalized in three files; the optional run_id property is built by one helper across all three telemetry events; check's --max-transition-samples parsing reuses its own positiveInteger helper; validate drops a leftover re-export and redundant explicit-default args go away. Tests: the contrast candidate round-trip gains a real integration anchor (the actual browser script eval'd in-page, a wrapper asserting finish receives the page-script bbox shape) replacing regex-over-source as the primary guard; the redundant geometry source-golden and a duplicated deprecation-envelope assertion are dropped. |
||
|
|
94f6de8b8d |
feat(cli): persistence-tiered findings, frozen-sweep guard, occlusion coverage
Layout findings now distinguish held defects from entrance/exit transients: a dynamic issue seen at a single grid sample demotes to info, while content_overlap held across two-plus samples (or 500ms+) promotes to error, resolving the long-standing re-promotion TODO. Static compositions keep their severity. check gains a sweep_static error when a 3s+ composition shows zero geometry change across every sample (a frozen timeline makes every green verdict unreliable); skipped when the motion sidecar already reported motion_frozen. text_occluded findings carry a coveredFraction; atomic labels (short, no whitespace) flag on any cover while prose needs 15%, since partial cover changes what a short label reads as. Deprecation-test scaffolding consolidates into deprecationTestHarness; tier logic and logger tests restructured under the complexity gate without suppression markers. Detection mechanics adapted from Adam Rosler's open-sourced visual-linter design (github.com/Adam-Rosler/hyperframes-visual-linter-design); the elementFromPoint paint model, opt-out attributes, and single-audit architecture are unchanged. |
||
|
|
58f45ef758 |
feat(cli): deprecate validate, inspect, layout in favor of check
One stderr notice per invocation and _meta.deprecated: true in JSON mode (shared helper next to withMeta; layout owns both inspect and layout via createInspectCommand). Help descriptions gain the pointer. No behavior change; removal ships separately once migration telemetry says usage has decayed. fix(producer): route info/debug logs to stderr — the compiler's 'Localized remote media' line was landing on stdout ahead of validate's --json payload, breaking every piped consumer. Diagnostics now share stderr with warn/error; render progress uses its own channel. |
||
|
|
feb256df8a |
refactor(cli): unify seek/settle and Chrome launch across browser commands
seekCompositionTimeline becomes the single seek implementation with per-caller settle options (rAF mode, font wait, settle sleep), replacing the divergent local seekTo copies in validate and layout. All three launch paths now build args via the engine's buildChromeArgs; screenshot paths keep the engine's software-GPU default for deterministic output. inspect gains one transient content_overlap warning on product-promo (t=12.22s): the gsap.ticker.tick flush samples timeline state the old layout seek missed. |
||
|
|
906c8d04f8 |
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>
|
||
|
|
3f49f107eb |
fix(cli): validate seeks the runtime player directly, not raw timelines (#1895)
* fix(cli): validate seeks the runtime player directly, not raw timelines validate's seekTo() only checked for window.__hf.seek (a bridge object the producer's render-pipeline file server injects) before falling back to grabbing window.__timelines and calling .seek() on each raw GSAP timeline directly. validate serves compositions through a plain static file server that never injects that bridge, so this fallback ran on every single validate invocation. Seeking a raw timeline moves the animation state but skips the runtime's own [data-start]/[data-duration] visibility sync (syncMediaForCurrentState in packages/core/src/runtime/init.ts), which is what sets an off-window clip's inline visibility/display styles. Skipping it left elements outside their timeline window looking fully visible to any check that reads computed style afterward at that seek time. This surfaced as validate's WCAG contrast audit (contrast-audit.browser.js) flagging text in off-window clips against whatever background happened to be behind them, since its own visibility filtering trusts the runtime to have already hidden them. Fix: prefer window.__player.renderSeek, which the composition runtime exposes directly on every page load (no bridge required) and which does run the visibility sync, before falling back to the __hf/raw timeline paths. No changes needed to contrast-audit.browser.js itself since its existing visibility check now sees correct computed style. No new test added: seekTo's branch selection runs entirely inside a page.evaluate() callback, which Puppeteer serializes via .toString() for the browser context, so it can't import and call a project-local window.__player stub from a jsdom/vitest test without testing a copy of the logic rather than the shipped code. Verified instead by reading the runtime chain end-to-end: window.__player.renderSeek is always set by packages/core/src/runtime/init.ts's createPlayerApiCompat, calls through to player.renderSeek, which calls syncMediaForCurrentState(). * fix(cli): wait for runtime seek target in validate |
||
|
|
0338be97fd |
fix(cli): validate navigation timeout honors --timeout, hints on CDN scripts (#1929)
`validate` navigated the page with a hardcoded 10s timeout that ignored the --timeout option. A composition that loads GSAP (or any library) from a CDN <script> in <head> blocks `domcontentloaded` until that script finishes downloading; on a slow network that exceeds 10s and validate fails with an opaque "Navigation timeout of 10000ms exceeded" — even though the full render (much larger budget) rides it out fine, and even though --timeout (the documented "wait longer for slow loads" knob) had no effect on navigation. The only recourse was to change the composition (vendor the script locally). Reported precisely, with the exact error and the observation that render's 60s budget masks it while validate's 10s trips. Fix: - resolveNavigationTimeoutMs(optTimeout) = max(10s floor, --timeout), so --timeout now also extends the navigation budget. Default behavior is unchanged: the default --timeout (3000) stays clamped to the 10s floor. - navigationTimeoutHint() replaces Puppeteer's opaque timeout error with an actionable message naming the likely cause (a blocking CDN <script>) and the two fixes (vendor locally / raise --timeout). Any non-timeout error is rethrown unchanged. - --timeout help text updated to note it also governs navigation. Both helpers are pure and exported; validateInBrowser wires them around the single page.goto. No behavior change for compositions that navigate within 10s. Test: resolveNavigationTimeoutMs (floor kept for unset/small/zero, raised past the floor) and navigationTimeoutHint (rewrites a nav-timeout error with CDN + --timeout guidance; returns null for other errors so the caller rethrows as-is). validate suite 14 tests pass. |
||
|
|
b087f1e3c0 |
fix(cli): validate stops misreporting slow-loading media as unreadable (#1849)
Two independent post-release feedback reports of validate warning about audio duration despite an explicit, correct data-duration slot, one of them naming a timeout explicitly. Root cause: auditClipDurations reads each <video>/<audio> element's intrinsic .duration via a single page.evaluate() snapshot taken after a flat, unconditional page-settle sleep (opts.timeout ?? 3000ms, shared with other audits). Per the HTML spec, HTMLMediaElement.duration is NaN until metadata loads. A slow-loading audio file (large narration WAV, remote source) can still be mid-fetch when that sleep elapses — el.duration is NaN at that exact instant, which the audit permanently records as "could not read the duration" even though the render pipeline (which properly awaits media readiness) handles the same file fine. Fix: race each not-yet-ready element's loadedmetadata/error event against a deadline instead of taking one fixed-time snapshot. Elements already ready resolve immediately (no added latency in the common case); only genuinely slow elements get a real second chance before the warning fires. The race/cleanup wiring lives twice by necessity — once inline inside the page.evaluate() closure (Puppeteer serializes and re-runs that closure in an isolated browser realm with no access to this module), and once as the exported, duck-typed raceMediaReady for a real, deterministic unit test via Node's built-in EventTarget (no browser or DOM library needed). The comment on raceMediaReady flags that both copies must move together. |
||
|
|
cf573f7f3f |
fix(core,producer,cli): pre-flight validation for empty/malformed sub-compositions (#1831)
* fix(core,producer,cli): pre-flight validation for empty/malformed sub-compositions The #1 render failure bucket in production telemetry (PostHog project 356858, dashboard 1783183 "HyperFrames — Bottom-Line & Activation"; ~65-69K occurrences / ~27-28K affected users over 30 days, ~80% via AI-agent authoring flows) is a `data-composition-src` reference pointing at a scene file that is empty, malformed, or missing. Root cause, traced end-to-end: - The literal error "Composition HTML is empty or could not be parsed: <path>" is real (not a PostHog paraphrase) — thrown by a since-reverted guard in packages/core/src/compiler/inlineSubCompositions.ts (#1364), then changed to a silent skip in #1678 to avoid aborting renders on partial content during authoring. #1629 added per-assembler guards for 3 skill workflows (product-launch-video, faceless-explainer, pr-to-video), but general-video and hand-authored flows — where the dominant filename `scene-title.html` (40K+/68K of the bucket) originates — have no assembler and thus no guard. #1678 assumed the assembler guards from #1629 covered this pre-render; they only covered 3 of the many authoring flows. - On current `main`, an empty/malformed data-composition-src file no longer crashes or throws during render — it's silently dropped by the tolerant inliner. Reproduced locally: `hyperframes render` on a project with an empty scene-title.html "succeeds" after ~93s (two 45s pollSubCompositionTimelines timeouts) with the scene silently missing from the output video. `hyperframes validate` also reports "No console errors" for the same broken project. - The raw `Cannot destructure property 'firstElementChild' of 'documentElement' as it is null` crash reproduces directly against linkedom (the DOMParser polyfill packages/cli/src/utils/dom.ts installs in the real CLI runtime) for empty and non-HTML input — confirmed with a standalone repro script, not just inferred. jsdom/happy-dom (used in this repo's own test environment) are spec-compliant and never produce a null documentElement, which is why this needed a linkedom-specific test file. Fix: - New shared helper `checkSubCompositionUsability` (packages/core/src/compiler/subCompositionValidity.ts) is the single source of truth for "is this data-composition-src file usable" — mirrors the inliner's own parse/template/body logic so all callers agree. - `inlineSubCompositions.ts` (preview/studio bundling) now uses the shared helper internally but keeps its #1678 tolerant skip-and-continue behavior unchanged — mid-authoring iteration on a partial project must keep working. `onMissingComposition` now also receives a human-readable reason. - New render-only pre-flight (`assertSubCompositionsUsable` in packages/producer/src/services/htmlCompiler.ts) walks every data-composition-src reference (including nested ones, root-relative, matching parseSubCompositions' own resolution) before any compilation work starts, and throws naming every offending file at once. This is unconditional — not gated behind --strict — because a render that silently drops a scene is strictly worse than one that refuses to start. Confirmed locally: render now fails in ~0.4s with an actionable message instead of "succeeding" after 93s with a missing scene. - New `hyperframes lint` rule `missing_or_empty_sub_composition` (packages/cli/src/utils/lintProject.ts) surfaces the same check as a file-scoped, actionable lint error (already unconditional — lint exits 1 on any error). - `hyperframes validate` now also runs this check before launching a browser, so it no longer reports "No console errors" for a project with a broken sub-composition. - `packages/core/src/parsers/htmlParser.ts`: guarded every `documentElement`-may-be-null access (parseHtml, updateElementInHtml, addElementToHtml, removeElementFromHtml, extractCompositionMetadata, validateCompositionHtml) with a new typed `CompositionHtmlParseError` (or, for validateCompositionHtml's collect-and-report contract, a typed validation failure) instead of a raw crash. Tests: empty file, whitespace-only, malformed/non-HTML, missing file, nested sub-compositions (both happy path and broken-grandchild), and the happy path — at the shared-helper, lint, and render pre-flight layers. Not changed: the AI-agent authoring skills (skills/*). general-video and hand-authored flows have no assemble-index.mjs equivalent to guard, so the fix is at the CLI/render layer instead — flow-agnostic, covers every authoring path, and the skills' existing "run lint/validate and stop on failure" guidance now actually catches this class of mistake once run. Not run in this environment: the producer package's full regression-harness test suite (`bun test` in packages/producer) — it performs heavy real rendering (S3 asset downloads, Google Fonts fetches, full video encodes) and did not complete in a reasonable time in this sandbox. Verified instead via the targeted test file for all touched code (76/76 passing), whole-repo typecheck/build/oxlint, `fallow audit` (complexity/duplication/dead-code gate, clean), and manual end-to-end CLI runs (render/lint/validate) against reproduction projects, including a nested sub-composition scenario. CI should run the full producer suite before merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * refactor(parsers,lint): port empty-composition pre-flight to extracted packages Rebased onto main, which extracted @hyperframes/lint from core (lint depends only on parsers, not core). Relocate checkSubCompositionUsability from core to @hyperframes/parsers so both core (inliner) and lint can consume it without a core<->lint cycle; core keeps a @deprecated re-export shim. Correctness fixes from code review: - checkSubCompositionUsability now returns "no-composition-root" when the <template>/<body> content has no [data-composition-id] element (previously a marker-free placeholder body passed both guards). - lint's missing/empty sub-composition rule now only checks files reachable via data-composition-src from the root (matching render pre-flight), instead of a raw filesystem walk that false-positived on orphaned files. - drop `as string` cast in inlineSubCompositions in favor of an explicit null guard (per CLAUDE.md). Review-comment items: - move EmptyCompositionError JSDoc above the class (was above the adapter fn). - correct stale circular-ref comment to match actual silent-skip behavior. - rewrite self-contradicting lint message ("silently drop") to describe the new loud render-pre-flight abort. - add the __PLACEHOLDER__ (/^__[A-Z_]+__$/) skip to the render pre-flight so it agrees with lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f0c4dee705 |
fix(slideshow): present media controls (#1601)
* fix(slideshow): harden media controls in present decks
* refactor(slideshow): clear Fallow audit findings
Decompose flagged high-CRAP functions and extract production-code
duplications so the audit gate clears.
- core/runtime/bridge.ts handler — replace the 14-branch if-chain with a
CONTROL_HANDLERS dispatch table; flash-elements payload handling moves
to its own helper. Behavior preserved (all existing bridge.test.ts
cases hit the same dispatchers via the public installRuntimeControlBridge
API).
- player/slideshow/SlideshowController syncTo — split into
isValidSyncTarget / isCrossSlide / rerootStackTo helpers. The
stopSlideMedia decision and the stack re-rooting are now individually
named; the public method is a 4-line orchestrator.
- cli/commands/validate.ts run — extract emitJsonReport / emitTextReport
so the orchestrator no longer carries the dual JSON/text branches.
Cuts the cyclomatic complexity flagged by fallow after the
shouldIgnoreRequestFailure signature expansion shifted the fingerprint.
- player/hyperframes-player.ts — _setIframeMediaMuted and _stopIframeMedia
shared a `try { iframeDoc = contentDocument } catch { return }` preamble
(clone group 15). Extract _getSameOriginIframeDocument(): Document | null
and have both call sites consume it.
- studio/panels/SlideshowPanel.tsx — the notes controller's debounce-tail
and explicit flush() shared the pending-drain pattern (clone group 16).
Extract a drainPending() closure both call.
- player/hyperframes-player.test.ts — collapse the new stopMedia / muted
tests' repeated Object.defineProperty(iframe, "contentDocument", { get })
shape behind a stubIframeContentDocument helper.
No behavior changes — refactor only. Existing tests cover the affected
paths unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(validate): split run further; ignore test dup parity
Second Fallow pass surfaced two minor follow-ups after the first cut:
- packages/cli/src/commands/validate.ts run + emitTextReport still
carried minor CRAP findings (43.1 / 37.1, threshold 30). Extract
printValidationResult / formatConsoleEntry / formatTotals /
emitFailureReport so run becomes a try/catch + delegation, well
below the threshold; emitTextReport drops the inline format loops.
- .fallowrc.jsonc duplicates.ignore: add hyperframes-player.test.ts
alongside the existing SlideshowPanel.test.ts entry. Same reasoning
documented there — parallel arrange/act/assert test cases are
intentionally self-contained for readability; collapsing them under
shared fixtures would couple unrelated scenarios (same-origin vs
realm media, audio-locked permutations, seek bridge variants).
No behavior changes — refactor + config-policy parity only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4d05b475f0 |
feat: add Stronkter catalog blocks (#570)
## Problem The Catalog did not include the four prompt-matched Stronkter one-shot HyperFrames projects, and registry metadata only supported a plain author string, so there was no structured way to show creator attribution or the original generation prompt on generated catalog pages. ## What this fixes - Adds four Catalog blocks matching the provided prompts, in order: - `north-korea-locked-down` - `apple-money-count` - `nyc-paris-flight` - `goonvpn-youtube-spot` - Attributes each block to [Stronkter](https://x.com/Stronkter). - Stores and renders the original source prompt for each generated catalog page. - Adds a local realistic map plate for the North Korea block so rendering does not depend on live map tile requests. - Extends registry item metadata/schema with `authorUrl` and `sourcePrompt`. - Updates catalog page generation to read items from `registry/registry.json`, keeping generated docs aligned to the public registry manifest. - Ignores normal browser media preload `net::ERR_ABORTED` request failures for media assets during `hyperframes validate`, while preserving failures for real missing assets. ## Root cause The imported projects are Catalog-ready compositions, but the registry/docs pipeline did not have first-class source-prompt or linked-author fields to expose creator credit on generated MDX pages. The audio-backed compositions also surfaced a validation edge case: Chrome can report aborted media preload requests as `net::ERR_ABORTED` even when the audio file exists and playback is valid. ## Verification ### Local - `bun run --filter @hyperframes/cli test src/commands/validate.test.ts` - `bun run --filter @hyperframes/core test src/registry/types.test.ts` - `bun run sync-schemas:check` - `bunx oxlint packages/cli/src/commands/validate.ts packages/cli/src/commands/validate.test.ts packages/core/src/registry/types.ts packages/core/src/registry/types.test.ts scripts/generate-catalog-pages.ts` - `bunx oxfmt --check ...` on changed source, registry, docs, and composition files - `git diff --check` - `bun packages/cli/src/cli.ts lint` and `validate` against temp installed projects for all four blocks - Lefthook pre-commit: lint/format/typecheck on the initial commit, plus format on the amend - Lefthook commit-msg: commitlint ### Browser - Exercised all four blocks through HyperFrames preview routes with `agent-browser`. - Captured playback screenshots and WebM recordings for: - `north-korea-locked-down` - `apple-money-count` - `nyc-paris-flight` - `goonvpn-youtube-spot` ## Notes - The zip also contained unrelated project directories, but this PR intentionally includes only the four prompt-matched Catalog blocks requested here. - The imported one-shot compositions may trigger the existing large-composition lint warning, but there are no lint errors and runtime validation passes. |