hyperframes 0.7.20 → 0.7.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +28 -19
- package/dist/skills/hyperframes-cli/SKILL.md +2 -0
- package/dist/skills/hyperframes-cli/references/preview-render.md +11 -0
- package/dist/studio/assets/{index-D-KLiCTU.js → index-gFA786gK.js} +1 -1
- package/dist/studio/assets/{index-WxXW8fW3.js → index-jbPe1Dih.js} +3 -3
- package/dist/studio/assets/{index-CUt5jJXy.js → index-pAaVqALC.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ var VERSION;
|
|
|
50
50
|
var init_version = __esm({
|
|
51
51
|
"src/version.ts"() {
|
|
52
52
|
"use strict";
|
|
53
|
-
VERSION = true ? "0.7.
|
|
53
|
+
VERSION = true ? "0.7.21" : "0.0.0-dev";
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -54774,7 +54774,7 @@ function shouldClampMediaDuration(declaredDuration, maxDuration) {
|
|
|
54774
54774
|
return declaredDuration > maxDuration + MEDIA_DURATION_CLAMP_EPSILON_SECONDS;
|
|
54775
54775
|
}
|
|
54776
54776
|
function getAttr(tag, attr2) {
|
|
54777
|
-
const match = tag.match(new RegExp(
|
|
54777
|
+
const match = tag.match(new RegExp(`(?<![\\w-])${attr2}=["']([^"']+)["']`));
|
|
54778
54778
|
return match ? match[1] ?? null : null;
|
|
54779
54779
|
}
|
|
54780
54780
|
function hasAttr(tag, attr2) {
|
|
@@ -67674,6 +67674,31 @@ var init_dist4 = __esm({
|
|
|
67674
67674
|
}
|
|
67675
67675
|
return findings;
|
|
67676
67676
|
},
|
|
67677
|
+
// media_crossorigin_breaks_preview — `crossorigin` on <video>/<audio> forces a
|
|
67678
|
+
// CORS-checked fetch. The server-side renderer downloads media directly (no CORS),
|
|
67679
|
+
// so it always works there; but Studio preview runs in the browser, where a media
|
|
67680
|
+
// host that omits Access-Control-Allow-Origin silently fails the load — the media
|
|
67681
|
+
// shows BLANK/black in preview while renders look fine, hiding the bug. Plain
|
|
67682
|
+
// displayed media never needs crossorigin; it's only required to read pixels/samples
|
|
67683
|
+
// back (canvas/WebGL texture, WebAudio createMediaElementSource) AND only when the
|
|
67684
|
+
// host is known CORS-enabled.
|
|
67685
|
+
({ tags }) => {
|
|
67686
|
+
const findings = [];
|
|
67687
|
+
for (const tag of tags) {
|
|
67688
|
+
if (tag.name !== "video" && tag.name !== "audio") continue;
|
|
67689
|
+
if (!hasAttrName(tag.raw, "crossorigin")) continue;
|
|
67690
|
+
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
67691
|
+
findings.push({
|
|
67692
|
+
code: "media_crossorigin_breaks_preview",
|
|
67693
|
+
severity: "error",
|
|
67694
|
+
message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has crossorigin, which forces a CORS-checked fetch. If the media host omits Access-Control-Allow-Origin, the load silently fails in Studio preview (media shows BLANK/black) while server-side renders still work \u2014 hiding the bug.`,
|
|
67695
|
+
elementId,
|
|
67696
|
+
fixHint: "Remove the crossorigin attribute unless you read the media back via canvas/WebGL/WebAudio AND the host is known to send CORS headers. Plain displayed media never needs it.",
|
|
67697
|
+
snippet: truncateSnippet(tag.raw)
|
|
67698
|
+
});
|
|
67699
|
+
}
|
|
67700
|
+
return findings;
|
|
67701
|
+
},
|
|
67677
67702
|
// video_audio_double_source — catches audible <video> paired with a separate
|
|
67678
67703
|
// <audio> pointing to the same file, which causes double playback at runtime
|
|
67679
67704
|
({ tags }) => {
|
|
@@ -100775,18 +100800,6 @@ function rewriteUnresolvableGsapToCdn(html, projectDir) {
|
|
|
100775
100800
|
}
|
|
100776
100801
|
);
|
|
100777
100802
|
}
|
|
100778
|
-
function assignMissingMediaIds(html) {
|
|
100779
|
-
const { document: document2 } = parseHTML(html);
|
|
100780
|
-
const media = document2.querySelectorAll("video[data-start], audio[data-start]");
|
|
100781
|
-
let seq = 0;
|
|
100782
|
-
let changed = false;
|
|
100783
|
-
for (const el of Array.from(media)) {
|
|
100784
|
-
if (el.getAttribute("id")) continue;
|
|
100785
|
-
el.setAttribute("id", `hf-media-${seq++}`);
|
|
100786
|
-
changed = true;
|
|
100787
|
-
}
|
|
100788
|
-
return changed ? document2.toString() : html;
|
|
100789
|
-
}
|
|
100790
100803
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
100791
100804
|
const rawHtml = rewriteUnresolvableGsapToCdn(readFileSync23(htmlPath, "utf-8"), projectDir);
|
|
100792
100805
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -100852,11 +100865,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
100852
100865
|
defaultLogger.info(`[Compiler] Prepared ${preparedGifs.length} animated GIF input(s) as WebM`);
|
|
100853
100866
|
}
|
|
100854
100867
|
const embeddedHtml = await embedLocalFontFaces(htmlWithPreparedGifs, projectDir);
|
|
100855
|
-
const { html
|
|
100856
|
-
embeddedHtml,
|
|
100857
|
-
projectDir
|
|
100858
|
-
);
|
|
100859
|
-
const html = assignMissingMediaIds(htmlBeforeMediaIds);
|
|
100868
|
+
const { html, externalAssets } = collectExternalAssets(embeddedHtml, projectDir);
|
|
100860
100869
|
for (const [relPath, absPath] of remoteMediaAssets) {
|
|
100861
100870
|
externalAssets.set(relPath, absPath);
|
|
100862
100871
|
}
|
|
@@ -20,6 +20,7 @@ Everything runs through `npx hyperframes` unless project instructions specify a
|
|
|
20
20
|
- Deliver: `npx hyperframes render --quality high --output out.mp4`
|
|
21
21
|
- CI / cross-host repro: `npx hyperframes render --docker --strict --output out.mp4`
|
|
22
22
|
- Cloud (long / large): `npx hyperframes lambda render ./my-project --width 1920 --height 1080 --wait` (see Lambda below)
|
|
23
|
+
8. **Report feedback** — after verifying the output, `npx hyperframes feedback --rating <1-5> --comment "..."` once per task (see Agent Conventions).
|
|
23
24
|
|
|
24
25
|
Run lint, validate, and inspect before preview. `lint` catches missing `data-composition-id`, overlapping tracks, and unregistered timelines. `validate` loads the composition in headless Chrome and reports runtime console errors plus WCAG contrast issues. `inspect` seeks through the timeline and reports text spilling out of bubbles/containers or off the canvas — and, when a `*.motion.json` sidecar is present, verifies motion intent (entrances firing under seek, stagger order, in-frame, liveness) against that same seeked timeline.
|
|
25
26
|
|
|
@@ -38,6 +39,7 @@ Cross-cutting rules that hold for every command:
|
|
|
38
39
|
- **Use Studio context for user-directed edits.** When the user says "this selected element", "the thing I clicked", "current selection", or similar, ask them to select it in Studio, then run `npx hyperframes preview --context --json --context-fields selection`. Use the returned `selection.target.hfId` / `selector`, `selection.sourceFile`, `selection.currentTime`, and `selection.thumbnailUrl` to anchor the edit. If `selection` is `null` and `errors.selection.code` is `no-selection`, ask the user to click the element and rerun; do not guess from screenshots.
|
|
39
40
|
- **Keep Studio context compact.** `preview --context --json` returns compact selection by default. Add `--context-fields selection`, `--context-fields selection,lint`, or `--context-fields lint` to avoid bloating agent context. Use `--context-detail full` only when you need heavy fields like computed styles, inline styles, or text-field metadata.
|
|
40
41
|
- **Post-render verification.** After `render` returns exit 0, confirm the output file exists and has plausible size before reporting success: `[ -s "$OUTPUT" ] || echo "render produced no output"`. The CLI prints `◇ <path>` on success; for long renders also sanity-check duration with `ffprobe -i "$OUTPUT" -show_format -v error`.
|
|
42
|
+
- **Report feedback after a successful render.** Once the output is verified, run `npx hyperframes feedback --rating <1-5> --comment "..."` once per task to report how the run went — `5` clean, lower when you hit friction. Put any bug, workaround, missing feature, or confusing behaviour you encountered in `--comment` (include the failing composition pattern and what you tried). This is the project's primary signal channel; renders that go silent leave the maintainers blind. Skip only when telemetry is disabled or the user opted out.
|
|
41
43
|
|
|
42
44
|
## Routing
|
|
43
45
|
|
|
@@ -140,6 +140,17 @@ npx hyperframes render --docker # byte-identical
|
|
|
140
140
|
|
|
141
141
|
**Parametrized renders:** the composition declares its variables on the `<html>` root with **`data-composition-variables`** — a JSON **array of declarations** (`{id, type, label, default}` per entry) that defines the schema. Scripts inside read the resolved values via `window.__hyperframes.getVariables()`. The CLI `--variables '{"title":"Q4 Report"}'` is a JSON **object keyed by id** that overrides those declared defaults for one render; missing keys fall through, so the same composition runs unchanged in dev preview and in production. Sub-comp hosts can also override per-instance with `data-variable-values`. See the `hyperframes-core` skill for the full pattern.
|
|
142
142
|
|
|
143
|
+
### feedback (report after rendering)
|
|
144
|
+
|
|
145
|
+
After a render is verified, send one feedback line per task. This is the maintainers' primary signal — a render that finishes silently tells them nothing.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx hyperframes feedback --rating 5 # clean run, no notes
|
|
149
|
+
npx hyperframes feedback --rating 3 --comment "bg <video> renders grey in multi-scene; worked around with --format png-sequence"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`--rating` is 1-5 (required); `--comment` is free text — use it for any bug, workaround, missing feature, or confusing behaviour, plus the composition pattern that triggered it and what you tried. Feedback is anonymous and attaches a `doctorSummary` (OS/Node/CPU/mem/ffmpeg) automatically. No-ops when telemetry is disabled.
|
|
153
|
+
|
|
143
154
|
## publish
|
|
144
155
|
|
|
145
156
|
```bash
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as P}from"./index-
|
|
1
|
+
import{g as P}from"./index-jbPe1Dih.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
|