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:
WaterrrForever
2026-06-30 16:58:00 +08:00
committed by GitHub
co-authored by Claude Opus 4.8
parent a4eacaec37
commit a4303137cb
18 changed files with 251 additions and 31 deletions
+14
View File
@@ -277,6 +277,8 @@ interface VideoDescriptor {
src: string;
width: number;
height: number;
sourceWidth: number;
sourceHeight: number;
top: number;
left: number;
heading: string;
@@ -315,8 +317,14 @@ const VIDEO_SCAN_EXPR = `(() => {
if (!ariaLabel && wrapper) ariaLabel = wrapper.getAttribute('aria-label') || '';
return {
src: src,
// width/height are the DOM display box (what the page laid the element out
// at); sourceWidth/Height are the clip's intrinsic resolution. Size planners
// off the source dims, not the display box (a 1920x1080 clip can display at
// 904x613). 0 when metadata has not loaded yet.
width: Math.round(rect.width),
height: Math.round(rect.height),
sourceWidth: v.videoWidth || 0,
sourceHeight: v.videoHeight || 0,
top: Math.round(rect.top),
left: Math.round(rect.left),
heading: heading,
@@ -418,6 +426,8 @@ export async function captureVideoManifest(
filename: k,
width: 0,
height: 0,
sourceWidth: 0,
sourceHeight: 0,
top: 0,
left: 0,
heading: "",
@@ -441,6 +451,8 @@ export async function captureVideoManifest(
filename: string;
width: number;
height: number;
sourceWidth: number;
sourceHeight: number;
heading: string;
caption: string;
ariaLabel: string;
@@ -508,6 +520,8 @@ export async function captureVideoManifest(
filename: v.filename,
width: v.width,
height: v.height,
sourceWidth: v.sourceWidth,
sourceHeight: v.sourceHeight,
heading: v.heading,
caption: v.caption,
ariaLabel: v.ariaLabel,