diff --git a/skills-manifest.json b/skills-manifest.json index 27534f19e8..cff2dd75a7 100644 --- a/skills-manifest.json +++ b/skills-manifest.json @@ -6,8 +6,8 @@ "files": 140 }, "faceless-explainer": { - "hash": "b772a9b6c8118c2c", - "files": 22 + "hash": "5e64350379329147", + "files": 23 }, "figma": { "hash": "517e4dc53c13ea05", @@ -30,7 +30,7 @@ "files": 11 }, "hyperframes-core": { - "hash": "a270c70ced8cf952", + "hash": "5d3b652de1b652a4", "files": 19 }, "hyperframes-creative": { diff --git a/skills/faceless-explainer/scripts/assemble-index.mjs b/skills/faceless-explainer/scripts/assemble-index.mjs index 8a7554500a..90d6eef6ad 100644 --- a/skills/faceless-explainer/scripts/assemble-index.mjs +++ b/skills/faceless-explainer/scripts/assemble-index.mjs @@ -32,12 +32,14 @@ // [--audio-meta audio_meta.json]. On disk: each built frame's src html, // capture/{assets,assets/videos,screenshots}/ for staging, compositions/captions.html. // Writes: /index.html + stages assets/ + (guard ① below) -// repairs a frame file in place when its root is missing data-width/height. +// repairs a frame file in place when its root is missing +// data-width/data-height/data-duration. // // Pre-assembly frame guards (run in the same pass that reads each frame, so common // `lint` failures surface HERE instead of after assembly + a wasted render): -// ① AUTO-REPAIR — a sub-comp root missing data-width/data-height: inject the canvas -// dims (the renderer needs them on the cloned root; else lint root_missing_dimensions). +// ① AUTO-REPAIR — a sub-comp root missing data-width/data-height/data-duration: +// inject the canvas dims and storyboard duration. The transition injector +// extends that static duration when it pads the outgoing frame tail. // ② HARD FAIL — a timed element (data-start+duration+track-index) that is not the root // and lacks class="clip" (shows the whole frame), or two same-track clips that overlap. // (Media inside a sub-comp is NOT a violation: the runtime seeks + decodes nested @@ -162,7 +164,7 @@ function findRootTag(html) { } // Returns { errors: string[], repairedHtml: string|null, repairNote: string|null }. -function guardFrame(html, label) { +function guardFrame(html, label, durationSeconds) { const errors = []; // Scan a copy with comments + + +`; +} + +test("assembly materializes frame root duration before transition injection", () => { + const project = mkdtempSync(join(tmpdir(), "frame-root-duration-")); + try { + write( + join(project, "STORYBOARD.md"), + `--- +format: 1920x1080 +--- + +## Frame 1 — First + +- duration: 2s +- transition_in: cut +- status: animated +- src: compositions/frames/01-first.html + +## Frame 2 — Second + +- duration: 2s +- transition_in: crossfade 0.5s +- status: animated +- src: compositions/frames/02-second.html +`, + ); + const firstPath = join(project, "compositions", "frames", "01-first.html"); + const secondPath = join(project, "compositions", "frames", "02-second.html"); + write(firstPath, frameWithoutRootDuration("01-first", 2)); + write(secondPath, frameWithoutRootDuration("02-second", 2)); + + execFileSync( + process.execPath, + [assembleScript, "--storyboard", join(project, "STORYBOARD.md"), "--hyperframes", project], + { encoding: "utf8" }, + ); + + assert.match( + readFileSync(firstPath, "utf8"), + /data-composition-id="01-first"[^>]*data-duration="2"/, + ); + assert.match( + readFileSync(secondPath, "utf8"), + /data-composition-id="02-second"[^>]*data-duration="2"/, + ); + + assert.doesNotThrow(() => + execFileSync( + process.execPath, + [ + transitionsScript, + "inject", + "--storyboard", + join(project, "STORYBOARD.md"), + "--hyperframes", + project, + ], + { encoding: "utf8", stdio: "pipe" }, + ), + ); + assert.match( + readFileSync(firstPath, "utf8"), + /data-composition-id="01-first"[^>]*data-duration="2.5"/, + ); + } finally { + rmSync(project, { recursive: true, force: true }); + } +}); diff --git a/skills/hyperframes-core/references/frame-worker-core.md b/skills/hyperframes-core/references/frame-worker-core.md index 6b1da927f2..3dbf5ade58 100644 --- a/skills/hyperframes-core/references/frame-worker-core.md +++ b/skills/hyperframes-core/references/frame-worker-core.md @@ -56,7 +56,7 @@ Shared law for every narrative frame, each load-bearing; your workflow's delta a 1. **Read** — your packet top to bottom (your frame block, the inlined blueprint, the inlined rule recipes), then `frame.md` (the look). Internalize the self-check codes below before you write — most lethal is **template transport**: every `