@piercebarney/whs-eleventy 2026.10.5 → 2026.10.7

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/lib/compliance.js CHANGED
@@ -51,6 +51,37 @@ function walk(dir) {
51
51
  return out;
52
52
  }
53
53
 
54
+ // Mask out every @media (prefers-reduced-motion: no-preference) { ... }
55
+ // block's entire contents (string search + brace-counting to find the
56
+ // matching close, so nested rulesets inside the media block are correctly
57
+ // included in what gets masked) — whatever remains is, by construction,
58
+ // outside the required gate.
59
+ function maskGatedMotionBlocks(css) {
60
+ const marker = /@media[^{]*prefers-reduced-motion\s*:\s*no-preference[^{]*\{/;
61
+ let out = "";
62
+ let rest = css;
63
+ let m;
64
+ while ((m = rest.match(marker))) {
65
+ let depth = 1;
66
+ let i = m.index + m[0].length;
67
+ while (i < rest.length && depth > 0) {
68
+ if (rest[i] === "{") depth++;
69
+ else if (rest[i] === "}") depth--;
70
+ i++;
71
+ }
72
+ out += rest.slice(0, m.index);
73
+ rest = rest.slice(i);
74
+ }
75
+ return out + rest;
76
+ }
77
+
78
+ // (?<![\w-]) rather than a bare \b: a custom property like --pico-transition:
79
+ // is not a transition *declaration* — \b alone still matches right after the
80
+ // hyphen in "-transition", which false-positives on exactly that shape (found
81
+ // in the reference template's own vendored pico.min.css).
82
+ const MOTION_PROP = /(?<![\w-])(transition|animation|scroll-behavior)\s*:/;
83
+ const hasUngatedMotion = (css) => MOTION_PROP.test(maskGatedMotionBlocks(css));
84
+
54
85
  // grep the tracked source tree (not _site, not node_modules).
55
86
  function grepSrc(re, exts = [".js", ".njk", ".css", ".json"]) {
56
87
  const roots = ["src", "static", "scripts"].map((d) => path.join(ROOT, d));
@@ -594,9 +625,28 @@ const CHECKS = {
594
625
  a11y: () => {
595
626
  if (!/\ba11y\b/.test(JSON.parse(read("package.json") || "{}").scripts?.check || ""))
596
627
  return { status: FAIL, note: "no a11y stage in the gate" };
628
+ // Scan the authored source tree (src/static/scripts), not built _site —
629
+ // a passthrough-copied third-party stylesheet (e.g. a CSS framework
630
+ // vendored via addPassthroughCopy) lands in _site but was never authored
631
+ // by, or gate-able by, this project; scanning it produced a false FAIL
632
+ // against the reference template's own vendored pico.min.css.
633
+ const cssFiles = ["src", "static", "scripts"]
634
+ .map((d) => path.join(ROOT, d))
635
+ .flatMap((r) => walk(r))
636
+ .filter((f) => f.endsWith(".css"));
637
+ const ungated = cssFiles.filter((f) => hasUngatedMotion(fs.readFileSync(f, "utf8")));
638
+ if (ungated.length)
639
+ return {
640
+ status: FAIL,
641
+ note: `transition/animation not gated behind prefers-reduced-motion in ${ungated
642
+ .map((f) => path.relative(ROOT, f))
643
+ .join(", ")}`,
644
+ };
597
645
  return {
598
646
  status: MANUAL,
599
- note: "axe scan is in the gate; confirm the checklist items beyond the scan (focus order, live regions)",
647
+ note:
648
+ "axe scan is in the gate; prefers-reduced-motion gating confirmed; " +
649
+ "confirm remaining checklist items beyond the scan (focus order, live regions)",
600
650
  };
601
651
  },
602
652
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piercebarney/whs-eleventy",
3
- "version": "2026.10.5",
3
+ "version": "2026.10.7",
4
4
  "description": "The web house style's Eleventy + Netlify tooling — the compliance sweep, the infra doctor, the link/CSP integrity check, and the a11y scan, shared by every project on the stack.",
5
5
  "bin": {
6
6
  "whs": "cli.js"
@@ -21,6 +21,71 @@ enforced, new *opt-in* chapters/capabilities, tooling-only changes — is
21
21
 
22
22
  ---
23
23
 
24
+ ## 2026-10-07 [breaking]
25
+
26
+ ### core: a11y
27
+
28
+ `core.md#a11y`'s Contract never named a WCAG version/level as its
29
+ conformance target — only "WCAG AA" for contrast. It now declares **WCAG 2.1
30
+ Level AA** explicitly as the target, anchored to US law (the ADA Title II
31
+ rule and US web-accessibility litigation/settlements reference WCAG 2.1 AA;
32
+ Section 508's WCAG 2.0 AA is already covered by it) rather than WCAG 2.2 AA
33
+ — the upgrade trigger to 2.2 AA is recorded, not scheduled, for once US rules
34
+ reference it. **Breaking:** a project that was previously compliant by
35
+ meeting the checklist plus passing the automated scan must now conform to
36
+ all of WCAG 2.1 AA, including criteria the checklist and scan don't
37
+ individually name (e.g. captions, reflow, keyboard operability everywhere)
38
+ — a real added requirement, not just a clarification of an already-met bar.
39
+ No tooling change: Eleventy's `whs a11y` scan already runs `wcag2a`/
40
+ `wcag2aa`/`wcag21a`/`wcag21aa` — it already matched this target. SvelteKit's
41
+ and Phoenix's scan gaps (no automated a11y tooling yet) are pre-existing and
42
+ unrelated to this change. Decided by the owner in
43
+ `feedback/2026-09-26-external-standards-survey.md` ("Decisions (2026-09-26)"
44
+ item 1); see that file for the full WCAG-version rationale, including why
45
+ 2.1 and not 2.2.
46
+
47
+ ### core: styling
48
+
49
+ The contrast line in `#styling` now also says "WCAG 2.1 AA" (was "WCAG AA"),
50
+ matching the version now named in `#a11y`.
51
+
52
+ ### stacks/eleventy-netlify.md, stacks/phoenix.md, stacks/sveltekit-netlify.md
53
+
54
+ Each of `stacks/eleventy-netlify.md`, `stacks/phoenix.md`, and
55
+ `stacks/sveltekit-netlify.md`'s `#a11y` Core-contract pointer line now
56
+ mentions WCAG 2.1 AA alongside the existing checklist/scan pointer — no
57
+ change to any stack's own mechanism.
58
+
59
+ ---
60
+
61
+ ## 2026-10-06 [breaking]
62
+
63
+ ### packages/whs-eleventy
64
+
65
+ `a11y`'s chapter in `lib/compliance.js` was 100% unconditional `MANUAL` — it
66
+ never actually checked any of the Contract specs it listed. This adds real
67
+ mechanization for one of them: `core.md#a11y` requires every transition/
68
+ animation to be gated behind `@media (prefers-reduced-motion: no-preference)`
69
+ (also called out in `core.md`'s own anti-patterns list). Found via a real
70
+ project's manual anti-patterns review catching a violation the compliance
71
+ sweep itself missed. The check scans authored source (`src/static/scripts`),
72
+ not built output, so vendored/passthrough-copied third-party CSS a project
73
+ doesn't author is correctly excluded. Unlike this week's other silent-pass
74
+ fixes, this doesn't just correct a check that had a redundant real backstop
75
+ elsewhere (`client-logic`, 2026-10-03) or a pure-doc mismatch with no
76
+ enforcement (`forms`, 2026-10-04) — this is genuinely new detection where
77
+ none existed before. That means it can flip an already-`MANUAL`-passing real
78
+ project's `a11y` status to `FAIL` on its very next compliance run, if that
79
+ project has real, ungated transition/animation CSS in its own authored
80
+ source — not a bug in this fix, but the intended, working check finally
81
+ running. Known, narrow limitation: vendor-prefixed declarations
82
+ (`-webkit-transition:` etc.) aren't detected — an acceptable, low-probability
83
+ gap, consistent with other crude-but-honest heuristics already in this file.
84
+ No `core.md` chapter changed — `packages/whs-eleventy`-only. No `feedback/`
85
+ file. Fixed in `6576a04`.
86
+
87
+ ---
88
+
24
89
  ## 2026-10-05 [non-breaking]
25
90
 
26
91
  ### templates/commands
package/standard/core.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Web project house style — CORE (stack-agnostic)
2
2
 
3
- **Version:** 2026-10-05 · **Status:** active
3
+ **Version:** 2026-10-07 · **Status:** active
4
4
 
5
5
  This is the stack-agnostic contract every web project follows, regardless of
6
6
  framework, host, or CSS system. It says **what** must be true, with concrete
@@ -244,7 +244,7 @@ contrast-correct at the token level.
244
244
  - **Strict-CSP-safe:** no `style-src 'unsafe-inline'`, no inline `style=`
245
245
  attributes in templates, no per-page `<style>` blocks. Utility-class systems
246
246
  (Tailwind) are fine; inline style strings are not.
247
- - Contrast meets WCAG AA. When a pair fails, fix the **token**, not the usage.
247
+ - Contrast meets WCAG 2.1 AA. When a pair fails, fix the **token**, not the usage.
248
248
  - Theme is applied via a data attribute set before first paint (`#theme`).
249
249
  - A second stylesheet only for a genuinely separable widget, justified in a
250
250
  comment.
@@ -834,8 +834,11 @@ states.
834
834
 
835
835
  ## a11y — Accessibility checklist
836
836
 
837
- **Contract.** Every page meets the checklist below. An automated a11y scan over
838
- every page runs in the gate — and is understood to catch regressions, not
837
+ **Contract.** Every page conforms to **WCAG 2.1 Level AA** — the level US
838
+ accessibility law references (the ADA Title II rule; Section 508's WCAG 2.0 AA
839
+ is covered by it). The checklist below is the house's concrete mechanism for
840
+ the parts of AA most often missed, not the whole of it. An automated a11y scan
841
+ over every page runs in the gate — and is understood to catch regressions, not
839
842
  everything.
840
843
 
841
844
  **Specs.**
@@ -858,7 +861,7 @@ everything.
858
861
  keyboard button — don't re-bind Enter/Space or add `role`/`tabindex`.
859
862
  - **Icons:** decorative → `aria-hidden="true"`; meaningful → `<title>` in the
860
863
  SVG or `aria-label` on the control.
861
- - **Contrast** meets WCAG AA; fix failures at the token (`#brand-source`), not
864
+ - **Contrast** meets WCAG 2.1 AA; fix failures at the token (`#brand-source`), not
862
865
  with a one-off override.
863
866
  - **Focus visibility:** a visible focus ring on every interactive element; if
864
867
  you override the default, keep one.
@@ -1467,7 +1470,7 @@ governs it and follow that chapter — the slugs are the index (a `<script>` →
1467
1470
  - production-url: https://example.com
1468
1471
  - content-type: tool # tool | article (article => feeds)
1469
1472
  - publishing-rate: ~5 pages/week
1470
- - standard-version: 2026-10-05 # recommended — enables standard-version drift tracking (#compliance)
1473
+ - standard-version: 2026-10-07 # recommended — enables standard-version drift tracking (#compliance)
1471
1474
  ```
1472
1475
 
1473
1476
  If the section is absent, the assistant's first action is to run the flow above