hyperframes 0.8.75 → 0.8.77

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.
@@ -13009,6 +13009,13 @@ function parseGsapScriptAcornForWrite(script) {
13009
13009
  }
13010
13010
  }
13011
13011
  function parseGsapScriptAcorn(script) {
13012
+ const parsed = parseMemo.get(script) ?? parseGsapScriptAcornUncached(script);
13013
+ parseMemo.delete(script);
13014
+ parseMemo.set(script, parsed);
13015
+ if (parseMemo.size > PARSE_MEMO_ENTRIES) parseMemo.delete(parseMemo.keys().next().value);
13016
+ return structuredClone(parsed);
13017
+ }
13018
+ function parseGsapScriptAcornUncached(script) {
13012
13019
  try {
13013
13020
  const ast = parseProgram2(script);
13014
13021
  const scope = collectScopeBindings(ast);
@@ -13156,7 +13163,7 @@ function extractGsapLabels(script) {
13156
13163
  return [];
13157
13164
  }
13158
13165
  }
13159
- var PROPERTY_GROUPS, PROP_TO_GROUP, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION;
13166
+ var PROPERTY_GROUPS, PROP_TO_GROUP, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION, PARSE_MEMO_ENTRIES, parseMemo;
13160
13167
  var init_gsapParserAcorn = __esm({
13161
13168
  "../parsers/dist/gsapParserAcorn.js"() {
13162
13169
  "use strict";
@@ -13236,9 +13243,14 @@ var init_gsapParserAcorn = __esm({
13236
13243
  ]);
13237
13244
  PERCENTAGE_KEY_RE = /^(\d+(?:\.\d+)?)%$/;
13238
13245
  GSAP_DEFAULT_DURATION = 0.5;
13246
+ PARSE_MEMO_ENTRIES = 4;
13247
+ parseMemo = /* @__PURE__ */ new Map();
13239
13248
  }
13240
13249
  });
13241
13250
 
13251
+ // src/renderSetupWorker.ts
13252
+ import { constants as constants2, setPriority } from "os";
13253
+
13242
13254
  // src/browser/manager.ts
13243
13255
  import { execSync, spawnSync } from "child_process";
13244
13256
  import { randomUUID } from "crypto";
@@ -27624,6 +27636,81 @@ function collectDeclaredVariableIds(htmlTagRaw) {
27624
27636
  }
27625
27637
  return declared;
27626
27638
  }
