mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix: storyboard-angle review follow-ups (M1 bg-on-clip, B3 slideshow, parser guard, CLI fixes) (#1791)
* fix(skills): storyboard review — bg-on-clip rule, slideshow output, parser parity guard Addresses the storyboard-angle review (jrusso1020): - M1 (invisible text): frame-worker.md (x3) + SKILL.md Step 5 (x3) now require a frame's full-bleed background on a class=clip layer, never the #root / data-composition-id element (the root is clip-gated to its scene window, so a background on it is not a dependable ground and dark text can land on the black host body). The assembler already paints frame.md's canvas onto index #root as the base ground; the per-frame clip rides on top. - B3 (slideshow truncates to slide 1): slideshow/SKILL.md gains an Output section (decks render via 'present'; 'render index.html' captures only the first composition; linear main-line MP4 export is deferred). - Parser drift: vendoredParity.test.ts guards the three vendored storyboard.mjs copies (byte-identical + parse-parity with @hyperframes/core). - skills-manifest.json regenerated for the edited SKILL.md files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): storyboard review — lint, validate help, snapshot, inspect, capture, render Addresses the CLI findings from the storyboard-angle review (jrusso1020): - lint (@hyperframes/lint): accept vendor-prefixed system-font keywords -apple-system / BlinkMacSystemFont so a system stack with a generic fallback no longer trips font_family_without_font_face (+ test). - help: list 'validate' under Project in 'hyperframes --help' (was runnable but undocumented). - snapshot: honor -o/--output (the flag did not exist; output was hardcoded to snapshots/). The dir is resolved once and threaded through capture + contact sheet + Gemini. - snapshot: split font status into loaded / error / unused with a one-line summary; only a real 'error' is reported as FAILED (an unrequested @font-face is 'unused', not a contradiction with 'loaded'). - inspect: suppress text_occluded across a scene-to-scene crossfade (occluder in a different data-composition-id mount while a scene is mid-fade); a same-scene or two-settled-scenes overlap still flags. - inspect: suppress content_overlap between in-flow siblings governed by the same flex/grid container (tight stacks / number lockups are layout slop). - capture: record source resolution (videoWidth/Height) in video-manifest.json alongside the DOM display box; consumers size off the source dims. - render: warn when the target carries a slideshow island (render captures only the first scene, so the MP4 is truncated to slide 1; use 'present'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a4eacaec37
commit
a4303137cb
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseStoryboard as coreParse } from "./parseStoryboard.js";
|
||||
|
||||
// Sync guard for the hand-vendored parser.
|
||||
//
|
||||
// Each standalone skill ships a plain-JS copy of this parser at
|
||||
// scripts/lib/storyboard.mjs — skills install via `npx skills add`, where a
|
||||
// script can't reach @hyperframes/core (and the core export is .ts that node
|
||||
// can't load). The copies MUST stay in lockstep with core. This test fails the
|
||||
// moment they drift, so a parser change can't silently leave the skills behind:
|
||||
// 1. every vendored copy is byte-identical to the others, and
|
||||
// 2. a vendored copy parses a representative storyboard identically to core.
|
||||
// If you change the parser, update every path below in the same commit.
|
||||
const VENDORED_RELATIVE = [
|
||||
"../../../../skills/faceless-explainer/scripts/lib/storyboard.mjs",
|
||||
"../../../../skills/pr-to-video/scripts/lib/storyboard.mjs",
|
||||
"../../../../skills/product-launch-video/scripts/lib/storyboard.mjs",
|
||||
];
|
||||
|
||||
const vendoredUrls = VENDORED_RELATIVE.map((rel) => new URL(rel, import.meta.url));
|
||||
|
||||
// Exercises frontmatter (known keys + an unknown global), H2/H3 frame headings,
|
||||
// every recognized meta key + an alias (transition / vo / description), an unknown
|
||||
// status (warning + stash under extra), an unparseable duration (warning), unknown
|
||||
// extra keys, and free-form narrative — the surfaces most likely to drift.
|
||||
const SAMPLE = `---
|
||||
format: 1080x1920
|
||||
message: "Ship it"
|
||||
arc: Hook → Proof → CTA
|
||||
audience: builders
|
||||
campaign: spring
|
||||
---
|
||||
|
||||
## Frame 1 — Hook
|
||||
- duration: 3s
|
||||
- status: animated
|
||||
- transition: cut
|
||||
- vo: "Open cold."
|
||||
- src: compositions/frames/01-hook.html
|
||||
- poster: 2s
|
||||
- effect: shimmer
|
||||
|
||||
Open on the promise.
|
||||
|
||||
### Beat 2 — Detail
|
||||
- duration: four
|
||||
- status: glowing
|
||||
- description: a quiet hold
|
||||
- voiceover: "The payoff."
|
||||
|
||||
Land it.
|
||||
`;
|
||||
|
||||
describe("vendored storyboard parser parity", () => {
|
||||
it("every vendored copy is byte-identical", () => {
|
||||
const bodies = vendoredUrls.map((u) => readFileSync(fileURLToPath(u), "utf8"));
|
||||
for (let i = 1; i < bodies.length; i++) {
|
||||
expect(
|
||||
bodies[i],
|
||||
`${VENDORED_RELATIVE[i]} drifted from ${VENDORED_RELATIVE[0]} — re-vendor it`,
|
||||
).toBe(bodies[0]);
|
||||
}
|
||||
});
|
||||
|
||||
it("a vendored copy parses identically to core", async () => {
|
||||
const vendored = await import(vendoredUrls[0].href);
|
||||
expect(vendored.parseStoryboard(SAMPLE)).toEqual(coreParse(SAMPLE));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user