@hyperframes/lint 0.8.166 → 0.8.168

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/browser.js CHANGED
@@ -2740,8 +2740,9 @@ function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
2740
2740
  const hits = [];
2741
2741
  for (const style of styles) {
2742
2742
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2743
+ const content = stripCssComments(style.content);
2743
2744
  let match;
2744
- while ((match = ruleWithBody.exec(stripCssComments(style.content))) !== null) {
2745
+ while ((match = ruleWithBody.exec(content)) !== null) {
2745
2746
  const hit = canvasZoomInRule(match[1] ?? "", match[2] ?? "", rootId, rootClasses);
2746
2747
  if (hit) hits.push(hit);
2747
2748
  }
@@ -3387,37 +3388,6 @@ var compositionRules = [
3387
3388
  // defaults aren't applied.
3388
3389
  // Checked on every element that declares: <html>, or the composition root.
3389
3390
  ({ tags }) => tags.filter((tag) => readJsonAttr(tag.raw, "data-composition-variables")).flatMap((tag) => variablesDeclarationFindings(tag, tags)),
3390
- // html_dir_attribute_breaks_render — valid non-LTR dir values on
3391
- // <html> renders correctly in preview/snapshot but produces a fully
3392
- // blank/black video from render, with no other lint/validate/inspect
3393
- // check catching it (output file size, far smaller than expected, is the
3394
- // only tell). Confirmed independently by two separate reports, both
3395
- // diagnosing the same exact trigger and the same fix: drop dir from
3396
- // <html>, keep lang, and scope `direction: rtl` to individual
3397
- // text-containing elements via CSS instead (text still bidi-shapes
3398
- // correctly). Advisory-only — this does not attempt to fix the render
3399
- // pipeline's own root cause (suspected to be a capture step that clips a
3400
- // fixed top-left-origin screenshot region, which RTL layout can shift the
3401
- // actual content away from), only surfaces the already-confirmed footgun
3402
- // before someone hits it blind.
3403
- ({ tags }) => {
3404
- const htmlTag = findHtmlTag(tags);
3405
- if (!htmlTag) return [];
3406
- const dir = readAttr(htmlTag.raw, "dir");
3407
- if (!dir) return [];
3408
- const normalizedDir = dir.toLowerCase();
3409
- if (normalizedDir !== "rtl" && normalizedDir !== "auto") return [];
3410
- const scopedDirection = normalizedDir === "auto" ? 'dir="auto"' : `direction: ${normalizedDir}`;
3411
- return [
3412
- {
3413
- code: "html_dir_attribute_breaks_render",
3414
- severity: "error",
3415
- message: `<html dir="${dir}"> renders correctly in preview/snapshot but produces a fully blank/black video from render. This is a confirmed, silent failure.`,
3416
- fixHint: `Remove dir="${dir}" from <html>. Keep lang, and scope ${scopedDirection} to individual text-containing elements instead. Text still shapes correctly via the browser's own bidi algorithm.`,
3417
- snippet: truncateSnippet(htmlTag.raw)
3418
- }
3419
- ];
3420
- },
3421
3391
  // subcomposition_blanks_before_host
3422
3392
  // Warns when a full-bleed sub-composition slot ends before the host composition
3423
3393
  // does, leaving the slot blank for the remainder (issue #1540). Scoped narrowly to
@@ -6243,7 +6213,8 @@ var NODE_ATTRS = [
6243
6213
  "data-track-index",
6244
6214
  "data-track-kind",
6245
6215
  "data-composition-id",
6246
- "data-composition-src"
6216
+ "data-composition-src",
6217
+ "data-hf-media-start-basis"
6247
6218
  ];
6248
6219
  function toNode(open) {
6249
6220
  const attrs = {};
@@ -6351,6 +6322,33 @@ function clipsPastRootFindings(root, rows) {
6351
6322
  ];
6352
6323
  });
6353
6324
  }
6325
+ function followsParentVisibility(clip) {
6326
+ return clip.tag !== "audio" && clip.attrs["data-hf-media-start-basis"] !== "global";
6327
+ }
6328
+ function isTimed(timing) {
6329
+ return timing.start !== null && timing.end !== null;
6330
+ }
6331
+ function neverVisibleNestedFindings(parent, severity) {
6332
+ const window = readClipTiming2({ getAttribute: (name) => parent.attrs[name] ?? null });
6333
+ return topLevelElements(parent).flatMap((clip) => {
6334
+ const inner = isSubCompositionHost(clip) ? [] : neverVisibleNestedFindings(clip, severity);
6335
+ const own = readClipTiming2({ getAttribute: (name) => clip.attrs[name] ?? null });
6336
+ if (!followsParentVisibility(clip) || !isTimed(window) || !isTimed(own)) return inner;
6337
+ if (own.end > window.start && own.start < window.end) return inner;
6338
+ const shows = `${hundredths(window.start)}s to ${hundredths(window.end)}s`;
6339
+ return [
6340
+ {
6341
+ code: "nested_clip_never_visible",
6342
+ severity,
6343
+ message: `${describe(clip)} plays from ${hundredths(own.start)}s to ${hundredths(own.end)}s, but it sits inside ${describe(parent)}, which only shows from ${shows}, so it never appears. A nested data-start counts from the composition's start, not from ${describe(parent)}.`,
6344
+ elementId: clip.attrs.id,
6345
+ fixHint: `Give ${describe(clip)} a data-start between ${shows} in composition time, or move it out of ${describe(parent)}.`,
6346
+ snippet: truncateSnippet(clip.open.raw)
6347
+ },
6348
+ ...inner
6349
+ ];
6350
+ });
6351
+ }
6354
6352
  var structureRules = [
6355
6353
  (ctx) => {
6356
6354
  if (ctx.options.isSubComposition) return [];
@@ -6364,6 +6362,12 @@ var structureRules = [
6364
6362
  ...captionFindings(rows, severity),
6365
6363
  ...clipsPastRootFindings(root, rows)
6366
6364
  ];
6365
+ },
6366
+ (ctx) => {
6367
+ const root = buildTree(ctx);
6368
+ if (!root) return [];
6369
+ const severity = ctx.options.host === "studio" ? "error" : "warning";
6370
+ return topLevelElements(root).filter((row) => !isSubCompositionHost(row)).flatMap((row) => neverVisibleNestedFindings(row, severity));
6367
6371
  }
6368
6372
  ];
6369
6373
 
package/dist/index.js CHANGED
@@ -2746,8 +2746,9 @@ function stylesheetCanvasZoomHits(styles, rootId, rootClasses) {
2746
2746
  const hits = [];
2747
2747
  for (const style of styles) {
2748
2748
  const ruleWithBody = /([^{}]+)\{([^{}]*)\}/g;
2749
+ const content = stripCssComments(style.content);
2749
2750
  let match;
2750
- while ((match = ruleWithBody.exec(stripCssComments(style.content))) !== null) {
2751
+ while ((match = ruleWithBody.exec(content)) !== null) {
2751
2752
  const hit = canvasZoomInRule(match[1] ?? "", match[2] ?? "", rootId, rootClasses);
2752
2753
  if (hit) hits.push(hit);
2753
2754
  }
@@ -3393,37 +3394,6 @@ var compositionRules = [
3393
3394
  // defaults aren't applied.
3394
3395
  // Checked on every element that declares: <html>, or the composition root.
3395
3396
  ({ tags }) => tags.filter((tag) => readJsonAttr(tag.raw, "data-composition-variables")).flatMap((tag) => variablesDeclarationFindings(tag, tags)),
3396
- // html_dir_attribute_breaks_render — valid non-LTR dir values on
3397
- // <html> renders correctly in preview/snapshot but produces a fully
3398
- // blank/black video from render, with no other lint/validate/inspect
3399
- // check catching it (output file size, far smaller than expected, is the
3400
- // only tell). Confirmed independently by two separate reports, both
3401
- // diagnosing the same exact trigger and the same fix: drop dir from
3402
- // <html>, keep lang, and scope `direction: rtl` to individual
3403
- // text-containing elements via CSS instead (text still bidi-shapes
3404
- // correctly). Advisory-only — this does not attempt to fix the render
3405
- // pipeline's own root cause (suspected to be a capture step that clips a
3406
- // fixed top-left-origin screenshot region, which RTL layout can shift the
3407
- // actual content away from), only surfaces the already-confirmed footgun
3408
- // before someone hits it blind.
3409
- ({ tags }) => {
3410
- const htmlTag = findHtmlTag(tags);
3411
- if (!htmlTag) return [];
3412
- const dir = readAttr(htmlTag.raw, "dir");
3413
- if (!dir) return [];
3414
- const normalizedDir = dir.toLowerCase();
3415
- if (normalizedDir !== "rtl" && normalizedDir !== "auto") return [];
3416
- const scopedDirection = normalizedDir === "auto" ? 'dir="auto"' : `direction: ${normalizedDir}`;
3417
- return [
3418
- {
3419
- code: "html_dir_attribute_breaks_render",
3420
- severity: "error",
3421
- message: `<html dir="${dir}"> renders correctly in preview/snapshot but produces a fully blank/black video from render. This is a confirmed, silent failure.`,
3422
- fixHint: `Remove dir="${dir}" from <html>. Keep lang, and scope ${scopedDirection} to individual text-containing elements instead. Text still shapes correctly via the browser's own bidi algorithm.`,
3423
- snippet: truncateSnippet(htmlTag.raw)
3424
- }
3425
- ];
3426
- },
3427
3397
  // subcomposition_blanks_before_host
3428
3398
  // Warns when a full-bleed sub-composition slot ends before the host composition
3429
3399
  // does, leaving the slot blank for the remainder (issue #1540). Scoped narrowly to
@@ -6249,7 +6219,8 @@ var NODE_ATTRS = [
6249
6219
  "data-track-index",
6250
6220
  "data-track-kind",
6251
6221
  "data-composition-id",
6252
- "data-composition-src"
6222
+ "data-composition-src",
6223
+ "data-hf-media-start-basis"
6253
6224
  ];
6254
6225
  function toNode(open) {
6255
6226
  const attrs = {};
@@ -6357,6 +6328,33 @@ function clipsPastRootFindings(root, rows) {
6357
6328
  ];
6358
6329
  });
6359
6330
  }
6331
+ function followsParentVisibility(clip) {
6332
+ return clip.tag !== "audio" && clip.attrs["data-hf-media-start-basis"] !== "global";
6333
+ }
6334
+ function isTimed(timing) {
6335
+ return timing.start !== null && timing.end !== null;
6336
+ }
6337
+ function neverVisibleNestedFindings(parent, severity) {
6338
+ const window = readClipTiming2({ getAttribute: (name) => parent.attrs[name] ?? null });
6339
+ return topLevelElements(parent).flatMap((clip) => {
6340
+ const inner = isSubCompositionHost(clip) ? [] : neverVisibleNestedFindings(clip, severity);
6341
+ const own = readClipTiming2({ getAttribute: (name) => clip.attrs[name] ?? null });
6342
+ if (!followsParentVisibility(clip) || !isTimed(window) || !isTimed(own)) return inner;
6343
+ if (own.end > window.start && own.start < window.end) return inner;
6344
+ const shows = `${hundredths(window.start)}s to ${hundredths(window.end)}s`;
6345
+ return [
6346
+ {
6347
+ code: "nested_clip_never_visible",
6348
+ severity,
6349
+ message: `${describe(clip)} plays from ${hundredths(own.start)}s to ${hundredths(own.end)}s, but it sits inside ${describe(parent)}, which only shows from ${shows}, so it never appears. A nested data-start counts from the composition's start, not from ${describe(parent)}.`,
6350
+ elementId: clip.attrs.id,
6351
+ fixHint: `Give ${describe(clip)} a data-start between ${shows} in composition time, or move it out of ${describe(parent)}.`,
6352
+ snippet: truncateSnippet(clip.open.raw)
6353
+ },
6354
+ ...inner
6355
+ ];
6356
+ });
6357
+ }
6360
6358
  var structureRules = [
6361
6359
  (ctx) => {
6362
6360
  if (ctx.options.isSubComposition) return [];
@@ -6370,6 +6368,12 @@ var structureRules = [
6370
6368
  ...captionFindings(rows, severity),
6371
6369
  ...clipsPastRootFindings(root, rows)
6372
6370
  ];
6371
+ },
6372
+ (ctx) => {
6373
+ const root = buildTree(ctx);
6374
+ if (!root) return [];
6375
+ const severity = ctx.options.host === "studio" ? "error" : "warning";
6376
+ return topLevelElements(root).filter((row) => !isSubCompositionHost(row)).flatMap((row) => neverVisibleNestedFindings(row, severity));
6373
6377
  }
6374
6378
  ];
6375
6379
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.166",
3
+ "version": "0.8.168",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -35,7 +35,7 @@
35
35
  "linkedom": "^0.18.12",
36
36
  "postcss": "^8.5.8",
37
37
  "postcss-selector-parser": "^7.1.4",
38
- "@hyperframes/parsers": "0.8.166"
38
+ "@hyperframes/parsers": "0.8.168"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",