27639
+ function variablesDeclarationFindings(tag, tags) {
27640
+ const raw = readJsonAttr(tag.raw, "data-composition-variables");
27641
+ if (!raw) return [];
27642
+ let parsed;
27643
+ try {
27644
+ parsed = JSON.parse(raw);
27645
+ } catch (err) {
27646
+ const reason = err instanceof Error ? err.message : "unknown";
27647
+ return [
27648
+ {
27649
+ code: "invalid_composition_variables_declaration",
27650
+ severity: "error",
27651
+ message: `data-composition-variables is not valid JSON (${reason}).`,
27652
+ fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
27653
+ snippet: truncateSnippet(tag.raw)
27654
+ }
27655
+ ];
27656
+ }
27657
+ if (!Array.isArray(parsed)) {
27658
+ return [
27659
+ {
27660
+ code: "invalid_composition_variables_declaration",
27661
+ severity: "error",
27662
+ message: "data-composition-variables must be a JSON array of variable declarations.",
27663
+ fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
27664
+ snippet: truncateSnippet(tag.raw)
27665
+ }
27666
+ ];
27667
+ }
27668
+ const findings = [];
27669
+ const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES);
27670
+ const varSrcIds = /* @__PURE__ */ new Set();
27671
+ for (const other of tags) {
27672
+ const bound = readAttr(other.raw, "data-var-src");
27673
+ if (bound) varSrcIds.add(bound);
27674
+ }
27675
+ for (let i = 0; i < parsed.length; i += 1) {
27676
+ const entry = parsed[i];
27677
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
27678
+ findings.push({
27679
+ code: "invalid_composition_variables_declaration",
27680
+ severity: "error",
27681
+ message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
27682
+ snippet: truncateSnippet(tag.raw)
27683
+ });
27684
+ continue;
27685
+ }
27686
+ const e = entry;
27687
+ const missing = [];
27688
+ if (typeof e.id !== "string") missing.push("id");
27689
+ if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
27690
+ if (typeof e.label !== "string") missing.push("label");
27691
+ if (!("default" in e)) missing.push("default");
27692
+ if (missing.length > 0) {
27693
+ findings.push({
27694
+ code: "invalid_composition_variables_declaration",
27695
+ severity: "error",
27696
+ message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
27697
+ snippet: truncateSnippet(tag.raw)
27698
+ });
27699
+ continue;
27700
+ }
27701
+ const id = String(e.id);
27702
+ if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
27703
+ findings.push({
27704
+ code: "unloadable_media_variable_default",
27705
+ severity: "error",
27706
+ message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
27707
+ fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
27708
+ snippet: truncateSnippet(tag.raw)
27709
+ });
27710
+ }
27711
+ }
27712
+ return findings;
27713
+ }
27627
27714
  function collectAllDeclaredVariableIds(tags) {
27628
27715
  const all = /* @__PURE__ */ new Set();
27629
27716
  for (const tag of tags) {
@@ -28099,84 +28186,8 @@ var compositionRules = [
28099
28186
  // on any structural problem. Surface JSON / shape failures so authors
28100
28187
  // catch them at lint time rather than wondering why their `getVariables()`
28101
28188
  // defaults aren't applied.
28102
- // fallow-ignore-next-line complexity
28103
- ({ tags }) => {
28104
- const htmlTag = findHtmlTag(tags);
28105
- if (!htmlTag) return [];
28106
- const raw = readJsonAttr(htmlTag.raw, "data-composition-variables");
28107
- if (!raw) return [];
28108
- let parsed;
28109
- try {
28110
- parsed = JSON.parse(raw);
28111
- } catch (err) {
28112
- const reason = err instanceof Error ? err.message : "unknown";
28113
- return [
28114
- {
28115
- code: "invalid_composition_variables_declaration",
28116
- severity: "error",
28117
- message: `data-composition-variables is not valid JSON (${reason}).`,
28118
- fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
28119
- snippet: truncateSnippet(htmlTag.raw)
28120
- }
28121
- ];
28122
- }
28123
- if (!Array.isArray(parsed)) {
28124
- return [
28125
- {
28126
- code: "invalid_composition_variables_declaration",
28127
- severity: "error",
28128
- message: "data-composition-variables must be a JSON array of variable declarations.",
28129
- fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
28130
- snippet: truncateSnippet(htmlTag.raw)
28131
- }
28132
- ];
28133
- }
28134
- const findings = [];
28135
- const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES);
28136
- const varSrcIds = /* @__PURE__ */ new Set();
28137
- for (const tag of tags) {
28138
- const bound = readAttr(tag.raw, "data-var-src");
28139
- if (bound) varSrcIds.add(bound);
28140
- }
28141
- for (let i = 0; i < parsed.length; i += 1) {
28142
- const entry = parsed[i];
28143
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
28144
- findings.push({
28145
- code: "invalid_composition_variables_declaration",
28146
- severity: "error",
28147
- message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
28148
- snippet: truncateSnippet(htmlTag.raw)
28149
- });
28150
- continue;
28151
- }
28152
- const e = entry;
28153
- const missing = [];
28154
- if (typeof e.id !== "string") missing.push("id");
28155
- if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
28156
- if (typeof e.label !== "string") missing.push("label");
28157
- if (!("default" in e)) missing.push("default");
28158
- if (missing.length > 0) {
28159
- findings.push({
28160
- code: "invalid_composition_variables_declaration",
28161
- severity: "error",
28162
- message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
28163
- snippet: truncateSnippet(htmlTag.raw)
28164
- });
28165
- continue;
28166
- }
28167
- const id = String(e.id);
28168
- if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
28169
- findings.push({
28170
- code: "unloadable_media_variable_default",
28171
- severity: "error",
28172
- message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
28173
- fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
28174
- snippet: truncateSnippet(htmlTag.raw)
28175
- });
28176
- }
28177
- }
28178
- return findings;
28179
- },
28189
+ // Checked on every element that declares: <html>, or the composition root.
28190
+ ({ tags }) => tags.filter((tag) => readJsonAttr(tag.raw, "data-composition-variables")).flatMap((tag) => variablesDeclarationFindings(tag, tags)),
28180
28191
  // html_dir_attribute_breaks_render — valid non-LTR dir values on
28181
28192
  // <html> renders correctly in preview/snapshot but produces a fully
28182
28193
  // blank/black video from render, with no other lint/validate/inspect
@@ -31837,63 +31848,41 @@ function isSnippetFragment(html) {
31837
31848
  }
31838
31849
 
31839
31850
  // src/utils/orphanCleanup.ts
31840
- import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
31851
+ import { execFileSync as execFileSync3 } from "child_process";
31841
31852
  import { readFileSync as readFileSync4 } from "fs";
