@hyperframes/lint 0.7.70 → 0.7.72
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 +59 -25
- package/dist/browser.js.map +1 -1
- package/dist/index.js +59 -25
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -861,6 +861,7 @@ var coreRules = [
|
|
|
861
861
|
];
|
|
862
862
|
|
|
863
863
|
// src/rules/media.ts
|
|
864
|
+
import { validateColorGradingContract } from "@hyperframes/parsers/color-grading-contract";
|
|
864
865
|
function escapeRegExp2(value) {
|
|
865
866
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
866
867
|
}
|
|
@@ -1063,6 +1064,56 @@ var mediaRules = [
|
|
|
1063
1064
|
}
|
|
1064
1065
|
return findings;
|
|
1065
1066
|
},
|
|
1067
|
+
// color_grading_* — grading is a structured media-only contract. Unknown
|
|
1068
|
+
// keys are ignored by the runtime, so catch them before an agent can report
|
|
1069
|
+
// controls that never actually rendered.
|
|
1070
|
+
({ tags }) => {
|
|
1071
|
+
const findings = [];
|
|
1072
|
+
for (const tag of tags) {
|
|
1073
|
+
const raw = readDecodedAttr(tag.raw, "data-color-grading");
|
|
1074
|
+
if (raw === null) continue;
|
|
1075
|
+
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
1076
|
+
const report = (code, message, fixHint) => {
|
|
1077
|
+
findings.push({
|
|
1078
|
+
code,
|
|
1079
|
+
severity: "error",
|
|
1080
|
+
message,
|
|
1081
|
+
elementId,
|
|
1082
|
+
fixHint,
|
|
1083
|
+
snippet: truncateSnippet(tag.raw)
|
|
1084
|
+
});
|
|
1085
|
+
};
|
|
1086
|
+
if (tag.name !== "video" && tag.name !== "img") {
|
|
1087
|
+
report(
|
|
1088
|
+
"color_grading_non_media",
|
|
1089
|
+
`data-color-grading on <${tag.name}> has no effect. The shader runtime only grades real <video> and <img> elements.`,
|
|
1090
|
+
"Move the grading attribute to the real <video> or <img> media element. Do not attach it to a wrapper or CSS background."
|
|
1091
|
+
);
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
const trimmed = raw.trim();
|
|
1095
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) continue;
|
|
1096
|
+
let parsed;
|
|
1097
|
+
try {
|
|
1098
|
+
parsed = JSON.parse(trimmed);
|
|
1099
|
+
} catch {
|
|
1100
|
+
report(
|
|
1101
|
+
"color_grading_invalid_json",
|
|
1102
|
+
"data-color-grading contains malformed JSON and will not render.",
|
|
1103
|
+
'Use valid JSON, for example {"preset":"skin-soft","intensity":0.6,"adjust":{"highlights":-0.08}}.'
|
|
1104
|
+
);
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
for (const issue of validateColorGradingContract(parsed)) {
|
|
1108
|
+
report(
|
|
1109
|
+
"color_grading_invalid_structure",
|
|
1110
|
+
`data-color-grading ${issue.path} ${issue.message}.`,
|
|
1111
|
+
issue.hint ?? "Use the documented media-treatment contract and correct or remove the invalid value."
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return findings;
|
|
1116
|
+
},
|
|
1066
1117
|
// video_missing_muted
|
|
1067
1118
|
({ tags }) => {
|
|
1068
1119
|
const findings = [];
|
|
@@ -1149,27 +1200,6 @@ var mediaRules = [
|
|
|
1149
1200
|
}
|
|
1150
1201
|
return findings;
|
|
1151
1202
|
},
|
|
1152
|
-
// media_in_subcomposition — <video>/<audio> only render as a DIRECT child of the host
|
|
1153
|
-
// root (index.html). Inside a sub-composition <template> the runtime never seeks/decodes
|
|
1154
|
-
// them, so they render BLANK/black in preview and renders — and the other lint/validate
|
|
1155
|
-
// passes otherwise miss it (only a per-frame snapshot reveals the blank panel).
|
|
1156
|
-
({ tags, options }) => {
|
|
1157
|
-
const findings = [];
|
|
1158
|
-
if (!options.isSubComposition) return findings;
|
|
1159
|
-
for (const tag of tags) {
|
|
1160
|
-
if (tag.name !== "video" && tag.name !== "audio") continue;
|
|
1161
|
-
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
1162
|
-
findings.push({
|
|
1163
|
-
code: "media_in_subcomposition",
|
|
1164
|
-
severity: "error",
|
|
1165
|
-
message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> is inside a sub-composition. The runtime only drives media that is a DIRECT child of the host root (index.html); media inside a sub-comp <template> is never seeked/decoded and renders BLANK/black in preview and renders.`,
|
|
1166
|
-
elementId,
|
|
1167
|
-
fixHint: "Move the media OUT of the sub-composition: place the <video>/<audio> as a direct child of #root in index.html, positioned over the scene, and drive any per-scene motion on the MAIN timeline at global time (a sub-comp timeline cannot reach host elements). See composition-patterns.md archetype B.",
|
|
1168
|
-
snippet: truncateSnippet(tag.raw)
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
return findings;
|
|
1172
|
-
},
|
|
1173
1203
|
// self_closing_media_tag
|
|
1174
1204
|
({ source }) => {
|
|
1175
1205
|
const findings = [];
|
|
@@ -2426,8 +2456,12 @@ ${right.raw}`)
|
|
|
2426
2456
|
return findings;
|
|
2427
2457
|
},
|
|
2428
2458
|
// gsap_infinite_repeat
|
|
2429
|
-
({ scripts }) => {
|
|
2459
|
+
({ scripts, rootTag }) => {
|
|
2430
2460
|
const findings = [];
|
|
2461
|
+
const declaredDuration = Number.parseFloat(
|
|
2462
|
+
rootTag ? readAttr(rootTag.raw, "data-duration") ?? "" : ""
|
|
2463
|
+
);
|
|
2464
|
+
const hasFiniteCompositionWindow = Number.isFinite(declaredDuration) && declaredDuration > 0;
|
|
2431
2465
|
const pattern = /repeat\s*:\s*-1(?!\d)/g;
|
|
2432
2466
|
for (const { snippet } of scanScriptsForRegexMatches(scripts, pattern, {
|
|
2433
2467
|
stripComments: true,
|
|
@@ -2436,9 +2470,9 @@ ${right.raw}`)
|
|
|
2436
2470
|
})) {
|
|
2437
2471
|
findings.push({
|
|
2438
2472
|
code: "gsap_infinite_repeat",
|
|
2439
|
-
severity: "error",
|
|
2440
|
-
message:
|
|
2441
|
-
fixHint: "
|
|
2473
|
+
severity: hasFiniteCompositionWindow ? "warning" : "error",
|
|
2474
|
+
message: hasFiniteCompositionWindow ? `GSAP tween uses \`repeat: -1\` (infinite), but the composition declares a finite ${declaredDuration}s window. HyperFrames clips deterministic seeking and export to that explicit duration.` : "GSAP tween uses `repeat: -1` (infinite) without a finite composition `data-duration`. The timeline can report an unbounded duration and make render planning fail.",
|
|
2475
|
+
fixHint: hasFiniteCompositionWindow ? "Keep the explicit finite composition `data-duration`. Use a finite repeat count only when the loop itself must end before the composition does." : "Add a finite composition `data-duration`, or replace `repeat: -1` with `repeat: Math.max(0, Math.floor(totalDuration / singleCycleDuration) - 1)`.",
|
|
2442
2476
|
snippet: truncateSnippet(snippet)
|
|
2443
2477
|
});
|
|
2444
2478
|
}
|