@piercebarney/whs-eleventy 2026.10.6 → 2026.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/cli.js +2 -0
- package/lib/compliance.js +10 -1
- package/lib/content-check.js +5 -4
- package/lib/doctor.js +2 -2
- package/lib/html.js +61 -0
- package/package.json +2 -1
- package/standard/CHANGELOG.md +136 -0
- package/standard/core.md +23 -10
package/README.md
CHANGED
|
@@ -7,9 +7,10 @@ the stack runs, so a fix lands once and `npm update` carries it everywhere.
|
|
|
7
7
|
```
|
|
8
8
|
whs compliance [--strict] codebase-vs-standard conformance sweep
|
|
9
9
|
whs doctor [--live|--deploy-preflight] infrastructure drift check
|
|
10
|
+
whs html html-validate (standard preset) over every _site/ page
|
|
10
11
|
whs links built-output link / CSP / JSON-LD integrity
|
|
11
12
|
whs a11y axe-core WCAG 2.1 A/AA scan of _site/
|
|
12
|
-
whs content-check lint · validate · build · links (no browser)
|
|
13
|
+
whs content-check lint · validate · build · html · links (no browser)
|
|
13
14
|
whs --version tooling + bundled-standard version
|
|
14
15
|
```
|
|
15
16
|
|
|
@@ -29,6 +30,7 @@ Then in `package.json`:
|
|
|
29
30
|
```json
|
|
30
31
|
{
|
|
31
32
|
"scripts": {
|
|
33
|
+
"html": "whs html",
|
|
32
34
|
"links": "whs links",
|
|
33
35
|
"a11y": "whs a11y",
|
|
34
36
|
"doctor": "whs doctor",
|
package/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// whs status [--hook] standard-version drift, at a glance
|
|
5
5
|
// whs compliance [--strict] codebase-vs-standard conformance sweep
|
|
6
6
|
// whs doctor [--live|--deploy-preflight] infrastructure drift check
|
|
7
|
+
// whs html html-validate (standard preset) over every _site/ page
|
|
7
8
|
// whs links built-output link / CSP / JSON-LD integrity
|
|
8
9
|
// whs a11y axe-core WCAG 2.1 A/AA scan of _site/
|
|
9
10
|
// whs content-check the content-relevant slice of the gate
|
|
@@ -20,6 +21,7 @@ const COMMANDS = {
|
|
|
20
21
|
status: "status.js",
|
|
21
22
|
compliance: "compliance.js",
|
|
22
23
|
doctor: "doctor.js",
|
|
24
|
+
html: "html.js",
|
|
23
25
|
links: "check-links.js",
|
|
24
26
|
a11y: "a11y.js",
|
|
25
27
|
"content-check": "content-check.js",
|
package/lib/compliance.js
CHANGED
|
@@ -217,6 +217,12 @@ const CHECKS = {
|
|
|
217
217
|
|
|
218
218
|
rendering: () => {
|
|
219
219
|
if (!has("src/404.njk")) return { status: FAIL, note: "no src/404.njk" };
|
|
220
|
+
// core.md#rendering requires every built page to be valid HTML — mirrors
|
|
221
|
+
// the a11y chapter's own "is the scan actually wired into the gate" check
|
|
222
|
+
// below, rather than trusting that the stage exists just because the
|
|
223
|
+
// package ships one.
|
|
224
|
+
if (!/\bhtml\b/.test(JSON.parse(read("package.json") || "{}").scripts?.check || ""))
|
|
225
|
+
return { status: FAIL, note: "no html stage in the gate" };
|
|
220
226
|
// A page whose <main> is empty/near-empty in the built output is the
|
|
221
227
|
// classic "content only appears after client JS runs" regression the
|
|
222
228
|
// Contract forbids — a crude but real content-presence check, distinct
|
|
@@ -232,7 +238,10 @@ const CHECKS = {
|
|
|
232
238
|
status: FAIL,
|
|
233
239
|
note: `${thin.length} page(s) with near-empty <main> — content may be client-rendered only`,
|
|
234
240
|
};
|
|
235
|
-
return {
|
|
241
|
+
return {
|
|
242
|
+
status: PASS,
|
|
243
|
+
note: "server-rendered HTML with real content; 404 page present; html-validate stage in the gate",
|
|
244
|
+
};
|
|
236
245
|
},
|
|
237
246
|
|
|
238
247
|
assets: () => {
|
package/lib/content-check.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The content-relevant slice of the gate, for an agent editing content
|
|
2
|
-
// (CONTENT.md). Runs lint → validate → build → links — no browser, so
|
|
3
|
-
// (that's backstopped by `npm run check` in bin/deploy before
|
|
2
|
+
// (CONTENT.md). Runs lint → validate → build → html → links — no browser, so
|
|
3
|
+
// no a11y (that's backstopped by `npm run check` in bin/deploy before
|
|
4
|
+
// anything ships).
|
|
4
5
|
//
|
|
5
6
|
// whs content-check
|
|
6
7
|
//
|
|
@@ -79,7 +80,7 @@ function main() {
|
|
|
79
80
|
console.log(`\nℹ ${reqs.length} open request(s) in requests/ awaiting the build arm.\n`);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
const steps = ["lint", "validate", "build", "links"];
|
|
83
|
+
const steps = ["lint", "validate", "build", "html", "links"];
|
|
83
84
|
for (const s of steps) {
|
|
84
85
|
process.stdout.write(`content-check: npm run ${s}\n`);
|
|
85
86
|
try {
|
|
@@ -89,7 +90,7 @@ function main() {
|
|
|
89
90
|
process.exit(1);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
|
-
console.log("\ncontent-check: ok — lint · validate · build · links");
|
|
93
|
+
console.log("\ncontent-check: ok — lint · validate · build · html · links");
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
module.exports = { strayFiles, parsePorcelain, splitStray, RESERVED_SET };
|
package/lib/doctor.js
CHANGED
|
@@ -34,7 +34,7 @@ const loadSite = () => projectRequire("src/_data/site.js");
|
|
|
34
34
|
// feeds the /audit/ page's live-header check via _data/headerExpect.js.
|
|
35
35
|
const HEADER_EXPECT = asRegexMap();
|
|
36
36
|
|
|
37
|
-
const GATE_STAGES = ["lint", "validate", "test", "build", "links", "a11y"];
|
|
37
|
+
const GATE_STAGES = ["lint", "validate", "test", "build", "html", "links", "a11y"];
|
|
38
38
|
|
|
39
39
|
function readText(rel) {
|
|
40
40
|
try {
|
|
@@ -122,7 +122,7 @@ function runRepoChecks({ preflight = false } = {}) {
|
|
|
122
122
|
const absent = GATE_STAGES.filter((s) => !check.includes(s));
|
|
123
123
|
add(
|
|
124
124
|
"gate-stages",
|
|
125
|
-
"`npm run check` runs lint · validate · test · build · links · a11y",
|
|
125
|
+
"`npm run check` runs lint · validate · test · build · html · links · a11y",
|
|
126
126
|
check !== "" && absent.length === 0,
|
|
127
127
|
"warn",
|
|
128
128
|
absent.length ? `not referenced: ${absent.join(", ")}` : "complete",
|
package/lib/html.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// HTML validity — the `html` step of the gate (whs html). Walks the built
|
|
2
|
+
// _site/ recursively (every *.html — error pages and /audit/ included, not
|
|
3
|
+
// just the sitemap) and runs html-validate's `html-validate:standard` preset
|
|
4
|
+
// against each file, exiting non-zero on any error.
|
|
5
|
+
//
|
|
6
|
+
// `standard`, not `recommended`: recommended layers in style opinions (e.g.
|
|
7
|
+
// uppercase DOCTYPE) that core.md#rendering's Contract never asked for — spec
|
|
8
|
+
// conformance only (core.md#rendering: "no stray end tags, no bad nesting, no
|
|
9
|
+
// duplicate ids"). An inline `<!-- [html-validate-disable-next] -->` comment
|
|
10
|
+
// is the escape hatch for a rule that's a false positive on a specific line;
|
|
11
|
+
// no per-project config file — one fixed config keeps every project on the
|
|
12
|
+
// stack checked the same way.
|
|
13
|
+
|
|
14
|
+
const fs = require("node:fs");
|
|
15
|
+
const path = require("node:path");
|
|
16
|
+
const { HtmlValidate } = require("html-validate");
|
|
17
|
+
|
|
18
|
+
const SITE = "_site";
|
|
19
|
+
|
|
20
|
+
function walkHtml(dir) {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
23
|
+
const p = path.join(dir, e.name);
|
|
24
|
+
if (e.isDirectory()) out.push(...walkHtml(p));
|
|
25
|
+
else if (e.name.endsWith(".html")) out.push(p);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
if (!fs.existsSync(SITE)) {
|
|
32
|
+
console.error("html: _site/ missing — build first");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const files = walkHtml(SITE);
|
|
37
|
+
if (!files.length) {
|
|
38
|
+
console.error("html: no *.html files found under _site/");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const hv = new HtmlValidate({ extends: ["html-validate:standard"] });
|
|
43
|
+
const { valid, results } = await hv.validateMultipleFiles(files);
|
|
44
|
+
|
|
45
|
+
const failing = results.filter((r) => r.messages.length);
|
|
46
|
+
if (!valid || failing.length) {
|
|
47
|
+
const totalErrors = failing.reduce((n, r) => n + r.errorCount, 0);
|
|
48
|
+
console.error(`\nhtml: ${totalErrors} error(s) across ${failing.length} page(s):\n`);
|
|
49
|
+
for (const r of failing) {
|
|
50
|
+
console.error(` ${path.relative(SITE, r.filePath)}`);
|
|
51
|
+
for (const m of r.messages) {
|
|
52
|
+
console.error(` [${m.ruleId}] ${m.line}:${m.column} — ${m.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(` html: ${files.length} pages, valid per the WHATWG HTML Living Standard`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piercebarney/whs-eleventy",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.8",
|
|
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"
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"axe-core": "^4.13.0",
|
|
35
|
+
"html-validate": "^11.16.0",
|
|
35
36
|
"node-html-parser": "^9.0.2",
|
|
36
37
|
"sirv": "^3.0.2"
|
|
37
38
|
},
|
package/standard/CHANGELOG.md
CHANGED
|
@@ -21,6 +21,142 @@ enforced, new *opt-in* chapters/capabilities, tooling-only changes — is
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
## 2026-10-08 [breaking]
|
|
25
|
+
|
|
26
|
+
### core: rendering
|
|
27
|
+
|
|
28
|
+
`#rendering`'s Contract never required HTML *validity* — only that content
|
|
29
|
+
render without client JS and that server/client rendering logic not be
|
|
30
|
+
duplicated. It now adds: every built page — including error pages and
|
|
31
|
+
`/audit/` — must be valid HTML per the **WHATWG HTML Living Standard** (the
|
|
32
|
+
spec W3C itself defers to). Spec conformance only (no stray end tags, no bad
|
|
33
|
+
nesting, no duplicate `id`s) — no style opinions such as DOCTYPE case or
|
|
34
|
+
attribute quoting. Rationale: browsers repair malformed markup silently, so
|
|
35
|
+
invalid structure otherwise ships unseen, and WCAG 2.2 dropped its 4.1.1
|
|
36
|
+
Parsing criterion, so an axe scan no longer catches it either. Decided by
|
|
37
|
+
the owner in `feedback/2026-09-26-external-standards-survey.md`
|
|
38
|
+
("Sequencing" step 2, row 3).
|
|
39
|
+
|
|
40
|
+
### core: the-gate
|
|
41
|
+
|
|
42
|
+
`#the-gate`'s drift-guarded stage order gains an **HTML validity** step,
|
|
43
|
+
placed after `build` and before `link/reference integrity`: lint/format
|
|
44
|
+
check → content validation → domain tests → build → HTML validity →
|
|
45
|
+
link/reference integrity → a11y scan.
|
|
46
|
+
|
|
47
|
+
**Breaking:** a project that was fully compliant before this change (a gate
|
|
48
|
+
that ran build → links → a11y with no HTML-validity step) is no longer
|
|
49
|
+
compliant after it — `#the-gate` now requires an HTML-validity stage that
|
|
50
|
+
didn't exist as a requirement before, and `whs compliance` / `whs doctor`
|
|
51
|
+
(see `packages/whs-eleventy` below) now `FAIL`/warn on its absence rather
|
|
52
|
+
than silently passing.
|
|
53
|
+
|
|
54
|
+
**Adopter action (eleventy):** `npm update @piercebarney/whs-eleventy`, add
|
|
55
|
+
`"html": "whs html"` to `package.json` scripts, add `npm run html` to
|
|
56
|
+
`check` (after `build`, before `links`), then fix any invalid markup the new
|
|
57
|
+
check reports. Re-pin `standard-version` to `2026-10-08` once done.
|
|
58
|
+
|
|
59
|
+
### packages/whs-eleventy
|
|
60
|
+
|
|
61
|
+
New `whs html` command (`lib/html.js`): walks every `*.html` file under the
|
|
62
|
+
built `_site/` recursively (error pages and `/audit/` included, not just the
|
|
63
|
+
sitemap) and runs it through `html-validate`'s `html-validate:standard`
|
|
64
|
+
preset (spec conformance only — not `recommended`'s style opinions), exiting
|
|
65
|
+
non-zero on any error. New dependency `html-validate ^11.16.0`. `whs
|
|
66
|
+
compliance`'s `#rendering` check now `FAIL`s if the project's `check` script
|
|
67
|
+
doesn't reference an `html` stage. `whs doctor`'s `GATE_STAGES` list and its
|
|
68
|
+
`gate-stages` check gain `html`. `whs content-check`'s lighter slice
|
|
69
|
+
(`lint → validate → build → links`) gains `html`, run before `links`. Tests
|
|
70
|
+
added for the new compliance/content-check behavior.
|
|
71
|
+
|
|
72
|
+
### templates/eleventy-netlify
|
|
73
|
+
|
|
74
|
+
Wires the new stage: `package.json` gains an `"html": "whs html"` script and
|
|
75
|
+
inserts `npm run html` into `check` between `build` and `links`; `ci.yml`'s
|
|
76
|
+
inline gate-order comment updated to match. `CLAUDE.md`, `README.md`, and
|
|
77
|
+
`CONTENT.md` document the new stage in their gate-order prose.
|
|
78
|
+
`standard-version` re-pinned to `2026-10-08`.
|
|
79
|
+
|
|
80
|
+
### templates/sveltekit-netlify
|
|
81
|
+
|
|
82
|
+
Implements the chapter for the first time (no prior HTML-validity tooling
|
|
83
|
+
existed for this stack): new `html-validate` devDependency, a
|
|
84
|
+
`.htmlvalidate.json` extending `html-validate:standard`, a new `"html":
|
|
85
|
+
"html-validate \"build/**/*.html\""` script run recursively over every
|
|
86
|
+
built page, and `npm run html` added to `check` after `build`. `README.md`,
|
|
87
|
+
`TEMPLATE.md`, and `ci.yml`'s inline gate-order comment updated to match.
|
|
88
|
+
This stack has no static error/404 page yet (`adapter-static`, no
|
|
89
|
+
`fallback`, no `+error.svelte`), so `core.md#rendering`'s "error pages"
|
|
90
|
+
clause has nothing to check today — the recursive glob will pick one up
|
|
91
|
+
automatically the day this binding adds one.
|
|
92
|
+
|
|
93
|
+
### stacks/eleventy-netlify.md, stacks/sveltekit-netlify.md
|
|
94
|
+
|
|
95
|
+
Each `#rendering` Core-contract pointer line now mentions the "every built
|
|
96
|
+
page is valid HTML" requirement; each documents its stack's real mechanism
|
|
97
|
+
(`whs html` / `html-validate` respectively) and its place in `#the-gate`'s
|
|
98
|
+
now-seven-step order.
|
|
99
|
+
|
|
100
|
+
### stacks/phoenix.md
|
|
101
|
+
|
|
102
|
+
Documented as a **`TODO`** in `#rendering`/`#the-gate` — no template change,
|
|
103
|
+
no tooling exists yet. The candidate approach is recorded (a ConnCase/
|
|
104
|
+
LiveView test fetching each sitemap route through html-validate or the Nu
|
|
105
|
+
Html Checker, wired as a `mix html_validity` alias). The `mix check`
|
|
106
|
+
placeholder-step snippet's order is also corrected while touching this
|
|
107
|
+
section: it previously listed `a11y` before `links.check`, ahead of where
|
|
108
|
+
`#the-gate`'s HTML-validity → links → a11y order calls for; it's now
|
|
109
|
+
`html_validity → links.check → a11y`, fixing that pre-existing drift as well
|
|
110
|
+
as adding the new placeholder step.
|
|
111
|
+
|
|
112
|
+
**Gaps, not fixed by this change:** Phoenix has no HTML-validity tooling —
|
|
113
|
+
`TODO`, same pattern as its existing `a11y`/`links.check` gaps. SvelteKit's
|
|
114
|
+
template has no 404/error page today, so nothing exists yet for its new
|
|
115
|
+
`html` step to check on that front.
|
|
116
|
+
|
|
117
|
+
**Adopter action (sveltekit):** `npm install` (picks up `html-validate` +
|
|
118
|
+
`.htmlvalidate.json`), add `"html": "html-validate \"build/**/*.html\""` to
|
|
119
|
+
scripts and into `check` after `build`, re-pin `standard-version`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 2026-10-07 [breaking]
|
|
124
|
+
|
|
125
|
+
### core: a11y
|
|
126
|
+
|
|
127
|
+
`core.md#a11y`'s Contract never named a WCAG version/level as its
|
|
128
|
+
conformance target — only "WCAG AA" for contrast. It now declares **WCAG 2.1
|
|
129
|
+
Level AA** explicitly as the target, anchored to US law (the ADA Title II
|
|
130
|
+
rule and US web-accessibility litigation/settlements reference WCAG 2.1 AA;
|
|
131
|
+
Section 508's WCAG 2.0 AA is already covered by it) rather than WCAG 2.2 AA
|
|
132
|
+
— the upgrade trigger to 2.2 AA is recorded, not scheduled, for once US rules
|
|
133
|
+
reference it. **Breaking:** a project that was previously compliant by
|
|
134
|
+
meeting the checklist plus passing the automated scan must now conform to
|
|
135
|
+
all of WCAG 2.1 AA, including criteria the checklist and scan don't
|
|
136
|
+
individually name (e.g. captions, reflow, keyboard operability everywhere)
|
|
137
|
+
— a real added requirement, not just a clarification of an already-met bar.
|
|
138
|
+
No tooling change: Eleventy's `whs a11y` scan already runs `wcag2a`/
|
|
139
|
+
`wcag2aa`/`wcag21a`/`wcag21aa` — it already matched this target. SvelteKit's
|
|
140
|
+
and Phoenix's scan gaps (no automated a11y tooling yet) are pre-existing and
|
|
141
|
+
unrelated to this change. Decided by the owner in
|
|
142
|
+
`feedback/2026-09-26-external-standards-survey.md` ("Decisions (2026-09-26)"
|
|
143
|
+
item 1); see that file for the full WCAG-version rationale, including why
|
|
144
|
+
2.1 and not 2.2.
|
|
145
|
+
|
|
146
|
+
### core: styling
|
|
147
|
+
|
|
148
|
+
The contrast line in `#styling` now also says "WCAG 2.1 AA" (was "WCAG AA"),
|
|
149
|
+
matching the version now named in `#a11y`.
|
|
150
|
+
|
|
151
|
+
### stacks/eleventy-netlify.md, stacks/phoenix.md, stacks/sveltekit-netlify.md
|
|
152
|
+
|
|
153
|
+
Each of `stacks/eleventy-netlify.md`, `stacks/phoenix.md`, and
|
|
154
|
+
`stacks/sveltekit-netlify.md`'s `#a11y` Core-contract pointer line now
|
|
155
|
+
mentions WCAG 2.1 AA alongside the existing checklist/scan pointer — no
|
|
156
|
+
change to any stack's own mechanism.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
24
160
|
## 2026-10-06 [breaking]
|
|
25
161
|
|
|
26
162
|
### packages/whs-eleventy
|
package/standard/core.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Web project house style — CORE (stack-agnostic)
|
|
2
2
|
|
|
3
|
-
**Version:** 2026-10-
|
|
3
|
+
**Version:** 2026-10-08 · **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
|
|
@@ -173,7 +173,8 @@ validation status.
|
|
|
173
173
|
|
|
174
174
|
**Contract.** Core content and navigation work with no client JavaScript. Client
|
|
175
175
|
JS enhances; it never gates the essentials. Any logic that renders on both the
|
|
176
|
-
server and the client is written once.
|
|
176
|
+
server and the client is written once. Every built page is valid HTML per the
|
|
177
|
+
WHATWG HTML Living Standard.
|
|
177
178
|
|
|
178
179
|
**Specs.**
|
|
179
180
|
- The server-rendered (or prerendered) HTML is complete: a crawler or a
|
|
@@ -186,9 +187,17 @@ server and the client is written once.
|
|
|
186
187
|
- Nothing that matters (core content, primary CTA) blocks on a network fetch.
|
|
187
188
|
Live data arrives after first paint and degrades to a clear fallback state
|
|
188
189
|
(see `#third-parties`).
|
|
190
|
+
- Every built page — including error pages and `/audit/` — is valid HTML per
|
|
191
|
+
the **WHATWG HTML Living Standard** (the spec W3C itself defers to): no stray
|
|
192
|
+
end tags, no bad nesting, no duplicate `id`s. Browsers repair malformed
|
|
193
|
+
markup silently, so invalid structure otherwise ships unseen; WCAG 2.2
|
|
194
|
+
dropped its 4.1.1 Parsing criterion, so an axe scan no longer catches it
|
|
195
|
+
either. The check is spec conformance only — no style opinions (DOCTYPE
|
|
196
|
+
case, attribute quoting, and the like).
|
|
189
197
|
|
|
190
198
|
**Verify.** Load a key page with JS disabled: content and links present.
|
|
191
|
-
Grep for duplicated render logic.
|
|
199
|
+
Grep for duplicated render logic. An HTML validator runs over every built
|
|
200
|
+
page — including error pages and `/audit/` — in the gate.
|
|
192
201
|
|
|
193
202
|
**Implementation.** See your stack's impl doc, same slug.
|
|
194
203
|
|
|
@@ -244,7 +253,7 @@ contrast-correct at the token level.
|
|
|
244
253
|
- **Strict-CSP-safe:** no `style-src 'unsafe-inline'`, no inline `style=`
|
|
245
254
|
attributes in templates, no per-page `<style>` blocks. Utility-class systems
|
|
246
255
|
(Tailwind) are fine; inline style strings are not.
|
|
247
|
-
- Contrast meets WCAG AA. When a pair fails, fix the **token**, not the usage.
|
|
256
|
+
- Contrast meets WCAG 2.1 AA. When a pair fails, fix the **token**, not the usage.
|
|
248
257
|
- Theme is applied via a data attribute set before first paint (`#theme`).
|
|
249
258
|
- A second stylesheet only for a genuinely separable widget, justified in a
|
|
250
259
|
comment.
|
|
@@ -834,8 +843,11 @@ states.
|
|
|
834
843
|
|
|
835
844
|
## a11y — Accessibility checklist
|
|
836
845
|
|
|
837
|
-
**Contract.** Every page
|
|
838
|
-
|
|
846
|
+
**Contract.** Every page conforms to **WCAG 2.1 Level AA** — the level US
|
|
847
|
+
accessibility law references (the ADA Title II rule; Section 508's WCAG 2.0 AA
|
|
848
|
+
is covered by it). The checklist below is the house's concrete mechanism for
|
|
849
|
+
the parts of AA most often missed, not the whole of it. An automated a11y scan
|
|
850
|
+
over every page runs in the gate — and is understood to catch regressions, not
|
|
839
851
|
everything.
|
|
840
852
|
|
|
841
853
|
**Specs.**
|
|
@@ -858,7 +870,7 @@ everything.
|
|
|
858
870
|
keyboard button — don't re-bind Enter/Space or add `role`/`tabindex`.
|
|
859
871
|
- **Icons:** decorative → `aria-hidden="true"`; meaningful → `<title>` in the
|
|
860
872
|
SVG or `aria-label` on the control.
|
|
861
|
-
- **Contrast** meets WCAG AA; fix failures at the token (`#brand-source`), not
|
|
873
|
+
- **Contrast** meets WCAG 2.1 AA; fix failures at the token (`#brand-source`), not
|
|
862
874
|
with a one-off override.
|
|
863
875
|
- **Focus visibility:** a visible focus ring on every interactive element; if
|
|
864
876
|
you override the default, keep one.
|
|
@@ -883,8 +895,9 @@ enforcement point runs that same command — never a hand-picked subset.
|
|
|
883
895
|
|
|
884
896
|
**Specs.**
|
|
885
897
|
- One gate command runs, ordered cheapest-first: lint/format check → content
|
|
886
|
-
validation → domain tests → build →
|
|
887
|
-
(Optionally a performance budget once the site has a
|
|
898
|
+
validation → domain tests → build → HTML validity → link/reference
|
|
899
|
+
integrity → a11y scan. (Optionally a performance budget once the site has a
|
|
900
|
+
baseline.)
|
|
888
901
|
- The same command is invoked by: the pre-push git hook, the CI workflow, the
|
|
889
902
|
deploy script, and the assistant before it reports work complete.
|
|
890
903
|
- There is **no "CI-only" variant** and no partial local run. If the gate
|
|
@@ -1467,7 +1480,7 @@ governs it and follow that chapter — the slugs are the index (a `<script>` →
|
|
|
1467
1480
|
- production-url: https://example.com
|
|
1468
1481
|
- content-type: tool # tool | article (article => feeds)
|
|
1469
1482
|
- publishing-rate: ~5 pages/week
|
|
1470
|
-
- standard-version: 2026-10-
|
|
1483
|
+
- standard-version: 2026-10-08 # recommended — enables standard-version drift tracking (#compliance)
|
|
1471
1484
|
```
|
|
1472
1485
|
|
|
1473
1486
|
If the section is absent, the assistant's first action is to run the flow above
|