diff --git a/packages/lint/src/rules/media.test.ts b/packages/lint/src/rules/media.test.ts
index 5a38bf02c5..9aaca84eb4 100644
--- a/packages/lint/src/rules/media.test.ts
+++ b/packages/lint/src/rules/media.test.ts
@@ -48,6 +48,25 @@ describe("media rules", () => {
expect(finding?.message).toContain("FROZEN");
});
+ it("flags media that has data-hf-id but no real id", async () => {
+ // Regression: readAttr(tag, "id") used a \b boundary that matched the
+ // trailing `id="…"` inside `data-hf-id="…"`, so media carrying only a
+ // Studio-stamped data-hf-id passed the check and then rendered as a blank
+ // wash (video) / silent (audio). data-hf-id is NOT a render id.
+ const html = `
+
+
+
+
+
+
+`;
+ const result = await lintHyperframeHtml(html);
+ const findings = result.findings.filter((f) => f.code === "media_missing_id");
+ expect(findings).toHaveLength(2);
+ expect(findings.every((f) => f.severity === "error")).toBe(true);
+ });
+
it("does not flag media elements that have id", async () => {
const html = `
diff --git a/packages/lint/src/utils.ts b/packages/lint/src/utils.ts
index 21c24ce6e5..65362db2e9 100644
--- a/packages/lint/src/utils.ts
+++ b/packages/lint/src/utils.ts
@@ -112,7 +112,11 @@ export function findRootTag(source: string): OpenTag | null {
export function readAttr(tagSource: string, attr: string): string | null {
if (!tagSource) return null;
const escaped = attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- const match = tagSource.match(new RegExp(`\\b${escaped}\\s*=\\s*["']([^"']+)["']`, "i"));
+ // `(?`/`