@brandlab-digital/conformance 3.15.1 → 3.16.0

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.
Files changed (32) hide show
  1. package/README.md +6 -1
  2. package/dist/current-release.js +5 -5
  3. package/dist/current-release.js.map +1 -1
  4. package/dist/site-system/checks.d.ts +6 -0
  5. package/dist/site-system/checks.d.ts.map +1 -1
  6. package/dist/site-system/checks.js +1 -0
  7. package/dist/site-system/checks.js.map +1 -1
  8. package/dist/site-system/rules/design/d4-distinctness.d.ts +2 -0
  9. package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -1
  10. package/dist/site-system/rules/design/d4-distinctness.js +14 -2
  11. package/dist/site-system/rules/design/d4-distinctness.js.map +1 -1
  12. package/dist/site-system/rules/full/estate.d.ts +5 -0
  13. package/dist/site-system/rules/full/estate.d.ts.map +1 -1
  14. package/dist/site-system/rules/full/estate.js +5 -0
  15. package/dist/site-system/rules/full/estate.js.map +1 -1
  16. package/dist/site-system/rules/full/index.d.ts +2 -1
  17. package/dist/site-system/rules/full/index.d.ts.map +1 -1
  18. package/dist/site-system/rules/full/index.js +5 -2
  19. package/dist/site-system/rules/full/index.js.map +1 -1
  20. package/dist/site-system/rules/full/k12-manifests.d.ts +4 -0
  21. package/dist/site-system/rules/full/k12-manifests.d.ts.map +1 -1
  22. package/dist/site-system/rules/full/k12-manifests.js +11 -0
  23. package/dist/site-system/rules/full/k12-manifests.js.map +1 -1
  24. package/dist/site-system/rules/full/k13-signatures.d.ts +17 -0
  25. package/dist/site-system/rules/full/k13-signatures.d.ts.map +1 -0
  26. package/dist/site-system/rules/full/k13-signatures.js +74 -0
  27. package/dist/site-system/rules/full/k13-signatures.js.map +1 -0
  28. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +1 -1
  29. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -1
  30. package/dist/site-system/rules/sections/k2-skin-coverage.js +2 -0
  31. package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -1
  32. package/package.json +2 -2
package/README.md CHANGED
@@ -116,7 +116,12 @@ or no record keeps the specific-time rule. Noindex, the call link and everything
116
116
  section carries `data-bl-slot="<slot>"`, or lists `<slot>:no-…` in `data-bl-slot-skip` (a warning, so the record is
117
117
  re-checked). The skip is read only from the top-level section element (the outermost `data-bl-section`): a site
118
118
  element that wraps `LocalFacts` in its own section hides the skip, and the page fails as if no reason were given.
119
- Render `LocalFacts` itself as the `SectionSequence` candidate, not inside a wrapper. **E1-pattern** (rendered, full, from 3.14.0): every location page outside `docs/recorded-titles.json`
119
+ Render `LocalFacts` itself as the `SectionSequence` candidate, not inside a wrapper. **K13** (rendered, full, from
120
+ 3.16.0): signature sections (`data-bl-section="signature"`) carry a `data-bl-signature` name that their manifest
121
+ lists, at most two on the home page, one on a service page and none elsewhere (site-core `SIGNATURE_LIMITS`), and
122
+ the home page's are the ones `strategy.ts` records in `look.homeSections.signatures`, so D4 compares the real look (a
123
+ recorded one the home page doesn't show is a warning). K12 leaves placed signatures out of its order walk and, from
124
+ 3.16.0, warns when an optional or recommended section with data drops for the cap. **E1-pattern** (rendered, full, from 3.14.0): every location page outside `docs/recorded-titles.json`
120
125
  has the H1 "{Service} {Place}" (no "in", no "the") and a title that starts with it, at most 60 characters
121
126
  (site-core `locationHeadingProblem`). **LOC-INDEX** also reads `estate` in strategy.ts (3.14.0): every location page
122
127
  is indexed now or in a wave, a `return` wave's pages are in the build already, and a region page at a suburb's slug
