Files
hyperframes/packages/cli/src/registry/registryComponents.test.ts
T
Miguel Ángel e7f9918d21 fix(lint): drop false media_in_subcomposition rule (#2765)
The media_in_subcomposition rule blanket-errored every <video>/<audio>
inside a sub-composition, claiming nested media is "never seeked/decoded
and renders blank/black". This is false: the runtime discovers media with
a flat document.querySelectorAll("video, audio"), resolves each element's
host composition via closest("[data-composition-id]"), and rebases its
local data-start by the accumulated absolute start of every ancestor
composition (packages/core/src/runtime/{media,startResolver}.ts). Media
seeks and decodes at any nesting depth, verified end to end through the
producer render path.

- Remove the rule and flip its test to assert nested media is NOT flagged.
- Drop the now-dead media_in_subcomposition clause from the registry
  components test.
- Drop the equivalent pre-render guard from the faceless-explainer and
  pr-to-video assemble scripts.
- Correct the reference docs (hyperframes-core SKILL, data-attributes,
  variables-and-media, composition-patterns; hyperframes-cli
  lint-validate-inspect): media works at any depth. Preserve the one real
  constraint, that a sub-comp timeline cannot reach host-root elements, so
  host-root media motion is authored on the main timeline.
2026-07-24 23:12:48 +02:00

70 lines
2.3 KiB
TypeScript

import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { lintHyperframeHtml } from "@hyperframes/lint";
import { describe, expect, it } from "vitest";
const componentsDir = resolve(
dirname(fileURLToPath(import.meta.url)),
"../../../../registry/components",
);
interface RegistryManifest {
files: Array<{ path: string; type: string }>;
}
async function invalidInstallableMedia(entryName: string): Promise<string[]> {
const itemDir = join(componentsDir, entryName);
const manifest = JSON.parse(
readFileSync(join(itemDir, "registry-item.json"), "utf8"),
) as RegistryManifest;
const invalidMedia: string[] = [];
for (const file of manifest.files) {
if (file.type !== "hyperframes:snippet" || !file.path.endsWith(".html")) continue;
const result = await lintHyperframeHtml(readFileSync(join(itemDir, file.path), "utf8"), {
isSubComposition: true,
});
for (const finding of result.findings) {
if (finding.code !== "media_missing_src") continue;
invalidMedia.push(`${entryName}/${file.path}: ${finding.code}`);
}
}
return invalidMedia;
}
async function invalidDemoMedia(entryName: string): Promise<string[]> {
const demoPath = join(componentsDir, entryName, "demo.html");
if (!existsSync(demoPath)) return [];
const result = await lintHyperframeHtml(readFileSync(demoPath, "utf8"));
return result.findings
.filter((finding) => finding.code === "media_missing_src")
.map((finding) => `${entryName}/demo.html: ${finding.code}`);
}
describe("registry components", () => {
it("ships installable snippets without invalid nested media", async () => {
const invalidMedia: string[] = [];
for (const entry of readdirSync(componentsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
invalidMedia.push(...(await invalidInstallableMedia(entry.name)));
}
expect(invalidMedia).toEqual([]);
});
it("ships demos without source-less media", async () => {
const invalidMedia: string[] = [];
for (const entry of readdirSync(componentsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
invalidMedia.push(...(await invalidDemoMedia(entry.name)));
}
expect(invalidMedia).toEqual([]);
});
});