hyperframes 0.7.19 → 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 +59 -6
- 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-CURAsFZX.js → index-gFA786gK.js} +1 -1
- package/dist/studio/assets/{index-DXWeH-EY.js → index-jbPe1Dih.js} +3 -3
- package/dist/studio/assets/{index-hueu10iU.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) {
|
|
@@ -65567,13 +65567,15 @@ function findRootTag(source) {
|
|
|
65567
65567
|
function readAttr(tagSource, attr2) {
|
|
65568
65568
|
if (!tagSource) return null;
|
|
65569
65569
|
const escaped = attr2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
65570
|
-
const match = tagSource.match(new RegExp(
|
|
65570
|
+
const match = tagSource.match(new RegExp(`(?<![\\w-])${escaped}\\s*=\\s*["']([^"']+)["']`, "i"));
|
|
65571
65571
|
return match?.[1] || null;
|
|
65572
65572
|
}
|
|
65573
65573
|
function readJsonAttr(tagSource, attr2) {
|
|
65574
65574
|
if (!tagSource) return null;
|
|
65575
65575
|
const escaped = attr2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
65576
|
-
const match = tagSource.match(
|
|
65576
|
+
const match = tagSource.match(
|
|
65577
|
+
new RegExp(`(?<![\\w-])${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i")
|
|
65578
|
+
);
|
|
65577
65579
|
if (!match) return null;
|
|
65578
65580
|
return match[1] ?? match[2] ?? null;
|
|
65579
65581
|
}
|
|
@@ -67672,6 +67674,31 @@ var init_dist4 = __esm({
|
|
|
67672
67674
|
}
|
|
67673
67675
|
return findings;
|
|
67674
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
|
+
},
|
|
67675
67702
|
// video_audio_double_source — catches audible <video> paired with a separate
|
|
67676
67703
|
// <audio> pointing to the same file, which causes double playback at runtime
|
|
67677
67704
|
({ tags }) => {
|
|
@@ -99121,6 +99148,17 @@ var init_captureStageError = __esm({
|
|
|
99121
99148
|
}
|
|
99122
99149
|
});
|
|
99123
99150
|
|
|
99151
|
+
// ../producer/src/services/render/captureBeyondViewport.ts
|
|
99152
|
+
function resolveVideoCaptureBeyondViewport(videoCount, browserGpuMode) {
|
|
99153
|
+
if (videoCount <= 0) return void 0;
|
|
99154
|
+
return browserGpuMode === "hardware";
|
|
99155
|
+
}
|
|
99156
|
+
var init_captureBeyondViewport = __esm({
|
|
99157
|
+
"../producer/src/services/render/captureBeyondViewport.ts"() {
|
|
99158
|
+
"use strict";
|
|
99159
|
+
}
|
|
99160
|
+
});
|
|
99161
|
+
|
|
99124
99162
|
// ../producer/src/services/render/captureCost.ts
|
|
99125
99163
|
import { join as join34 } from "path";
|
|
99126
99164
|
function estimateCaptureCostMultiplier(compiled) {
|
|
@@ -104896,6 +104934,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
104896
104934
|
}
|
|
104897
104935
|
const framesDir = join49(workDir, "captured-frames");
|
|
104898
104936
|
if (!existsSync43(framesDir)) mkdirSync23(framesDir, { recursive: true });
|
|
104937
|
+
const resolvedBrowserGpuMode = await resolveBrowserGpuMode(cfg.browserGpuMode, {
|
|
104938
|
+
chromePath: resolveHeadlessShellPath(cfg),
|
|
104939
|
+
browserTimeout: cfg.browserTimeout
|
|
104940
|
+
});
|
|
104941
|
+
updateCaptureObservability({ browserGpuMode: resolvedBrowserGpuMode });
|
|
104942
|
+
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(
|
|
104943
|
+
composition.videos.length,
|
|
104944
|
+
resolvedBrowserGpuMode
|
|
104945
|
+
);
|
|
104899
104946
|
const captureOptions = {
|
|
104900
104947
|
width,
|
|
104901
104948
|
height,
|
|
@@ -104904,7 +104951,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
104904
104951
|
quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95,
|
|
104905
104952
|
variables: job.config.variables,
|
|
104906
104953
|
deviceScaleFactor,
|
|
104907
|
-
...
|
|
104954
|
+
...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {}
|
|
104908
104955
|
};
|
|
104909
104956
|
resolvedCaptureBeyondViewport = captureOptions.captureBeyondViewport ?? resolvedCaptureBeyondViewport;
|
|
104910
104957
|
if (resolvedCaptureBeyondViewport !== void 0) {
|
|
@@ -105425,6 +105472,7 @@ var init_renderOrchestrator = __esm({
|
|
|
105425
105472
|
init_hdrMode();
|
|
105426
105473
|
init_perfSummary();
|
|
105427
105474
|
init_captureStageError();
|
|
105475
|
+
init_captureBeyondViewport();
|
|
105428
105476
|
init_captureCost();
|
|
105429
105477
|
init_observability();
|
|
105430
105478
|
init_compileStage();
|
|
@@ -107260,6 +107308,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
107260
107308
|
rebuildExtractedFramesFromPlanDir(planDir, planVideos.extracted)
|
|
107261
107309
|
)
|
|
107262
107310
|
) : null;
|
|
107311
|
+
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(
|
|
107312
|
+
planVideos?.videos.length ?? 0,
|
|
107313
|
+
"software"
|
|
107314
|
+
);
|
|
107263
107315
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes2(4).toString("hex")}`;
|
|
107264
107316
|
mkdirSync27(workDir, { recursive: true });
|
|
107265
107317
|
const framesDir = join57(workDir, "captured-frames");
|
|
@@ -107286,7 +107338,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
107286
107338
|
// declare `data-composition-variables` leave this undefined and the
|
|
107287
107339
|
// engine skips the `evaluateOnNewDocument` injection.
|
|
107288
107340
|
variables: encoder.variables,
|
|
107289
|
-
...
|
|
107341
|
+
...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
|
|
107290
107342
|
// lock the BeginFrame warmup loop to a fixed iteration count so
|
|
107291
107343
|
// `beginFrameTimeTicks` is host-independent. Only chunks ever set this.
|
|
107292
107344
|
lockWarmupTicks: true
|
|
@@ -107454,6 +107506,7 @@ var init_renderChunk = __esm({
|
|
|
107454
107506
|
init_logger();
|
|
107455
107507
|
init_encodeStage();
|
|
107456
107508
|
init_captureStage();
|
|
107509
|
+
init_captureBeyondViewport();
|
|
107457
107510
|
init_freezePlan();
|
|
107458
107511
|
init_planHash();
|
|
107459
107512
|
init_runtimeEnvSnapshot();
|
|
@@ -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};
|