Architecture
How Chobble Template works, from content files to deployable artifact
This is the guided walkthrough of how Chobble Template works, for every reader — site builders, content editors, reviewers, maintainers, and AI assistants. It explains the whole system once, end to end, and links to the reference that owns each area in depth. It is deliberately fork-safe: no secrets, credentials, or organization-internal detail, so it can travel unchanged into any site built from this template.
The one-paragraph version: a site is a git repository full of Markdown files
whose YAML frontmatter declares arrays of typed content blocks. Schemas
validate those arrays at build time and fill omitted fields from defaults;
block templates render them; a token-based theme styles them; Eleventy writes
a self-contained _site/ directory that any static host can serve. There is
no server, no database, and no form backend: a contact form posts to an
outside form service, and the site itself holds no user data — the static
boundary is a design commitment, not an accident of scope.
Design Commitments
These recur throughout the system and explain most of its shape:
- Content is data, and invalid data fails the build. Unknown block types, unknown top-level block keys, missing required values, placeholder site identity, and missing translation labels all abort with loud, file-specific errors. Nothing falls back to a default that silently publishes.
- A site is a fork, not a dependency. Content, configuration, and theme
live in the site's own repository. Template updates arrive only through a
deliberate, reviewed
upstreammerge, and the quality gates travel with the fork so an update that breaks a site says so immediately. - Accessibility is structural. Semantic templates, keyboard-operable
components, and visible focus live in the block library, not in each site.
npm testaudits every built page against axe-core's WCAG 2.2 AA rules; contrast and keyboard flow still need human review where automation cannot see rendered pixels. - Generated artifacts have exactly one source. Block schemas feed the
block reference, the PagesCMS config, and the CMS types. The config and
site schemas feed the startup validation of
config.jsonandsite.json, the config defaults, the CMS site editor, and the tables in the Site Builder Reference, alongsidepackage.jsonand the shared CMS definitions. Freshness tests fail when a generated file drifts from its source, so nobody hand-edits output.
How To Read This Repository
| You want to… | Read |
|---|---|
| Understand how the whole system fits together | this file |
| Install, build, and run a site | README quick start; Site Builder Reference |
| Start a new site or change its shape | project setup |
| Author or edit pages, news, guides, snippets | content authoring |
| Know every block's fields and examples | generated block reference, or the live /blocks/ gallery |
| Understand rendering, columns, sidebars | layouts |
| Publish in more than one language | languages |
| Rebuild an existing site page for page | site mirroring |
| Verify a change before handoff | verification |
| Change the template's own code | engineering guide; library map; test quality criteria |
| Drive an AI assistant against a site | Agent Skill |
| Deploy | README deployment |
The Site Builder Reference is generated from
package.json and the CMS definitions; BLOCKS_LAYOUT.md
is a short navigation page to the block and layout references. If those two
disagree with reality, regenerate rather than trusting either.
Repository Tour
| Path | What lives there |
|---|---|
src/pages/ |
Site pages: Markdown files whose frontmatter declares blocks |
src/news/ |
Dated news posts; filenames YYYY-MM-DD-slug.md supply the date |
src/guide-categories/, src/guide-pages/ |
Categorised documentation pages |
src/snippets/ |
Reusable regions (footer-content, right-content) and snippet-block sources, with optional per-language folders |
src/images/, src/files/, src/assets/ |
Source media, downloadable files, and static assets such as fonts; copied through to the output |
src/_data/ |
Site identity and behaviour: site.json, config.json (fields and settings declared in src/_lib/config/site-schema.js and config-schema.js), languages.json, translations.json, strings.json, meta.json, blockLayouts.json |
src/_includes/ |
Templates, including design-system/blocks/ — one template per block type |
src/_layouts/ |
Page shells; base.html is the root layout |
src/css/ |
theme.scss (the site's token overrides), prebuilt themes, and the design-system/ partials the blocks rely on |
src/_lib/ |
All build-time and browser JavaScript; see the library map |
scripts/ |
Build wrapper, generators, checks, and tooling |
test/ |
The suite, its fixtures, and the fork simulation |
docs/ |
The generated Site Builder Reference, served at /docs/ on each deployment |
skills/chobble-template-site-builder/ |
The Agent Skill: workflow, references, and evals |
.github/workflows/ |
CI and the deployment workflows |
.pages.yml |
Generated PagesCMS editing config |
.eleventy.js |
Eleventy entry point: plugin and subsystem registration |
CLAUDE.md |
Handwritten engineering policy for agents and maintainers |
What Happens During A Build
npm run build runs scripts/eleventy-build.js, a fail-fast wrapper around
the Eleventy CLI, then checks every internal link. In order:
- Site data validation.
validateSiteData()scans the identity files for placeholder values before Eleventy starts; then, as Eleventy loads its data,src/_lib/config/validated-config.jsrejects asite.jsonorconfig.jsonkey its schema does not declare or a value it does not accept (including missing identity:name,url,description), astrings.jsonlabel with no default, and incomplete language labels — every problem at once, before any page renders. - Eleventy configuration.
.eleventy.jssets the input (src/), output (_site/), and layout directories, registers path-prefix URL rewriting, JSON-LD schema output, RSS, and Markdown amendments, then runs each subsystem'sconfigure*function — theCONFIGURATORSlist: blocks, breadcrumbs, filters, navigation, news, guides, images, icons, SCSS, the style and JS bundlers, the HTML transform, and the unused-image report. - Collections and defaults. Each content directory's data is normalized
in
src/_lib/collections/(news dates from filenames, navigation keys, guide category links), so fork templates read one shape regardless of what an author wrote. - Block validation and defaults. Every authored block array — page
frontmatter, snippet blocks, the footer and sidebar regions — is checked
against the schema registry in
src/_lib/utils/block-schema.jsand has omitted fields filled from schemadefaults, nested fields included. Unknown block types and unknown top-level keys fail here with the file and field named. - Rendering.
src/_layouts/base.htmlprovides the HTML shell; block arrays flow throughdesign-system/blocks.html(which applies the column layout matching fromblockLayouts.json) todesign-system/render-block.htmlor the full-width renderer, and on to the resolved per-block template. The layouts reference owns this chain in detail, including the section wrapper and column semantics. - Images. Block images go through the shared eleventy-img/sharp
pipeline: responsive formats and sizes, aspect-ratio cropping, base64 LQIP
placeholders, and a content-hash cache under
.image-cache/. The build ends with an unused-image report so dead assets cannot accumulate quietly. - HTML transforms. Output HTML passes through
src/_lib/transforms/in phases: a fast string phase (URL and email linkification, external-link attributes) and a DOM phase that only runs when a page actually has tables, phone numbers, or local images to wrap. - Bundles. SCSS compiles the design system and the active theme into
one stylesheet; browser JavaScript in
src/_lib/public/bundles with esbuild. Fonts and favicon copy through fromsrc/assets/. - Search and links. Pagefind indexes the built pages for the static
/search/page, thennpm run check:linksverifies every internal link, includinghreflangtargets, against the routes that were actually written.
npm run serve runs the same wrapper with Eleventy's dev server and
incremental rebuilds. Both commands fail loudly — the wrapper scans build
output for error patterns and prints a failure banner rather than exiting
cleanly on a broken build.
The Content Model
A page is frontmatter and nothing else — body Markdown below the closing delimiter is rejected for pages, news, and guides:
---
name: About
permalink: /about/
blocks:
- type: hero
content: |
# About us
One H1 per page, then a logical heading sequence.
- type: markdown
content: |
## What we do
Approved body copy.
---
- Block types are defined once, in schema modules under
src/_lib/utils/block-schema/, and everything else derives from them: the template undersrc/_includes/design-system/blocks/, the SCSS partial undersrc/css/design-system/, the generated block reference, the PagesCMS field components, and the/blocks/gallery that renders the same tested examples. - Columns and sidebars are declarative:
blockLayouts.jsonmaps page tags to column layouts (a claim-queue matcher, documented in the layouts reference), and the presence ofsrc/snippets/right-content.mdswitches on a site-wide sidebar. Neither needs template edits. - Snippets are shared regions resolved per language:
src/snippets/<language>/<name>.mdoverridessrc/snippets/<name>.md.footer-contentandright-contentare the named global regions. - News and guides are optional collections a site enables through the CMS customizer; forks that do not use them delete their demo content, and the fork simulation proves the template's own tests still pass afterwards.
The Editing Layer
PagesCMS provides a graphical, Git-based editing surface over the same
content: .pages.yml is generated from the block schemas, the site schema,
and the saved
cms_config in src/_data/site.json, so the editor's forms always match what
the build validates. npm run customise-cms tailors the editor to the
collections and features a site actually uses (interactive, or
non-interactive with --list-collections, --list-features, and --dry-run)
and regenerates the config and its TypeScript declarations together.
Markdown fields always use the visual rich-text editor. CMS
choices control what editors see, not what Eleventy publishes.
Languages
A page's language is its URL prefix; each language's pages live in a folder
named like the prefix. src/_data/languages.json declares every published
language and must translate every chrome label the interface shows — the
build fails on a missing one. src/_data/translations.json pairs the pages
that say the same thing, which is what produces reciprocal hreflang links,
the x-default, and the switcher's targets. Menus, snippets, and labels
resolve per language with no template branching. The
languages reference
owns the full pattern, including the checks to run.
Theming
All visual tokens are CSS custom properties declared at :root in
src/css/_variables.scss and overridden by the active
src/css/theme.scss. A fork restyles a site by
replacing theme.scss — starting from a prebuilt theme or the live
/theme-editor/ page, which previews and exports a complete token set.
Brand fonts are self-hosted: woff2 files in src/assets/fonts/, @font-face
rules at the top of theme.scss, and the --font-family-* tokens pointed at
them. Core design-system files should not be edited for a site's look; the
token system is the intended extension point, and the
Sass defaults are the reference for what exists.
Browser JavaScript
The shipped site runs a small set of hand-written, framework-free modules from
src/_lib/public/, bundled with esbuild: the disclosure menu (Escape to
close, focus return, collapse_menu config), the gallery dialog, the theme
switcher and editor, search results rendering, and scroll-reveal animations
that respect reduced-motion preferences. Interactive components degrade to
native behaviour without JavaScript — menus stay operable as plain
disclosures. There is no framework and no client-side data layer; a page is
usable HTML before any script loads.
Generated Artifacts
Never hand-edit these; change their sources and regenerate. Freshness tests compare each artifact against its source and fail on drift:
| Artifact | Generated from | Command |
|---|---|---|
skills/chobble-template-site-builder/references/blocks.md |
block schemas and canonical examples | npm run generate-blocks-reference |
.pages.yml |
block schemas and the saved cms_config |
npm run generate-pages-yml |
src/_lib/types/pages-cms-generated.d.ts |
.pages.yml |
npm run generate-cms-types |
docs/developer-reference.md |
package.json, the shared CMS definitions, and the site and config schemas |
npm run generate-developer-reference |
npm run generate-references runs the three generators in order and stops on
the first failure. After changing a block schema, follow the full protocol in
the engineering guide: schema module, template, SCSS partial,
then regeneration and review of all four artifacts.
Quality Gates
npm test (test/run-tests.js) runs checks in parallel
lanes: code-quality tests; Biome lint; Stylelint for SCSS; knip for dead
exports; typecheck plus a strict-mode ratchet; copy-paste detection with its
own deletion-only ratchet; a full build followed by the axe-core WCAG 2.2 AA
audit of every built page; unit tests with coverage thresholds; and
integration tests that spawn real Eleventy builds.
Two properties matter as much as the list:
- The tests are fork-safe. They read fixtures from
test/fixtures/, not the fork's own content — in-process imports ofsrc/_data/*.jsonare redirected there, and the test-site factory overlays the same fixtures.npm run test:forkproves it: it deletes the demo content, rewrites site data the way a real fork does, and runs the whole suite against the result. - Ratchets only tighten. The copy-paste and code-quality baselines are
deletion-only legacy records; an entry can be removed but a new violation
cannot be added. Mutation testing (
npm run mutation) is available to check whether tests actually detect changes to production operators.
What the tests mean and how to write good ones is owned by the test quality criteria. What automation cannot settle — rendered contrast, keyboard flow, content accuracy — is flagged for manual review by the verification reference.
Deployment Boundary
The deliverable is the _site/ directory: self-contained, no application
server, no build-time secrets. SITE_URL stamps the public origin into
canonical URLs, the sitemap, feeds, and social metadata; PATH_PREFIX
rewrites internal URLs for hosts that serve the site from a subpath. The
repository ships a workflow that builds on every push and deploys to Bunny
Sites — main to production, other branches to staging — plus CI for tests.
Repository docs/ publish alongside the site at /docs/ on that deployment. To host anywhere else, run
npm run build and point any static host or pipeline at _site/.
Extending The System
- A new block type: add the schema module under
src/_lib/utils/block-schema/, register it insrc/_lib/utils/block-schema.js, add the template undersrc/_includes/design-system/blocks/and its SCSS partial undersrc/css/design-system/, then runnpm run generate-referencesand review all four artifacts. - A new site setting: declare it in
src/_lib/config/config-schema.js(type, choices, default, description), add it toSiteConfiginsrc/_lib/types/config.d.ts, read it from the mergedconfig, and runnpm run generate-references. A newsite.jsonfield works the same way throughsite-schema.jsandSiteInfo; give it alabelto expose it in the CMS. The build rejects undeclared settings, so a setting with no reader is dead weight a fork will copy. - A new filter: register it in the central registry in
src/_lib/eleventy/filters.js— a gate checks that registered filters have template consumers. - A new collection: follow the news/guides pattern — directory data and
normalization in
src/_lib/collections/, thin.11tydata.jsre-exports in the content directory, then CMS choices through the customizer. - Anything else: the engineering guide owns the change workflow, code policy, and testing rules that apply first; this file only maps the territory.
Provenance
This is version 2 of the Chobble Template: rebuilt from version 1, relicensed from AGPL to MIT by its sole author, and cut down to a static core: no checkout or payments, no server, and no user data stored. Products can be added to a quote, which posts to a form service like the contact form. See LICENSE.