@@ -23,19 +23,19 @@ export const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze({
23
23
  "kind": "brandlab-runtime-release-contract",
24
24
  "packages": {
25
25
  "@brandlab-digital/conformance": {
26
- "version": "3.15.1"
26
+ "version": "3.16.0"
27
27
  },
28
28
  "@brandlab-digital/eslint-plugin": {
29
- "version": "3.15.1"
29
+ "version": "3.16.0"
30
30
  },
31
31
  "@brandlab-digital/site-core": {
32
- "version": "3.15.1"
32
+ "version": "3.16.0"
33
33
  },
34
34
  "@brandlab-digital/site-next": {
35
- "version": "3.15.1"
35
+ "version": "3.16.0"
36
36
  }
37
37
  },
38
- "releaseVersion": "3.15.1",
38
+ "releaseVersion": "3.16.0",
39
39
  "runtime": {
40
40
  "bundler": "turbopack",
41
41
  "cacheComponents": false,
@@ -1 +1 @@
1
- {"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,QAAQ;SACpB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;KACF;IACD,gBAAgB,EAAE,QAAQ;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.15.1\"\n },\n \"@brandlab-digital/eslint-plugin\": {\n \"version\": \"3.15.1\"\n },\n \"@brandlab-digital/site-core\": {\n \"version\": \"3.15.1\"\n },\n \"@brandlab-digital/site-next\": {\n \"version\": \"3.15.1\"\n }\n },\n \"releaseVersion\": \"3.15.1\",\n \"runtime\": {\n \"bundler\": \"turbopack\",\n \"cacheComponents\": false,\n \"next\": \"16.4.2\",\n \"nextVersionPolicy\": \"exact\",\n \"node\": \"24.x\",\n \"packageManager\": \"npm@11.14.1\",\n \"react\": \"19.2.8\",\n \"reactDom\": \"19.2.8\",\n \"router\": \"app\",\n \"sharp\": \"0.35.5\",\n \"typescript\": \"6.0.3\"\n },\n \"schemaVersion\": \"brandlab.runtime-release-contract.v1\"\n},\n) as unknown as BrandlabRuntimeReleaseContract;\n\nvalidateBrandlabRuntimeReleaseContract(BRANDLAB_CURRENT_RUNTIME_RELEASE);\n"]}
1
+ {"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,QAAQ;SACpB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,QAAQ;SACpB;KACF;IACD,gBAAgB,EAAE,QAAQ;IAC1B,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.16.0\"\n },\n \"@brandlab-digital/eslint-plugin\": {\n \"version\": \"3.16.0\"\n },\n \"@brandlab-digital/site-core\": {\n \"version\": \"3.16.0\"\n },\n \"@brandlab-digital/site-next\": {\n \"version\": \"3.16.0\"\n }\n },\n \"releaseVersion\": \"3.16.0\",\n \"runtime\": {\n \"bundler\": \"turbopack\",\n \"cacheComponents\": false,\n \"next\": \"16.4.2\",\n \"nextVersionPolicy\": \"exact\",\n \"node\": \"24.x\",\n \"packageManager\": \"npm@11.14.1\",\n \"react\": \"19.2.8\",\n \"reactDom\": \"19.2.8\",\n \"router\": \"app\",\n \"sharp\": \"0.35.5\",\n \"typescript\": \"6.0.3\"\n },\n \"schemaVersion\": \"brandlab.runtime-release-contract.v1\"\n},\n) as unknown as BrandlabRuntimeReleaseContract;\n\nvalidateBrandlabRuntimeReleaseContract(BRANDLAB_CURRENT_RUNTIME_RELEASE);\n"]}
@@ -326,6 +326,12 @@ export declare const SITE_SYSTEM_CHECKS: readonly [{
326
326
  readonly from: "3.14.0";
327
327
  readonly kind: "rendered";
328
328
  readonly summary: "Trade lines inside sections (matrix part B): for trees, the council permit note in LocalFacts on suburb and service-in-suburb pages and the qualifications and insurance lines on the about page, or a data-backed reason they are hidden.";
329
+ }, {
330
+ readonly id: "K13";
331
+ readonly tier: "full";
332
+ readonly from: "3.16.0";
333
+ readonly kind: "rendered";
334
+ readonly summary: "Signature sections: a name, at most two on the home page and one on a service page, none elsewhere, as the manifest lists them; the home page's are the ones strategy.ts records for D4.";
329
335
  }, {
330
336
  readonly id: "E5";
331
337
  readonly tier: "full";
@@ -1 +1 @@
1
- {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/site-system/checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,iBAAiB,uCAAwC,CAAC;AACvE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,kFAAkF;AAClF,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,WAAW,CAAC;AAEhE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,KAAK,MAAM,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC;CAClC;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyEgB,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAI1E,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,EACpD,IAAI,EAAE;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAClE,OAAO,CAIT;AAED,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,4GAA4G;IAC5G,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAAC;IAChE,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B"}
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/site-system/checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,iBAAiB,uCAAwC,CAAC;AACvE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,kFAAkF;AAClF,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,WAAW,CAAC;AAEhE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,KAAK,MAAM,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC;CAClC;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0EgB,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAI1E,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,EACpD,IAAI,EAAE;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAClE,OAAO,CAIT;AAED,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,4GAA4G;IAC5G,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAAC;IAChE,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B"}
@@ -67,6 +67,7 @@ export const SITE_SYSTEM_CHECKS = [
67
67
  // full: page families, location estates, section manifests, links and the data contract
68
68
  { id: "K12", tier: "full", from: "3.12.0", kind: "rendered", summary: "Section manifest: every required section rendered or skipped with a data-backed reason; conditional sections render when their data exists; canonical or recorded order." },
69
69
  { id: "K12-slots", tier: "full", from: "3.14.0", kind: "rendered", summary: "Trade lines inside sections (matrix part B): for trees, the council permit note in LocalFacts on suburb and service-in-suburb pages and the qualifications and insurance lines on the about page, or a data-backed reason they are hidden." },
70
+ { id: "K13", tier: "full", from: "3.16.0", kind: "rendered", summary: "Signature sections: a name, at most two on the home page and one on a service page, none elsewhere, as the manifest lists them; the home page's are the ones strategy.ts records for D4." },
70
71
  { id: "E5", tier: "full", from: "3.12.0", kind: "static", summary: "No orphan indexable pages, from the route catalogue and link data." },
71
72
  { id: "E10", tier: "full", from: "3.12.0", kind: "static", summary: "Phrase-owner uniqueness in strategy data; no service-in-suburb page for the suburb page phrase; no service-region hub for the main trade." },
72
73
  { id: "L3", tier: "full", from: "3.12.0", kind: "static", summary: "Neighbours by real distance." },
@@ -1 +1 @@
1
- {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/site-system/checks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Site System v2 ready gate: every check, the adoption tier it blocks at and the release that brings it.\n *\n * A site declares `siteSystem: { tier }` in brandlab.config.ts. Checks of that tier and the tiers below it block;\n * checks of higher tiers report without blocking. A check blocks only from the release named here, so new checks\n * arrive with version bumps and never turn the fleet red at once. Holdouts use the project's Romie-owned\n * `exceptions` record. D6 (leave reviews as they are) removes every review-exclusion clause; D7 (no call tracking)\n * means no number-swap check.\n *\n * `kind` says where a check runs:\n * - `journey`: the Playwright site-system journeys (`defineBrandlabSiteSystemTests`), in the site's e2e run;\n * - `rendered`: rules over the built site's server HTML and assets (`brandlab-ready` reads them);\n * - `static`: source and data rules (`brandlab-verify` and `brandlab-ready`);\n * - `record`: launch-day and human steps recorded with `brandlab-ready record <id> --evidence <ref>`.\n */\n\nexport const SITE_SYSTEM_TIERS = [\"shell\", \"enquiry\", \"full\"] as const;\nexport type SiteSystemTier = (typeof SITE_SYSTEM_TIERS)[number];\n\n/** The design check D4 blocks every new build and redesign, whatever its tier. */\nexport type SiteSystemCheckScope = SiteSystemTier | \"new-build\";\n\nexport type SiteSystemCheckKind = \"journey\" | \"rendered\" | \"static\" | \"record\";\n\nexport interface SiteSystemCheck {\n readonly id: string;\n readonly tier: SiteSystemCheckScope;\n /** The first release in which the check blocks at its tier. */\n readonly from: `3.${number}.0`;\n readonly kind: SiteSystemCheckKind;\n readonly summary: string;\n /**\n * `launch`: a not-run result blocks only under `brandlab-ready --launch`, where it counts as a failure. For checks\n * that need production values in the shell (H13), so a CI run reports them as not run without failing the tier.\n */\n readonly notRunBlocks?: \"launch\";\n}\n\nexport const SITE_SYSTEM_CHECKS = [\n // shell: header, nav, call bar, placements, crawlable links, contrast and targets, call links, budgets, legal\n { id: \"N1\", tier: \"shell\", from: \"3.9.0\", kind: \"rendered\", summary: \"Required nav links, About in the first two, services before Areas, Contact last, labels from the closed list, recorded orders kept.\" },\n { id: \"N2\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every dropdown link and hub link is an <a href> in the server HTML.\" },\n { id: \"N3\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Hover opens after a short delay and a diagonal path stays open; mouse-out closes; nothing sticks open.\" },\n { id: \"N4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"A chevron click after hover keeps the panel open; a link click closes it; click, Enter and outside click work.\" },\n { id: \"N5\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Keyboard and ARIA: aria-expanded follows the panel, Arrow keys move, Escape closes and returns focus; axe passes open and closed.\" },\n { id: \"N6\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"The header stays at the top with the phone and quote visible at every desktop width; the compact band (1024 to 1199 px) shows them too.\" },\n { id: \"N8\", tier: \"shell\", from: \"3.9.0\", kind: \"journey\", summary: \"The drawer is a modal dialog that traps focus and resets; the call bar uses placement fab; back-to-top appears (from 3.11.0).\" },\n { id: \"K1\", tier: \"shell\", from: \"3.9.0\", kind: \"rendered\", summary: \"Shared components in use for the parts the tier covers (data-bl present); no literal tel: outside PhoneLink.\" },\n { id: \"K3\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Text contrast 4.5:1 and non-text 3:1 in the browser.\" },\n { id: \"K4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Targets at least 24 px, and 44 px for primary actions (call, quote, menu).\" },\n { id: \"K5\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Focus is never fully hidden by the sticky header or call bar at 390 px, in Chromium and WebKit.\" },\n { id: \"K6\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Phone and quote in the same relative order on every page, in the header, hero and call bar.\" },\n { id: \"K7\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Dropdown links, accordion and tab panels and full review text are in the server HTML.\" },\n { id: \"K10\", tier: \"shell\", from: \"3.8.0\", kind: \"record\", summary: \"Adoption parity in the lenient harness: no section, link or text lost against the baseline.\" },\n { id: \"C4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Phone taps report an allowed placement (header, footer, hero, cta, fab, contact, location, page, emergency).\" },\n { id: \"C5\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Call-to-action labels come from the closed list or a recorded label.\" },\n { id: \"C9\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No interstitial overlay in the first screen.\" },\n { id: \"C10\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every tel: href is E.164 (national digits for 13, 1300 and 1800) and matches the evidence-file phone.\" },\n { id: \"E1\", tier: \"shell\", from: \"3.8.0\", kind: \"static\", summary: \"Title patterns on new or changed URLs; recorded titles unchanged.\" },\n { id: \"E2\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Exactly one H1 on every page, rendered once.\" },\n { id: \"E8\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No banned phrase from any of the three layers, environmental claims included.\" },\n { id: \"T3\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"A licence line renders wherever licensed work is advertised, with the licensee where it differs.\" },\n { id: \"T4\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No hard-coded rating or review numbers; proof claims resolve to renderable evidence.\" },\n { id: \"T11\", tier: \"shell\", from: \"3.8.0\", kind: \"static\", summary: \"No page for work over $5,000 outside the licence classes held; licence conditions carried.\" },\n { id: \"X1\", tier: \"shell\", from: \"3.8.0\", kind: \"record\", summary: \"Legacy URLs answer 200 or one redirect hop; earning-URL depth, titles and dollar figures kept.\" },\n { id: \"B1\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Lab budgets: JavaScript, third-party requests, hero image and HTML within budget; no unrecorded chat widget, pixel, heatmap or tag manager.\" },\n { id: \"L10-hero\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: 'No kilometre figures, \"km from\" or \"based in\" in hero copy.' },\n { id: \"W2\", tier: \"shell\", from: \"3.13.0\", kind: \"journey\", summary: \"Prefetch by link role at 390 x 844 and 1440 x 900: at most 8 pages on landing and 12 after a full scroll; none from footer, area, nearby, map or finder links, hidden links or the current page; prefetch bytes above twice the page's HTML warn.\" },\n { id: \"W5\", tier: \"shell\", from: \"3.13.0\", kind: \"static\", summary: \"vercel.json regions equal hosting.originRegion (syd1 by default); any other region has an origin-region-v1 decision whose reviewBy has not passed.\" },\n { id: \"W10\", tier: \"shell\", from: \"3.13.0\", kind: \"rendered\", summary: \"robots.txt and the recorded firewall rules leave every search and answer crawler open; no Crawl-delay for them.\" },\n\n // enquiry: the form, thank-you page, reviews, proof strip, footer, CTA band, contact page, prices, media, density\n { id: \"C1\", tier: \"enquiry\", from: \"3.8.0\", kind: \"journey\", summary: \"Money pages: the form in the first section; at 390 x 844 the H1, a call and a quote action in the first viewport; at 1440 x 900 the form.\" },\n { id: \"C2\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: 'Form layout and labels; phone and email required; no helper or reassurance lines, no \"(required)\", no job select.' },\n { id: \"E9\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every cost guide carries at least 8 dollar figures in a renderable price state.\" },\n { id: \"T5\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Reviews from the feed, with Google's rating, count and link beside any selection; none on the contact page; no empty reviews section.\" },\n { id: \"K8\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Section contract: no empty section, one H2 each, at most one band tone, neighbours differ, proof within the first five sections.\" },\n { id: \"C7\", tier: \"enquiry\", from: \"3.10.0\", kind: \"rendered\", summary: \"Thank-you page: a reply time, a tel: link and noindex, follow.\" },\n { id: \"C8\", tier: \"enquiry\", from: \"3.10.0\", kind: \"rendered\", summary: \"Forgiving form: tolerant phone input (13, 1300 and 1800 too), autocomplete tokens, no capture on the photo input.\" },\n { id: \"C11\", tier: \"enquiry\", from: \"3.10.0\", kind: \"static\", summary: \"The test marker is stripped before delivery and reporting; /api/contact is rate-limited.\" },\n { id: \"N9\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: 'Footer content: column 1 brand, licences, socials and contact; services, areas and company columns; © name, ABN and legal links; no CTA, \"based in\", hours or guides column.' },\n { id: \"K2\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"Every rendered shared component has a skin entry; no reference-skin class ships.\" },\n { id: \"K11\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"Density: no section with more than one H2, more than 8 cards or more than 2 calls to action; measure and section padding within limits.\" },\n { id: \"K12-contact\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"The contact page is the page header, then the contact block, and nothing else.\" },\n { id: \"T6\", tier: \"enquiry\", from: \"3.11.0\", kind: \"static\", summary: \"Every image has a media.ts entry with provenance.\" },\n { id: \"T7\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"No image is the main image of two pages.\" },\n { id: \"T8\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: 'Prices: GST flag set; no \"+ GST\" or \"from $X\" teasers on cards and menus; one band per job site-wide.' },\n { id: \"T9\", tier: \"enquiry\", from: \"3.11.0\", kind: \"static\", summary: \"No image in /public carries location metadata.\" },\n { id: \"H13\", tier: \"enquiry\", from: \"3.14.0\", kind: \"static\", notRunBlocks: \"launch\", summary: \"The business inbox can receive enquiries: a real sender (no .invalid, example or sandbox domain), MX records for the inbox, the sender domain verified in Resend and the inbox not on Resend's suppression list (not run without the inbox variable and a full-access Resend key in the shell, which blocks only at launch).\" },\n\n // full: page families, location estates, section manifests, links and the data contract\n { id: \"K12\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"Section manifest: every required section rendered or skipped with a data-backed reason; conditional sections render when their data exists; canonical or recorded order.\" },\n { id: \"K12-slots\", tier: \"full\", from: \"3.14.0\", kind: \"rendered\", summary: \"Trade lines inside sections (matrix part B): for trees, the council permit note in LocalFacts on suburb and service-in-suburb pages and the qualifications and insurance lines on the about page, or a data-backed reason they are hidden.\" },\n { id: \"E5\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No orphan indexable pages, from the route catalogue and link data.\" },\n { id: \"E10\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Phrase-owner uniqueness in strategy data; no service-in-suburb page for the suburb page phrase; no service-region hub for the main trade.\" },\n { id: \"L3\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Neighbours by real distance.\" },\n { id: \"L4\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"No copy rotated by page address, no name-swapped FAQs, no other suburb's job without the nearby label.\" },\n { id: \"L6\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Location pages have 6 or more inbound links from 3 or more page types.\" },\n { id: \"L7\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Location pages are 3 clicks or fewer from home.\" },\n { id: \"L8\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"A hub per family per region.\" },\n { id: \"L9\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"Data contract on service-in-suburb pages: typical prices or the cost-guide link, reviews or their fallback, the quote path (form with the suburb prefilled, phone, call bar); the nearby job card or none.\" },\n { id: \"L10\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: 'No kilometre figures, \"km from\", \"based in\" or year stamps as local detail in location copy.' },\n { id: \"S4\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No hand-written per-service or per-suburb files; no dynamic route with zero pages.\" },\n { id: \"W9\", tier: \"full\", from: \"3.13.0\", kind: \"journey\", summary: \"URL variants: an unknown slug in each family answers the shared 404, the uppercase slug the same 404 or one permanent redirect to its lowercase form; a tracking query serves the same cached page as the clean URL.\" },\n { id: \"W9-params\", tier: \"full\", from: \"3.13.0\", kind: \"static\", summary: \"Pages under a dynamic route never read searchParams, so a query string cannot make an estate page dynamic.\" },\n { id: \"LOC-INDEX\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No location route is noindex or left out of the sitemap unless the recorded wave schedule dates it (Romie's 20 Oct decision: every suburb page stays indexed).\" },\n { id: \"E1-pattern\", tier: \"full\", from: \"3.14.0\", kind: \"rendered\", summary: 'Location pages outside the recorded titles have the H1 \"{Service} {Place}\" (no \"in\", no \"the\") and a title that starts with it, at most 60 characters.' },\n { id: \"W3\", tier: \"full\", from: \"3.13.0\", kind: \"static\", summary: \"No estate family (suburb, service-in-suburb, region, council) refreshes more often than every 7 days unless siteSystem.refreshExceptions records the reason and a decision id.\" },\n\n // new builds and redesigns\n { id: \"D4\", tier: \"new-build\", from: \"3.8.0\", kind: \"static\", summary: \"Same-trade distinctness: at least four of seven look levers differ from every listed competitor (advisory in 3.8.0, blocking from 3.9.0).\" },\n] as const satisfies readonly SiteSystemCheck[];\n\nexport type SiteSystemCheckId = (typeof SITE_SYSTEM_CHECKS)[number][\"id\"];\n\nconst TIER_RANK: Record<SiteSystemTier, number> = { shell: 0, enquiry: 1, full: 2 };\n\n/** A release as one comparable number: \"3.8.0\" < \"3.10.0\". */\nexport function siteSystemReleaseRank(version: string): number {\n const [major = 0, minor = 0, patch = 0] = version.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);\n return major * 1_000_000 + minor * 1_000 + patch;\n}\n\n/**\n * Whether a check blocks for a site at `tier` on `release`. Checks of higher tiers, and checks newer than the\n * installed release, report without blocking. D4 blocks new builds and redesigns from 3.9.0 whatever their tier.\n */\nexport function siteSystemCheckBlocks(\n check: Pick<SiteSystemCheck, \"id\" | \"tier\" | \"from\">,\n site: { tier: SiteSystemTier; release: string; newBuild?: boolean },\n): boolean {\n if (check.tier === \"new-build\") return site.newBuild === true && siteSystemReleaseRank(site.release) >= siteSystemReleaseRank(\"3.9.0\");\n if (siteSystemReleaseRank(site.release) < siteSystemReleaseRank(check.from)) return false;\n return TIER_RANK[check.tier] <= TIER_RANK[site.tier];\n}\n\n/** One result of one check, from any runner. `brandlab-ready` merges them into the ready report. */\nexport interface SiteSystemFinding {\n readonly checkId: SiteSystemCheckId;\n /** `not-run`: the rule could not judge the check here (a missing key or input); it names what to supply. */\n readonly status: \"pass\" | \"fail\" | \"not-run\" | \"not-applicable\";\n /** The page path or file the result is about, when it is about one. */\n readonly target?: string;\n /** One plain sentence: what passed or what is wrong and where. */\n readonly message: string;\n /** A warning never blocks: the check still passes and the report shows \"pass (warning)\". */\n readonly severity?: \"warning\";\n}\n"]}
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/site-system/checks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Site System v2 ready gate: every check, the adoption tier it blocks at and the release that brings it.\n *\n * A site declares `siteSystem: { tier }` in brandlab.config.ts. Checks of that tier and the tiers below it block;\n * checks of higher tiers report without blocking. A check blocks only from the release named here, so new checks\n * arrive with version bumps and never turn the fleet red at once. Holdouts use the project's Romie-owned\n * `exceptions` record. D6 (leave reviews as they are) removes every review-exclusion clause; D7 (no call tracking)\n * means no number-swap check.\n *\n * `kind` says where a check runs:\n * - `journey`: the Playwright site-system journeys (`defineBrandlabSiteSystemTests`), in the site's e2e run;\n * - `rendered`: rules over the built site's server HTML and assets (`brandlab-ready` reads them);\n * - `static`: source and data rules (`brandlab-verify` and `brandlab-ready`);\n * - `record`: launch-day and human steps recorded with `brandlab-ready record <id> --evidence <ref>`.\n */\n\nexport const SITE_SYSTEM_TIERS = [\"shell\", \"enquiry\", \"full\"] as const;\nexport type SiteSystemTier = (typeof SITE_SYSTEM_TIERS)[number];\n\n/** The design check D4 blocks every new build and redesign, whatever its tier. */\nexport type SiteSystemCheckScope = SiteSystemTier | \"new-build\";\n\nexport type SiteSystemCheckKind = \"journey\" | \"rendered\" | \"static\" | \"record\";\n\nexport interface SiteSystemCheck {\n readonly id: string;\n readonly tier: SiteSystemCheckScope;\n /** The first release in which the check blocks at its tier. */\n readonly from: `3.${number}.0`;\n readonly kind: SiteSystemCheckKind;\n readonly summary: string;\n /**\n * `launch`: a not-run result blocks only under `brandlab-ready --launch`, where it counts as a failure. For checks\n * that need production values in the shell (H13), so a CI run reports them as not run without failing the tier.\n */\n readonly notRunBlocks?: \"launch\";\n}\n\nexport const SITE_SYSTEM_CHECKS = [\n // shell: header, nav, call bar, placements, crawlable links, contrast and targets, call links, budgets, legal\n { id: \"N1\", tier: \"shell\", from: \"3.9.0\", kind: \"rendered\", summary: \"Required nav links, About in the first two, services before Areas, Contact last, labels from the closed list, recorded orders kept.\" },\n { id: \"N2\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every dropdown link and hub link is an <a href> in the server HTML.\" },\n { id: \"N3\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Hover opens after a short delay and a diagonal path stays open; mouse-out closes; nothing sticks open.\" },\n { id: \"N4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"A chevron click after hover keeps the panel open; a link click closes it; click, Enter and outside click work.\" },\n { id: \"N5\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Keyboard and ARIA: aria-expanded follows the panel, Arrow keys move, Escape closes and returns focus; axe passes open and closed.\" },\n { id: \"N6\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"The header stays at the top with the phone and quote visible at every desktop width; the compact band (1024 to 1199 px) shows them too.\" },\n { id: \"N8\", tier: \"shell\", from: \"3.9.0\", kind: \"journey\", summary: \"The drawer is a modal dialog that traps focus and resets; the call bar uses placement fab; back-to-top appears (from 3.11.0).\" },\n { id: \"K1\", tier: \"shell\", from: \"3.9.0\", kind: \"rendered\", summary: \"Shared components in use for the parts the tier covers (data-bl present); no literal tel: outside PhoneLink.\" },\n { id: \"K3\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Text contrast 4.5:1 and non-text 3:1 in the browser.\" },\n { id: \"K4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Targets at least 24 px, and 44 px for primary actions (call, quote, menu).\" },\n { id: \"K5\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Focus is never fully hidden by the sticky header or call bar at 390 px, in Chromium and WebKit.\" },\n { id: \"K6\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Phone and quote in the same relative order on every page, in the header, hero and call bar.\" },\n { id: \"K7\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Dropdown links, accordion and tab panels and full review text are in the server HTML.\" },\n { id: \"K10\", tier: \"shell\", from: \"3.8.0\", kind: \"record\", summary: \"Adoption parity in the lenient harness: no section, link or text lost against the baseline.\" },\n { id: \"C4\", tier: \"shell\", from: \"3.8.0\", kind: \"journey\", summary: \"Phone taps report an allowed placement (header, footer, hero, cta, fab, contact, location, page, emergency).\" },\n { id: \"C5\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Call-to-action labels come from the closed list or a recorded label.\" },\n { id: \"C9\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No interstitial overlay in the first screen.\" },\n { id: \"C10\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every tel: href is E.164 (national digits for 13, 1300 and 1800) and matches the evidence-file phone.\" },\n { id: \"E1\", tier: \"shell\", from: \"3.8.0\", kind: \"static\", summary: \"Title patterns on new or changed URLs; recorded titles unchanged.\" },\n { id: \"E2\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Exactly one H1 on every page, rendered once.\" },\n { id: \"E8\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No banned phrase from any of the three layers, environmental claims included.\" },\n { id: \"T3\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"A licence line renders wherever licensed work is advertised, with the licensee where it differs.\" },\n { id: \"T4\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"No hard-coded rating or review numbers; proof claims resolve to renderable evidence.\" },\n { id: \"T11\", tier: \"shell\", from: \"3.8.0\", kind: \"static\", summary: \"No page for work over $5,000 outside the licence classes held; licence conditions carried.\" },\n { id: \"X1\", tier: \"shell\", from: \"3.8.0\", kind: \"record\", summary: \"Legacy URLs answer 200 or one redirect hop; earning-URL depth, titles and dollar figures kept.\" },\n { id: \"B1\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: \"Lab budgets: JavaScript, third-party requests, hero image and HTML within budget; no unrecorded chat widget, pixel, heatmap or tag manager.\" },\n { id: \"L10-hero\", tier: \"shell\", from: \"3.8.0\", kind: \"rendered\", summary: 'No kilometre figures, \"km from\" or \"based in\" in hero copy.' },\n { id: \"W2\", tier: \"shell\", from: \"3.13.0\", kind: \"journey\", summary: \"Prefetch by link role at 390 x 844 and 1440 x 900: at most 8 pages on landing and 12 after a full scroll; none from footer, area, nearby, map or finder links, hidden links or the current page; prefetch bytes above twice the page's HTML warn.\" },\n { id: \"W5\", tier: \"shell\", from: \"3.13.0\", kind: \"static\", summary: \"vercel.json regions equal hosting.originRegion (syd1 by default); any other region has an origin-region-v1 decision whose reviewBy has not passed.\" },\n { id: \"W10\", tier: \"shell\", from: \"3.13.0\", kind: \"rendered\", summary: \"robots.txt and the recorded firewall rules leave every search and answer crawler open; no Crawl-delay for them.\" },\n\n // enquiry: the form, thank-you page, reviews, proof strip, footer, CTA band, contact page, prices, media, density\n { id: \"C1\", tier: \"enquiry\", from: \"3.8.0\", kind: \"journey\", summary: \"Money pages: the form in the first section; at 390 x 844 the H1, a call and a quote action in the first viewport; at 1440 x 900 the form.\" },\n { id: \"C2\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: 'Form layout and labels; phone and email required; no helper or reassurance lines, no \"(required)\", no job select.' },\n { id: \"E9\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Every cost guide carries at least 8 dollar figures in a renderable price state.\" },\n { id: \"T5\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Reviews from the feed, with Google's rating, count and link beside any selection; none on the contact page; no empty reviews section.\" },\n { id: \"K8\", tier: \"enquiry\", from: \"3.8.0\", kind: \"rendered\", summary: \"Section contract: no empty section, one H2 each, at most one band tone, neighbours differ, proof within the first five sections.\" },\n { id: \"C7\", tier: \"enquiry\", from: \"3.10.0\", kind: \"rendered\", summary: \"Thank-you page: a reply time, a tel: link and noindex, follow.\" },\n { id: \"C8\", tier: \"enquiry\", from: \"3.10.0\", kind: \"rendered\", summary: \"Forgiving form: tolerant phone input (13, 1300 and 1800 too), autocomplete tokens, no capture on the photo input.\" },\n { id: \"C11\", tier: \"enquiry\", from: \"3.10.0\", kind: \"static\", summary: \"The test marker is stripped before delivery and reporting; /api/contact is rate-limited.\" },\n { id: \"N9\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: 'Footer content: column 1 brand, licences, socials and contact; services, areas and company columns; © name, ABN and legal links; no CTA, \"based in\", hours or guides column.' },\n { id: \"K2\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"Every rendered shared component has a skin entry; no reference-skin class ships.\" },\n { id: \"K11\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"Density: no section with more than one H2, more than 8 cards or more than 2 calls to action; measure and section padding within limits.\" },\n { id: \"K12-contact\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"The contact page is the page header, then the contact block, and nothing else.\" },\n { id: \"T6\", tier: \"enquiry\", from: \"3.11.0\", kind: \"static\", summary: \"Every image has a media.ts entry with provenance.\" },\n { id: \"T7\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: \"No image is the main image of two pages.\" },\n { id: \"T8\", tier: \"enquiry\", from: \"3.11.0\", kind: \"rendered\", summary: 'Prices: GST flag set; no \"+ GST\" or \"from $X\" teasers on cards and menus; one band per job site-wide.' },\n { id: \"T9\", tier: \"enquiry\", from: \"3.11.0\", kind: \"static\", summary: \"No image in /public carries location metadata.\" },\n { id: \"H13\", tier: \"enquiry\", from: \"3.14.0\", kind: \"static\", notRunBlocks: \"launch\", summary: \"The business inbox can receive enquiries: a real sender (no .invalid, example or sandbox domain), MX records for the inbox, the sender domain verified in Resend and the inbox not on Resend's suppression list (not run without the inbox variable and a full-access Resend key in the shell, which blocks only at launch).\" },\n\n // full: page families, location estates, section manifests, links and the data contract\n { id: \"K12\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"Section manifest: every required section rendered or skipped with a data-backed reason; conditional sections render when their data exists; canonical or recorded order.\" },\n { id: \"K12-slots\", tier: \"full\", from: \"3.14.0\", kind: \"rendered\", summary: \"Trade lines inside sections (matrix part B): for trees, the council permit note in LocalFacts on suburb and service-in-suburb pages and the qualifications and insurance lines on the about page, or a data-backed reason they are hidden.\" },\n { id: \"K13\", tier: \"full\", from: \"3.16.0\", kind: \"rendered\", summary: \"Signature sections: a name, at most two on the home page and one on a service page, none elsewhere, as the manifest lists them; the home page's are the ones strategy.ts records for D4.\" },\n { id: \"E5\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No orphan indexable pages, from the route catalogue and link data.\" },\n { id: \"E10\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Phrase-owner uniqueness in strategy data; no service-in-suburb page for the suburb page phrase; no service-region hub for the main trade.\" },\n { id: \"L3\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Neighbours by real distance.\" },\n { id: \"L4\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"No copy rotated by page address, no name-swapped FAQs, no other suburb's job without the nearby label.\" },\n { id: \"L6\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Location pages have 6 or more inbound links from 3 or more page types.\" },\n { id: \"L7\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"Location pages are 3 clicks or fewer from home.\" },\n { id: \"L8\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"A hub per family per region.\" },\n { id: \"L9\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: \"Data contract on service-in-suburb pages: typical prices or the cost-guide link, reviews or their fallback, the quote path (form with the suburb prefilled, phone, call bar); the nearby job card or none.\" },\n { id: \"L10\", tier: \"full\", from: \"3.12.0\", kind: \"rendered\", summary: 'No kilometre figures, \"km from\", \"based in\" or year stamps as local detail in location copy.' },\n { id: \"S4\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No hand-written per-service or per-suburb files; no dynamic route with zero pages.\" },\n { id: \"W9\", tier: \"full\", from: \"3.13.0\", kind: \"journey\", summary: \"URL variants: an unknown slug in each family answers the shared 404, the uppercase slug the same 404 or one permanent redirect to its lowercase form; a tracking query serves the same cached page as the clean URL.\" },\n { id: \"W9-params\", tier: \"full\", from: \"3.13.0\", kind: \"static\", summary: \"Pages under a dynamic route never read searchParams, so a query string cannot make an estate page dynamic.\" },\n { id: \"LOC-INDEX\", tier: \"full\", from: \"3.12.0\", kind: \"static\", summary: \"No location route is noindex or left out of the sitemap unless the recorded wave schedule dates it (Romie's 20 Oct decision: every suburb page stays indexed).\" },\n { id: \"E1-pattern\", tier: \"full\", from: \"3.14.0\", kind: \"rendered\", summary: 'Location pages outside the recorded titles have the H1 \"{Service} {Place}\" (no \"in\", no \"the\") and a title that starts with it, at most 60 characters.' },\n { id: \"W3\", tier: \"full\", from: \"3.13.0\", kind: \"static\", summary: \"No estate family (suburb, service-in-suburb, region, council) refreshes more often than every 7 days unless siteSystem.refreshExceptions records the reason and a decision id.\" },\n\n // new builds and redesigns\n { id: \"D4\", tier: \"new-build\", from: \"3.8.0\", kind: \"static\", summary: \"Same-trade distinctness: at least four of seven look levers differ from every listed competitor (advisory in 3.8.0, blocking from 3.9.0).\" },\n] as const satisfies readonly SiteSystemCheck[];\n\nexport type SiteSystemCheckId = (typeof SITE_SYSTEM_CHECKS)[number][\"id\"];\n\nconst TIER_RANK: Record<SiteSystemTier, number> = { shell: 0, enquiry: 1, full: 2 };\n\n/** A release as one comparable number: \"3.8.0\" < \"3.10.0\". */\nexport function siteSystemReleaseRank(version: string): number {\n const [major = 0, minor = 0, patch = 0] = version.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);\n return major * 1_000_000 + minor * 1_000 + patch;\n}\n\n/**\n * Whether a check blocks for a site at `tier` on `release`. Checks of higher tiers, and checks newer than the\n * installed release, report without blocking. D4 blocks new builds and redesigns from 3.9.0 whatever their tier.\n */\nexport function siteSystemCheckBlocks(\n check: Pick<SiteSystemCheck, \"id\" | \"tier\" | \"from\">,\n site: { tier: SiteSystemTier; release: string; newBuild?: boolean },\n): boolean {\n if (check.tier === \"new-build\") return site.newBuild === true && siteSystemReleaseRank(site.release) >= siteSystemReleaseRank(\"3.9.0\");\n if (siteSystemReleaseRank(site.release) < siteSystemReleaseRank(check.from)) return false;\n return TIER_RANK[check.tier] <= TIER_RANK[site.tier];\n}\n\n/** One result of one check, from any runner. `brandlab-ready` merges them into the ready report. */\nexport interface SiteSystemFinding {\n readonly checkId: SiteSystemCheckId;\n /** `not-run`: the rule could not judge the check here (a missing key or input); it names what to supply. */\n readonly status: \"pass\" | \"fail\" | \"not-run\" | \"not-applicable\";\n /** The page path or file the result is about, when it is about one. */\n readonly target?: string;\n /** One plain sentence: what passed or what is wrong and where. */\n readonly message: string;\n /** A warning never blocks: the check still passes and the report shows \"pass (warning)\". */\n readonly severity?: \"warning\";\n}\n"]}
@@ -30,6 +30,8 @@ export interface RecordedStrategy {
30
30
  look: RecordedLook;
31
31
  competitors: RecordedCompetitor[];
32
32
  }
33
+ /** The site's own recorded look (K13 reads its home signatures, 3.16.0); null when there is none. */
34
+ export declare function readRecordedLook(projectRoot: string): RecordedLook | null;
33
35
  /** Reads the strategy record without running the site. Returns a string when it cannot be read as plain data. */
34
36
  export declare function readStrategy(projectRoot: string): RecordedStrategy | string | null;
35
37
  export declare function checkD4(projectRoot: string): SiteSystemFinding[];
@@ -1 +1 @@
1
- {"version":3,"file":"d4-distinctness.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/design/d4-distinctness.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAmD,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEtG,2CAA2C;AAC3C,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,wHAAwH;AACxH,eAAO,MAAM,SAAS,sHAQZ,CAAC;AACX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAMlC,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACpB;AAWD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AAmCD,0GAA0G;AAC1G,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA4BhF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,EAAE,CAAC;IACrB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,4FAA4F;IAC5F,UAAU,EAAE,OAAO,EAAE,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,CAUnF;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAYD,iHAAiH;AACjH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAwBlF;AAMD,wBAAgB,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAwChE"}
1
+ {"version":3,"file":"d4-distinctness.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/design/d4-distinctness.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAmD,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEtG,2CAA2C;AAC3C,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,wHAAwH;AACxH,eAAO,MAAM,SAAS,sHAQZ,CAAC;AACX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAMlC,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACpB;AAWD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AAmCD,0GAA0G;AAC1G,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA4BhF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,EAAE,CAAC;IACrB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,4FAA4F;IAC5F,UAAU,EAAE,OAAO,EAAE,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,CAUnF;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AA0BD,qGAAqG;AACrG,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAGzE;AAED,iHAAiH;AACjH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAiBlF;AAMD,wBAAgB,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAwChE"}
@@ -129,8 +129,8 @@ function readLook(value) {
129
129
  }
130
130
  return look;
131
131
  }
132
- /** Reads the strategy record without running the site. Returns a string when it cannot be read as plain data. */
133
- export function readStrategy(projectRoot) {
132
+ /** The strategy record as plain data; a string when it cannot be read as plain data, null when there is none. */
133
+ function strategyRecord(projectRoot) {
134
134
  const file = join(projectRoot, D4_STRATEGY_FILE);
135
135
  if (!existsSync(file))
136
136
  return null;
@@ -142,6 +142,18 @@ export function readStrategy(projectRoot) {
142
142
  if (!isLiteralObject(value) || !isFullyResolved(value)) {
143
143
  return `${D4_STRATEGY_FILE} is not plain data, so the look levers cannot be read; write the strategy as a literal.`;
144
144
  }
145
+ return value;
146
+ }
147
+ /** The site's own recorded look (K13 reads its home signatures, 3.16.0); null when there is none. */
148
+ export function readRecordedLook(projectRoot) {
149
+ const value = strategyRecord(projectRoot);
150
+ return value === null || typeof value === "string" ? null : readLook(value["look"]);
151
+ }
152
+ /** Reads the strategy record without running the site. Returns a string when it cannot be read as plain data. */
153
+ export function readStrategy(projectRoot) {
154
+ const value = strategyRecord(projectRoot);
155
+ if (value === null || typeof value === "string")
156
+ return value;
145
157
  const look = readLook(value["look"]);
146
158
  // defineSiteStrategy accepts a record without these, but D4 cannot pass a new build or redesign without them.
147
159
  if (!look)
@@ -1 +1 @@
1
- {"version":3,"file":"d4-distinctness.js","sourceRoot":"","sources":["../../../../src/site-system/rules/design/d4-distinctness.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * D4: same-trade distinctness on the look levers (Site System v2 §7.7).\n *\n * `src/data/strategy.ts` records the site's own look and every same-trade competitor's. The rule compares the site\n * with each competitor lever by lever and fails when fewer than four of the seven differ from any one of them. The\n * seven are the display font, the action-colour family, the hero variant, the header variant, the card style (radius,\n * border and shadow tokens), the photo treatment (crop ratio, scrim and frame) and the home page's section order and\n * signature sections. A shared display font counts as the same lever, an action hue within 20 degrees counts as the\n * same, and a card style, photo treatment or home page layout is the same only when every part of it matches. The\n * levers are the recorded values: this reads the record, it does not measure the rendered look.\n *\n * D4 blocks every new build and redesign from 3.9.0 and is advisory before that (`siteSystemCheckBlocks`).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { LiteralReader, isFullyResolved, isLiteralObject, type Literal } from \"../enquiry/literal.js\";\n\n/** Where the site records its strategy. */\nexport const D4_STRATEGY_FILE = \"src/data/strategy.ts\";\n\n/** The recorded levers (spec §7.7), as site-core's `SITE_LOOK_LEVERS` lists them (a test keeps the two lists equal). */\nexport const D4_LEVERS = [\n \"displayFont\",\n \"actionHue\",\n \"heroVariant\",\n \"headerVariant\",\n \"cardStyle\",\n \"photoTreatment\",\n \"homeSections\",\n] as const;\nexport type D4Lever = (typeof D4_LEVERS)[number];\n\n/** An action hue this close to a competitor's, in degrees, counts as the same. */\nexport const D4_SAME_HUE_DEGREES = 20;\n/** At least this many levers must differ from every competitor. */\nexport const D4_MIN_DIFFERING = 4;\n\n/** The parts of the levers that record several values. All of them must match for the lever to be the same. */\nconst CARD_STYLE_PARTS = [\"radius\", \"border\", \"shadow\"] as const;\nconst PHOTO_TREATMENT_PARTS = [\"cropRatio\", \"scrim\", \"frame\"] as const;\n\nexport type RecordedLook = Partial<Record<D4Lever, Literal>>;\n\nexport interface RecordedCompetitor {\n name: string;\n domain: string;\n look: RecordedLook;\n}\n\n/** The first family of a font stack, without quotes, lowercased: `\"Plus Jakarta Sans\", sans-serif` is `plus jakarta sans`. */\nfunction fontFamily(value: string): string {\n return (value.split(\",\")[0] ?? \"\").replace(/[\"']/g, \"\").trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\nfunction words(value: string): string {\n return value.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/** Smallest angle between two hues, in degrees, on the colour wheel. */\nexport function hueDistance(left: number, right: number): number {\n const gap = Math.abs(left - right) % 360;\n return Math.min(gap, 360 - gap);\n}\n\nfunction textParts(value: Literal | undefined, parts: readonly string[]): string[] | null {\n if (!isLiteralObject(value)) return null;\n const found = parts.map((part) => value[part]);\n return found.every((item): item is string => typeof item === \"string\" && item.trim() !== \"\") ? found.map(words) : null;\n}\n\nfunction textList(value: Literal | undefined): string[] | null {\n return Array.isArray(value) && value.every((item): item is string => typeof item === \"string\" && item.trim() !== \"\") ? value.map(words) : null;\n}\n\nfunction homeSections(value: Literal | undefined): { order: string[]; signatures: string[] } | null {\n if (!isLiteralObject(value)) return null;\n const order = textList(value[\"sectionOrder\"]);\n const signatures = textList(value[\"signatures\"]);\n return order && order.length > 0 && signatures ? { order, signatures: [...new Set(signatures)].sort() } : null;\n}\n\n/** The lever's value if it is recorded in the shape the lever needs, otherwise undefined. */\nfunction recordedLever(lever: D4Lever, value: Literal | undefined): Literal | undefined {\n switch (lever) {\n case \"actionHue\":\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n case \"cardStyle\":\n return textParts(value, CARD_STYLE_PARTS) ? value : undefined;\n case \"photoTreatment\":\n return textParts(value, PHOTO_TREATMENT_PARTS) ? value : undefined;\n case \"homeSections\":\n return homeSections(value) ? value : undefined;\n default:\n return typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n }\n}\n\n/** Whether one lever is the same in both looks. A lever recorded in the wrong shape is never the same. */\nexport function sameLever(lever: D4Lever, left: Literal, right: Literal): boolean {\n switch (lever) {\n case \"actionHue\":\n return typeof left === \"number\" && typeof right === \"number\" && hueDistance(left, right) <= D4_SAME_HUE_DEGREES;\n case \"displayFont\":\n return typeof left === \"string\" && typeof right === \"string\" && fontFamily(left) === fontFamily(right);\n case \"cardStyle\":\n case \"photoTreatment\": {\n const parts = lever === \"cardStyle\" ? CARD_STYLE_PARTS : PHOTO_TREATMENT_PARTS;\n const a = textParts(left, parts);\n const b = textParts(right, parts);\n return a !== null && b !== null && a.every((part, index) => part === b[index]);\n }\n case \"homeSections\": {\n const a = homeSections(left);\n const b = homeSections(right);\n return (\n a !== null &&\n b !== null &&\n a.order.length === b.order.length &&\n a.order.every((id, index) => id === b.order[index]) &&\n a.signatures.length === b.signatures.length &&\n a.signatures.every((name, index) => name === b.signatures[index])\n );\n }\n default:\n return typeof left === \"string\" && typeof right === \"string\" && words(left) === words(right);\n }\n}\n\nexport interface LookComparison {\n differing: D4Lever[];\n same: D4Lever[];\n /** Levers that are not recorded on one side; they can be neither the same nor different. */\n unrecorded: D4Lever[];\n}\n\nexport function compareLooks(own: RecordedLook, rival: RecordedLook): LookComparison {\n const result: LookComparison = { differing: [], same: [], unrecorded: [] };\n for (const lever of D4_LEVERS) {\n const left = recordedLever(lever, own[lever]);\n const right = recordedLever(lever, rival[lever]);\n if (left === undefined || right === undefined) result.unrecorded.push(lever);\n else if (sameLever(lever, left, right)) result.same.push(lever);\n else result.differing.push(lever);\n }\n return result;\n}\n\nexport interface RecordedStrategy {\n look: RecordedLook;\n competitors: RecordedCompetitor[];\n}\n\nfunction readLook(value: Literal | undefined): RecordedLook | null {\n if (!isLiteralObject(value)) return null;\n const look: RecordedLook = {};\n for (const lever of D4_LEVERS) {\n const recorded = recordedLever(lever, value[lever]);\n if (recorded !== undefined) look[lever] = recorded;\n }\n return look;\n}\n\n/** Reads the strategy record without running the site. Returns a string when it cannot be read as plain data. */\nexport function readStrategy(projectRoot: string): RecordedStrategy | string | null {\n const file = join(projectRoot, D4_STRATEGY_FILE);\n if (!existsSync(file)) return null;\n const reader = new LiteralReader(readFileSync(file, \"utf8\"), D4_STRATEGY_FILE);\n const argument = reader.callArguments(\"defineSiteStrategy\")[0] ?? reader.constant(\"strategy\");\n if (!argument) return `${D4_STRATEGY_FILE} does not define the strategy with defineSiteStrategy({ … }).`;\n const value = reader.evaluate(argument);\n if (!isLiteralObject(value) || !isFullyResolved(value)) {\n return `${D4_STRATEGY_FILE} is not plain data, so the look levers cannot be read; write the strategy as a literal.`;\n }\n const look = readLook(value[\"look\"]);\n // defineSiteStrategy accepts a record without these, but D4 cannot pass a new build or redesign without them.\n if (!look) return `${D4_STRATEGY_FILE} has no \"look\" for the site; a new build or redesign must record it.`;\n const rawCompetitors = value[\"competitors\"];\n if (!Array.isArray(rawCompetitors)) return `${D4_STRATEGY_FILE} has no \"competitors\" list; a new build or redesign must record it.`;\n const competitors: RecordedCompetitor[] = [];\n for (const [index, item] of rawCompetitors.entries()) {\n const competitorLook = isLiteralObject(item) ? readLook(item[\"look\"]) : null;\n if (!isLiteralObject(item) || !competitorLook || typeof item[\"domain\"] !== \"string\") {\n return `${D4_STRATEGY_FILE} competitor ${index + 1} needs a domain and a look.`;\n }\n competitors.push({ name: typeof item[\"name\"] === \"string\" ? item[\"name\"] : item[\"domain\"], domain: item[\"domain\"], look: competitorLook });\n }\n return { look, competitors };\n}\n\nfunction list(levers: readonly string[]): string {\n return levers.join(\", \");\n}\n\nexport function checkD4(projectRoot: string): SiteSystemFinding[] {\n const strategy = readStrategy(projectRoot);\n if (strategy === null) {\n return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: `The site has no ${D4_STRATEGY_FILE}, so its look cannot be compared with its competitors'.` }];\n }\n if (typeof strategy === \"string\") return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: strategy }];\n if (strategy.competitors.length === 0) {\n return [{ checkId: \"D4\", status: \"not-applicable\", message: \"The strategy lists no competitors, so there is no look to compare against.\" }];\n }\n\n const ownMissing = D4_LEVERS.filter((lever) => strategy.look[lever] === undefined);\n if (ownMissing.length > 0) {\n return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: `The site's own look does not record ${list(ownMissing)}, so it cannot be compared.` }];\n }\n\n const failures: SiteSystemFinding[] = [];\n let fewest = { domain: \"\", count: Number.POSITIVE_INFINITY };\n for (const competitor of strategy.competitors) {\n const comparison = compareLooks(strategy.look, competitor.look);\n if (comparison.unrecorded.length > 0) {\n failures.push({ checkId: \"D4\", status: \"fail\", target: competitor.domain, message: `The look recorded for ${competitor.domain} is missing ${list(comparison.unrecorded)}, so it cannot be compared.` });\n continue;\n }\n if (comparison.differing.length < fewest.count) fewest = { domain: competitor.domain, count: comparison.differing.length };\n if (comparison.differing.length < D4_MIN_DIFFERING) {\n failures.push({\n checkId: \"D4\",\n status: \"fail\",\n target: competitor.domain,\n message: `The look is too close to ${competitor.domain}: ${comparison.differing.length} of ${D4_LEVERS.length} levers differ and at least ${D4_MIN_DIFFERING} must (same: ${list(comparison.same)}).`,\n });\n }\n }\n if (failures.length > 0) return failures;\n const count = strategy.competitors.length;\n return [{\n checkId: \"D4\",\n status: \"pass\",\n message: `The look differs from ${count === 1 ? \"the listed competitor\" : `all ${count} listed competitors`} on at least ${D4_MIN_DIFFERING} of ${D4_LEVERS.length} levers (closest: ${fewest.domain} with ${fewest.count}).`,\n }];\n}\n"]}
1
+ {"version":3,"file":"d4-distinctness.js","sourceRoot":"","sources":["../../../../src/site-system/rules/design/d4-distinctness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAgB,MAAM,uBAAuB,CAAC;AAEtG,2CAA2C;AAC3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD,wHAAwH;AACxH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,aAAa;IACb,WAAW;IACX,aAAa;IACb,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,cAAc;CACN,CAAC;AAGX,kFAAkF;AAClF,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AACtC,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,+GAA+G;AAC/G,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AACjE,MAAM,qBAAqB,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAU,CAAC;AAUvE,8HAA8H;AAC9H,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACpG,CAAC;AAED,SAAS,KAAK,CAAC,KAAa;IAC1B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;IACzC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,SAAS,CAAC,KAA0B,EAAE,KAAwB;IACrE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzH,CAAC;AAED,SAAS,QAAQ,CAAC,KAA0B;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjJ,CAAC;AAED,SAAS,YAAY,CAAC,KAA0B;IAC9C,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IACjD,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACjH,CAAC;AAED,6FAA6F;AAC7F,SAAS,aAAa,CAAC,KAAc,EAAE,KAA0B;IAC/D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,WAAW;YACd,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACjF,KAAK,WAAW;YACd,OAAO,SAAS,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAChE,KAAK,gBAAgB;YACnB,OAAO,SAAS,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACrE,KAAK,cAAc;YACjB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD;YACE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,CAAC;AACH,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,SAAS,CAAC,KAAc,EAAE,IAAa,EAAE,KAAc;IACrE,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,WAAW;YACd,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,mBAAmB,CAAC;QAClH,KAAK,aAAa;YAChB,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;QACzG,KAAK,WAAW,CAAC;QACjB,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,MAAM,KAAK,GAAG,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,qBAAqB,CAAC;YAC/E,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACjC,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAClC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO,CACL,CAAC,KAAK,IAAI;gBACV,CAAC,KAAK,IAAI;gBACV,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM;gBACjC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACnD,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM;gBAC3C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAClE,CAAC;QACJ,CAAC;QACD;YACE,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC;IACjG,CAAC;AACH,CAAC;AASD,MAAM,UAAU,YAAY,CAAC,GAAiB,EAAE,KAAmB;IACjE,MAAM,MAAM,GAAmB,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC3E,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACxE,IAAI,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC3D,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAOD,SAAS,QAAQ,CAAC,KAA0B;IAC1C,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,IAAI,GAAiB,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACpD,IAAI,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;IACrD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iHAAiH;AACjH,SAAS,cAAc,CAAC,WAAmB;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC9F,IAAI,CAAC,QAAQ;QAAE,OAAO,GAAG,gBAAgB,+DAA+D,CAAC;IACzG,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,OAAO,GAAG,gBAAgB,yFAAyF,CAAC;IACtH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAC1C,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACrC,8GAA8G;IAC9G,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,gBAAgB,sEAAsE,CAAC;IAC5G,MAAM,cAAc,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;QAAE,OAAO,GAAG,gBAAgB,qEAAqE,CAAC;IACpI,MAAM,WAAW,GAAyB,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;QACrD,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7E,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;YACpF,OAAO,GAAG,gBAAgB,eAAe,KAAK,GAAG,CAAC,6BAA6B,CAAC;QAClF,CAAC;QACD,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IAC7I,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,IAAI,CAAC,MAAyB;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,WAAmB;IACzC,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,mBAAmB,gBAAgB,yDAAyD,EAAE,CAAC,CAAC;IAC9K,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1H,IAAI,QAAQ,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,4EAA4E,EAAE,CAAC,CAAC;IAC9I,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC;IACnF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,uCAAuC,IAAI,CAAC,UAAU,CAAC,6BAA6B,EAAE,CAAC,CAAC;IACtK,CAAC;IAED,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,IAAI,MAAM,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;IAC7D,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,yBAAyB,UAAU,CAAC,MAAM,eAAe,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,6BAA6B,EAAE,CAAC,CAAC;YACxM,SAAS;QACX,CAAC;QACD,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK;YAAE,MAAM,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAC3H,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YACnD,QAAQ,CAAC,IAAI,CAAC;gBACZ,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,OAAO,EAAE,4BAA4B,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,SAAS,CAAC,MAAM,OAAO,SAAS,CAAC,MAAM,+BAA+B,gBAAgB,gBAAgB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI;aACtM,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC;IAC1C,OAAO,CAAC;YACN,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,yBAAyB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,OAAO,KAAK,qBAAqB,gBAAgB,gBAAgB,OAAO,SAAS,CAAC,MAAM,qBAAqB,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,KAAK,IAAI;SAC9N,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * D4: same-trade distinctness on the look levers (Site System v2 §7.7).\n *\n * `src/data/strategy.ts` records the site's own look and every same-trade competitor's. The rule compares the site\n * with each competitor lever by lever and fails when fewer than four of the seven differ from any one of them. The\n * seven are the display font, the action-colour family, the hero variant, the header variant, the card style (radius,\n * border and shadow tokens), the photo treatment (crop ratio, scrim and frame) and the home page's section order and\n * signature sections. A shared display font counts as the same lever, an action hue within 20 degrees counts as the\n * same, and a card style, photo treatment or home page layout is the same only when every part of it matches. The\n * levers are the recorded values: this reads the record, it does not measure the rendered look.\n *\n * D4 blocks every new build and redesign from 3.9.0 and is advisory before that (`siteSystemCheckBlocks`).\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SiteSystemFinding } from \"../../checks.js\";\nimport { LiteralReader, isFullyResolved, isLiteralObject, type Literal } from \"../enquiry/literal.js\";\n\n/** Where the site records its strategy. */\nexport const D4_STRATEGY_FILE = \"src/data/strategy.ts\";\n\n/** The recorded levers (spec §7.7), as site-core's `SITE_LOOK_LEVERS` lists them (a test keeps the two lists equal). */\nexport const D4_LEVERS = [\n \"displayFont\",\n \"actionHue\",\n \"heroVariant\",\n \"headerVariant\",\n \"cardStyle\",\n \"photoTreatment\",\n \"homeSections\",\n] as const;\nexport type D4Lever = (typeof D4_LEVERS)[number];\n\n/** An action hue this close to a competitor's, in degrees, counts as the same. */\nexport const D4_SAME_HUE_DEGREES = 20;\n/** At least this many levers must differ from every competitor. */\nexport const D4_MIN_DIFFERING = 4;\n\n/** The parts of the levers that record several values. All of them must match for the lever to be the same. */\nconst CARD_STYLE_PARTS = [\"radius\", \"border\", \"shadow\"] as const;\nconst PHOTO_TREATMENT_PARTS = [\"cropRatio\", \"scrim\", \"frame\"] as const;\n\nexport type RecordedLook = Partial<Record<D4Lever, Literal>>;\n\nexport interface RecordedCompetitor {\n name: string;\n domain: string;\n look: RecordedLook;\n}\n\n/** The first family of a font stack, without quotes, lowercased: `\"Plus Jakarta Sans\", sans-serif` is `plus jakarta sans`. */\nfunction fontFamily(value: string): string {\n return (value.split(\",\")[0] ?? \"\").replace(/[\"']/g, \"\").trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\nfunction words(value: string): string {\n return value.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/** Smallest angle between two hues, in degrees, on the colour wheel. */\nexport function hueDistance(left: number, right: number): number {\n const gap = Math.abs(left - right) % 360;\n return Math.min(gap, 360 - gap);\n}\n\nfunction textParts(value: Literal | undefined, parts: readonly string[]): string[] | null {\n if (!isLiteralObject(value)) return null;\n const found = parts.map((part) => value[part]);\n return found.every((item): item is string => typeof item === \"string\" && item.trim() !== \"\") ? found.map(words) : null;\n}\n\nfunction textList(value: Literal | undefined): string[] | null {\n return Array.isArray(value) && value.every((item): item is string => typeof item === \"string\" && item.trim() !== \"\") ? value.map(words) : null;\n}\n\nfunction homeSections(value: Literal | undefined): { order: string[]; signatures: string[] } | null {\n if (!isLiteralObject(value)) return null;\n const order = textList(value[\"sectionOrder\"]);\n const signatures = textList(value[\"signatures\"]);\n return order && order.length > 0 && signatures ? { order, signatures: [...new Set(signatures)].sort() } : null;\n}\n\n/** The lever's value if it is recorded in the shape the lever needs, otherwise undefined. */\nfunction recordedLever(lever: D4Lever, value: Literal | undefined): Literal | undefined {\n switch (lever) {\n case \"actionHue\":\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n case \"cardStyle\":\n return textParts(value, CARD_STYLE_PARTS) ? value : undefined;\n case \"photoTreatment\":\n return textParts(value, PHOTO_TREATMENT_PARTS) ? value : undefined;\n case \"homeSections\":\n return homeSections(value) ? value : undefined;\n default:\n return typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n }\n}\n\n/** Whether one lever is the same in both looks. A lever recorded in the wrong shape is never the same. */\nexport function sameLever(lever: D4Lever, left: Literal, right: Literal): boolean {\n switch (lever) {\n case \"actionHue\":\n return typeof left === \"number\" && typeof right === \"number\" && hueDistance(left, right) <= D4_SAME_HUE_DEGREES;\n case \"displayFont\":\n return typeof left === \"string\" && typeof right === \"string\" && fontFamily(left) === fontFamily(right);\n case \"cardStyle\":\n case \"photoTreatment\": {\n const parts = lever === \"cardStyle\" ? CARD_STYLE_PARTS : PHOTO_TREATMENT_PARTS;\n const a = textParts(left, parts);\n const b = textParts(right, parts);\n return a !== null && b !== null && a.every((part, index) => part === b[index]);\n }\n case \"homeSections\": {\n const a = homeSections(left);\n const b = homeSections(right);\n return (\n a !== null &&\n b !== null &&\n a.order.length === b.order.length &&\n a.order.every((id, index) => id === b.order[index]) &&\n a.signatures.length === b.signatures.length &&\n a.signatures.every((name, index) => name === b.signatures[index])\n );\n }\n default:\n return typeof left === \"string\" && typeof right === \"string\" && words(left) === words(right);\n }\n}\n\nexport interface LookComparison {\n differing: D4Lever[];\n same: D4Lever[];\n /** Levers that are not recorded on one side; they can be neither the same nor different. */\n unrecorded: D4Lever[];\n}\n\nexport function compareLooks(own: RecordedLook, rival: RecordedLook): LookComparison {\n const result: LookComparison = { differing: [], same: [], unrecorded: [] };\n for (const lever of D4_LEVERS) {\n const left = recordedLever(lever, own[lever]);\n const right = recordedLever(lever, rival[lever]);\n if (left === undefined || right === undefined) result.unrecorded.push(lever);\n else if (sameLever(lever, left, right)) result.same.push(lever);\n else result.differing.push(lever);\n }\n return result;\n}\n\nexport interface RecordedStrategy {\n look: RecordedLook;\n competitors: RecordedCompetitor[];\n}\n\nfunction readLook(value: Literal | undefined): RecordedLook | null {\n if (!isLiteralObject(value)) return null;\n const look: RecordedLook = {};\n for (const lever of D4_LEVERS) {\n const recorded = recordedLever(lever, value[lever]);\n if (recorded !== undefined) look[lever] = recorded;\n }\n return look;\n}\n\n/** The strategy record as plain data; a string when it cannot be read as plain data, null when there is none. */\nfunction strategyRecord(projectRoot: string): Record<string, Literal> | string | null {\n const file = join(projectRoot, D4_STRATEGY_FILE);\n if (!existsSync(file)) return null;\n const reader = new LiteralReader(readFileSync(file, \"utf8\"), D4_STRATEGY_FILE);\n const argument = reader.callArguments(\"defineSiteStrategy\")[0] ?? reader.constant(\"strategy\");\n if (!argument) return `${D4_STRATEGY_FILE} does not define the strategy with defineSiteStrategy({ … }).`;\n const value = reader.evaluate(argument);\n if (!isLiteralObject(value) || !isFullyResolved(value)) {\n return `${D4_STRATEGY_FILE} is not plain data, so the look levers cannot be read; write the strategy as a literal.`;\n }\n return value;\n}\n\n/** The site's own recorded look (K13 reads its home signatures, 3.16.0); null when there is none. */\nexport function readRecordedLook(projectRoot: string): RecordedLook | null {\n const value = strategyRecord(projectRoot);\n return value === null || typeof value === \"string\" ? null : readLook(value[\"look\"]);\n}\n\n/** Reads the strategy record without running the site. Returns a string when it cannot be read as plain data. */\nexport function readStrategy(projectRoot: string): RecordedStrategy | string | null {\n const value = strategyRecord(projectRoot);\n if (value === null || typeof value === \"string\") return value;\n const look = readLook(value[\"look\"]);\n // defineSiteStrategy accepts a record without these, but D4 cannot pass a new build or redesign without them.\n if (!look) return `${D4_STRATEGY_FILE} has no \"look\" for the site; a new build or redesign must record it.`;\n const rawCompetitors = value[\"competitors\"];\n if (!Array.isArray(rawCompetitors)) return `${D4_STRATEGY_FILE} has no \"competitors\" list; a new build or redesign must record it.`;\n const competitors: RecordedCompetitor[] = [];\n for (const [index, item] of rawCompetitors.entries()) {\n const competitorLook = isLiteralObject(item) ? readLook(item[\"look\"]) : null;\n if (!isLiteralObject(item) || !competitorLook || typeof item[\"domain\"] !== \"string\") {\n return `${D4_STRATEGY_FILE} competitor ${index + 1} needs a domain and a look.`;\n }\n competitors.push({ name: typeof item[\"name\"] === \"string\" ? item[\"name\"] : item[\"domain\"], domain: item[\"domain\"], look: competitorLook });\n }\n return { look, competitors };\n}\n\nfunction list(levers: readonly string[]): string {\n return levers.join(\", \");\n}\n\nexport function checkD4(projectRoot: string): SiteSystemFinding[] {\n const strategy = readStrategy(projectRoot);\n if (strategy === null) {\n return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: `The site has no ${D4_STRATEGY_FILE}, so its look cannot be compared with its competitors'.` }];\n }\n if (typeof strategy === \"string\") return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: strategy }];\n if (strategy.competitors.length === 0) {\n return [{ checkId: \"D4\", status: \"not-applicable\", message: \"The strategy lists no competitors, so there is no look to compare against.\" }];\n }\n\n const ownMissing = D4_LEVERS.filter((lever) => strategy.look[lever] === undefined);\n if (ownMissing.length > 0) {\n return [{ checkId: \"D4\", status: \"fail\", target: D4_STRATEGY_FILE, message: `The site's own look does not record ${list(ownMissing)}, so it cannot be compared.` }];\n }\n\n const failures: SiteSystemFinding[] = [];\n let fewest = { domain: \"\", count: Number.POSITIVE_INFINITY };\n for (const competitor of strategy.competitors) {\n const comparison = compareLooks(strategy.look, competitor.look);\n if (comparison.unrecorded.length > 0) {\n failures.push({ checkId: \"D4\", status: \"fail\", target: competitor.domain, message: `The look recorded for ${competitor.domain} is missing ${list(comparison.unrecorded)}, so it cannot be compared.` });\n continue;\n }\n if (comparison.differing.length < fewest.count) fewest = { domain: competitor.domain, count: comparison.differing.length };\n if (comparison.differing.length < D4_MIN_DIFFERING) {\n failures.push({\n checkId: \"D4\",\n status: \"fail\",\n target: competitor.domain,\n message: `The look is too close to ${competitor.domain}: ${comparison.differing.length} of ${D4_LEVERS.length} levers differ and at least ${D4_MIN_DIFFERING} must (same: ${list(comparison.same)}).`,\n });\n }\n }\n if (failures.length > 0) return failures;\n const count = strategy.competitors.length;\n return [{\n checkId: \"D4\",\n status: \"pass\",\n message: `The look differs from ${count === 1 ? \"the listed competitor\" : `all ${count} listed competitors`} on at least ${D4_MIN_DIFFERING} of ${D4_LEVERS.length} levers (closest: ${fewest.domain} with ${fewest.count}).`,\n }];\n}\n"]}
@@ -12,11 +12,14 @@ export interface ManifestData {
12
12
  readonly id: string;
13
13
  readonly requirement: string;
14
14
  }[];
15
+ /** A signature section (3.16.0) carries its name and, when the site placed it, the section it follows. */
15
16
  readonly rendered: readonly {
16
17
  readonly id: string;
17
18
  readonly section: string;
18
19
  readonly requirement: string;
19
20
  readonly tone?: string;
21
+ readonly signature?: string;
22
+ readonly after?: string;
20
23
  }[];
21
24
  readonly skipped: readonly {
22
25
  readonly id: string;
@@ -91,6 +94,8 @@ export interface EstatePage {
91
94
  readonly manifest: ManifestState;
92
95
  /** Top-level `data-bl-section` ids, in document order. */
93
96
  readonly sections: readonly string[];
97
+ /** The `data-bl-signature` names of the top-level signature sections, in document order (3.16.0). */
98
+ readonly signatures?: readonly string[];
94
99
  /**
95
100
  * Per top-level section (3.14.0): the `data-bl-slot` values inside it and the `slot:reason` pairs of its
96
101
  * `data-bl-slot-skip` attribute.
@@ -1 +1 @@
1
- {"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/estate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/full/estate.ts"],"names":[],"mappings":"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"}
@@ -162,6 +162,9 @@ function summarise(item, sitemapOf, geography, aliases) {
162
162
  linkGroups: [],
163
163
  manifest: readManifest(elements),
164
164
  sections: topLevelSections(root).map(sectionId),
165
+ signatures: topLevelSections(root)
166
+ .filter((section) => sectionId(section) === "signature")
167
+ .map((section) => attr(section, "data-bl-signature") ?? ""),
165
168
  slots: sectionSlots(root),
166
169
  h1: h1Element ? textOf(h1Element) : "",
167
170
  ...(titleOf(elements) !== undefined ? { title: titleOf(elements) } : {}),
@@ -229,6 +232,8 @@ function readManifest(elements) {
229
232
  id: entry.id,
230
233
  section: typeof entry.section === "string" ? entry.section : entry.id,
231
234
  requirement: entry.requirement,
235
+ ...(typeof entry.signature === "string" ? { signature: entry.signature } : {}),
236
+ ...(typeof entry.after === "string" ? { after: entry.after } : {}),
232
237
  })),
233
238
  skipped: value.skipped.map((entry) => ({
234
239
  id: entry.id,