@piercebarney/whs-eleventy 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,625 @@
1
+ # Changelog
2
+
3
+ Slug-keyed. A change to a shared concept (a `core.md` chapter) lists the slug so
4
+ "when did the deploy philosophy last change, and which docs moved" is answerable
5
+ at a glance. Stack-only changes list the file.
6
+
7
+ Format: `## <date>` → `### core: <slug>` / `### <stack>` entries.
8
+
9
+ ---
10
+
11
+ ## 2026-09-01 — the shared tooling package (#compliance mechanism, eleventy)
12
+
13
+ ### stacks/eleventy-netlify.md
14
+
15
+ - **`@piercebarney/whs-eleventy` — the `whs` CLI.** The generic eleventy checks
16
+ (compliance sweep, doctor, link/CSP, a11y, content-check) move out of every
17
+ project's `scripts/` into one shared package (`packages/whs-eleventy/`).
18
+ Projects add it as a `devDependency` and run `whs compliance` / `whs links` /
19
+ … ; a fix lands once and `npm update` carries it. Only the asset *generators*
20
+ (`scripts/og*.js`, `scripts/icons.js`, `og-card.js`) stay per-project. The
21
+ `#the-gate` `package.json` snippet, `#compliance`, `#a11y` (now axe-core via
22
+ headless Chrome, not `pa11y`), `#audit-page`, the file tree, and the
23
+ new-project + migration checklists are updated to match.
24
+ - The drift check no longer needs the `~/.claude/standards` path: the package
25
+ bundles a `core.md` + `CHANGELOG.md` snapshot (overridable with
26
+ `WHS_STANDARD`), and reports a visible `MANUAL` row — never a silent pass —
27
+ when the standard can't be resolved. Its CHANGELOG slug parser now reads every
28
+ slug on a multi-slug `### core: …` line.
29
+
30
+ ### tooling
31
+
32
+ - **`bin/set-version <YYYY-MM-DD>`** bumps every version string the standard
33
+ carries — `**Version:**` (`core.md` + 3 stack docs), `index.json` `version`,
34
+ the `#adopting` block pin, each `templates/*/CLAUDE.md` `standard-version`, and
35
+ `packages/*/package.json` `version` (the date dotted, `2026-09-01` →
36
+ `2026.9.1`) — in one shot. `bin/check`'s fifth check fails if they disagree.
37
+ - **`.github/workflows/publish.yml`** publishes `@piercebarney/whs-eleventy` to
38
+ npm when a `YYYY-MM-DD` version tag is pushed — it lints + tests the package,
39
+ checks the tag date (dotted) matches `package.json`, and publishes unless that
40
+ version is already up. Needs the `@piercebarney` npm scope and an `NPM_TOKEN`
41
+ repo secret. `--provenance` is omitted (needs a public source repo).
42
+ - `standard.yml` gains a `package` job (prettier + eslint + `node:test`); the
43
+ `templates` job installs the file: package's own tree and points
44
+ `WHS_STANDARD` at the checkout so drift is checked against the live standard.
45
+
46
+ ---
47
+
48
+ ## 2026-08-31 — the Cowork ↔ Claude Code loop: concept brief + content ops (#content-model)
49
+
50
+ ### core: adopting
51
+
52
+ - Stack-selection **Q1** no longer offers SvelteKit as a selectable framework.
53
+ It is one of: Eleventy + Netlify *(default)* · Elixir/Phoenix *(database /
54
+ server-side state in scope)* · other. `stacks/sveltekit.md` is now a
55
+ **parked** stub — friction points and open questions written, per-section
56
+ "how" not — and the flow says to treat SvelteKit as "other" until a real
57
+ project forces those decisions. `README.md` doc map + flow and `index.json`
58
+ (`status: parked`, reworded warning) follow; the sveltekit STATUS banner
59
+ says PARKED. No `**Version:**` change (same day).
60
+
61
+ ### core: content-model
62
+
63
+ - The "non-developer editing" spec gains an **agent-editing** clause: where an
64
+ LLM agent edits the content data files directly, it follows a documented
65
+ per-project content-ops protocol — what it may change (content, not code), a
66
+ pre-commit content check (the content-relevant slice of the gate), a
67
+ `content:` commit convention. Header + block-template pin → `2026-08-31`.
68
+
69
+ ### stacks/eleventy-netlify.md
70
+
71
+ - `#content-model` gains the mechanism: a project-root **`CONTENT.md`** (the
72
+ content set, the commit convention, "deploys are a human step") and
73
+ **`scripts/content-check.js`** / `npm run content-check` — `lint → validate →
74
+ build → links` (no browser, no `test`; a11y backstopped by `bin/deploy`),
75
+ plus a warning when the working tree has changes outside the content set.
76
+ `compliance.js`'s `content-model` row reports whether the protocol is
77
+ present. Header → `2026-08-31`.
78
+ - `#internal-links`: the glossary is labelled **opt-in** — the baseline now has
79
+ no glossary wiring at all. `src/_data/glossary.js` (empty stub) and the no-op
80
+ `glossify` filter are removed; `explainer.njk` drops `| glossify`;
81
+ `check-links.js` wraps the `glossary.js` require in `try/catch` so the
82
+ `/glossary/#slug` check activates only when the add-on ships the file. The
83
+ `glossary` add-on gains an `explainer.njk` and its README/config-snippet stop
84
+ referring to a baseline no-op.
85
+
86
+ ### stacks/phoenix.md · sveltekit.md
87
+
88
+ - `#content-model` each gets a one-line agent-editing note (`CONTENT.md` + a
89
+ `content.check` alias) — `TODO`, pointing at the eleventy pattern. Headers →
90
+ `2026-08-31`.
91
+
92
+
93
+
94
+ ### templates/concept-brief.md (new)
95
+
96
+ - A stack-agnostic fill-in brief that ideation (Cowork / a Claude chat)
97
+ produces and Claude Code builds from. Sections map to concrete output: **the
98
+ idea** → home intro / about / meta descriptions; **House style** (a fenced
99
+ `- key: value` block) → copied into the project's `CLAUDE.md`, answers the
100
+ `#adopting` five questions; **Brand** (concept-level — "muted teal, calm, a
101
+ leaf mark") → `src/_data/brand.js`, verified AA on `/audit/`; **Content**
102
+ (collection name, extra fields, 3–5 seed entries) → the content model +
103
+ schema + rendered collection; **Pages & layout** → the templates;
104
+ **Add-on packs** (checklist) → which `add-ons/<name>/` to apply; **Meta** →
105
+ name / pkg / currency / owner.
106
+
107
+ ### templates/eleventy-netlify/bin/init · new-project.sh
108
+
109
+ - `bin/init --brief <path>`: reads the brief's House-style block + Meta (no
110
+ deps — a line parser), writes the House-style answers into `CLAUDE.md`
111
+ (dropping the decision-menu comments), and takes name / package / URL /
112
+ currency from Meta + the block; prompts only for what the brief left blank.
113
+ - `new-project.sh <stack> <dest> --brief <path>` resolves a relative brief
114
+ path before `cd`-ing, forwards it, and errors with a checklist pointer when
115
+ the stack has no template.
116
+
117
+ ### docs — adopting pointer
118
+
119
+ - `core.md#adopting` / `README.md` / `TEMPLATE.md` gain a pointer: the filled
120
+ brief is the recommended input to the new-project flow. No contract or spec
121
+ change of its own — the version move above is the content-ops clause. An
122
+ existing project re-checks `#adopting` and finds nothing to do: the brief is
123
+ new-project tooling.
124
+
125
+ ---
126
+
127
+ ## 2026-08-30 — a copy-and-modify template for Eleventy + Netlify (#adopting)
128
+
129
+ ### templates/eleventy-netlify (new)
130
+
131
+ - A copy-and-modify starter for the Eleventy + Netlify stack, co-located at
132
+ `templates/eleventy-netlify/`, versioned / CHANGELOG'd / consistency-checked
133
+ with the docs. Its structure was lifted from a live compliant project; from
134
+ here it is maintained **against the standard** (`core.md` +
135
+ `stacks/eleventy-netlify.md`, verified by `templates/verify.sh`), not synced
136
+ from any project. `npm run check` and `npm run compliance` pass on commit 1
137
+ (`28 pass · 0 fail`), so a new project starts at "fill in brand + content".
138
+ - **Baseline:** the always-true compliant skeleton — the generic
139
+ tooling/config/scripts verbatim, the GWP files with `__PKG_NAME__` /
140
+ `__SITE_NAME__` / `https://example.com` / `USD` placeholders, a neutral
141
+ `guides` content model with 3 sample entries, and one example client-logic
142
+ module. An obviously-placeholder magenta brand (still AA) so nothing
143
+ un-branded ships by accident.
144
+ - **`add-ons/<name>/`** — opt-in packs mirroring the opt-in chapters:
145
+ `ads` (`#ads`), `ai-proxy` (`#llm-integration` — a worked Netlify Function
146
+ proxy + a pure tested transform), `feed` (article feeds, via
147
+ `@11ty/eleventy-plugin-rss@3`'s `feedPlugin`), `glossary`
148
+ (`#internal-links`), `calculator` (`#client-logic` / `#domain-tests`). Each
149
+ is a folder + a numbered copy-in README. All five smoke-tested against a
150
+ fresh init'd project (`ai-proxy`'s model call excepted — no key in CI).
151
+ - **`bin/init`** (Node, no deps) substitutes the placeholders, installs,
152
+ commits, and runs the gate, then deletes the template-meta files.
153
+ `templates/new-project.sh` wraps copy + `git init` + `bin/init`.
154
+ - **`templates/verify.sh`** runs each template's `check` + `compliance
155
+ --strict` and fails on `standard-version` drift. **`bin/check`** runs the
156
+ four doc-consistency checks (CORE-slug · slug-set mirror · no restatement ·
157
+ sterile). **`.github/workflows/standard.yml`** runs both on every push / PR
158
+ / weekly — the weekly run is the drift + supply-chain alarm. (The stray
159
+ nested `templates/eleventy-netlify/.github/workflows/template-verify.yml` —
160
+ inert, since GitHub only reads root workflows — is removed.)
161
+ - `scripts/compliance.js` + `test/compliance.test.js` sweep the Part J
162
+ chapters (`llm-integration` / `agent-artifacts` / `usage-metering`) — they
163
+ were missing from the seed (it predated 2026-08-29) despite the
164
+ `2026-08-30` pin. `N/A` for a static site unless `ai: llm-api`. The
165
+ `stacks/eleventy-netlify.md#compliance` PASS-criteria table gains the row.
166
+ - `compliance.js` `#ci-cd` no longer depends on the **live** `core.hooksPath`
167
+ state — that's "`npm install` has run here", not a codebase property, so a
168
+ fresh clone stays compliant. `doctor` still surfaces it on `/audit/` as a
169
+ warn. The template's `prepare` script is guarded to no-op unless it's the
170
+ repo root, so `npm ci` in a template checked out inside another repo (this
171
+ one) doesn't touch the outer git config. `#ci-cd` gains a one-liner.
172
+
173
+ ### core: adopting
174
+
175
+ - New **"Starting point"** paragraph after the stack-selection flow: where a
176
+ `templates/<stack>/` exists, starting from it *is* the new-project path; the
177
+ impl doc's checklist is the by-hand equivalent and the annotated inventory.
178
+ Stack-agnostic wording — only `eleventy-netlify` has one today. Header version
179
+ and the block-template `standard-version` pin → `2026-08-30`.
180
+
181
+ ### stacks/eleventy-netlify.md
182
+
183
+ - `#adopting`: a **"Fastest path — start from the template"** lead-in above the
184
+ new-project checklist (`new-project.sh` → `bin/init` → `brand.js` + content →
185
+ `npm run deploy`); the 11-step checklist kept verbatim as the by-hand path /
186
+ inventory. `#ads` and `#llm-integration` each gain a one-line pointer to their
187
+ `add-ons/<name>/` pack. Header version → `2026-08-30`.
188
+ - `#styling` (`pico-classes`) + `#brand-source`: `src/tokens.css.njk` maps
189
+ `brand.js`'s core palette (primary, background, text, danger) onto the
190
+ matching `--pico-*` vars, so a `brand.js` colour change reaches the **live
191
+ CSS** — not just re-pinned hexes Pico already has. The template does this;
192
+ the earlier pattern of pinning a Pico theme's hexes into `brand.js` left the
193
+ live CSS unchanged and is a `#brand-source` gap. Pico's build still supplies
194
+ the derived neutrals.
195
+
196
+ ### stacks/phoenix.md · sveltekit.md
197
+
198
+ - `#adopting`: a one-line "no starter template for this stack yet — follow the
199
+ checklist; `stacks/eleventy-netlify.md` has the reference template". Header
200
+ versions → `2026-08-30`.
201
+
202
+ ### README.md
203
+
204
+ - Doc-map gains a `templates/` row. A "Starting a new project" paragraph in
205
+ "How to use it". "Home" notes the templates ship in-repo and a remote unlocks
206
+ `degit`. Consistency checks gain `templates/verify.sh` as the 4th check.
207
+ "Proposing changes" gains the template-sync rule (update the template in the
208
+ same change as a mirrored-section edit, re-pin, run `verify.sh`).
209
+
210
+ ### index.json
211
+
212
+ - Each framework gains a `template` field (`templates/eleventy-netlify/` or
213
+ `null`). `version` → `2026-08-30`.
214
+
215
+ ### not a standard-content change
216
+
217
+ - No CORE contract changed. The `#adopting` edit adds a path, not a rule; the
218
+ `stacks/eleventy-netlify.md` `#styling` / `#brand-source` / `#compliance`
219
+ edits clarify existing mechanism; the version bump is for the template pin +
220
+ the doc-set additions above.
221
+
222
+ ---
223
+
224
+ ## 2026-08-30 — the standard is a git repo
225
+
226
+ ### repo
227
+
228
+ - Consolidated the two loose copies (the canonical set under
229
+ `~/.claude/standards/web-house-style/` and a stale pre-`#compliance` copy at
230
+ `~/Dev/web-house-style/`) into one git-tracked project at
231
+ `~/Dev/web-house-style/`, default branch `main`. `~/.claude/standards/web-house-style/`
232
+ is now a symlink to it, so every existing reference resolves unchanged.
233
+ - `README.md` "Provisional home" → "Home", updated for the repo layout and the
234
+ remaining deferred items (global `CLAUDE.md` pointer, a GitHub remote,
235
+ existing-project migration).
236
+ - No standard-content change — versions and slugs untouched.
237
+
238
+ ---
239
+
240
+ ## 2026-08-29 — AI & agent capabilities, opt-in (#llm-integration, #agent-artifacts, #usage-metering)
241
+
242
+ ### core: llm-integration · agent-artifacts · usage-metering (new chapters)
243
+
244
+ - New **Part J — AI & agent capabilities (opt-in)**, three chapters, inserted
245
+ before the old Part J (Meta), which becomes **Part K**. All three are
246
+ need-gated the way `#beyond-this-standard` and `#ads` are — opt-in per project,
247
+ **not** a consequence of the framework. A project that calls no model API is
248
+ unaffected (all three `N/A`).
249
+ - **`#llm-integration`** — in scope whenever the project calls a model
250
+ provider's API at all. Server-side-only key handling per environment
251
+ (extends `#secrets`), explicit timeout + capped-backoff retry + an app-side
252
+ per-user request ceiling, per-request token/cost telemetry + spend-anomaly
253
+ alert (extends `#observability`), a moderation/safety layer sized to the
254
+ subject's risk (three tiers), a plain visible AI disclosure to end users, and
255
+ a pre-launch provider-usage-policy fit check with a recorded date. The
256
+ response transform is a pure tested function (`#domain-tests`).
257
+ - **`#agent-artifacts`** — in scope only when users generate downloadable files
258
+ via model-driven code execution. Sandboxed execution (no route to secrets /
259
+ DB / network), object-storage with an enforced TTL, authorization-checked
260
+ delivery with defensive `Content-Type` / `Content-Disposition` / `nosniff` /
261
+ CSP, per-artifact cost as its own `#observability` signal.
262
+ - **`#usage-metering`** — in scope only when pricing isn't flat. An immutable
263
+ per-subject event log, server-side pre-spend plan-tier enforcement, a
264
+ scheduled reconciliation job against the payment processor (idempotent usage
265
+ reporting, webhook-driven tier state), a user-facing usage view from the same
266
+ rows, tested partial-failure paths, tested allowance boundaries.
267
+ - Threaded one-liners into `#third-parties` (a model provider is a declared third
268
+ party; key-bearing → server-side), `#privacy` (sending end-user content to a
269
+ provider is a privacy-page entry; use the no-train/zero-retention mode),
270
+ `#secrets` (model-provider key = per-env, spend-capped, scoped, rotatable
271
+ without redeploy), `#observability` (per-request token/cost + reconciliation
272
+ drift join the loop), `#beyond-this-standard` (an LLM/agent surface is its own
273
+ opt-in axis; a single stateless call is a serverless function, streaming /
274
+ artifacts / metering are past the envelope), `#compliance` (the Part J chapters
275
+ are `N/A` when `ai: none`), `#out-of-scope` (ML infrastructure — fine-tuning,
276
+ RAG retrieval layer, evals harness, prompt-versioning — is not covered),
277
+ `#anti-patterns` (six new entries).
278
+ - `#adopting`: a **5th** stack-selection question ("Does the project call an
279
+ LLM / agent API?", default No, with the `+artifacts` / `+metering` follow-ups);
280
+ `ai: none` added to the `CLAUDE.md` block template. The example block's
281
+ `standard-version` pin and the doc header move to `2026-08-29`.
282
+ - Slug registry gains `llm-integration` · `agent-artifacts` · `usage-metering`
283
+ (after `observability`).
284
+
285
+ ### stacks/phoenix.md
286
+
287
+ - Three new mirrored sections (the concrete "how" this change is grounded in):
288
+ `#llm-integration` — a context module + `Req` with explicit timeout, an Ash
289
+ `Conversation` / `Message` resource for chat state, LiveView streamed assigns
290
+ for token streaming, a `LLM.Usage` Ash resource written per call, `Oban` for
291
+ async summarisation/metering, a `Moderation` pass, the provider origin in the
292
+ `ThirdParties` manifest; `#agent-artifacts` — an Oban job driving a sandboxed
293
+ runner (a separate container / microVM, never the app node), R2/S3 storage with
294
+ a TTL + an Oban cron sweeper, an authorization-checked `ArtifactController`
295
+ download with defensive headers; `#usage-metering` — a `UsageEvent` immutable
296
+ Ash resource, a pre-spend `Ash.Policy` / plan check, an `Oban` reconciliation
297
+ worker against Stripe with idempotent usage records and webhook handlers.
298
+ Adds AI items to the new-project checklist and the migration list; two
299
+ Phoenix-specific anti-patterns.
300
+
301
+ ### stacks/eleventy-netlify.md
302
+
303
+ - Three new mirrored sections, all **out-of-scope-with-a-pointer**: a single
304
+ stateless model call belongs in a `netlify/functions/*` proxy (the one
305
+ sanctioned serverless carve-out — key in the Function env, timeout, the origin
306
+ in `_data/thirdparties.js`, the privacy page updated, a visible AI disclosure,
307
+ the transform pure-tested); conversation state, streaming, sandboxed code
308
+ execution, and usage metering are all past `#beyond-this-standard` → use
309
+ `stacks/phoenix.md`. `ai: none` added to the `CLAUDE.md` block template and a
310
+ change-checklist line.
311
+
312
+ ### stacks/sveltekit.md
313
+
314
+ - Three new mirrored sections — `TODO` stubs with hints (a `+server.js` endpoint
315
+ or a serverless function for the keyed call under `adapter-static`; anything
316
+ stateful/streamed/metered → `core.md#beyond-this-standard` and the Phoenix
317
+ reference). `ai:` note added to the change checklist.
318
+
319
+ ---
320
+
321
+ ## 2026-08-28 — codebase-vs-standard conformance (#compliance)
322
+
323
+ ### core: compliance (new chapter)
324
+
325
+ - New chapter `#compliance` in Part I, between `#the-gate` and `#ci-cd`.
326
+ Contract: one command reports the codebase's state against **every chapter** —
327
+ `PASS` / `FAIL` / `MANUAL` / `N/A` — as the drift-and-completeness sweep that
328
+ `#the-gate` (regressions only) does not do. It is **not** part of the gate: a
329
+ `FAIL` is a migration gap, not a regression, and gating on it would freeze work
330
+ mid-migration. Specs: walks every CORE chapter + mirrored impl section; summary
331
+ line `N pass · M fail · K manual`; runs on demand and at build time
332
+ (cache-aware) feeding the audit page's Compliance panel; the deploy pre-flight
333
+ warns on any `PASS → FAIL` since the last deploy; a standard-version drift row
334
+ compares the pinned `standard-version` to `core.md`'s version header and emits
335
+ `MANUAL: re-check #<slug>` per CHANGELOG slug changed since. Reuses existing
336
+ machinery (repo introspection + link-check output scan). The assistant runs it
337
+ at onboarding, when the pin is behind, before a multi-chapter release, and when
338
+ asked "are we still in line?".
339
+ - Threaded into `#the-gate` (a bullet: completeness + version drift are
340
+ `#compliance`'s job, not the gate's), `#audit-page` (a Compliance tab),
341
+ `#ci-cd` (production-deploy pre-flight warns on `PASS → FAIL`), `#adopting`
342
+ (the "four questions" fix, a `#compliance` line in the directive, an
343
+ "Onboarding an existing project" paragraph, a Change-checklist item),
344
+ `#observability` (a re-run trigger), `#anti-patterns` (compliance in the gate /
345
+ treating a `FAIL` as a regression, working the backlog without re-running it,
346
+ a stale `standard-version` pin).
347
+ - Slug registry gains `compliance` (after `the-gate`).
348
+
349
+ ### stacks/eleventy-netlify.md
350
+
351
+ - New `compliance` mirrored section: `scripts/compliance.js`,
352
+ `"compliance": "node scripts/compliance.js"` in `package.json` (**not** in
353
+ `check`), a per-chapter PASS-criteria table, the MANUAL list, the
354
+ standard-version drift check (`standard-version` in `CLAUDE.md` vs `**Version:**`
355
+ in `core.md` vs `### core:` entries in `CHANGELOG.md`), `_data/infra.js`
356
+ running it cache-aware for the audit-page Compliance tab, `bin/deploy`
357
+ diffing against `.cache/compliance-last.json` and warning on `PASS → FAIL`
358
+ (non-zero exit only under `--strict`). "Compliance" added to the audit-page
359
+ tabs; the House-style directive block, the new-project checklist, and the
360
+ existing-project migration note updated.
361
+
362
+ ### stacks/phoenix.md
363
+
364
+ - New `compliance` mirrored section: a `mix whs.check` task (**not** in the
365
+ `check` alias), reusing `mix links.check`'s output scan and the `/audit`
366
+ repo-config introspection; an Ash-flavored per-chapter table; the
367
+ standard-version drift row; `/audit` Compliance tab + `bin/deploy` pre-flight
368
+ wiring.
369
+
370
+ ### stacks/sveltekit.md
371
+
372
+ - New `compliance` mirrored section — `TODO` stub with a hint
373
+ (`scripts/compliance.js` or a Vite task over `build/` + repo, the drift row,
374
+ the prerendered `/audit` Compliance tab).
375
+
376
+ ---
377
+
378
+ ## 2026-08-28 — the `CLAUDE.md` House-style directive
379
+
380
+ ### core: adopting
381
+
382
+ - The recommended `CLAUDE.md` content changed from a bare data block (with one
383
+ passive "read the docs" sub-bullet) to a **`## House style` section**: a
384
+ directive paragraph — *use the standard for all work unless a request
385
+ explicitly says otherwise; check the governing chapter before implementing;
386
+ a conflict with the current code is a migration gap to surface, not route
387
+ around; never silently diverge from a Contract, record deliberate deviations;
388
+ where the standard is silent, do what's idiomatic; run the gate before
389
+ reporting done* — followed by the data block. Verify updated to require both
390
+ parts. Header version → 2026-08-28.
391
+
392
+ ### stacks/eleventy-netlify.md · phoenix.md · sveltekit.md
393
+
394
+ - Each `adopting` section now shows its filled-in `## House style` section with
395
+ the gate command substituted (`npm run check` / `mix check`) and any
396
+ stack-specific `CLAUDE.md` prose (Eleventy: the generated-assets note;
397
+ Phoenix: the brand-tokens note) folded in. New-project checklists point at it.
398
+
399
+ ---
400
+
401
+ ## 2026-08-28 — display advertising (AdSense)
402
+
403
+ ### core: ads (new chapter)
404
+
405
+ - New chapter `#ads` in Part G, after `#privacy`. Contract: display ads are
406
+ opt-in per project (`ads:` key in the `CLAUDE.md` block, `none` default);
407
+ consent-gated via a provider-certified CMP; every ad origin declared in the
408
+ manifest; slots carry reserved dimensions (CLS); the loader never runs on a
409
+ non-production build; `ads.txt` is generated/managed; the CSP relaxation is
410
+ the widest it ever gets, scoped to the ad directives, documented, and never
411
+ touches `default-src` or adds `'unsafe-eval'`.
412
+ - Threaded one-liners into `#security-headers` (the ad CSP relaxation),
413
+ `#privacy` (ads always need a CMP + cookies — never privacy-first),
414
+ `#third-parties` (an ad provider is a declared third party), `#noindex` (same
415
+ `isProduction` gate covers ads), `#observability` (ads regress CLS/LCP;
416
+ reserved slots hold CLS), `#out-of-scope` (ad density / editorial),
417
+ `#anti-patterns` (ad loader before consent, ads on non-prod, drifted
418
+ `ads.txt`, unreserved slots, non-certified CMP, `'unsafe-eval'`).
419
+ - `#adopting`: a 4th stack-selection question ("Display advertising?" default
420
+ No); `ads: none` added to the `CLAUDE.md` block template.
421
+ - Slug registry gains `ads`.
422
+
423
+ ### stacks/eleventy-netlify.md
424
+
425
+ - New `ads` mirrored section: `_data/ads.js`, `_includes/ad-slot.njk` (wrapper
426
+ div + `<ins class="adsbygoogle">`, no inline `style`), the `push({})` in
427
+ `site.js`, `src/ads.txt.njk`, the `style.css` reserved-height rules, the
428
+ `netlify.toml` CSP fence with a representative AdSense/CMP directive set, the
429
+ `build.isProduction` gate on the CMP + loader + macro, `check-links.js`
430
+ non-prod / `ads.txt` checks, and an `/audit/` **Ads** tab. Google's GDPR
431
+ message named as the low-friction certified CMP.
432
+ - Tree, `noindex`, `security-headers`, `internal-links`, `audit-page`,
433
+ `adopting`, and `anti-patterns` gained ad cross-refs.
434
+
435
+ ### stacks/phoenix.md
436
+
437
+ - New `ads` mirrored section: `<App>Web.Ads` module (`enabled?/0` =
438
+ `config_env() == :prod`), `config.exs` holds the ids, `<.ad_slot>` component,
439
+ `/ads.txt` controller, the CSP in the `SecurityHeaders` plug, the LiveView
440
+ nonce vs `'unsafe-inline'` note. `noindex` / `audit-page` / `anti-patterns`
441
+ cross-refs.
442
+
443
+ ### stacks/sveltekit.md
444
+
445
+ - New `ads` mirrored section (skeleton + hints): `src/lib/ads.js`, the loaders in
446
+ `+layout.svelte` gated on `import.meta.env.PROD` + a consent store, an
447
+ `<AdSlot>` component, `/ads.txt` as a `+server.js` route, the `svelte.config.js`
448
+ `csp` interaction.
449
+
450
+ ---
451
+
452
+ ## 2026-08-28 — hardening from the first full Eleventy migration
453
+
454
+ Fixes and gaps surfaced running the first full Eleventy migration end to end.
455
+
456
+ ### core: (client-logic, theme, assets, internal-links, security-headers)
457
+
458
+ - **`client-logic`** — the page wiring is **CSP-safe**: no inline `<script>`, no
459
+ `on*=`; it's a loaded file, and for a per-page calculator the browser-only
460
+ wiring sits in a `typeof document !== "undefined"` block *inside the same pure
461
+ module* (one file, DOM-free in the test runner). Resolves the standing
462
+ contradiction with `security-headers`' `script-src 'self'`.
463
+ - **`theme`** — the anti-FOUC script is a separate render-blocking file (or a
464
+ hashed inline block), never a raw inline `<script>` — same CSP reason.
465
+ - **`assets`** — added: verify a custom font's subset actually covers the
466
+ site's text (a mis-scoped subset fails silently to the system stack); serve
467
+ the web manifest as `application/manifest+json`.
468
+ - **`internal-links`** — the build check also fails on any inline `<script>` /
469
+ `<style>` / `on*=` / `style=` in the output (CSP-regression guard), and on any
470
+ CSP-granted origin missing from the manifest (the reverse isn't required —
471
+ a host/form-processor is in the manifest but loads nothing).
472
+ - **`security-headers`** — Verify reworded to the one-directional CSP↔manifest
473
+ check; note that the CSP only applies once served (check live + click through
474
+ in a real browser after deploy); don't restate platform-managed HSTS.
475
+
476
+ ### stacks/eleventy-netlify.md
477
+
478
+ - `client-logic` / `theme` — the calc module now carries the browser wiring
479
+ block; `data-calc=` on the button; anti-FOUC → `static/theme-init.js`.
480
+ - `og-image` — Satori needs a **static** ttf/otf/woff (not woff2, not a
481
+ variable font's `fvar`); bundle `@fontsource/<family>` static weights; build
482
+ the card as a vnode object, drop `satori-html`.
483
+ - `the-gate` — `"test": "node --test test/*.test.js"` (the glob; bare
484
+ `node --test test/` errors on newer Node). `png-to-ico` + `@fontsource/*` in
485
+ deps; `node-html-parser` (check-links) in devDeps; note `npm ci` installs
486
+ both tiers.
487
+ - `ci-cd` — `actions/checkout@v5` / `setup-node@v5` (v4 → deprecated Node 20);
488
+ shallow checkout is fine for the `git log` `<lastmod>`; `NETLIFY_SITE_ID` is
489
+ the UUID Netlify now labels "Project ID".
490
+ - `security-headers` — `[[headers]]` for `/site.webmanifest` content-type.
491
+ - `internal-links` — the CSP-safety + CSP-origin scan documented in
492
+ `check-links.js`; ad/privacy opt-out `<a>` allowlist.
493
+ - `adopting` Pattern A — steps 6/8 reworded to write CSP-shaped modules up front
494
+ and land `theme-init.js` + the inline-code scan with the headers.
495
+
496
+ ### stacks/sveltekit.md
497
+
498
+ - `theme` hint — anti-FOUC must be a hashed inline block (SvelteKit `csp`) or a
499
+ `<script src>`, not raw inline.
500
+
501
+ ---
502
+
503
+ ## 2026-08-28 — solo is the default working mode
504
+
505
+ ### core: ci-cd
506
+
507
+ - Reframed from a PR-centric flow with solo accommodations to **solo as the
508
+ documented default**: branch → pre-push hook runs the gate → fast-forward
509
+ `main` → push; open a pull request only for a concrete reason (CI vetting a
510
+ branch before `main`, the rendered diff, a paper trail), never as
511
+ self-approval ceremony; multi-committer projects add the PR/review layer on
512
+ top and nothing below changes. New specs: the pre-push hook is *the*
513
+ enforcement point solo; push `main` after each merge (offsite copy + CI
514
+ actually runs); CI is a clean-room environment-drift check, not a merge gate.
515
+ Rationale gained a paragraph; Verify now checks the hook is *installed*.
516
+
517
+ ### core: repo-hygiene
518
+
519
+ - "one logical change per pull request" → "per commit (and per PR, on projects
520
+ that use them)". Contract line "small reviewable changes" → "small
521
+ self-contained changes".
522
+
523
+ ### core: adopting
524
+
525
+ - "PR review checklist" → "Change checklist" — run before a merge to `main` and
526
+ again before `bin/deploy`; "all in this PR" → "all in the same change".
527
+
528
+ ### stacks/eleventy-netlify.md · phoenix.md · sveltekit.md
529
+
530
+ - `ci-cd` mirrors: the Eleventy binding states the solo flow up front and
531
+ `ci.yml` triggers on `push: {}` (any branch) + `pull_request`; "every merged
532
+ PR" → "everything on `main`"; "PR review checklist" → "Change checklist"
533
+ across all three; migration `[one PR:]` markers → `[together:]`; the
534
+ "busy PR flow" build-minutes framing dropped.
535
+
536
+ ## 2026-08-27 — netlify deploy `--no-build`
537
+
538
+ ### stacks/eleventy-netlify.md
539
+
540
+ - `ci-cd` — corrected the deploy command. The doc claimed `netlify deploy`
541
+ "without `--build`" uploads the artifact without building; the current Netlify
542
+ CLI (v17+, verified on v27) runs `netlify.toml`'s `build.command` **by
543
+ default** on `netlify deploy`. Without `--no-build` it rebuilds `_site/` with
544
+ no `DEPLOY_TARGET`, i.e. in preview mode, and ships `noindex` + `robots`
545
+ `Disallow: /` to production. Surfaced by the first real `bin/deploy` on the
546
+ first Eleventy migration — production served `noindex` for ~35 min.
547
+ `bin/deploy`, the `deploy.yml`/PR-preview snippets, the "zero build minutes"
548
+ line, and the anti-pattern entry all now carry `--no-build`.
549
+
550
+ ### stacks/sveltekit.md
551
+
552
+ - `ci-cd` hint — same `--no-build` fix in the `adapter-static` + Netlify note.
553
+
554
+ ## 2026-08-27 — initial split
555
+
556
+ The single 1518-line "Eleventy + Netlify static-site house style" document was
557
+ split into a stack-agnostic CORE contract plus per-stack implementation
558
+ bindings.
559
+
560
+ ### core: all
561
+
562
+ - Created `core.md` — ~34 contract chapters (`Contract / Specs / Rationale /
563
+ Verify / Implementation`), each with a frozen slug. The 23 sections of the
564
+ source doc were dispositioned CORE / IMPL / SPLIT; every stack-agnostic
565
+ principle landed in CORE with its concrete specs preserved verbatim (the CSP
566
+ string, the OG/Twitter tag table, the a11y checklist, the security-header
567
+ values, the OG image dimensions).
568
+ - `#privacy` — encodes the "consent-gated allowed" decision: default is
569
+ no-tracking; third-party analytics (incl. GA/GTM) permitted **only** when
570
+ privacy-first or gated behind a CMP that loads nothing until opt-in.
571
+ - `#ci-cd` — genericized from the Netlify "a git push never triggers a build"
572
+ framing to "deploys are gated, sanctioned, no bypass, on your schedule";
573
+ push-to-deploy is explicitly fine where the gate runs first.
574
+ - `#security-headers` — "framing denied" accepts `X-Frame-Options: DENY` **or**
575
+ `SAMEORIGIN` + `frame-ancestors 'none'` (surfaced by writing the Phoenix
576
+ binding, where `put_secure_browser_headers` uses SAMEORIGIN).
577
+ - `#assets` — "a system font stack also satisfies 'no external font origin'"
578
+ (surfaced by the Phoenix binding).
579
+ - `#content-model` — the validation-gate contract holds for DB-backed content
580
+ via changeset/resource validation; "no database" is not a CORE rule (it is the
581
+ static envelope, addressed in `#beyond-this-standard`).
582
+
583
+ ### stacks/eleventy-netlify.md
584
+
585
+ - The source doc reworked as the Eleventy binding (~90% mechanism reuse). 4
586
+ impl-only sections (`stack-baseline`, `project-layout`, `eleventy-config`,
587
+ `templating`) + 31 mirrored sections + Eleventy-specific `adopting` /
588
+ `out-of-scope` / `anti-patterns`.
589
+ - Two migration paths added, keyed to common starting conditions (Pattern A:
590
+ self-hosted, content in templates, no CI; Pattern B: CDN assets + a form
591
+ processor, content already externalized).
592
+
593
+ ### stacks/phoenix.md
594
+
595
+ - New binding, grounded in a real production Phoenix/Ash codebase (unnamed —
596
+ the standard is project-agnostic). Every CORE contract could be expressed as a
597
+ Phoenix "how" — none had to move back to Eleventy-specific.
598
+ - Notable idiomatic contrasts: cached `SitemapController` + `Ash.Notifier`
599
+ invalidation (not a build-time template); `bin/deploy {env}` `git push` to a
600
+ release remote (push-to-deploy is fine); the serverless key-hiding proxy
601
+ dissolves (call from a context module); `internal-links` partly free via
602
+ verified routes `~p`.
603
+ - The common non-compliance of an existing Phoenix app (no CSP, per-page meta
604
+ unassigned, no JSON-LD, marketing scripts with no CMP, no a11y tooling, a
605
+ `.env` in git history, an unrouted health check, a misplaced `force_ssl`
606
+ `exclude:` key, unauthenticated AshAdmin, no CI workflow) is documented
607
+ per-section as **common gaps** and consolidated into a migration checklist.
608
+
609
+ ### stacks/sveltekit.md
610
+
611
+ - New binding as a **stub**: full mirrored-slug skeleton, per-section "how" is
612
+ `TODO` with a concrete hint. The `where-sveltekit-fights-the-grain` (5
613
+ friction points + 3 places it helps) and `open-questions-for-later` (6 items)
614
+ sections are written.
615
+
616
+ ### deleted
617
+
618
+ - `eleventy-house-style.md` (the source) — superseded by `core.md` +
619
+ `stacks/eleventy-netlify.md`.
620
+
621
+ ### not done (deferred)
622
+
623
+ - Move the doc set to a permanent home; wire the global `~/.claude/CLAUDE.md`
624
+ pointer; backfill the House-style block into and migrate existing Eleventy
625
+ projects. Each needs its own review.