@hyperframes/lint 0.7.71 → 0.7.73

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
@@ -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 = [];