31842
31853
  function killOrphanedProcesses() {
31843
31854
  if (process.platform === "win32") return 0;
31844
- let killed = 0;
31845
- for (const name of ["chrome-headless-shell", "chrome_headless_shell"]) {
31846
- killed += killOrphansByName(name);
31847
- }
31848
- killed += killOrphansByName("puppeteer_dev_chrome_profile");
31849
- return killed;
31855
+ return killOrphansMatching([
31856
+ "chrome-headless-shell",
31857
+ "chrome_headless_shell",
31858
+ "puppeteer_dev_chrome_profile"
31859
+ ]);
31850
31860
  }
31851
31861
  function killProcessTree(pid, signal = "SIGTERM") {
31852
31862
  void terminateProcessTree(pid, { signal }).catch(() => void 0);
31853
31863
  }
31854
- function killOrphansByName(processName) {
31855
- const uid = getUid();
31856
- const userFlag = uid !== null ? `-u ${uid} ` : "";
31857
- let pids;
31864
+ function killOrphansMatching(markers) {
31865
+ const uid = process.getuid?.();
31866
+ const owner = uid === void 0 ? ["-A"] : ["-U", String(uid)];
31867
+ let table;
31858
31868
  try {
31859
- const raw = execSync2(`pgrep ${userFlag}-f ${processName}`, {
31869
+ table = execFileSync3("ps", [...owner, "-ww", "-o", "pid=,ppid=,args="], {
31860
31870
  encoding: "utf-8",
31861
31871
  timeout: 3e3
31862
- }).trim();
31863
- if (!raw) return 0;
31864
- pids = raw.split("\n").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n) && n > 0);
31872
+ });
31865
31873
  } catch {
31866
31874
  return 0;
31867
31875
  }
31868
31876
  let killed = 0;
31869
- for (const pid of pids) {
31870
- if (!isOrphan(pid)) continue;
31871
- killProcessTree(pid);
31877
+ for (const line of table.split("\n")) {
31878
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
31879
+ if (!match || match[2] !== "1") continue;
31880
+ if (!markers.some((marker) => match[3].includes(marker))) continue;
31881
+ killProcessTree(Number(match[1]));
31872
31882
  killed++;
31873
31883
  }
31874
31884
  return killed;
31875
31885
  }
31876
- var _cachedUid;
31877
- function getUid() {
31878
- if (_cachedUid !== void 0) return _cachedUid;
31879
- try {
31880
- _cachedUid = execSync2("id -u", { encoding: "utf-8", timeout: 1e3 }).trim();
31881
- } catch {
31882
- _cachedUid = null;
31883
- }
31884
- return _cachedUid;
31885
- }
31886
- function isOrphan(pid) {
31887
- try {
31888
- const ppid = execSync2(`ps -p ${pid} -o ppid=`, {
31889
- encoding: "utf-8",
31890
- timeout: 2e3
31891
- }).trim();
31892
- return ppid === "1";
31893
- } catch {
31894
- return false;
31895
- }
31896
- }
31897
31886
 
31898
31887
  // src/renderSetupWorkerLifecycle.ts
31899
31888
  var RENDER_SETUP_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
