@brandlab-digital/conformance 3.15.1 → 3.16.1
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/README.md +6 -1
- package/dist/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/site-system/checks.d.ts +6 -0
- package/dist/site-system/checks.d.ts.map +1 -1
- package/dist/site-system/checks.js +1 -0
- package/dist/site-system/checks.js.map +1 -1
- package/dist/site-system/rules/design/d4-distinctness.d.ts +2 -0
- package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -1
- package/dist/site-system/rules/design/d4-distinctness.js +14 -2
- package/dist/site-system/rules/design/d4-distinctness.js.map +1 -1
- package/dist/site-system/rules/full/estate.d.ts +5 -0
- package/dist/site-system/rules/full/estate.d.ts.map +1 -1
- package/dist/site-system/rules/full/estate.js +5 -0
- package/dist/site-system/rules/full/estate.js.map +1 -1
- package/dist/site-system/rules/full/index.d.ts +2 -1
- package/dist/site-system/rules/full/index.d.ts.map +1 -1
- package/dist/site-system/rules/full/index.js +5 -2
- package/dist/site-system/rules/full/index.js.map +1 -1
- package/dist/site-system/rules/full/k12-manifests.d.ts +4 -0
- package/dist/site-system/rules/full/k12-manifests.d.ts.map +1 -1
- package/dist/site-system/rules/full/k12-manifests.js +11 -0
- package/dist/site-system/rules/full/k12-manifests.js.map +1 -1
- package/dist/site-system/rules/full/k13-signatures.d.ts +17 -0
- package/dist/site-system/rules/full/k13-signatures.d.ts.map +1 -0
- package/dist/site-system/rules/full/k13-signatures.js +74 -0
- package/dist/site-system/rules/full/k13-signatures.js.map +1 -0
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
- package/dist/site-system/rules/sections/k2-skin-coverage.js +2 -0
- package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k13-signatures.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k13-signatures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,OAAO,EAAyD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtG,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAA0C,CAAC;AAK7G,yGAAyG;AACzG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AASD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,EAAE,CA6ClE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readRecordedLook } from "../design/d4-distinctness.js";
|
|
2
|
+
import { isLiteralObject } from "../enquiry/literal.js";
|
|
3
|
+
import { fail, listOf, notApplicable, outcome, plural, warning } from "./common.js";
|
|
4
|
+
/** As site-core's SIGNATURE_LIMITS (a test keeps the two equal). */
|
|
5
|
+
export const K13_SIGNATURE_LIMITS = Object.freeze({ home: 2, service: 1 });
|
|
6
|
+
/** As site-core's SIGNATURE_NAME_PATTERN. */
|
|
7
|
+
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
|
+
/** A recorded signature ("Grass-edge divider") in the form of a rendered name ("grass-edge-divider"). */
|
|
9
|
+
export function signatureKey(text) {
|
|
10
|
+
return text
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
13
|
+
.replace(/^-+|-+$/g, "");
|
|
14
|
+
}
|
|
15
|
+
/** The home signatures `strategy.ts` records for D4; null when it records no look. */
|
|
16
|
+
function recordedHomeSignatures(projectRoot) {
|
|
17
|
+
const home = readRecordedLook(projectRoot)?.homeSections;
|
|
18
|
+
if (!isLiteralObject(home) || !Array.isArray(home["signatures"]))
|
|
19
|
+
return null;
|
|
20
|
+
return home["signatures"].filter((item) => typeof item === "string").map(signatureKey);
|
|
21
|
+
}
|
|
22
|
+
export function checkK13(context) {
|
|
23
|
+
const failures = [];
|
|
24
|
+
let checked = 0;
|
|
25
|
+
const homeRendered = new Set();
|
|
26
|
+
let homeSeen = false;
|
|
27
|
+
for (const page of context.estate.pages) {
|
|
28
|
+
const names = page.signatures ?? [];
|
|
29
|
+
const family = page.family;
|
|
30
|
+
if (family === "home")
|
|
31
|
+
homeSeen = true;
|
|
32
|
+
if (names.length === 0)
|
|
33
|
+
continue;
|
|
34
|
+
checked += 1;
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
if (!NAME.test(name))
|
|
37
|
+
failures.push(fail("K13", page.path, `A signature section on ${page.path} has ${name ? `the name "${name}"` : "no data-bl-signature name"}; a name is lower-case words joined by hyphens.`));
|
|
38
|
+
}
|
|
39
|
+
const limit = family ? (K13_SIGNATURE_LIMITS[family] ?? 0) : 0;
|
|
40
|
+
if (names.length > limit) {
|
|
41
|
+
failures.push(fail("K13", page.path, `${page.path} renders ${plural(names.length, "signature section")}; ${family ?? "this"} pages take ${limit === 0 ? "none" : `at most ${limit}`}.`));
|
|
42
|
+
}
|
|
43
|
+
if (page.manifest.kind === "one") {
|
|
44
|
+
const listed = page.manifest.manifest.rendered.filter((entry) => entry.id === "signature").map((entry) => entry.signature ?? "");
|
|
45
|
+
if (listed.join(" ") !== names.join(" ")) {
|
|
46
|
+
failures.push(fail("K13", page.path, `${page.path} renders the signatures ${listOf(names)}, but its manifest lists ${listed.length > 0 ? listOf(listed) : "none"}.`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (family === "home")
|
|
50
|
+
for (const name of names)
|
|
51
|
+
homeRendered.add(name);
|
|
52
|
+
}
|
|
53
|
+
const recorded = recordedHomeSignatures(context.projectRoot);
|
|
54
|
+
if (homeRendered.size > 0) {
|
|
55
|
+
if (recorded === null) {
|
|
56
|
+
failures.push(fail("K13", "src/data/strategy.ts", `The home page renders the signatures ${listOf([...homeRendered])}, but strategy.ts records no look.homeSections.signatures, so D4 can't count them.`));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const missing = [...homeRendered].filter((name) => !recorded.includes(name));
|
|
60
|
+
if (missing.length > 0) {
|
|
61
|
+
failures.push(fail("K13", "src/data/strategy.ts", `The home page renders the signatures ${listOf(missing)}, which strategy.ts doesn't record in look.homeSections.signatures (${recorded.length > 0 ? listOf(recorded) : "none"}), so D4 compares a different look.`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (homeSeen && recorded) {
|
|
66
|
+
for (const name of recorded.filter((item) => !homeRendered.has(item))) {
|
|
67
|
+
failures.push(warning("K13", "src/data/strategy.ts", `strategy.ts records the home signature "${name}", which the home page doesn't render.`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (checked === 0 && failures.length === 0)
|
|
71
|
+
return [notApplicable("K13", "No page renders a signature section.")];
|
|
72
|
+
return outcome("K13", failures, `${plural(checked, "page")} render signature sections within their family's limit, named as their manifests list them, and the home page's are the ones strategy.ts records for D4.`);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=k13-signatures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k13-signatures.js","sourceRoot":"","sources":["../../../../src/site-system/rules/full/k13-signatures.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K13: signature sections (Site System v2 §7.7, 3.16.0). A client's signature features keep its own character inside\n * the shared skeleton, under the same rules as every other section. Each top-level `data-bl-section=\"signature\"` has a\n * `data-bl-signature` name (lower-case words joined by hyphens) that matches its manifest entry; a page renders at most\n * two on the home page, one on a service page and none elsewhere (site-core's `SIGNATURE_LIMITS`); and the home page's\n * signatures are the ones `strategy.ts` records in `look.homeSections.signatures`, so D4 compares the real look. A\n * recorded signature the home page doesn't render is a warning. Accessibility, budgets and truth are the page-wide\n * checks' (axe, K3, K4, B1, T4, E8); the section contract is K8's and K11's.\n */\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { readRecordedLook } from \"../design/d4-distinctness.js\";\nimport { isLiteralObject } from \"../enquiry/literal.js\";\nimport { fail, listOf, notApplicable, outcome, plural, warning, type FullContext } from \"./common.js\";\n\n/** As site-core's SIGNATURE_LIMITS (a test keeps the two equal). */\nexport const K13_SIGNATURE_LIMITS: Readonly<Record<string, number>> = Object.freeze({ home: 2, service: 1 });\n\n/** As site-core's SIGNATURE_NAME_PATTERN. */\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/** A recorded signature (\"Grass-edge divider\") in the form of a rendered name (\"grass-edge-divider\"). */\nexport function signatureKey(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/** The home signatures `strategy.ts` records for D4; null when it records no look. */\nfunction recordedHomeSignatures(projectRoot: string): string[] | null {\n const home = readRecordedLook(projectRoot)?.homeSections;\n if (!isLiteralObject(home) || !Array.isArray(home[\"signatures\"])) return null;\n return home[\"signatures\"].filter((item): item is string => typeof item === \"string\").map(signatureKey);\n}\n\nexport function checkK13(context: FullContext): SiteSystemFinding[] {\n const failures: SiteSystemFinding[] = [];\n let checked = 0;\n const homeRendered = new Set<string>();\n let homeSeen = false;\n for (const page of context.estate.pages) {\n const names = page.signatures ?? [];\n const family = page.family;\n if (family === \"home\") homeSeen = true;\n if (names.length === 0) continue;\n checked += 1;\n for (const name of names) {\n if (!NAME.test(name)) failures.push(fail(\"K13\", page.path, `A signature section on ${page.path} has ${name ? `the name \"${name}\"` : \"no data-bl-signature name\"}; a name is lower-case words joined by hyphens.`));\n }\n const limit = family ? (K13_SIGNATURE_LIMITS[family] ?? 0) : 0;\n if (names.length > limit) {\n failures.push(fail(\"K13\", page.path, `${page.path} renders ${plural(names.length, \"signature section\")}; ${family ?? \"this\"} pages take ${limit === 0 ? \"none\" : `at most ${limit}`}.`));\n }\n if (page.manifest.kind === \"one\") {\n const listed = page.manifest.manifest.rendered.filter((entry) => entry.id === \"signature\").map((entry) => entry.signature ?? \"\");\n if (listed.join(\" \") !== names.join(\" \")) {\n failures.push(fail(\"K13\", page.path, `${page.path} renders the signatures ${listOf(names)}, but its manifest lists ${listed.length > 0 ? listOf(listed) : \"none\"}.`));\n }\n }\n if (family === \"home\") for (const name of names) homeRendered.add(name);\n }\n\n const recorded = recordedHomeSignatures(context.projectRoot);\n if (homeRendered.size > 0) {\n if (recorded === null) {\n failures.push(fail(\"K13\", \"src/data/strategy.ts\", `The home page renders the signatures ${listOf([...homeRendered])}, but strategy.ts records no look.homeSections.signatures, so D4 can't count them.`));\n } else {\n const missing = [...homeRendered].filter((name) => !recorded.includes(name));\n if (missing.length > 0) {\n failures.push(fail(\"K13\", \"src/data/strategy.ts\", `The home page renders the signatures ${listOf(missing)}, which strategy.ts doesn't record in look.homeSections.signatures (${recorded.length > 0 ? listOf(recorded) : \"none\"}), so D4 compares a different look.`));\n }\n }\n }\n if (homeSeen && recorded) {\n for (const name of recorded.filter((item) => !homeRendered.has(item))) {\n failures.push(warning(\"K13\", \"src/data/strategy.ts\", `strategy.ts records the home signature \"${name}\", which the home page doesn't render.`));\n }\n }\n if (checked === 0 && failures.length === 0) return [notApplicable(\"K13\", \"No page renders a signature section.\")];\n return outcome(\"K13\", failures, `${plural(checked, \"page\")} render signature sections within their family's limit, named as their manifests list them, and the home page's are the ones strategy.ts records for D4.`);\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { type ParsedPage } from "../enquiry/common.js";
|
|
|
3
3
|
/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */
|
|
4
4
|
export declare const K2_SKINNED_HOOKS: readonly ["header", "drawer", "utility", "callbar", "back-to-top", "quote-button", "phone-link", "text-link", "button", "container", "quote-form", "photo-upload", "dialog", "field", "thank-you", "rating-chip", "footer", "accordion", "price-table", "eyebrow", "card", "badge", "chip", "stat", "tick-list", "link-list", "media", "breadcrumbs", "quote-checklist"];
|
|
5
5
|
/** `data-bl-section` ids of the sections the library renders and skins. */
|
|
6
|
-
export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services", "projects", "gallery", "before-after", "licences", "emergency"];
|
|
6
|
+
export declare const K2_SKINNED_SECTIONS: readonly ["reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services", "projects", "gallery", "before-after", "licences", "emergency", "signature"];
|
|
7
7
|
export declare const K2_REFERENCE_CLASS: RegExp;
|
|
8
8
|
/** Hook names the project's own stylesheets select: `[data-bl="x"]` and `[data-bl-section="x"]`. */
|
|
9
9
|
export declare function hooksStyledBySite(projectRoot: string): Set<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,0WAOnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAI1F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,0WAOnB,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,8LAMtB,CAAC;AAEX,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAqB9D,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA0BlE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAqC9F"}
|
|
@@ -34,6 +34,8 @@ export const K2_SKINNED_SECTIONS = [
|
|
|
34
34
|
"reviews", "cta", "faqs", "price-guide", "proof-strip", "page-header", "contact", "hero", "services",
|
|
35
35
|
// 3.15.0 proof sections
|
|
36
36
|
"projects", "gallery", "before-after", "licences", "emergency",
|
|
37
|
+
// 3.16.0 signature section frame
|
|
38
|
+
"signature",
|
|
37
39
|
];
|
|
38
40
|
export const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;
|
|
39
41
|
function classesOf(element) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the\n * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them\n * from its skin stylesheet) join the lists; from 3.15.0 the proof sections (projects, gallery, before-after,\n * licences, emergency) and the quote checklist.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n // 3.14.0 primitives\n \"eyebrow\", \"card\", \"badge\", \"chip\", \"stat\", \"tick-list\", \"link-list\", \"media\", \"breadcrumbs\",\n // 3.15.0\n \"quote-checklist\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\n \"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\", \"hero\", \"services\",\n // 3.15.0 proof sections\n \"projects\", \"gallery\", \"before-after\", \"licences\", \"emergency\",\n] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"k2-skin-coverage.js","sourceRoot":"","sources":["../../../../src/site-system/rules/sections/k2-skin-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * K2: skin coverage (Site System v2 §13.2, §8.4). Every shared component a page renders has a skin entry, so a site\n * never ships an unstyled look-alike, and no reference-skin class (the test skins the library's own tests use) ships.\n *\n * What the HTML can show, defined exactly:\n * - A component is a rendered element with `data-bl=\"<name>\"` or `data-bl-section=\"<id>\"`, for the names the library\n * skins (SKINNED_HOOKS and SKINNED_SECTIONS below). Hooks without a skin entry (the skip link, the honeypot, a star\n * row) and the site's own compositions are not components in this sense. From 3.14.0 the library's hero and the\n * primitives (button actions share `button`; cards, badges, chips and stats take no classes, so a site covers them\n * from its skin stylesheet) join the lists; from 3.15.0 the proof sections (projects, gallery, before-after,\n * licences, emergency) and the quote checklist.\n * - The component is covered when, on at least one sampled page, its root element or one of its slot elements (an\n * element with `data-slot` inside it, outside any component nested in it) carries a class, because a skin gives a\n * component its look through `classes`; or when the project's own stylesheets under `src/` or `app/` select the\n * component by its hook (`[data-bl=\"footer\"]`, `[data-bl-section=\"faqs\"]`), which is how a site styles slots in plain CSS.\n * - A class token starting `ref-heavy-`, `ref-calm-` or `ref-pool-` on any element is a reference-skin class and fails.\n */\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { fail, notApplicable, pass, plural, type ParsedPage } from \"../enquiry/common.js\";\nimport { attr, descendants, type HtmlElement } from \"../enquiry/html.js\";\nimport { PageProblems } from \"../library/common.js\";\n\n/** `data-bl` names that have an entry in `BrandlabSkin` (ui-kit/brandlab-skin.ts). */\nexport const K2_SKINNED_HOOKS = [\n \"header\", \"drawer\", \"utility\", \"callbar\", \"back-to-top\", \"quote-button\", \"phone-link\", \"text-link\", \"button\", \"container\",\n \"quote-form\", \"photo-upload\", \"dialog\", \"field\", \"thank-you\", \"rating-chip\", \"footer\", \"accordion\", \"price-table\",\n // 3.14.0 primitives\n \"eyebrow\", \"card\", \"badge\", \"chip\", \"stat\", \"tick-list\", \"link-list\", \"media\", \"breadcrumbs\",\n // 3.15.0\n \"quote-checklist\",\n] as const;\n\n/** `data-bl-section` ids of the sections the library renders and skins. */\nexport const K2_SKINNED_SECTIONS = [\n \"reviews\", \"cta\", \"faqs\", \"price-guide\", \"proof-strip\", \"page-header\", \"contact\", \"hero\", \"services\",\n // 3.15.0 proof sections\n \"projects\", \"gallery\", \"before-after\", \"licences\", \"emergency\",\n // 3.16.0 signature section frame\n \"signature\",\n] as const;\n\nexport const K2_REFERENCE_CLASS = /^ref-(?:heavy|calm|pool)-/;\n\nfunction classesOf(element: HtmlElement): string[] {\n return (attr(element, \"class\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** The root and slot elements of one component, leaving out components nested inside it. */\nfunction skinSurface(root: HtmlElement): HtmlElement[] {\n const surface = [root];\n const visit = (node: HtmlElement) => {\n for (const child of node.children) {\n if (child.type !== \"element\") continue;\n if (attr(child, \"data-bl\") !== undefined || attr(child, \"data-bl-section\") !== undefined) continue;\n if (attr(child, \"data-slot\") !== undefined) surface.push(child);\n visit(child);\n }\n };\n visit(root);\n return surface;\n}\n\n/** Hook names the project's own stylesheets select: `[data-bl=\"x\"]` and `[data-bl-section=\"x\"]`. */\nexport function hooksStyledBySite(projectRoot: string): Set<string> {\n const styled = new Set<string>();\n const selector = /\\[\\s*data-bl(?:-section)?\\s*=\\s*[\"']?([a-z0-9-]+)[\"']?\\s*\\]/gi;\n const visit = (directory: string, depth: number) => {\n if (depth > 6) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path, depth + 1);\n else if (entry.isFile() && /\\.(?:css|scss)$/i.test(entry.name)) {\n try {\n for (const match of readFileSync(path, \"utf8\").matchAll(selector)) styled.add(match[1]!.toLowerCase());\n } catch {\n // an unreadable stylesheet styles nothing we can see\n }\n }\n }\n };\n for (const folder of [\"src\", \"app\", \"sites\"]) if (existsSync(join(projectRoot, folder))) visit(join(projectRoot, folder), 0);\n return styled;\n}\n\nexport function checkK2(pages: readonly ParsedPage[], projectRoot: string): SiteSystemFinding[] {\n const hooks = new Set<string>(K2_SKINNED_HOOKS);\n const sections = new Set<string>(K2_SKINNED_SECTIONS);\n /** Component key (\"footer\", \"section:faqs\") to whether any instance carries a skin class, and the first page. */\n const components = new Map<string, { covered: boolean; page: string; label: string }>();\n const references = new PageProblems();\n\n for (const page of pages) {\n for (const element of descendants(page.root)) {\n const reference = classesOf(element).find((token) => K2_REFERENCE_CLASS.test(token));\n if (reference) references.add(page.path, `A reference-skin class (${reference}) ships; those belong to the library's own tests`);\n\n const hook = attr(element, \"data-bl\");\n const section = attr(element, \"data-bl-section\");\n const key = hook !== undefined && hooks.has(hook) ? `hook:${hook}` : section !== undefined && sections.has(section) ? `section:${section}` : null;\n if (!key) continue;\n const label = hook !== undefined ? `data-bl=\"${hook}\"` : `data-bl-section=\"${section}\"`;\n const carries = skinSurface(element).some((candidate) => classesOf(candidate).length > 0);\n const known = components.get(key);\n if (known) known.covered ||= carries;\n else components.set(key, { covered: carries, page: page.path, label });\n }\n }\n\n const failures = references.findings(\"K2\");\n if (components.size === 0 && failures.length === 0) return [notApplicable(\"K2\", \"None of the sampled pages renders a shared component.\")];\n const uncovered = [...components].filter(([, component]) => !component.covered);\n if (uncovered.length > 0) {\n const styled = hooksStyledBySite(projectRoot);\n for (const [key, component] of uncovered) {\n const name = key.slice(key.indexOf(\":\") + 1);\n if (styled.has(name)) continue;\n failures.push(fail(\"K2\", component.page, `${component.label} is rendered (first on ${component.page}) with no skin class on its root or slots and no selector for it in the site's stylesheets, so it has no skin entry.`));\n }\n }\n if (failures.length > 0) return failures;\n return [pass(\"K2\", `Checked ${plural(components.size, \"shared component\")} on ${plural(pages.length, \"page\")}: each carries a skin class or is styled by the site's own CSS, and no reference-skin class ships.`)];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandlab-digital/conformance",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.1",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@brandlab-digital/site-core": "3.
|
|
32
|
+
"@brandlab-digital/site-core": "3.16.1",
|
|
33
33
|
"typescript": "6.0.3"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|