From f3800f3579ae210c9229135a80f2a0b2f6e31a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 15 Jul 2026 02:05:39 -0400 Subject: [PATCH] fix(lint): exempt caption cues from track density (#2461) * fix(lint): exempt caption cues from track density * style(media-use): satisfy resolver formatting gate --- packages/lint/src/rules/composition.test.ts | 16 ++++++++++++++++ packages/lint/src/rules/composition.ts | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/lint/src/rules/composition.test.ts b/packages/lint/src/rules/composition.test.ts index 4ab12647b..2a44596ec 100644 --- a/packages/lint/src/rules/composition.test.ts +++ b/packages/lint/src/rules/composition.test.ts @@ -173,6 +173,22 @@ describe("composition rules", () => { expect(finding).toBeUndefined(); }); + it("does not count transcript caption cues as dense track elements", async () => { + const html = ` + +
+
+
+
+
+
+`; + + const result = await lintHyperframeHtml(html, { filePath: "/project/index.html" }); + const finding = result.findings.find((f) => f.code === "timeline_track_too_dense"); + expect(finding).toBeUndefined(); + }); + it("does not count root composition or mounted sub-compositions as dense elements", async () => { const html = ` diff --git a/packages/lint/src/rules/composition.ts b/packages/lint/src/rules/composition.ts index eee2ae9a8..a2ffa4f69 100644 --- a/packages/lint/src/rules/composition.ts +++ b/packages/lint/src/rules/composition.ts @@ -15,6 +15,8 @@ import { COMPOSITION_VARIABLE_TYPES } from "@hyperframes/parsers/composition"; const MAX_COMPOSITION_LINES = 300; const MAX_TIMED_ELEMENTS_PER_TRACK = 3; const TRACK_DENSITY_EXEMPT_TAGS = new Set(["audio", "script", "style", "video"]); +const CAPTION_CUE_TOKEN = + /^(?:caption(?:[-_](?:group|word|line|block|cue|text))?|subtitle(?:[-_](?:group|line|cue|text))?|cg-.+)$/i; // `parseFloat("0.1") + parseFloat("0.2") = 0.30000000000000004`. Sub-second // authored adjacencies survive parse + add as a value a few ulps above the @@ -36,6 +38,15 @@ function countStructuralLines(source: string): number { return countPhysicalLines(source.replace(/]*>[\s\S]*?<\/style>/gi, "")); } +function isCaptionCue(tag: OpenTag): boolean { + const classTokens = (readAttr(tag.raw, "class") || "").split(/\s+/).filter(Boolean); + const id = readAttr(tag.raw, "id"); + return ( + classTokens.some((token) => CAPTION_CUE_TOKEN.test(token)) || + Boolean(id && CAPTION_CUE_TOKEN.test(id)) + ); +} + export function isRegistrySourceFile(filePath?: string): boolean { if (!filePath) return false; @@ -294,6 +305,7 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding const trackCounts = new Map(); for (const tag of tags) { if (TRACK_DENSITY_EXEMPT_TAGS.has(tag.name)) continue; + if (isCaptionCue(tag)) continue; if (isCompositionRootOrMount(tag.raw)) continue; if (!readAttr(tag.raw, "data-start")) continue;