@piercebarney/whs-eleventy 2026.9.3 → 2026.9.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/lib/compliance.js +48 -18
- package/package.json +3 -3
- package/standard/CHANGELOG.md +687 -17
- package/standard/core.md +75 -31
package/lib/compliance.js
CHANGED
|
@@ -664,16 +664,26 @@ const CHECKS = {
|
|
|
664
664
|
|
|
665
665
|
// ---- standard-version drift -----------------------------------------
|
|
666
666
|
|
|
667
|
-
// Every
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
667
|
+
// Every CHANGELOG entry (`## <date> [breaking|non-breaking]`) dated after
|
|
668
|
+
// `pin`, with the chapter slugs it touched. `### core: a · b · c` yields
|
|
669
|
+
// every slug on the line, not just the first; a
|
|
670
|
+
// `### stacks/eleventy-netlify.md` heading yields the binding marker. An
|
|
671
|
+
// entry heading with no severity tag (a fixture, or a pre-2026-09-04
|
|
672
|
+
// CHANGELOG snapshot) defaults to non-breaking — the real standard's own
|
|
673
|
+
// CHANGELOG.md always carries the tag (bin/check's 6th check enforces it).
|
|
674
|
+
function changelogEntries(changelog, pin) {
|
|
675
|
+
const entries = [];
|
|
676
|
+
let current = null;
|
|
673
677
|
for (const line of (changelog || "").split("\n")) {
|
|
674
|
-
const dateM = line.match(
|
|
675
|
-
|
|
676
|
-
|
|
678
|
+
const dateM = line.match(
|
|
679
|
+
/^##\s+([0-9]{4}-[0-9]{2}-[0-9]{2}).*?(?:\[(breaking|non-breaking)\])?\s*$/,
|
|
680
|
+
);
|
|
681
|
+
if (dateM) {
|
|
682
|
+
current = dateM[1] > pin ? { severity: dateM[2] || "non-breaking", slugs: new Set() } : null;
|
|
683
|
+
if (current) entries.push(current);
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (!current) continue;
|
|
677
687
|
const coreM = line.match(/^###\s+core:\s+(.+)/);
|
|
678
688
|
if (coreM) {
|
|
679
689
|
for (const part of coreM[1].split(/[·,]/)) {
|
|
@@ -687,15 +697,23 @@ function changelogSlugs(changelog, pin) {
|
|
|
687
697
|
// not a literal slug named "all" — the registry has no such slug,
|
|
688
698
|
// so emitting it as one would print a `MANUAL: re-check #all` row
|
|
689
699
|
// that looks real but isn't.
|
|
690
|
-
slugs.add("(all core chapters)");
|
|
700
|
+
current.slugs.add("(all core chapters)");
|
|
691
701
|
continue;
|
|
692
702
|
}
|
|
693
703
|
const slug = (cleaned.match(/^[a-z0-9-]+/) || [])[0];
|
|
694
|
-
if (slug) slugs.add(slug);
|
|
704
|
+
if (slug) current.slugs.add(slug);
|
|
695
705
|
}
|
|
696
706
|
}
|
|
697
|
-
if (/^###\s+stacks\/eleventy-netlify\.md/.test(line)) slugs.add("(eleventy binding)");
|
|
707
|
+
if (/^###\s+stacks\/eleventy-netlify\.md/.test(line)) current.slugs.add("(eleventy binding)");
|
|
698
708
|
}
|
|
709
|
+
return entries.map((e) => ({ severity: e.severity, slugs: [...e.slugs] }));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Every chapter slug the CHANGELOG records as changed after `pin`, flattened
|
|
713
|
+
// (severity dropped) — kept for callers that only need the slug set.
|
|
714
|
+
function changelogSlugs(changelog, pin) {
|
|
715
|
+
const slugs = new Set();
|
|
716
|
+
for (const e of changelogEntries(changelog, pin)) for (const s of e.slugs) slugs.add(s);
|
|
699
717
|
return [...slugs];
|
|
700
718
|
}
|
|
701
719
|
|
|
@@ -716,11 +734,22 @@ function versionDrift() {
|
|
|
716
734
|
if (pin >= current) return { pin, current, rows: [] };
|
|
717
735
|
|
|
718
736
|
const changelog = read("CHANGELOG.md", STANDARD) || "";
|
|
737
|
+
// Worst-case severity per slug: one breaking touch marks it breaking for
|
|
738
|
+
// good, even if a later (or earlier) entry touching the same slug since
|
|
739
|
+
// the pin was non-breaking.
|
|
740
|
+
const severityBySlug = new Map();
|
|
741
|
+
for (const entry of changelogEntries(changelog, pin)) {
|
|
742
|
+
for (const slug of entry.slugs) {
|
|
743
|
+
if (severityBySlug.get(slug) !== "breaking") severityBySlug.set(slug, entry.severity);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
719
746
|
return {
|
|
720
747
|
pin,
|
|
721
748
|
current,
|
|
722
|
-
rows:
|
|
723
|
-
|
|
749
|
+
rows: [...severityBySlug.entries()].map(([slug, severity]) =>
|
|
750
|
+
severity === "breaking"
|
|
751
|
+
? `BREAKING: re-check #${slug} — changed since the pinned ${pin}`
|
|
752
|
+
: `MANUAL: re-check #${slug} — changed since the pinned ${pin}`,
|
|
724
753
|
),
|
|
725
754
|
};
|
|
726
755
|
}
|
|
@@ -751,10 +780,11 @@ function runCompliance() {
|
|
|
751
780
|
|
|
752
781
|
function summarize({ results, drift }) {
|
|
753
782
|
const n = (s) => results.filter((r) => r.status === s).length;
|
|
783
|
+
const breakingDrift = drift.rows.filter((r) => r.startsWith("BREAKING:")).length;
|
|
754
784
|
return {
|
|
755
785
|
pass: n(PASS),
|
|
756
|
-
fail: n(FAIL),
|
|
757
|
-
manual: n(MANUAL) + drift.rows.length,
|
|
786
|
+
fail: n(FAIL) + breakingDrift,
|
|
787
|
+
manual: n(MANUAL) + (drift.rows.length - breakingDrift),
|
|
758
788
|
na: n(NA),
|
|
759
789
|
};
|
|
760
790
|
}
|
|
@@ -793,7 +823,7 @@ function main() {
|
|
|
793
823
|
writeCache({ ...payload, summary: s });
|
|
794
824
|
|
|
795
825
|
if (strict && s.fail) {
|
|
796
|
-
console.error("compliance --strict: FAIL chapters present.");
|
|
826
|
+
console.error("compliance --strict: FAIL chapters or BREAKING standard-version drift present.");
|
|
797
827
|
process.exit(1);
|
|
798
828
|
}
|
|
799
829
|
}
|
|
@@ -802,4 +832,4 @@ if (require.main === module) main();
|
|
|
802
832
|
|
|
803
833
|
// CHECKS is exported for the "coverage" test — nothing else should read it as
|
|
804
834
|
// data (call runCompliance() for results).
|
|
805
|
-
module.exports = { runCompliance, summarize, changelogSlugs, CHECKS };
|
|
835
|
+
module.exports = { runCompliance, summarize, changelogSlugs, changelogEntries, CHECKS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piercebarney/whs-eleventy",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.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"
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"sirv": "^3.0.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@eslint/js": "^
|
|
40
|
-
"eslint": "^9.
|
|
39
|
+
"@eslint/js": "^10.0.1",
|
|
40
|
+
"eslint": "^10.9.1",
|
|
41
41
|
"globals": "^17.11.0",
|
|
42
42
|
"prettier": "^3.9.6"
|
|
43
43
|
}
|
package/standard/CHANGELOG.md
CHANGED
|
@@ -6,9 +6,679 @@ at a glance. Stack-only changes list the file.
|
|
|
6
6
|
|
|
7
7
|
Format: `## <date>` → `### core: <slug>` / `### <stack>` entries.
|
|
8
8
|
|
|
9
|
+
**Severity.** Every `## <date>` heading also carries `[breaking]` or
|
|
10
|
+
`[non-breaking]`. An entry is **breaking** if a project that was fully
|
|
11
|
+
compliant *before* the change is no longer compliant *after* it — the change
|
|
12
|
+
adds or tightens a requirement, removes a previously-sanctioned pattern, or
|
|
13
|
+
corrects a documented mechanism that didn't actually work (so a project
|
|
14
|
+
following the old doc has broken/non-compliant behavior it thought was fine).
|
|
15
|
+
Everything else — wording fixes, doc corrections that don't change what's
|
|
16
|
+
enforced, new *opt-in* chapters/capabilities, tooling-only changes — is
|
|
17
|
+
**non-breaking**. `bin/check` fails if a heading is missing the tag.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2026-09-08 — content-ops: reservation becomes a real opt-in, not a hardcoded default [non-breaking]
|
|
22
|
+
|
|
23
|
+
Found while building `/whs:build`'s Content stage agent, which needs to know
|
|
24
|
+
whether a project's content is reserved before touching anything.
|
|
25
|
+
`core.md#content-model`'s Ownership paragraph already documented the default
|
|
26
|
+
correctly ("by default the party that builds the site may also edit its
|
|
27
|
+
content") — but `templates/eleventy-netlify/` shipped `CONTENT.md` (declaring
|
|
28
|
+
"reserved for: Cowork") unconditionally, with no way to opt out. Every
|
|
29
|
+
project scaffolded from it started reserved regardless of whether that was
|
|
30
|
+
true, contradicting the standard's own stated default.
|
|
31
|
+
|
|
32
|
+
### core: content-model
|
|
33
|
+
|
|
34
|
+
- New House-style field: `content-ops` (`in-repo` default | `reserved`).
|
|
35
|
+
Recorded as the sixth stack-selection question (`#adopting`). `CONTENT.md`
|
|
36
|
+
ships only when `reserved` is chosen; the voice guide ships either way —
|
|
37
|
+
clarified as not reservation-conditional (it was ambiguously grouped with
|
|
38
|
+
`CONTENT.md` before).
|
|
39
|
+
|
|
40
|
+
### templates/eleventy-netlify
|
|
41
|
+
|
|
42
|
+
- `bin/init` reads `content-ops` from the brief (default `in-repo`): deletes
|
|
43
|
+
`CONTENT.md` and `requests/` when not reserved, keeps them when it is.
|
|
44
|
+
- `CLAUDE.md`'s Content section carries both branches of the reservation
|
|
45
|
+
paragraph behind a `<!-- content-ops:reserved -->` /
|
|
46
|
+
`<!-- content-ops:in-repo -->` marker pair — the same mechanism-marker
|
|
47
|
+
idiom `{ include: "…" }` already uses elsewhere — and `bin/init` resolves
|
|
48
|
+
to whichever branch applies, dropping the other.
|
|
49
|
+
- Verified both branches end-to-end in a scratch copy: `content-ops: reserved`
|
|
50
|
+
keeps `CONTENT.md` + `requests/` and the reserved paragraph; the default
|
|
51
|
+
keeps neither and shows the in-repo paragraph instead.
|
|
52
|
+
|
|
53
|
+
## 2026-09-04 — sveltekit-netlify's brand token layer, and a Pico gap that predates it [non-breaking]
|
|
54
|
+
|
|
55
|
+
Third of the four functional gaps being closed ahead of the real project
|
|
56
|
+
adopting this stack. Scoping this one surfaced a bigger, previously
|
|
57
|
+
unnoticed gap: `AGENTS.md` and `stacks/sveltekit-netlify.md#styling` both
|
|
58
|
+
already asserted "Pico classless" as this template's CSS default, but Pico
|
|
59
|
+
was never actually installed, imported, or linked anywhere in the shipped
|
|
60
|
+
code — the "unstyled" state `TEMPLATE.md` flagged wasn't Pico-with-no-brand-
|
|
61
|
+
colors, it was no CSS framework at all. Fixing that was a real, if small,
|
|
62
|
+
scope expansion beyond "just add brand.ts" — confirmed with the user before
|
|
63
|
+
proceeding, on the grounds that the template's job is to satisfy
|
|
64
|
+
`core.md#styling` on day one the same as every other chapter, and the fix
|
|
65
|
+
doesn't foreclose Tailwind for the real project (still the documented
|
|
66
|
+
per-project alternative at adoption time, unaffected by this).
|
|
67
|
+
|
|
68
|
+
### templates/sveltekit-netlify
|
|
69
|
+
|
|
70
|
+
- Added `@picocss/pico@2.1.1` (matching `templates/eleventy-netlify/`'s
|
|
71
|
+
pin) to `dependencies`.
|
|
72
|
+
- New `src/lib/brand.ts`: the token source (colors, fonts, logo), the exact
|
|
73
|
+
field shape of `templates/eleventy-netlify/src/_data/brand.js`.
|
|
74
|
+
- New `src/routes/pico.css/+server.ts`: serves Pico's classless jade build
|
|
75
|
+
self-hosted at `/pico.css`, re-exporting the npm package's CSS via Vite's
|
|
76
|
+
`?raw` import — no vendor-file copy step needed the way Eleventy's
|
|
77
|
+
passthrough-copy config requires.
|
|
78
|
+
- New `src/routes/tokens.css/+server.ts`: generates the `--pico-*` remap
|
|
79
|
+
from `brand.ts`, mirroring `tokens.css.njk`'s logic. `app.html` links
|
|
80
|
+
`/pico.css` then `/tokens.css`.
|
|
81
|
+
- **A real gotcha, found only by actually rendering a page:** load order
|
|
82
|
+
alone doesn't decide the cascade here. Pico 2.1.1's own light-mode block
|
|
83
|
+
is `:root:not([data-theme="dark"])`, which out-specifies a plain
|
|
84
|
+
`:root{}` override regardless of which stylesheet loads second — the
|
|
85
|
+
brand remap silently lost to Pico's stock teal-green palette until the
|
|
86
|
+
override's selector was changed to match Pico's specificity. Caught with
|
|
87
|
+
a real Playwright screenshot (installed for this session — the built
|
|
88
|
+
site had never been visually rendered before), not by reading the CSS.
|
|
89
|
+
`[data-theme="dark"]` and the `prefers-color-scheme: dark` media block
|
|
90
|
+
didn't have this problem — Pico's own selectors there are already the
|
|
91
|
+
same specificity as the override, so source order alone decides,
|
|
92
|
+
correctly. Verified both branches with real screenshots (light and
|
|
93
|
+
forced-dark) showing the brand magenta, not Pico's defaults.
|
|
94
|
+
- Full `npm run check` gate passes.
|
|
95
|
+
|
|
96
|
+
### stacks/sveltekit-netlify.md: styling, brand-source
|
|
97
|
+
|
|
98
|
+
- `styling` now documents the shipped mechanism, including the specificity
|
|
99
|
+
gotcha above, so the next person touching this doesn't rediscover it the
|
|
100
|
+
hard way. `brand-source` now distinguishes the CSS consumer (built) from
|
|
101
|
+
the OG-card renderer and icon generator (still not built — `logo` sits in
|
|
102
|
+
`brand.ts` unused, waiting for either).
|
|
103
|
+
|
|
104
|
+
### templates/sveltekit-netlify/TEMPLATE.md
|
|
105
|
+
|
|
106
|
+
- The brand-layer "still owns" bullet replaced with a "replace the
|
|
107
|
+
placeholder palette" note, since the layer itself now exists.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 2026-09-04 — sveltekit-netlify's canonical tag, wired [non-breaking]
|
|
112
|
+
|
|
113
|
+
Second of the four functional gaps being closed ahead of the real project
|
|
114
|
+
adopting this stack (see the permalink-shape entry, below, for the full
|
|
115
|
+
context). `stacks/sveltekit-netlify.md#seo-urls` already named the exact
|
|
116
|
+
mechanism — `SITE_URL` from `src/lib/site.ts`, never `$page.url.origin` — but
|
|
117
|
+
no canonical tag actually shipped yet. This wires it.
|
|
118
|
+
|
|
119
|
+
### templates/sveltekit-netlify
|
|
120
|
+
|
|
121
|
+
- New `src/lib/components/Canonical.svelte`: reads `page.url.pathname` from
|
|
122
|
+
`$app/state` (the path only — never `.url.origin`, which is the
|
|
123
|
+
request/preview host, not necessarily production) and `SITE_URL` from
|
|
124
|
+
`src/lib/site.ts`, emitting `<link rel="canonical" href={SITE_URL +
|
|
125
|
+
pathname}>` from its own `<svelte:head>`.
|
|
126
|
+
- Mounted once in the root `+layout.svelte` — Svelte merges `<svelte:head>`
|
|
127
|
+
blocks from anywhere in the component tree, so every route gets a
|
|
128
|
+
canonical tag without a per-page component instance.
|
|
129
|
+
- Verified with a real build: every page's canonical trailing slash matches
|
|
130
|
+
its own permalink exactly — home (`https://example.com/`), a static route
|
|
131
|
+
(`.../about/`), the guides index, a guide detail page, and the audit page
|
|
132
|
+
all checked directly in the built HTML. Full `npm run check` gate passes.
|
|
133
|
+
|
|
134
|
+
### stacks/sveltekit-netlify.md: seo-urls, seo-meta, noindex
|
|
135
|
+
|
|
136
|
+
- `seo-urls`'s "not yet wired" note replaced with the actual mechanism and
|
|
137
|
+
verification. `seo-meta` now distinguishes the canonical tag (shared
|
|
138
|
+
component, shipped) from the still-deferred full OG/Twitter `<Seo>`
|
|
139
|
+
component (needs per-page `load` data, a bigger job — unchanged scope).
|
|
140
|
+
`noindex`'s Layer 3 note updated from "not yet wired" to a pointer at
|
|
141
|
+
`seo-urls`.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 2026-09-04 — sveltekit-netlify: fixes from an independent code review [non-breaking]
|
|
146
|
+
|
|
147
|
+
The four functional-gap fixes above were self-verified (real builds, real
|
|
148
|
+
screenshots) but not independently reviewed before landing. An `/code-review
|
|
149
|
+
high` pass across the full diff, run afterward, surfaced one real regression
|
|
150
|
+
and several smaller gaps the self-verification didn't check for. All fixed
|
|
151
|
+
and re-verified against real builds/tests, not just read over.
|
|
152
|
+
|
|
153
|
+
### netlify.toml
|
|
154
|
+
|
|
155
|
+
- **The regression.** The permalink-shape fix's `trailingSlash: 'always'`
|
|
156
|
+
moved the audit page's served/canonical URL to `/audit/`, but the
|
|
157
|
+
`X-Robots-Tag: noindex` header rule still targeted the bare `/audit` path.
|
|
158
|
+
Netlify's `for` header-path matching is exact-string outside an explicit
|
|
159
|
+
wildcard (confirmed against Netlify's own docs) — the rule silently
|
|
160
|
+
stopped matching the page it was written for. The in-HTML `<meta
|
|
161
|
+
robots>` tag still fired, so this wasn't a total exposure, but a
|
|
162
|
+
documented defense-in-depth layer quietly broke as an unaddressed side
|
|
163
|
+
effect of an unrelated change. Fixed with two explicit `[[headers]]`
|
|
164
|
+
rules (`/audit` and `/audit/`) rather than a wildcard.
|
|
165
|
+
|
|
166
|
+
### templates/sveltekit-netlify
|
|
167
|
+
|
|
168
|
+
- `.claude/hooks/og-guard.sh`: the rebuild's exit code was discarded (`||
|
|
169
|
+
true`, inherited verbatim from `templates/eleventy-netlify/`'s own hook,
|
|
170
|
+
which has the same bug) and the hook printed "rebuilt" unconditionally.
|
|
171
|
+
Fixed to print a different message on failure, naming the output files as
|
|
172
|
+
possibly stale rather than asserting they're current — verified by
|
|
173
|
+
actually breaking `brand.ts` and running the hook, not just reading the
|
|
174
|
+
diff.
|
|
175
|
+
- `src/lib/components/Canonical.svelte`: now wraps `page.url.pathname` in
|
|
176
|
+
`withTrailingSlash()` like every other internal href in this pass,
|
|
177
|
+
instead of trusting it already normalized — covers an error/404 render or
|
|
178
|
+
a future route opting out of the layout's `trailingSlash`.
|
|
179
|
+
`withTrailingSlash()`'s parameter type loosened from `ResolvedPathname` to
|
|
180
|
+
plain `string` so it accepts a raw pathname too; its return type stays
|
|
181
|
+
`ResolvedPathname` so the two existing `<a href>` call sites still satisfy
|
|
182
|
+
`eslint-plugin-svelte`'s navigation rule (a `<link rel="canonical">` was
|
|
183
|
+
never a target of that rule regardless).
|
|
184
|
+
- `src/lib/site.test.ts` (new): unit tests for `withTrailingSlash()` — pure,
|
|
185
|
+
trivially testable logic that shipped with none, despite vitest already
|
|
186
|
+
being wired into this template's gate and a direct precedent
|
|
187
|
+
(`content/validate.test.ts`) for testing exactly this kind of logic.
|
|
188
|
+
- `src/lib/staticRoutes.ts` (new, moved out of `sitemap.xml/+server.ts`) and
|
|
189
|
+
`src/lib/tokensCss.ts` (new, moved out of `tokens.css/+server.ts`): both
|
|
190
|
+
were flagged as hand-synced/duplicated logic with no drift check.
|
|
191
|
+
Factoring them out of their `+server.ts` files was **required**, not just
|
|
192
|
+
tidiness — SvelteKit restricts `+server.ts` exports to a fixed list
|
|
193
|
+
(`GET`, `prerender`, etc.) and rejects any other named export at build
|
|
194
|
+
time, caught only by actually running `vite build` after first trying to
|
|
195
|
+
export them inline. `staticRoutes.test.ts` checks `STATIC_ROUTES` against
|
|
196
|
+
the real `+page.svelte` route tree; `tokensCss.test.ts` checks the
|
|
197
|
+
generated CSS's light-mode selector against the *installed*
|
|
198
|
+
`@picocss/pico` package's own CSS, so a future Pico version bump that
|
|
199
|
+
changes that selector fails a test instead of silently losing the cascade
|
|
200
|
+
again (the exact bug the permalink-shape entry above found and fixed by
|
|
201
|
+
hand). `tokens.css/+server.ts`'s dark-mode block, previously written out
|
|
202
|
+
twice (once for `[data-theme="dark"]`, once for `prefers-color-scheme`),
|
|
203
|
+
is now built once and reused by both.
|
|
204
|
+
- `src/lib/brand.ts`: `color.muted` had zero consumers anywhere in this
|
|
205
|
+
template (unlike Eleventy's, where it feeds the OG card's subtitle — this
|
|
206
|
+
template's simpler single-line card has none) and, unlike `logo`, carried
|
|
207
|
+
no comment saying so. Commented as intentionally unused rather than left
|
|
208
|
+
to look like dead code.
|
|
209
|
+
|
|
210
|
+
### templates/eleventy-netlify/src/tokens.css.njk
|
|
211
|
+
|
|
212
|
+
- Added a comment cross-referencing `templates/sveltekit-netlify/src/lib/
|
|
213
|
+
tokensCss.ts` as the same mechanism for that stack, stating explicitly
|
|
214
|
+
that the two `--pico-*` remap blocks must be kept in sync by hand (not
|
|
215
|
+
factored into a shared module — the two templates don't share a runtime).
|
|
216
|
+
Verified `templates/verify.sh`'s eleventy-netlify branch still passes
|
|
217
|
+
(28 pass · 0 fail · 5 manual · 6 n/a) after this comment-only change.
|
|
218
|
+
|
|
219
|
+
### stacks/sveltekit-netlify.md: seo-urls, brand-source, audit-page, generated-asset-freshness
|
|
220
|
+
|
|
221
|
+
- Updated to describe the fixes above and their verification.
|
|
222
|
+
`brand-source` also had a stale paragraph from before the og-image entry
|
|
223
|
+
above — still said the OG-card renderer was "not yet built" after it had
|
|
224
|
+
been; corrected in the same pass since it was found while touching this
|
|
225
|
+
section anyway.
|
|
226
|
+
|
|
227
|
+
Last of the four functional gaps closed ahead of the real project adopting
|
|
228
|
+
this stack. `og-image`'s own note already named the front-runner mechanism
|
|
229
|
+
(a prerendered `+server.ts` route, `satori` + `@resvg/resvg-js`) without
|
|
230
|
+
committing to it; this takes it, and the same trick already proven for
|
|
231
|
+
`tokens.css` (item 3) turns out to solve
|
|
232
|
+
`core.md#generated-asset-freshness`'s tier 1 for free — a genuinely better
|
|
233
|
+
answer than Eleventy's `eleventy.after` hook, not merely an adequate
|
|
234
|
+
substitute for it, since there's no persisted file that can go stale between
|
|
235
|
+
builds at all.
|
|
236
|
+
|
|
237
|
+
### templates/sveltekit-netlify
|
|
238
|
+
|
|
239
|
+
- Added `satori@0.33.4`, `@resvg/resvg-js@2.6.2`, `@fontsource/inter@5.3.0`
|
|
240
|
+
(same versions as `templates/eleventy-netlify/scripts/og.js`) to
|
|
241
|
+
`dependencies`.
|
|
242
|
+
- New `src/routes/og/default.png/+server.ts`: renders the site-wide default
|
|
243
|
+
1200×630 OG card from `src/lib/brand.ts` (a primary-color bar + the site
|
|
244
|
+
name, Inter regardless of the live site's font — an OG card is a designed
|
|
245
|
+
asset). Deleted `static/og/default.png`, the hand-placed file this route
|
|
246
|
+
now replaces at the same path.
|
|
247
|
+
- **Scope check, not scope creep:** no page emits an `og:image` meta tag
|
|
248
|
+
anywhere in this template yet (that's `seo-meta`'s separately-deferred
|
|
249
|
+
full OG/Twitter table). So this generates the one site-wide default only,
|
|
250
|
+
not per-page cards — building those now would be output nothing
|
|
251
|
+
references. Flagged explicitly in `TEMPLATE.md` and `stacks/
|
|
252
|
+
sveltekit-netlify.md#og-image` so it isn't mistaken for full OG support.
|
|
253
|
+
- Verified beyond a successful build: `build/og/default.png` is a real
|
|
254
|
+
1200×630 8-bit PNG, ~10 KB (well under the ~1 MB ceiling), and was
|
|
255
|
+
actually viewed (Playwright's Chromium, installed last session, made this
|
|
256
|
+
possible) to confirm the brand color and text render correctly, not just
|
|
257
|
+
that `satori`/`resvg` didn't throw.
|
|
258
|
+
- `core.md#generated-asset-freshness`, all three tiers: (1) the build step —
|
|
259
|
+
free, as above; (2) `.claude/hooks/og-guard.sh` + `.claude/settings.json`,
|
|
260
|
+
the same `PostToolUse` pattern as `templates/eleventy-netlify/`'s
|
|
261
|
+
`og-guard.sh`, watching `brand.ts` and both generator `+server.ts` files,
|
|
262
|
+
rebuilding, and pointing the operator at the two output files directly
|
|
263
|
+
(this stack's audit page has no OG-gallery tab yet) — tested directly with
|
|
264
|
+
matching and non-matching `tool_input.file_path` values, not just read
|
|
265
|
+
over; (3) a new "Generated visual assets" paragraph in `AGENTS.md` (this
|
|
266
|
+
stack's actual instructions file — see below).
|
|
267
|
+
- Full `npm run check` gate passes.
|
|
268
|
+
|
|
269
|
+
### stacks/sveltekit-netlify.md: og-image, generated-asset-freshness, where-sveltekit-fights-the-grain
|
|
270
|
+
|
|
271
|
+
- `og-image` and `generated-asset-freshness` now describe what's built and
|
|
272
|
+
what verified it, including the explicit per-page-cards non-scope.
|
|
273
|
+
`where-sveltekit-fights-the-grain`#5 retitled from "still deferred" to
|
|
274
|
+
"resolved differently" — the `+server.ts` route isn't a lesser substitute
|
|
275
|
+
for `eleventy.after`, it has no staleness window at all, which Eleventy's
|
|
276
|
+
own mechanism can't claim.
|
|
277
|
+
|
|
278
|
+
### templates/sveltekit-netlify/TEMPLATE.md
|
|
279
|
+
|
|
280
|
+
- The "No OG-image generation" bullet replaced with what's actually built
|
|
281
|
+
and what's still missing (per-page cards, the meta tag). Noted `AGENTS.md`
|
|
282
|
+
uses this stack's real instructions filename, not `CLAUDE.md` — a
|
|
283
|
+
pre-existing naming difference from `templates/eleventy-netlify/`, not
|
|
284
|
+
something this change introduced or is fixing.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 2026-09-04 — sveltekit-netlify's permalink shape, actually resolved [non-breaking]
|
|
289
|
+
|
|
290
|
+
A real project is about to adopt `templates/sveltekit-netlify/` (closing the
|
|
291
|
+
"deliberately deferred" item in `TODO.md`), which turns the binding's four
|
|
292
|
+
self-documented gaps from theoretical to load-bearing. First of four: the
|
|
293
|
+
permalink shape. `stacks/sveltekit-netlify.md#seo-urls` had already found and
|
|
294
|
+
recorded that the naive fix — `trailingSlash: 'always'` alone — trades one
|
|
295
|
+
gap for a subtler one, since `resolve()` and a hand-built `sitemap.xml` don't
|
|
296
|
+
pick the setting up on their own. This closes it for real, verified against
|
|
297
|
+
an actual build's output files, rendered `href`s, and `sitemap.xml` content,
|
|
298
|
+
not just an exit code.
|
|
299
|
+
|
|
300
|
+
### templates/sveltekit-netlify
|
|
301
|
+
|
|
302
|
+
- `src/routes/+layout.ts`: added `export const trailingSlash = 'always';`.
|
|
303
|
+
- `src/lib/site.ts`: added `withTrailingSlash()`, typed `(path:
|
|
304
|
+
ResolvedPathname) => ResolvedPathname` rather than `string` — plain
|
|
305
|
+
`string` would make `eslint-plugin-svelte`'s
|
|
306
|
+
`svelte/no-navigation-without-resolve` rule stop recognizing the wrapped
|
|
307
|
+
`resolve()` call as safe, since that rule accepts an expression either by
|
|
308
|
+
syntax (a literal `resolve()` call) or by matching `$app/types`'s
|
|
309
|
+
`ResolvedPathname` type structurally — a generic wrapper satisfies neither
|
|
310
|
+
unless typed this way.
|
|
311
|
+
- `src/routes/guides/+page.svelte` and `guides/[slug]/+page.svelte`: every
|
|
312
|
+
`<a href>` built from `resolve('/guides/[slug]', { slug })` now wraps it in
|
|
313
|
+
`withTrailingSlash(...)`.
|
|
314
|
+
- `src/routes/sitemap.xml/+server.ts`: the static route list and guide-slug
|
|
315
|
+
URLs are plain literals under our control (not `resolve()` output), so
|
|
316
|
+
they're simply written with the trailing slash already baked in, rather
|
|
317
|
+
than routed through the same helper.
|
|
318
|
+
- Verified with a real `npm run build`: `build/about/index.html` (not
|
|
319
|
+
`about.html`); guide-list and guide-detail pages' rendered `href`s end in
|
|
320
|
+
`/`; `sitemap.xml`'s 8 entries (5 static + 3 guides) all end in `/`. Full
|
|
321
|
+
`npm run check` gate passes.
|
|
322
|
+
|
|
323
|
+
### stacks/sveltekit-netlify.md: seo-urls
|
|
324
|
+
|
|
325
|
+
- The permalink-shape note updated from "a real gap, not yet resolved" to
|
|
326
|
+
"resolved," describing the actual fix and the `ResolvedPathname`-typing
|
|
327
|
+
wrinkle the lint rule forced.
|
|
328
|
+
|
|
9
329
|
---
|
|
10
330
|
|
|
11
|
-
## 2026-09-
|
|
331
|
+
## 2026-09-04 — the standalone-page shape divergence, accepted and documented [non-breaking]
|
|
332
|
+
|
|
333
|
+
Triages `feedback/2026-09-03-pages-model-diverges-across-stacks.md`
|
|
334
|
+
(deleted, per this file's own triage convention). The note found that
|
|
335
|
+
"standalone pages are data" is implemented three different ways across the
|
|
336
|
+
templates — `eleventy-netlify`'s `pages.json` (an array, structured `body`
|
|
337
|
+
sections, `{ include }` markers, layout blocks), `sveltekit-netlify`'s (an
|
|
338
|
+
object keyed by page name, a flat `body: string[]`), `phoenix`'s (no
|
|
339
|
+
`pages.json` at all — markdown or a reviewed component) — and asked for a
|
|
340
|
+
decision: converge on one shape, or document the difference. The note's own
|
|
341
|
+
read was right: convergence "wants a second real adopting project on a
|
|
342
|
+
non-eleventy stack to justify the work" (`TODO.md`'s already-recorded
|
|
343
|
+
decision not to invest further in Phoenix/SvelteKit until one exists says
|
|
344
|
+
the same thing), so this takes the documented-divergence option.
|
|
345
|
+
|
|
346
|
+
### core: content-model
|
|
347
|
+
|
|
348
|
+
- New Spec: the standalone-page entry shape is a per-stack choice, named by
|
|
349
|
+
each stack's own impl doc (and, where one ships, the project's own
|
|
350
|
+
`CONTENT.md` "entry shape" section — the one an editing session actually
|
|
351
|
+
needs, read fresh each session rather than assumed from another project).
|
|
352
|
+
|
|
353
|
+
### stacks/sveltekit-netlify.md: content-model
|
|
354
|
+
|
|
355
|
+
- A real, previously-undocumented gap fixed while here: this section named
|
|
356
|
+
the files (`{guides,pages}.json` + zod) but never their field shape,
|
|
357
|
+
unlike the Eleventy and Phoenix bindings, which both already document
|
|
358
|
+
theirs. Added, verified against the real `*.schema.ts` files: `guides.json`
|
|
359
|
+
mirrors `eleventy-netlify`'s field set structurally; `pages.json` is
|
|
360
|
+
genuinely simpler (an object keyed by page name, a flat `body: string[]`,
|
|
361
|
+
no markers or blocks) — not just smaller, a different shape, and now
|
|
362
|
+
stated as a deliberate one.
|
|
363
|
+
|
|
364
|
+
### templates/sveltekit-netlify/CONTENT.md
|
|
365
|
+
|
|
366
|
+
- Gains "The entry shape — `guides.json`" / "`pages.json`" sections mirroring
|
|
367
|
+
`templates/eleventy-netlify/CONTENT.md`'s existing pattern — previously
|
|
368
|
+
named the content set files with no field-level reference at all, so an
|
|
369
|
+
editorial agent had nothing to read for this stack's shape short of the
|
|
370
|
+
binding doc (which the editorial-project instruction doesn't point it at —
|
|
371
|
+
`CONTENT.md` is the one per-session read).
|
|
372
|
+
|
|
373
|
+
## 2026-09-04 — Q1's SvelteKit criterion stops being circular [non-breaking]
|
|
374
|
+
|
|
375
|
+
Flagged in a review of the stack-selection guidance (`TODO.md`, prompted by
|
|
376
|
+
`PROJECT-OVERVIEW.md`'s planning conversation): Q1 told an adopter to pick
|
|
377
|
+
`sveltekit-netlify` "when a project specifically needs Svelte components"
|
|
378
|
+
without ever saying what would make that true, so it gave no real signal
|
|
379
|
+
against the default. Meanwhile `stacks/sveltekit-netlify.md`'s own
|
|
380
|
+
`where-sveltekit-fights-the-grain` #2 had already found, and buried, the
|
|
381
|
+
actual answer: SvelteKit hydrates every page by default, so picking it ships
|
|
382
|
+
the framework runtime site-wide, not only on the interactive page — a real
|
|
383
|
+
cost `core.md#client-logic`'s pure-function-plus-wiring pattern avoids
|
|
384
|
+
entirely for the common case (one calculator, one widget). Phoenix's
|
|
385
|
+
criterion ("a database or server-side state is in scope") was already crisp
|
|
386
|
+
by comparison — only the SvelteKit clause needed the fix. No project's
|
|
387
|
+
existing stack choice is invalidated — this sharpens guidance for a *future*
|
|
388
|
+
choice, it doesn't reopen a past one.
|
|
389
|
+
|
|
390
|
+
### core: adopting
|
|
391
|
+
|
|
392
|
+
- Q1's SvelteKit clause restated around the real trigger (several
|
|
393
|
+
interactive pieces genuinely sharing state, not one calculation) plus the
|
|
394
|
+
hydration-cost tradeoff, with a pointer to
|
|
395
|
+
`stacks/sveltekit-netlify.md#where-sveltekit-fights-the-grain` #2. Phoenix's
|
|
396
|
+
and the "other" bucket's wording untouched — re-read, no knock-on issue
|
|
397
|
+
found. All three stacks' own mirrored `adopting` sections re-read too
|
|
398
|
+
(none restate Q1's selection criteria — nothing to change there).
|
|
399
|
+
|
|
400
|
+
### README.md
|
|
401
|
+
|
|
402
|
+
- Q1's framework-question mirror gets the same tightened SvelteKit clause,
|
|
403
|
+
condensed to match this doc's shorter style.
|
|
404
|
+
|
|
405
|
+
## 2026-09-04 — the standard-version drift row is severity-tagged [breaking]
|
|
406
|
+
|
|
407
|
+
The other half of the severity-signal work (`TODO.md` #1 depended on this, not
|
|
408
|
+
just the tag itself): the `#compliance` drift row now distinguishes
|
|
409
|
+
`BREAKING` from `MANUAL` per slug, using each changed CHANGELOG entry's own
|
|
410
|
+
tag, so `whs compliance --strict` actually gates on being behind on a
|
|
411
|
+
breaking change rather than treating every changed slug — a typo fix and a
|
|
412
|
+
broken-mechanism correction alike — as the same easy-to-miss `MANUAL` note.
|
|
413
|
+
This is the concrete fix for the gap `TODO.md` #1's evidence section
|
|
414
|
+
documented: `everydaymoneycalc` being 8 entries behind (several of them
|
|
415
|
+
breaking, per the backfill above) produced no signal beyond a note buried in
|
|
416
|
+
a `MANUAL` list.
|
|
417
|
+
|
|
418
|
+
### core: compliance
|
|
419
|
+
|
|
420
|
+
- `#compliance`'s drift-row Spec: a slug from a `breaking` CHANGELOG entry
|
|
421
|
+
now emits `BREAKING: re-check #<slug>` and folds into the fail count
|
|
422
|
+
(gates `--strict`); a `non-breaking` one still emits the softer
|
|
423
|
+
`MANUAL: re-check #<slug>`. A slug touched by more than one entry since the
|
|
424
|
+
pin takes the worst severity seen.
|
|
425
|
+
|
|
426
|
+
### stacks/eleventy-netlify.md
|
|
427
|
+
|
|
428
|
+
- `packages/whs-eleventy/lib/compliance.js`'s `versionDrift()` reads each
|
|
429
|
+
entry's `[breaking]`/`[non-breaking]` tag via a new `changelogEntries()`
|
|
430
|
+
(`changelogSlugs()` kept, now a flattened view over it, for backward
|
|
431
|
+
compatibility) and emits the tagged row; `summarize()` folds `BREAKING`
|
|
432
|
+
rows into the fail count, `main()`'s `--strict` message updated to match.
|
|
433
|
+
`src/audit.njk`'s drift list bolds `BREAKING` rows. Verified end to end:
|
|
434
|
+
rolled `templates/eleventy-netlify/`'s pin back to `2026-08-27` and
|
|
435
|
+
confirmed `whs compliance` printed exactly 8 `BREAKING` + 8 `MANUAL` rows
|
|
436
|
+
matching the backfilled tags (`28 pass · 8 fail · 13 manual · 6 n/a` — 0
|
|
437
|
+
real chapter `FAIL`s, all 8 from drift), the built `/audit/` page bolding
|
|
438
|
+
only the `BREAKING` `<li>`s, then restored the pin. `npm run
|
|
439
|
+
check`/`compliance` re-verified clean afterward. 5 new tests added to
|
|
440
|
+
`packages/whs-eleventy/test/compliance.test.js` (54 total, all green).
|
|
441
|
+
|
|
442
|
+
### stacks/phoenix.md
|
|
443
|
+
|
|
444
|
+
- The still-unshipped `#compliance` design doc's drift-row description
|
|
445
|
+
updated to match — the same `BREAKING`/`MANUAL` split, once built, per
|
|
446
|
+
`versionDrift()`, "the pattern to port."
|
|
447
|
+
|
|
448
|
+
## 2026-09-04 — a severity signal on CHANGELOG entries [non-breaking]
|
|
449
|
+
|
|
450
|
+
Flagged in a planning conversation (`PROJECT-OVERVIEW.md`): entries were dated
|
|
451
|
+
but not marked as breaking vs. non-breaking, so a project couldn't tell from
|
|
452
|
+
the log alone whether it was safe to ignore an update or urgently behind — and
|
|
453
|
+
without that signal, an eventual upgrade path for already-adopted projects
|
|
454
|
+
(tracked in `TODO.md`) would have nothing to prioritize on. Every `## <date>`
|
|
455
|
+
heading, past and present, now carries `[breaking]` or `[non-breaking]`, per
|
|
456
|
+
the rule stated in this file's own header note. Not a `core.md`/`stacks/*.md`
|
|
457
|
+
content change — process/tooling only, no version bump (same precedent as the
|
|
458
|
+
2026-08-30 "the standard is a git repo" entry).
|
|
459
|
+
|
|
460
|
+
### tooling
|
|
461
|
+
|
|
462
|
+
- `bin/check` gains a sixth check: every `## <date>` CHANGELOG heading must
|
|
463
|
+
carry a severity tag. `CLAUDE.md` and `README.md`'s Consistency checks
|
|
464
|
+
section updated to match (six checks; `templates/verify.sh` is now the
|
|
465
|
+
*seventh*, not sixth).
|
|
466
|
+
|
|
467
|
+
### CHANGELOG.md
|
|
468
|
+
|
|
469
|
+
- All 20 existing entries backfilled with a severity tag, judged against the
|
|
470
|
+
rule stated in the new header note (breaking = a previously-compliant
|
|
471
|
+
project is no longer compliant after the change).
|
|
472
|
+
|
|
473
|
+
## 2026-09-04 — name the deploy hand-off in the content-ops protocol [breaking]
|
|
474
|
+
|
|
475
|
+
The content-arm interface (2026-09-03, below) defined how an LLM agent (Cowork)
|
|
476
|
+
edits reserved content, but left the last step implicit: the agent commits to
|
|
477
|
+
`main` and cannot deploy, and the only cue to the operator was "if it's
|
|
478
|
+
time-sensitive, say so in the commit message" — passive, no addressee. A real
|
|
479
|
+
adopter workflow (Cowork owning content post-launch) surfaced the gap. The
|
|
480
|
+
deploy boundary is unchanged — a person still runs the press — but the signal is
|
|
481
|
+
now named: a `DEPLOY:` line closing the agent's reply, and `[deploy by <date>]`
|
|
482
|
+
in the `content:` subject when there's a deadline.
|
|
483
|
+
|
|
484
|
+
### core: content-model
|
|
485
|
+
|
|
486
|
+
- The LLM-agent content-ops protocol gains a fifth element: the agent does not
|
|
487
|
+
deploy — it commits a finished change to the default branch and hands it to
|
|
488
|
+
the human deploy cadence (`#ci-cd`) with a session-closing report, deadline in
|
|
489
|
+
the commit subject. `#adopting`'s "Editorial operation" stable instruction
|
|
490
|
+
reworded to match ("never deploy — handed off, not shipped").
|
|
491
|
+
|
|
492
|
+
### stacks/eleventy-netlify.md: content-model
|
|
493
|
+
|
|
494
|
+
- New **Deploy hand-off** bullet: the `DEPLOY: awaiting …` / `DEPLOY: by <date>
|
|
495
|
+
…` closing line, the `[deploy by <date>]` commit-subject tag for the
|
|
496
|
+
operator's `git log <last-deploy>..main` batch review, and the two deploy
|
|
497
|
+
doorways (`deploy.yml` Run workflow / `npm run deploy`) — nothing in the
|
|
498
|
+
pre-flight or tooling changes.
|
|
499
|
+
- The representative `#compliance` `content-model` row in the mechanical-checks
|
|
500
|
+
table now spells out the protocol-presence note (`CONTENT.md`,
|
|
501
|
+
`content-check`, `requests/`) the sweep already emits — the row had listed
|
|
502
|
+
only the data + schema + validator half.
|
|
503
|
+
- `stacks/phoenix.md` and `stacks/sveltekit-netlify.md` content-model sections
|
|
504
|
+
and the three template `CONTENT.md`s (+
|
|
505
|
+
`templates/eleventy-netlify/CLAUDE.md`) mirror the hand-off wording.
|
|
506
|
+
|
|
507
|
+
## 2026-09-04 — the SvelteKit binding renamed to sveltekit-netlify, honestly scoped [non-breaking]
|
|
508
|
+
|
|
509
|
+
Caught in conversation, not tooling: `stacks/sveltekit.md` / `templates/sveltekit/`
|
|
510
|
+
were named as if this were a general-purpose SvelteKit binding, when its actual
|
|
511
|
+
origin and entire scope is narrower — it exists specifically to answer "Svelte,
|
|
512
|
+
static, on Netlify," because plain Svelte has no durable static-site tooling of
|
|
513
|
+
its own. The naming didn't say so, and that's an inconsistency the repo's own
|
|
514
|
+
implicit rule already flags: a binding scoped to exactly one host with no
|
|
515
|
+
alternative documented (`eleventy-netlify` is the model) gets the host in its
|
|
516
|
+
name; a binding documenting multiple real hosts (`phoenix` — Render default,
|
|
517
|
+
Gigalixir/Fly as the graduate path) correctly doesn't. `sveltekit` documented
|
|
518
|
+
only Netlify, so by that same rule it should have carried the host in its name
|
|
519
|
+
too — it didn't, and nothing explained why not.
|
|
520
|
+
|
|
521
|
+
### stacks/sveltekit-netlify.md (renamed from stacks/sveltekit.md)
|
|
522
|
+
|
|
523
|
+
- Retitled **SvelteKit + Netlify**. The intro now states the binding's real
|
|
524
|
+
origin up front — "why SvelteKit, if the actual need is just Svelte, static,
|
|
525
|
+
on Netlify" — rather than reading as a general SvelteKit offering that
|
|
526
|
+
happens to default to Netlify. A project that needs SvelteKit's actual
|
|
527
|
+
server capabilities (real SSR, live `+server.ts` endpoints, a database) is
|
|
528
|
+
explicitly called out as a different, larger binding this repo does not
|
|
529
|
+
have — not something this doc quietly under-scopes.
|
|
530
|
+
- Section-header labels (`How (SvelteKit)`, `Adopting this standard
|
|
531
|
+
(SvelteKit)`, etc.) deliberately keep the framework-only wording, matching
|
|
532
|
+
`stacks/eleventy-netlify.md`'s own convention of naming the binding
|
|
533
|
+
file/directory with the host but labeling in-body sections by framework
|
|
534
|
+
alone.
|
|
535
|
+
|
|
536
|
+
### templates/sveltekit-netlify (renamed from templates/sveltekit)
|
|
537
|
+
|
|
538
|
+
- Directory, all internal doc cross-references (`AGENTS.md`, `README.md`,
|
|
539
|
+
`TEMPLATE.md`), and the CI job (`templates-sveltekit-netlify`) renamed to
|
|
540
|
+
match. `AGENTS.md`'s `framework:` data-block value now reads
|
|
541
|
+
`sveltekit-netlify`. Along the way, fixed a real, unrelated inaccuracy found
|
|
542
|
+
in the same file: `TEMPLATE.md` told an adopter to rename `whs_sveltekit` in
|
|
543
|
+
`package.json` — the actual placeholder there is `__PKG_NAME__`
|
|
544
|
+
(`whs_sveltekit` was a leftover copy-paste from the Phoenix template's
|
|
545
|
+
equivalent instruction, which is accurate for Phoenix's real OTP app name
|
|
546
|
+
but was never true here).
|
|
547
|
+
|
|
548
|
+
### core: adopting
|
|
549
|
+
|
|
550
|
+
- Q1's framework list: `SvelteKit` → `SvelteKit + Netlify`, with the same
|
|
551
|
+
origin-story framing as the doc's own intro — a project with no Svelte
|
|
552
|
+
requirement should still default to Eleventy + Netlify; a project that
|
|
553
|
+
needs SvelteKit's server features has no binding here yet (falls to
|
|
554
|
+
"other").
|
|
555
|
+
|
|
556
|
+
### tooling
|
|
557
|
+
|
|
558
|
+
- `index.json`'s `sveltekit` key → `sveltekit-netlify` (file, template path,
|
|
559
|
+
and summary all updated to state the narrower scope explicitly).
|
|
560
|
+
`README.md`'s doc-map table, framework-question wording, and
|
|
561
|
+
`templates/verify.sh` description updated to match.
|
|
562
|
+
`templates/concept-brief.md`'s `framework:` comment and the one open
|
|
563
|
+
`feedback/` report referencing the old name updated for accuracy while
|
|
564
|
+
still open.
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## 2026-09-03 — a self-audit: gate/gitignore/doc gaps found by external review [non-breaking]
|
|
569
|
+
|
|
570
|
+
A skeptical outside audit of the standard's own repo (docs, templates, CI,
|
|
571
|
+
tooling) turned up a cluster of small-but-real defects — none a contract
|
|
572
|
+
violation, all either a check that couldn't catch its own failure mode, a
|
|
573
|
+
tooling side-effect leaking outside its own project, or a doc claim that had
|
|
574
|
+
drifted from what the repo actually contains. Fixed together as one pass;
|
|
575
|
+
see `git log` for the individual verification each one got (cold installs,
|
|
576
|
+
mutation tests, real builds, fetched external docs).
|
|
577
|
+
|
|
578
|
+
### core: adopting
|
|
579
|
+
|
|
580
|
+
- **Stale template-count claim.** The stack-selection flow's "Starting
|
|
581
|
+
point" paragraph said "Only `eleventy-netlify` has a template today; the
|
|
582
|
+
others follow the checklist" — both `templates/phoenix/` and
|
|
583
|
+
`templates/sveltekit/` have existed (with real, passing gates) since
|
|
584
|
+
2026-09-02. Corrected to point at `index.json`'s `active`/`draft` status
|
|
585
|
+
instead of a hardcoded, now-wrong count. Re-read each `stacks/*.md`
|
|
586
|
+
mirrored `adopting` section — all three already stated their own template's
|
|
587
|
+
real status correctly; only this file's flow-level prose had drifted.
|
|
588
|
+
|
|
589
|
+
### bin/check
|
|
590
|
+
|
|
591
|
+
- **Check #5 (version strings agree) couldn't detect a missing pin** — it
|
|
592
|
+
compared only the *distinct values* found, so a file missing its
|
|
593
|
+
`standard-version:`/`**Version:**` line entirely just vanished from the
|
|
594
|
+
comparison instead of failing it (confirmed: deleting
|
|
595
|
+
`templates/sveltekit/AGENTS.md`'s pin left the check green). Now checks
|
|
596
|
+
pin *presence* per expected file first, and names the file if one is
|
|
597
|
+
missing.
|
|
598
|
+
|
|
599
|
+
### templates/phoenix
|
|
600
|
+
|
|
601
|
+
- **The `git_hooks` monorepo guard was incomplete.** `config/dev.exs`
|
|
602
|
+
guarded the `hooks:` config behind `File.dir?(".git")` so the template
|
|
603
|
+
wouldn't install hooks into the standard's own repo when compiled in
|
|
604
|
+
place — but `git_hooks`'s `auto_install` (default `true`) runs before that
|
|
605
|
+
config is ever consulted, and unconditionally writes a `git_hooks.db`
|
|
606
|
+
marker into whatever `.git/hooks` it finds via `git rev-parse
|
|
607
|
+
--show-toplevel`. Confirmed landing in the standard's own `.git/hooks/`
|
|
608
|
+
a second time, guard already in place. Fixed with an explicit
|
|
609
|
+
`auto_install: false` in the guard's `else` branch — verified with a full
|
|
610
|
+
`rm -rf _build deps && mix deps.get && mix compile`, the exact sequence
|
|
611
|
+
that reproduced the bug, no longer touching the main checkout.
|
|
612
|
+
|
|
613
|
+
### repo-hygiene (root .gitignore)
|
|
614
|
+
|
|
615
|
+
- Added `erl_crash.dump` — the root `.gitignore` covered only Node/JS build
|
|
616
|
+
output; a BEAM crash dump landing at the repo root (any `mix` command run
|
|
617
|
+
from there, not inside `templates/phoenix/`) was untracked and therefore
|
|
618
|
+
invisible to `bin/check`'s `git grep`-based sterility scan despite
|
|
619
|
+
containing a personal filesystem path. `templates/phoenix/.gitignore`
|
|
620
|
+
already covered its own case; this was the repo-root gap.
|
|
621
|
+
|
|
622
|
+
### stacks/sveltekit.md, templates/*/netlify.toml
|
|
623
|
+
|
|
624
|
+
- Corrected the stated root cause of `#noindex` layer 2 not applying: the
|
|
625
|
+
doc said per-context header scoping "needs th[e] build pipeline to run"
|
|
626
|
+
(implying it would work if Netlify's build system did run) — Netlify's
|
|
627
|
+
docs are explicit that `[[headers]]` is never context-aware on *any*
|
|
628
|
+
deploy path; the pre-built-artifact deploy model is a second, independent
|
|
629
|
+
reason the documented workaround doesn't apply here, not the cause of the
|
|
630
|
+
first problem. `stacks/eleventy-netlify.md` and both templates'
|
|
631
|
+
`netlify.toml` already stated this correctly; only this file's prose had
|
|
632
|
+
drifted. Also dropped an unverifiable direct quotation of Netlify's docs
|
|
633
|
+
(substance was correct, exact wording wasn't confirmed) from this file and
|
|
634
|
+
`templates/eleventy-netlify/netlify.toml`.
|
|
635
|
+
- Corrected two "previously-undocumented" claims (the `vite.config.ts`
|
|
636
|
+
config-consolidation and `resolve()` from `$app/paths`) — both are real,
|
|
637
|
+
and both are documented on svelte.dev; what was actually true is that this
|
|
638
|
+
binding hadn't previously accounted for them.
|
|
639
|
+
- Documented a real, previously-unstated gap: the template's flat
|
|
640
|
+
`about.html`-shaped build output doesn't meet `core.md#adopting`'s
|
|
641
|
+
directory-style-permalink item. Tried the one-line fix
|
|
642
|
+
(`trailingSlash = 'always'`) and verified in a real build that it's not
|
|
643
|
+
sufficient alone — `resolve()` from `$app/paths` doesn't append the
|
|
644
|
+
trailing slash to match, nor does the hand-built sitemap route list, so it
|
|
645
|
+
trades one gap for a file-layout/link/sitemap mismatch. Left unfixed,
|
|
646
|
+
documented precisely instead.
|
|
647
|
+
|
|
648
|
+
### stacks/phoenix.md
|
|
649
|
+
|
|
650
|
+
- Tightened Render free-Postgres wording: "deleted 30 days after creation"
|
|
651
|
+
→ "expires 30 days after creation" (inaccessible immediately, permanently
|
|
652
|
+
deleted after a further 14-day grace period) — matches Render's own docs.
|
|
653
|
+
|
|
654
|
+
### .github/workflows/standard.yml
|
|
655
|
+
|
|
656
|
+
- Added a comment at the `templates` job's matrix declaration explaining
|
|
657
|
+
that the `[eleventy-netlify]` list is manually curated (not discovered
|
|
658
|
+
from `templates/*/` the way `templates/verify.sh` is) and when a new
|
|
659
|
+
template should be added to it — the file's top-of-file comment already
|
|
660
|
+
explained this design, but not at the point someone would actually edit.
|
|
661
|
+
|
|
662
|
+
### templates/new-project.sh
|
|
663
|
+
|
|
664
|
+
- The no-`bin/init`-yet branch's own comment said "fail loudly" but the
|
|
665
|
+
script exited 0 regardless — nothing after the `echo` produced a non-zero
|
|
666
|
+
status. Added the missing `exit 1`; verified against `sveltekit` (no
|
|
667
|
+
`bin/init` yet) going from exit 0 to exit 1, `eleventy-netlify`'s
|
|
668
|
+
(`bin/init`-having) path unaffected.
|
|
669
|
+
|
|
670
|
+
### packages/whs-eleventy, templates/eleventy-netlify
|
|
671
|
+
|
|
672
|
+
- Bumped `eslint`/`@eslint/js` off the EOL `9.39.5` pin (`npm warn
|
|
673
|
+
deprecated eslint@9.39.5: This version is no longer supported`) to
|
|
674
|
+
`10.9.1`/`10.0.1` in both — `templates/sveltekit/` was already on the
|
|
675
|
+
current major. Verified with a cold `rm -rf node_modules package-lock.json
|
|
676
|
+
&& npm install` plus the full gate for each: `whs-eleventy`'s `npm run
|
|
677
|
+
check` (49 tests), `eleventy-netlify`'s `npm run check` (lint → validate →
|
|
678
|
+
test → build → links → a11y), both green, no deprecation warning on
|
|
679
|
+
install.
|
|
680
|
+
|
|
681
|
+
## 2026-09-03 — the content-arm interface: a project and its coding arm [breaking]
|
|
12
682
|
|
|
13
683
|
A project built to the house style is often only part of a larger operation —
|
|
14
684
|
an editor-in-chief (a Claude Project, ideation + editorial context) that hands
|
|
@@ -133,7 +803,7 @@ for the common small-site case); a **`schedule` trigger** on the deploy workflow
|
|
|
133
803
|
sveltekit flat `string[]`, phoenix none) — converge or document the split, at
|
|
134
804
|
triage.
|
|
135
805
|
|
|
136
|
-
## 2026-09-02 — a starter template for the SvelteKit binding, moved out of parked
|
|
806
|
+
## 2026-09-02 — a starter template for the SvelteKit binding, moved out of parked [non-breaking]
|
|
137
807
|
|
|
138
808
|
`stacks/sveltekit.md` was a parked stub — every mirrored section's "How
|
|
139
809
|
(SvelteKit)" was `TODO`, not offered by the `#adopting` flow. A real need
|
|
@@ -255,7 +925,7 @@ and real `vite build` output, not written from the binding's prose alone.
|
|
|
255
925
|
|
|
256
926
|
---
|
|
257
927
|
|
|
258
|
-
## 2026-09-02 — Render as the Phoenix binding's default free-launch host
|
|
928
|
+
## 2026-09-02 — Render as the Phoenix binding's default free-launch host [non-breaking]
|
|
259
929
|
|
|
260
930
|
Netlify can't run this stack at all — LiveView needs a persistent BEAM node
|
|
261
931
|
(long-lived WebSocket connections, a warm Postgres pool), not a static
|
|
@@ -330,7 +1000,7 @@ isn't trustworthy here.
|
|
|
330
1000
|
|
|
331
1001
|
---
|
|
332
1002
|
|
|
333
|
-
## 2026-09-02 — a starter template for the Phoenix binding, and hardening the doc it's built from
|
|
1003
|
+
## 2026-09-02 — a starter template for the Phoenix binding, and hardening the doc it's built from [breaking]
|
|
334
1004
|
|
|
335
1005
|
Built `templates/phoenix/` the same way `templates/eleventy-netlify/` was
|
|
336
1006
|
proven out: generic placeholder content, independent of any real business,
|
|
@@ -465,7 +1135,7 @@ fabricated or imprecise APIs the same way the earlier `Plug.CSP` finding did.
|
|
|
465
1135
|
|
|
466
1136
|
---
|
|
467
1137
|
|
|
468
|
-
## 2026-09-02 — closing the gap between the compliance grid and the Contracts it checks
|
|
1138
|
+
## 2026-09-02 — closing the gap between the compliance grid and the Contracts it checks [non-breaking]
|
|
469
1139
|
|
|
470
1140
|
A pass through every chapter's actual enforcement, prompted by an outside
|
|
471
1141
|
evaluation that traced specific checks to specific failure modes they
|
|
@@ -624,7 +1294,7 @@ consistency defects the same pass turned up.
|
|
|
624
1294
|
|
|
625
1295
|
---
|
|
626
1296
|
|
|
627
|
-
## 2026-09-01 — the shared tooling package (#compliance mechanism, eleventy)
|
|
1297
|
+
## 2026-09-01 — the shared tooling package (#compliance mechanism, eleventy) [non-breaking]
|
|
628
1298
|
|
|
629
1299
|
### stacks/eleventy-netlify.md
|
|
630
1300
|
|
|
@@ -662,7 +1332,7 @@ consistency defects the same pass turned up.
|
|
|
662
1332
|
|
|
663
1333
|
---
|
|
664
1334
|
|
|
665
|
-
## 2026-08-31 — the Cowork ↔ Claude Code loop: concept brief + content ops (#content-model)
|
|
1335
|
+
## 2026-08-31 — the Cowork ↔ Claude Code loop: concept brief + content ops (#content-model) [breaking]
|
|
666
1336
|
|
|
667
1337
|
### core: adopting
|
|
668
1338
|
|
|
@@ -741,7 +1411,7 @@ consistency defects the same pass turned up.
|
|
|
741
1411
|
|
|
742
1412
|
---
|
|
743
1413
|
|
|
744
|
-
## 2026-08-30 — a copy-and-modify template for Eleventy + Netlify (#adopting)
|
|
1414
|
+
## 2026-08-30 — a copy-and-modify template for Eleventy + Netlify (#adopting) [non-breaking]
|
|
745
1415
|
|
|
746
1416
|
### templates/eleventy-netlify (new)
|
|
747
1417
|
|
|
@@ -838,7 +1508,7 @@ consistency defects the same pass turned up.
|
|
|
838
1508
|
|
|
839
1509
|
---
|
|
840
1510
|
|
|
841
|
-
## 2026-08-30 — the standard is a git repo
|
|
1511
|
+
## 2026-08-30 — the standard is a git repo [non-breaking]
|
|
842
1512
|
|
|
843
1513
|
### repo
|
|
844
1514
|
|
|
@@ -854,7 +1524,7 @@ consistency defects the same pass turned up.
|
|
|
854
1524
|
|
|
855
1525
|
---
|
|
856
1526
|
|
|
857
|
-
## 2026-08-29 — AI & agent capabilities, opt-in (#llm-integration, #agent-artifacts, #usage-metering)
|
|
1527
|
+
## 2026-08-29 — AI & agent capabilities, opt-in (#llm-integration, #agent-artifacts, #usage-metering) [non-breaking]
|
|
858
1528
|
|
|
859
1529
|
### core: llm-integration · agent-artifacts · usage-metering (new chapters)
|
|
860
1530
|
|
|
@@ -935,7 +1605,7 @@ consistency defects the same pass turned up.
|
|
|
935
1605
|
|
|
936
1606
|
---
|
|
937
1607
|
|
|
938
|
-
## 2026-08-28 — codebase-vs-standard conformance (#compliance)
|
|
1608
|
+
## 2026-08-28 — codebase-vs-standard conformance (#compliance) [non-breaking]
|
|
939
1609
|
|
|
940
1610
|
### core: compliance (new chapter)
|
|
941
1611
|
|
|
@@ -992,7 +1662,7 @@ consistency defects the same pass turned up.
|
|
|
992
1662
|
|
|
993
1663
|
---
|
|
994
1664
|
|
|
995
|
-
## 2026-08-28 — the `CLAUDE.md` House-style directive
|
|
1665
|
+
## 2026-08-28 — the `CLAUDE.md` House-style directive [breaking]
|
|
996
1666
|
|
|
997
1667
|
### core: adopting
|
|
998
1668
|
|
|
@@ -1015,7 +1685,7 @@ consistency defects the same pass turned up.
|
|
|
1015
1685
|
|
|
1016
1686
|
---
|
|
1017
1687
|
|
|
1018
|
-
## 2026-08-28 — display advertising (AdSense)
|
|
1688
|
+
## 2026-08-28 — display advertising (AdSense) [non-breaking]
|
|
1019
1689
|
|
|
1020
1690
|
### core: ads (new chapter)
|
|
1021
1691
|
|
|
@@ -1066,7 +1736,7 @@ consistency defects the same pass turned up.
|
|
|
1066
1736
|
|
|
1067
1737
|
---
|
|
1068
1738
|
|
|
1069
|
-
## 2026-08-28 — hardening from the first full Eleventy migration
|
|
1739
|
+
## 2026-08-28 — hardening from the first full Eleventy migration [breaking]
|
|
1070
1740
|
|
|
1071
1741
|
Fixes and gaps surfaced running the first full Eleventy migration end to end.
|
|
1072
1742
|
|
|
@@ -1117,7 +1787,7 @@ Fixes and gaps surfaced running the first full Eleventy migration end to end.
|
|
|
1117
1787
|
|
|
1118
1788
|
---
|
|
1119
1789
|
|
|
1120
|
-
## 2026-08-28 — solo is the default working mode
|
|
1790
|
+
## 2026-08-28 — solo is the default working mode [non-breaking]
|
|
1121
1791
|
|
|
1122
1792
|
### core: ci-cd
|
|
1123
1793
|
|
|
@@ -1150,7 +1820,7 @@ Fixes and gaps surfaced running the first full Eleventy migration end to end.
|
|
|
1150
1820
|
across all three; migration `[one PR:]` markers → `[together:]`; the
|
|
1151
1821
|
"busy PR flow" build-minutes framing dropped.
|
|
1152
1822
|
|
|
1153
|
-
## 2026-08-27 — netlify deploy `--no-build`
|
|
1823
|
+
## 2026-08-27 — netlify deploy `--no-build` [breaking]
|
|
1154
1824
|
|
|
1155
1825
|
### stacks/eleventy-netlify.md
|
|
1156
1826
|
|
|
@@ -1168,7 +1838,7 @@ Fixes and gaps surfaced running the first full Eleventy migration end to end.
|
|
|
1168
1838
|
|
|
1169
1839
|
- `ci-cd` hint — same `--no-build` fix in the `adapter-static` + Netlify note.
|
|
1170
1840
|
|
|
1171
|
-
## 2026-08-27 — initial split
|
|
1841
|
+
## 2026-08-27 — initial split [non-breaking]
|
|
1172
1842
|
|
|
1173
1843
|
The single 1518-line "Eleventy + Netlify static-site house style" document was
|
|
1174
1844
|
split into a stack-agnostic CORE contract plus per-stack implementation
|
package/standard/core.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Web project house style — CORE (stack-agnostic)
|
|
2
2
|
|
|
3
|
-
**Version:** 2026-09-
|
|
3
|
+
**Version:** 2026-09-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
|
|
7
7
|
specs. It never says **how** — that lives in one implementation doc per stack
|
|
8
|
-
(`stacks/eleventy-netlify.md`, `stacks/phoenix.md`, `stacks/sveltekit.md`).
|
|
8
|
+
(`stacks/eleventy-netlify.md`, `stacks/phoenix.md`, `stacks/sveltekit-netlify.md`).
|
|
9
9
|
|
|
10
10
|
**How to use this:** at project start, run the stack-selection flow
|
|
11
11
|
(`#adopting`). Then load **this file plus the one `stacks/*.md` file** matching
|
|
@@ -114,25 +114,43 @@ a build-time schema gate hard-fails the build on invalid content.
|
|
|
114
114
|
the same data files is acceptable — a separate database for site content is
|
|
115
115
|
not.
|
|
116
116
|
- **Ownership.** By default the party that builds the site may also edit its
|
|
117
|
-
content
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
content — recorded as `content-ops: in-repo` in `CLAUDE.md` (`#adopting`),
|
|
118
|
+
and no reservation file ships. A project that runs a **separate content
|
|
119
|
+
operation** — a non-developer, or an LLM agent editing the data files
|
|
120
|
+
directly — **reserves** that content: `content-ops: reserved` in `CLAUDE.md`,
|
|
121
|
+
and one file at the repo root (`CONTENT.md`) names the reserving party and
|
|
122
|
+
the exact set it owns, and is authoritative for every party. The builder
|
|
123
|
+
treats the reserved set as read-only and routes any change it needs there
|
|
124
|
+
through the project's escalation channel, never a direct edit. The reserved
|
|
125
|
+
set is enumerated at whatever granularity the project needs — all content,
|
|
126
|
+
or named collections/paths.
|
|
124
127
|
- An **LLM agent** editing reserved content follows a documented per-project
|
|
125
128
|
content-ops protocol: it changes only the reserved set; a pre-commit content
|
|
126
129
|
check (the content-relevant slice of the gate) **errors** on any staged change
|
|
127
130
|
outside that set; a `content:` commit convention; and an escalation channel for
|
|
128
|
-
anything structural, so a blocked request is recorded, not lost.
|
|
129
|
-
|
|
131
|
+
anything structural, so a blocked request is recorded, not lost. The agent does
|
|
132
|
+
not deploy: it commits a finished change to the default branch and hands it to
|
|
133
|
+
the human deploy cadence (`#ci-cd`) — a session-closing report that names what
|
|
134
|
+
landed and whether it is time-sensitive, with any deadline also carried in the
|
|
135
|
+
commit subject. See your stack's impl doc.
|
|
130
136
|
- **Drafts** are a data flag. A draft renders in local and preview builds
|
|
131
137
|
(reviewable) but is excluded from the production build and the sitemap.
|
|
132
138
|
Publishing is removing the flag.
|
|
133
139
|
- The project records its target publishing rate for new content pages in
|
|
134
140
|
`CLAUDE.md`. Bulk-publishing many pages at once depresses indexing; pace
|
|
135
141
|
deliberately.
|
|
142
|
+
- **The standalone-page entry shape is a per-stack choice, made explicitly.**
|
|
143
|
+
This chapter requires only that page content *is* data (above) — the
|
|
144
|
+
concrete fields, whether entries are an array or a simpler keyed object,
|
|
145
|
+
and how a mechanism-bound section is marked, is named by each stack's own
|
|
146
|
+
impl doc, and may legitimately differ between stacks. This is a documented
|
|
147
|
+
choice, not an inconsistency: forcing one shape onto every stack would cost
|
|
148
|
+
more (a rewrite of a working, unproven template) than it buys (one less
|
|
149
|
+
thing for a cross-stack content agent to learn) while only one stack has a
|
|
150
|
+
real adopting project. Where a project ships a `CONTENT.md` for agent
|
|
151
|
+
editing (above), that file's own "entry shape" section is the one an
|
|
152
|
+
editing session actually needs — read it fresh each session, don't assume
|
|
153
|
+
another project's stack taught you this one's shape.
|
|
136
154
|
|
|
137
155
|
**Rationale.** Content that lives in templates can't be validated, can't be
|
|
138
156
|
edited safely by non-developers, and drifts from its own stated facts (a
|
|
@@ -898,10 +916,17 @@ and it is **not** part of the gate.
|
|
|
898
916
|
page's Compliance panel (`#audit-page`); and the **deploy pre-flight**
|
|
899
917
|
(`#ci-cd`) **warns** on any chapter that went `PASS → FAIL` since the last
|
|
900
918
|
deploy (a real regression of something previously compliant).
|
|
901
|
-
- **Standard-version drift is a row.** The command compares
|
|
902
|
-
`standard-version:` (`#adopting`) to the standard's own
|
|
903
|
-
pin is behind, each `CHANGELOG` slug changed since
|
|
904
|
-
|
|
919
|
+
- **Standard-version drift is a row, severity-tagged.** The command compares
|
|
920
|
+
the project's pinned `standard-version:` (`#adopting`) to the standard's own
|
|
921
|
+
version header; if the pin is behind, each `CHANGELOG` slug changed since
|
|
922
|
+
becomes a re-check line, carrying the severity of the entry it came from
|
|
923
|
+
(`CHANGELOG.md`'s own `[breaking]` / `[non-breaking]` tag on the `## <date>`
|
|
924
|
+
heading — see its header note for the rule): `BREAKING: re-check #<slug>`
|
|
925
|
+
folds into the fail count and gates `--strict`; `MANUAL: re-check #<slug>`
|
|
926
|
+
stays a judgment call, same as any other `MANUAL` chapter result. A slug
|
|
927
|
+
touched by more than one entry since the pin takes the worst (a single
|
|
928
|
+
breaking touch marks it `BREAKING`, even if a later entry on the same slug
|
|
929
|
+
was non-breaking).
|
|
905
930
|
- The mechanical tier **reuses existing machinery** — the repo-config
|
|
906
931
|
introspection already in the audit doctor and the built-output scan already in
|
|
907
932
|
the link/reference check — not a parallel implementation.
|
|
@@ -1297,17 +1322,25 @@ records the result. "The gate passing" is the definition of compliant.
|
|
|
1297
1322
|
**Specs.**
|
|
1298
1323
|
|
|
1299
1324
|
**Stack-selection flow** — run at project start (or when onboarding an existing
|
|
1300
|
-
project), before non-trivial work. Ask
|
|
1325
|
+
project), before non-trivial work. Ask six questions with defaults:
|
|
1301
1326
|
|
|
1302
1327
|
1. **Framework?** Eleventy + Netlify *(default)* · Elixir/Phoenix *(when a
|
|
1303
1328
|
database or server-side state is in scope — `#beyond-this-standard`)* ·
|
|
1304
|
-
SvelteKit *(only when
|
|
1305
|
-
|
|
1306
|
-
`
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
the
|
|
1329
|
+
SvelteKit + Netlify *(only when the client-side need is genuinely
|
|
1330
|
+
component-shaped — several interactive pieces sharing state, not a single
|
|
1331
|
+
calculation `#client-logic`'s pure-function-plus-wiring pattern already
|
|
1332
|
+
covers; SvelteKit hydrates every page by default, so picking it means the
|
|
1333
|
+
framework runtime ships site-wide, not just where it's interactive — a
|
|
1334
|
+
real cost, not a free upgrade
|
|
1335
|
+
(`stacks/sveltekit-netlify.md#where-sveltekit-fights-the-grain` #2). Plain
|
|
1336
|
+
Svelte has no durable static-site tooling of its own, so this binding is
|
|
1337
|
+
SvelteKit + `adapter-static`, used minimally as a build tool with no
|
|
1338
|
+
server — a "static Svelte, on Netlify" need specifically, not a
|
|
1339
|
+
general-purpose SvelteKit offering; a project whose interactivity is one
|
|
1340
|
+
widget should still default to Eleventy + Netlify)* · other *(→ no binding
|
|
1341
|
+
exists; use this file as principles and do what is idiomatic for the
|
|
1342
|
+
technology — this covers SvelteKit with real server features too, which
|
|
1343
|
+
this standard does not have a binding for yet)*.
|
|
1311
1344
|
2. **CSS system?** Pico classless *(default)* · Pico class-based · Tailwind ·
|
|
1312
1345
|
Tailwind + component library · vanilla tokens. (Options are bound per stack
|
|
1313
1346
|
in the impl doc's `styling` section.)
|
|
@@ -1323,6 +1356,13 @@ project), before non-trivial work. Ask five questions with defaults:
|
|
|
1323
1356
|
`#agent-artifacts`); is pricing usage-based rather than flat? (→ add
|
|
1324
1357
|
`+metering`, `#usage-metering`). This gate is by real need, not by framework —
|
|
1325
1358
|
a plain CRUD app that calls no model is unaffected.
|
|
1359
|
+
6. **Who edits content after launch?** *Default: the builder* — record
|
|
1360
|
+
`content-ops: in-repo`; no reservation file ships, no protocol to follow.
|
|
1361
|
+
A **separate content operation** (a Claude Project / Cowork, a
|
|
1362
|
+
non-developer editing the data files directly) → record
|
|
1363
|
+
`content-ops: reserved`, `#content-model` Ownership — ships `CONTENT.md`
|
|
1364
|
+
(the reserved set + escalation protocol) at the project root. The voice
|
|
1365
|
+
guide ships either way, read by whichever party writes copy.
|
|
1326
1366
|
|
|
1327
1367
|
Follow-ups when relevant: production domain; content type (`tool` vs `article` —
|
|
1328
1368
|
`article` implies feeds); publishing rate for content sites.
|
|
@@ -1333,8 +1373,8 @@ the new-project path: the template is already compliant, so a new project begins
|
|
|
1333
1373
|
at "fill in brand + content" instead of "assemble the machinery". The impl doc's
|
|
1334
1374
|
numbered new-project checklist is then two things: the by-hand equivalent for a
|
|
1335
1375
|
stack with no template, and the annotated inventory of what the template
|
|
1336
|
-
contains.
|
|
1337
|
-
|
|
1376
|
+
contains. A template exists for every stack today (`templates/<stack>/`); `index.json`
|
|
1377
|
+
records which are `active` vs `draft`. The recommended input to the flow is a filled
|
|
1338
1378
|
`templates/concept-brief.md` (the idea, the answers above, a concept-level
|
|
1339
1379
|
brand, the content model, layout notes) — `README.md` has the hand-off. If the
|
|
1340
1380
|
brief cannot honestly fit an offered stack, that is a `feedback/` note (below)
|
|
@@ -1378,10 +1418,11 @@ governs it and follow that chapter — the slugs are the index (a `<script>` →
|
|
|
1378
1418
|
- scope: static+forms # or: +db, +auth, +ssr — see core.md#beyond-this-standard
|
|
1379
1419
|
- ads: none # or: adsense — see core.md#ads
|
|
1380
1420
|
- ai: none # or: llm-api (+artifacts if model-driven file downloads, +metering if usage-priced) — see core.md#llm-integration
|
|
1421
|
+
- content-ops: in-repo # or: reserved — a separate content operation owns content, see core.md#content-model
|
|
1381
1422
|
- production-url: https://example.com
|
|
1382
1423
|
- content-type: tool # tool | article (article => feeds)
|
|
1383
1424
|
- publishing-rate: ~5 pages/week
|
|
1384
|
-
- standard-version: 2026-09-
|
|
1425
|
+
- standard-version: 2026-09-08 # recommended — enables standard-version drift tracking (#compliance)
|
|
1385
1426
|
```
|
|
1386
1427
|
|
|
1387
1428
|
If the section is absent, the assistant's first action is to run the flow above
|
|
@@ -1389,14 +1430,17 @@ and propose it. Each impl doc's `adopting` section fills in the gate command and
|
|
|
1389
1430
|
adds any stack-specific `CLAUDE.md` prose (e.g. the generated-assets note).
|
|
1390
1431
|
|
|
1391
1432
|
**Editorial operation.** Where content is maintained after launch by an LLM
|
|
1392
|
-
agent rather than by in-repo development (
|
|
1393
|
-
content
|
|
1394
|
-
plus the per-project content-ops protocol
|
|
1395
|
-
|
|
1433
|
+
agent rather than by in-repo development (`content-ops: reserved`,
|
|
1434
|
+
`#content-model` — a *reserved* content set), `CONTENT.md` ships at the
|
|
1435
|
+
project root — the reserved set plus the per-project content-ops protocol.
|
|
1436
|
+
The voice guide ships regardless of `content-ops` (see the impl doc) — it's
|
|
1437
|
+
read by whichever party writes copy, builder or editorial operation alike.
|
|
1438
|
+
The operation's editor-in-chief — the human's standing ideation/editorial
|
|
1396
1439
|
context, typically a Claude Project — connects to the repo through a **thin,
|
|
1397
1440
|
stable instruction**: check out the repo; read `CONTENT.md` and the voice guide
|
|
1398
1441
|
at the start of each session; follow `CONTENT.md`; escalate through its channel;
|
|
1399
|
-
never deploy
|
|
1442
|
+
never deploy — a finished change is handed off for the human deploy cadence, not
|
|
1443
|
+
shipped. The detail stays in the repo, re-read each session — not in the
|
|
1400
1444
|
instruction — and `CONTENT.md` carries a paste-once "setting up" section for
|
|
1401
1445
|
exactly this. The build arm treats the reserved set as read-only and escalates
|
|
1402
1446
|
into the same channel.
|