@@ -31928,7 +31917,12 @@ var result;
31928
31917
  if (mode === "browser") {
31929
31918
  result = await ensureBrowser(input);
31930
31919
  } else if (mode === "lint") {
31931
- result = await lintProject(input.projectDir, input.entryFile);
31920
+ setPriority(constants2.priority.PRIORITY_LOW);
31921
+ result = await lintProject(
31922
+ input.projectDir,
31923
+ input.entryFile,
31924
+ input.host ? { host: input.host } : void 0
31925
+ );
31932
31926
  } else if (mode === "orphan-cleanup") {
31933
31927
  const killed = killOrphanedProcesses();
31934
31928
  result = killed;
@@ -69,12 +69,12 @@ Treat tiny unstyled content, canvas-sized icons, missing hero elements, or timel
69
69
 
70
70
  ## Agent conventions
71
71
 
72
- - **Search the catalog before writing motion by hand.** `npx hyperframes catalog --query "<the beat, in plain English>"`. Search is entirely local: there is no hosted tier, no account, and the query text is never sent anywhere. By default it ranks on vocabulary shared with the item's name, title and description, which misses any phrasing that does not reuse the catalog's own wording. Add `--on-device` to rank by meaning instead (see the offline tier below).
72
+ - **Search the catalog before writing motion by hand.** `npx hyperframes catalog --query "<the beat, in plain English>"`. Search is entirely local: there is no hosted tier, no account, and the query text is never sent anywhere. By default it ranks on vocabulary shared with the item's name, title, description and tags, which misses any phrasing that does not reuse the catalog's own wording. Add `--on-device` to rank by meaning instead (see the offline tier below).
73
73
  - **Query in English even when the video is not.** Both tiers index an English catalog, so a query in another script produces no searchable terms and returns nothing. Describe the move in English; the on-screen copy stays in whatever language the video needs. `No searchable words in query` means exactly this and is not a missing component, so do not report it as a catalog gap.
74
74
  - **Read which tier answered; never infer it from results appearing.** With `--json` the envelope carries `query`, `tier` (`on-device` or `words`), `tier_detail`, `dropped`, `unindexed`, `shown`, `total` and `results`, plus `top_score` when the answering tier produces one and `warnings` when a tier was asked for and could not run, or when a search returned nothing and a better tier is still waiting on someone's consent. A weak result on `words` is expected; the same result on `on-device` is a bug. `top_score` is on-device only and has no threshold behind it: the ranker returns the whole catalog in some order for every query, so read it as evidence rather than as a pass or fail.
75
75
  - **`dropped` and `unindexed` are opposite skews between the registry and the on-device index, and rewording the query fixes neither.** `dropped` counts ranked names this registry cannot install, so the strongest matches are the ones being lost. `unindexed` counts registry moves the index cannot see at all, which no query can ever return. Refreshing the registry is not the answer to either: its manifest carries a 24h TTL and heals itself, while the vectors are a separately published artifact fetched into `~/.hyperframes/catalog/`. Re-running with `--on-device` refetches that index when `unindexed` is above zero, so that is the remedy to hand the user. A pure over-coverage skew (`dropped` above zero while `unindexed` is zero) does not trigger the refetch; clearing `~/.hyperframes/catalog/` is the only way out of that one. Both counts are of names rather than of results, so either can exceed `total`.
76
76
  - **When a search comes back with nothing worth installing, say so.** `npx hyperframes feedback --search-miss "<the query you ran>" --wanted "<the move you needed>" --tier <the tier that answered>`. You do not have to assemble that line: `catalog --query` prints it pre-filled, and every `--json` search envelope carries it as `report_gap` with the query and tier already correct — fill in `--wanted` and send. This is the only path that sends a query anywhere, and it is a separate deliberate command precisely so plain `catalog --query` keeps its promise of sending nothing. **Report on either tier**, whenever the results do not do the thing; do not hold out for the on-device tier, which needs a consented 33 MB download and is therefore off in most agent runs — waiting for it means never reporting at all. The tier rides along in the report, so a vocabulary miss stays distinguishable from a meaning miss without you having to judge which one you hit. What comes back is a list of moves the catalog does not have yet, read directly rather than guessed from install counts, so the phrasing that matters is the effect you wanted, not the item name you imagined. It carries no rating and never lands in the rating metric.
77
- - **Offer the offline tier; never enable it silently.** A one-time ~33 MB download (a quantized ONNX build of `bge-small-en-v1.5` plus its tokenizer, pinned to a fixed revision) and the catalog vectors from the registry, both cached under `~/.hyperframes/`, neither added to the project or any package. Once cached it ranks by meaning with nothing sent. Say the size out loud and let the person decide, then pass `--on-device` (with `-y` to skip the prompt) once they agree. The interactive offer only fires on a TTY. Under `--json` there is no prompt, but a search that found nothing puts the same ask in `warnings`, so read that array and put the decision to the user yourself.
77
+ - **Offer the offline tier; never enable it silently.** A one-time ~33 MB download (a quantized ONNX build of `bge-small-en-v1.5` plus its tokenizer, pinned to a fixed revision) and the catalog vectors from the registry, both cached under `~/.hyperframes/`, neither added to the project or any package. Once cached it ranks by meaning with nothing sent. Say the size out loud and let the person decide, then pass `--on-device` (with `-y` to skip the prompt) once they agree. The interactive offer only fires on a TTY. Under `--json` there is no prompt, but a search that found nothing puts the same ask in `warnings`, so read that array and put the decision to the user yourself. When the person asks what the download is, why this model, or what leaves the machine, point them to https://hyperframes.heygen.com/developers/catalog-search.
78
78
 
79
79
  - Prefer `--json` for agent and CI calls. Server-mode `render`, `preview`, and `play` do not provide ordinary JSON output; `preview --selection --json` and `preview --context --json` are query-mode exceptions.
80
80
  - `doctor --json` always exits zero. Gate on its payload: