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 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.20" : "0.0.0-dev";
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(`${attr2}=["']([^"']+)["']`));
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: htmlBeforeMediaIds, externalAssets } = collectExternalAssets(
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-WxXW8fW3.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};
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};