@hyperframes/lint 0.8.186 → 0.8.188

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
@@ -2023,6 +2023,53 @@ function findRuntimeMediaSrcMutationFindings(ctx) {
2023
2023
  }
2024
2024
  return findings;
2025
2025
  }
2026
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
2027
+ "area",
2028
+ "base",
2029
+ "br",
2030
+ "col",
2031
+ "embed",
2032
+ "hr",
2033
+ "img",
2034
+ "input",
2035
+ "link",
2036
+ "meta",
2037
+ "source",
2038
+ "track",
2039
+ "wbr"
2040
+ ]);
2041
+ var closeTag = (name) => new RegExp(`</${name}>`, "gi");
2042
+ function innermostTimedWrapper(source, tags, media) {
2043
+ let found = null;
2044
+ for (const parent of tags) {
2045
+ if (parent.index >= media.index) break;
2046
+ if (parent.name === "video" || parent.name === "audio" || VOID_ELEMENTS.has(parent.name))
2047
+ continue;
2048
+ if (readDecodedAttr(parent.raw, "data-composition-id")) continue;
2049
+ const start = readAttr(parent.raw, "data-start");
2050
+ if (!start) continue;
2051
+ const events = [
2052
+ ...tags.filter((t) => t.name === parent.name && t.index > parent.index && t.index < media.index).map((t) => [t.index, 1]),
2053
+ ...[...source.substring(parent.index, media.index).matchAll(closeTag(parent.name))].map(
2054
+ (m) => [parent.index + m.index, -1]
2055
+ )
2056
+ ];
2057
+ let depth = 1;
2058
+ for (const [, delta] of events.sort((x, y) => x[0] - y[0])) {
2059
+ depth += delta;
2060
+ if (depth === 0) break;
2061
+ }
2062
+ if (depth > 0) {
2063
+ found = {
2064
+ name: parent.name,
2065
+ id: readAttr(parent.raw, "id") || void 0,
2066
+ start,
2067
+ duration: readAttr(parent.raw, "data-duration")
2068
+ };
2069
+ }
2070
+ }
2071
+ return found;
2072
+ }
2026
2073
  var mediaRules = [
2027
2074
  findNestedMediaStartBasisFindings,
2028
2075
  findSpeedRampOnNonMediaFindings,
@@ -2159,54 +2206,19 @@ var mediaRules = [
2159
2206
  // video_nested_in_timed_element
2160
2207
  ({ source, tags }) => {
2161
2208
  const findings = [];
2162
- const voidElements = /* @__PURE__ */ new Set([
2163
- "area",
2164
- "base",
2165
- "br",
2166
- "col",
2167
- "embed",
2168
- "hr",
2169
- "img",
2170
- "input",
2171
- "link",
2172
- "meta",
2173
- "source",
2174
- "track",
2175
- "wbr"
2176
- ]);
2177
- const timedTagPositions = [];
2178
- for (const tag of tags) {
2179
- if (tag.name === "video" || tag.name === "audio") continue;
2180
- if (voidElements.has(tag.name)) continue;
2181
- if (readDecodedAttr(tag.raw, "data-composition-id")) continue;
2182
- if (readAttr(tag.raw, "data-start")) {
2183
- timedTagPositions.push({
2184
- name: tag.name,
2185
- start: tag.index,
2186
- id: readAttr(tag.raw, "id") || void 0
2187
- });
2188
- }
2189
- }
2190
2209
  for (const tag of tags) {
2191
2210
  if (tag.name !== "video") continue;
2192
2211
  if (!readAttr(tag.raw, "data-start")) continue;
2193
- for (const parent of timedTagPositions) {
2194
- if (parent.start < tag.index) {
2195
- const parentClosePattern = new RegExp(`</${parent.name}>`, "gi");
2196
- const between = source.substring(parent.start, tag.index);
2197
- if (!parentClosePattern.test(between)) {
2198
- findings.push({
2199
- code: "video_nested_in_timed_element",
2200
- severity: "error",
2201
- message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
2202
- elementId: readAttr(tag.raw, "id") || void 0,
2203
- fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
2204
- snippet: truncateSnippet(tag.raw)
2205
- });
2206
- break;
2207
- }
2208
- }
2209
- }
2212
+ const parent = innermostTimedWrapper(source, tags, tag);
2213
+ if (!parent) continue;
2214
+ findings.push({
2215
+ code: "video_nested_in_timed_element",
2216
+ severity: "error",
2217
+ message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
2218
+ elementId: readAttr(tag.raw, "id") || void 0,
2219
+ fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
2220
+ snippet: truncateSnippet(tag.raw)
2221
+ });
2210
2222
  }
2211
2223
  return findings;
2212
2224
  },
@@ -2274,7 +2286,7 @@ var mediaRules = [
2274
2286
  return findings;
2275
2287
  },
2276
2288
  // media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
2277
- ({ tags }) => {
2289
+ ({ source, tags }) => {
2278
2290
  const findings = [];
2279
2291
  for (const tag of tags) {
2280
2292
  if (tag.name !== "video" && tag.name !== "audio") continue;
@@ -2282,12 +2294,13 @@ var mediaRules = [
2282
2294
  const hasId = readAttr(tag.raw, "id");
2283
2295
  const hasSrc = mediaHasResolvableSrc(tag, tags);
2284
2296
  if (hasSrc && !hasDataStart) {
2297
+ const wrapper = tag.name === "video" ? innermostTimedWrapper(source, tags, tag) : null;
2285
2298
  findings.push({
2286
2299
  code: "media_missing_data_start",
2287
2300
  severity: "error",
2288
2301
  message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
2289
2302
  elementId: hasId || void 0,
2290
- fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
2303
+ fixHint: wrapper ? `This <${tag.name}> sits inside the timed <${wrapper.name}${wrapper.id ? ` id="${wrapper.id}"` : ""}> (data-start="${wrapper.start}"), and the wrapper's data-start would then clash. Move the <${tag.name}> out as a direct child of the stage and give it that wrapper's data-start="${wrapper.start}"${wrapper.duration ? ` and data-duration="${wrapper.duration}"` : ""}.` : `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
2291
2304
  snippet: truncateSnippet(tag.raw)
2292
2305
  });
2293
2306
  }
package/dist/index.js CHANGED
@@ -2029,6 +2029,53 @@ function findRuntimeMediaSrcMutationFindings(ctx) {
2029
2029
  }
2030
2030
  return findings;
2031
2031
  }
2032
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
2033
+ "area",
2034
+ "base",
2035
+ "br",
2036
+ "col",
2037
+ "embed",
2038
+ "hr",
2039
+ "img",
2040
+ "input",
2041
+ "link",
2042
+ "meta",
2043
+ "source",
2044
+ "track",
2045
+ "wbr"
2046
+ ]);
2047
+ var closeTag = (name) => new RegExp(`</${name}>`, "gi");
2048
+ function innermostTimedWrapper(source, tags, media) {
2049
+ let found = null;
2050
+ for (const parent of tags) {
2051
+ if (parent.index >= media.index) break;
2052
+ if (parent.name === "video" || parent.name === "audio" || VOID_ELEMENTS.has(parent.name))
2053
+ continue;
2054
+ if (readDecodedAttr(parent.raw, "data-composition-id")) continue;
2055
+ const start = readAttr(parent.raw, "data-start");
2056
+ if (!start) continue;
2057
+ const events = [
2058
+ ...tags.filter((t) => t.name === parent.name && t.index > parent.index && t.index < media.index).map((t) => [t.index, 1]),
2059
+ ...[...source.substring(parent.index, media.index).matchAll(closeTag(parent.name))].map(
2060
+ (m) => [parent.index + m.index, -1]
2061
+ )
2062
+ ];
2063
+ let depth = 1;
2064
+ for (const [, delta] of events.sort((x, y) => x[0] - y[0])) {
2065
+ depth += delta;
2066
+ if (depth === 0) break;
2067
+ }
2068
+ if (depth > 0) {
2069
+ found = {
2070
+ name: parent.name,
2071
+ id: readAttr(parent.raw, "id") || void 0,
2072
+ start,
2073
+ duration: readAttr(parent.raw, "data-duration")
2074
+ };
2075
+ }
2076
+ }
2077
+ return found;
2078
+ }
2032
2079
  var mediaRules = [
2033
2080
  findNestedMediaStartBasisFindings,
2034
2081
  findSpeedRampOnNonMediaFindings,
@@ -2165,54 +2212,19 @@ var mediaRules = [
2165
2212
  // video_nested_in_timed_element
2166
2213
  ({ source, tags }) => {
2167
2214
  const findings = [];
2168
- const voidElements = /* @__PURE__ */ new Set([
2169
- "area",
2170
- "base",
2171
- "br",
2172
- "col",
2173
- "embed",
2174
- "hr",
2175
- "img",
2176
- "input",
2177
- "link",
2178
- "meta",
2179
- "source",
2180
- "track",
2181
- "wbr"
2182
- ]);
2183
- const timedTagPositions = [];
2184
- for (const tag of tags) {
2185
- if (tag.name === "video" || tag.name === "audio") continue;
2186
- if (voidElements.has(tag.name)) continue;
2187
- if (readDecodedAttr(tag.raw, "data-composition-id")) continue;
2188
- if (readAttr(tag.raw, "data-start")) {
2189
- timedTagPositions.push({
2190
- name: tag.name,
2191
- start: tag.index,
2192
- id: readAttr(tag.raw, "id") || void 0
2193
- });
2194
- }
2195
- }
2196
2215
  for (const tag of tags) {
2197
2216
  if (tag.name !== "video") continue;
2198
2217
  if (!readAttr(tag.raw, "data-start")) continue;
2199
- for (const parent of timedTagPositions) {
2200
- if (parent.start < tag.index) {
2201
- const parentClosePattern = new RegExp(`</${parent.name}>`, "gi");
2202
- const between = source.substring(parent.start, tag.index);
2203
- if (!parentClosePattern.test(between)) {
2204
- findings.push({
2205
- code: "video_nested_in_timed_element",
2206
- severity: "error",
2207
- message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
2208
- elementId: readAttr(tag.raw, "id") || void 0,
2209
- fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
2210
- snippet: truncateSnippet(tag.raw)
2211
- });
2212
- break;
2213
- }
2214
- }
2215
- }
2218
+ const parent = innermostTimedWrapper(source, tags, tag);
2219
+ if (!parent) continue;
2220
+ findings.push({
2221
+ code: "video_nested_in_timed_element",
2222
+ severity: "error",
2223
+ message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
2224
+ elementId: readAttr(tag.raw, "id") || void 0,
2225
+ fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
2226
+ snippet: truncateSnippet(tag.raw)
2227
+ });
2216
2228
  }
2217
2229
  return findings;
2218
2230
  },
@@ -2280,7 +2292,7 @@ var mediaRules = [
2280
2292
  return findings;
2281
2293
  },
2282
2294
  // media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
2283
- ({ tags }) => {
2295
+ ({ source, tags }) => {
2284
2296
  const findings = [];
2285
2297
  for (const tag of tags) {
2286
2298
  if (tag.name !== "video" && tag.name !== "audio") continue;
@@ -2288,12 +2300,13 @@ var mediaRules = [
2288
2300
  const hasId = readAttr(tag.raw, "id");
2289
2301
  const hasSrc = mediaHasResolvableSrc(tag, tags);
2290
2302
  if (hasSrc && !hasDataStart) {
2303
+ const wrapper = tag.name === "video" ? innermostTimedWrapper(source, tags, tag) : null;
2291
2304
  findings.push({
2292
2305
  code: "media_missing_data_start",
2293
2306
  severity: "error",
2294
2307
  message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
2295
2308
  elementId: hasId || void 0,
2296
- fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
2309
+ fixHint: wrapper ? `This <${tag.name}> sits inside the timed <${wrapper.name}${wrapper.id ? ` id="${wrapper.id}"` : ""}> (data-start="${wrapper.start}"), and the wrapper's data-start would then clash. Move the <${tag.name}> out as a direct child of the stage and give it that wrapper's data-start="${wrapper.start}"${wrapper.duration ? ` and data-duration="${wrapper.duration}"` : ""}.` : `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
2297
2310
  snippet: truncateSnippet(tag.raw)
2298
2311
  });
2299
2312
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.186",
3
+ "version": "0.8.188",
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.186"
38
+ "@hyperframes/parsers": "0.8.188"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",