- JavaScript 86.4%
- SCSS 5.7%
- HTML 4.5%
- XSLT 2.8%
- TypeScript 0.3%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
* Improve CI build efficiency and metrics
* Make indexed git dates exactly match the legacy per-file behavior
The template-only history pathspec hid renames and copies whose source
was a non-template file, so their targets recorded the transfer date as
the published date. Scan the full history with rename detection (cheap
without copy detection) and pair it with a template-path copy-only scan,
so transfer targets inherit the source's original publish date through
rename chains.
* Fix CI findings: deduplicate the scan, cover the git date modules
The full cpd gate flagged the duplicated record-header parse and the
twin git log invocations; extract splitHistoryRecord and scanHistory so
both scans share one implementation.
tests:unit enforces 100% line and function coverage, and three regions
were untested: the eleventy.before hook and filter bodies, the
assertGitSuccess failure branch, and the copy-origin rename-descendant
recursion. Invoke the registered handler and filters in the plugin
test, break a repo HEAD to exercise the failure path, and cover a copy
followed by a rename.
* Feed the parsed record to recordTransfers
jscpd normalizes identifiers, so the split-then-guard heads of
parseHistoryRecord and recordTransfers still normalized to an identical
fragment over the min-tokens threshold. recordTransfers now takes the
parsed { date, tokens } from scanHistory, leaving one split site.
* Wait for autosizes FCP restoration with a deadline
The FCP restoration tests asserted a fixed 50ms after runAutosizes, but
restoration runs through chained happy-dom timers, and under the
concurrent unit suite on a small CI runner the restore task lands past
that window once heavier test files share the CPU. Poll with a 2s
deadline instead of sleeping a fixed 50ms.
* Restore legacy merge semantics and count mode-only changes
The unrestricted full-history scan replayed every reachable branch
commit, so a conflict resolution that kept one parent's version of a
template still recorded the rejected parent's commit as the updated
date, and novel conflict resolutions missed the merge date. The
template pathspec scan is back as the primary index: its history
simplification is exactly what the legacy per-path queries saw.
The cheap full-history rename scan stays as a side channel: renames
whose source is outside the template pathspec get their original add
date backfilled with one targeted follow query and propagated through
rename descendants. Non-template rename targets are skipped, so the
backfill costs one query per cross-extension rename into a template.
Mode-only commits (100644 to 100755) keep the same blob, so they were
treated as no-ops and the updated date went stale; the raw change now
carries both modes and updates on a mode flip.
* Pin the test git repos to a main branch
git init on CI has no init.defaultBranch set, so the default branch
there is master and the merge test's checkout main failed. init -b
main makes the fixture deterministic in every environment.
* Handle novel merge resolutions, copied renames, and scan cost
Three review findings:
- A merge that resolves a template to content different from every
parent is interesting to git's simplified history and legacy git log
counts it, but --raw emits no diff for merges by default, so the
index stopped at the last parent-side commit. A merges-only scan now
replays first-parent diffs; records older than a path's recorded
update are skipped so real merges cannot regress newer commits.
- A template copied after a cross-extension rename inherited the
pre-backfill synthetic date; copy records from the template scan now
join renames as propagation edges for backfilled origins.
- renameRecords built and discarded the full dates map; it now filters
to transfer records and skips date reduction, and the parse state
machine collapsed into a shared recordChanges extractor used by both
replay paths.
* Apply merge diffs positionally and anchor rename origins
- replay novel merge resolutions at their position in the simplified
history instead of comparing author dates, fixing skewed author dates
(cherry-picks, rebases) being skipped or applied out of order
- replace a stale published date when a rename reuses a deleted path
- anchor rename-origin lookups at the transfer commit so later
incarnations of the source path cannot leak their dates
- remove the Claude Code and reviewdog GitHub workflows
* Use function declarations for multi-line helpers
bun 1.3.6's coverage instrumentation intermittently counts an
unassigned continuation line of a multi-line arrow as uncovered,
which fails the 100% coverage threshold in CI with all tests
passing. Function declarations attribute each line reliably.
* ci: dump git-dates coverage record on failure
* Make guard-line coverage independent of git copy detection
The patchOriginChain rejection return only executed when git reported
the successor as a copy record, which varies by git version, so CI's
coverage threshold failed with all tests passing. Route the guard
through a lineageMatches helper whose lines execute on every call, and
use function declarations for multi-line helpers to keep every line
attributed under bun 1.3.6's instrumentation.
* Stop rename-origin propagation at visited paths
A rename cycle (a.md to b.md back to a.md) whose commits share one
author timestamp satisfies the lineage guard on every hop and recursed
until the call stack overflowed, aborting the build. Track the paths
already visited in the walk.
* Remove generated devenv state from tracking
The .devenv directory is machine-local runtime state that predates
main's .gitignore entry; it was tracked by an add -A in this branch
before the merge brought the ignore rule.
* Key merge resolutions by commit and skip inherited adds
- pair each merge's first-parent diff with its date-only record by
commit hash, since author dates can collide across sequential merges
- replay only paths whose merge result differs from every parent
(detected via a combined-diff scan), so files inherited unchanged
from the second parent keep their own update dates
- remove the generated .devenv state directory that predates main's
.gitignore entry
* Mutate the indexByCommit accumulator in place
The map copy per matching record made building the merge indexes
quadratic in the number of merges; the reducer owns its accumulator,
so mutating it is safe.
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Chobble <code@chobble.com>
Co-authored-by: chobble-bot <bot@chobble.com>
|
||
| .claude/agents | ||
| .forgejo/workflows | ||
| .github | ||
| .zed | ||
| bin | ||
| packages/js-toolkit | ||
| screenshots | ||
| scripts | ||
| src | ||
| test | ||
| .claude.json | ||
| .codex | ||
| .eleventy.js | ||
| .envrc | ||
| .gitignore | ||
| .jscpd.json | ||
| .pages.yml | ||
| .stylelintrc.json | ||
| biome.json | ||
| BLOCKS_LAYOUT.md | ||
| bun.lock | ||
| bunfig.toml | ||
| CLAUDE.md | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| knip.json | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.strict.json | ||
The Chobble Template
⚠️ Don't forget to change the Formspark and Botpoison info in _data/config.json or in your repository's action secrets ⚠️
See this template in action at:
- example.chobble.com
- tradesperson-example.chobble.com
- southportorganics.co.uk
- house-of-desserts.co.uk
- ukegroupnorth.com
- myalarmsecurity.co.uk
- c-results.uk
- playsafeplayinspection.co.uk
- garsdalecottages.co.uk
- medwaymascots.co.uk
- funprouk.co.uk
Want me to make you a website based on this template? Hit me up at chobble.com.
💖 Want to support the development of this template? 💖 Donate at liberapay.com/chobble
An Eleventy starter for business websites. The GitHub action deploys to both Neocities and Bunny.net - you'll need to edit that for your setup.
Content Types
- Products - galleries, options with SKUs, FAQs, specifications, features lists, linked reviews with ratings
- Categories - product groupings with inherited thumbnails
- Events - one-off and recurring schedules, iCal feed generation
- News - blog posts with Atom feed
- Menus - categories, items, pricing, dietary indicators (vegan, gluten-free, etc.)
- Locations - multi-site support with sub-locations
- Properties - for holiday lets, linked to locations
- Reviews - linked to products, aggregate ratings
- Team - member profiles
- Snippets - reusable content blocks
Shopping Cart & Payments
- LocalStorage-based cart with quantity limits
- Stripe/Square checkout via external ecommerce backend
- Quote/enquiry mode (submit cart as request instead of payment)
- Auto-generated SKUs via GitHub Action
Theming
- 10 pre-built themes: Default, Neon, 90s Computer, Floral, Hacker, Monochrome, Ocean, Old Mac, Rainbow, Sunset
- Per-page theme overrides
- Visual theme editor at
/theme-editor/with export - CSS custom properties for colours, fonts, borders, layout
- SCSS support
- Bunny Fonts integration
Images
- Responsive images with
srcsetviaeleventy-img - Base64 LQIP placeholders
- Gallery component with thumbnail navigation and full-size overlay
- Custom aspect ratio cropping
Forms
SEO & Structured Data
- Schema.org markup for products (with reviews/ratings), events, FAQs, organisation
- Canonical URLs
- Sitemap
- Atom feed with XSL stylesheet
- Meta descriptions, noindex support
- hreflang tags and
og:localefor sites published in more than one language
Languages
A site is written in one language until it says otherwise, and nothing in the template names a language.
_data/languages.jsonlists every language the site publishes, each with acode,hreflang,og_locale,label,home_urlprefix,home_labelandbreadcrumb_label. Exactly one entry hasis_default: true, which is the base language: the one every URL outside another language's prefix is written in, and the onehreflang="x-default"points at._data/translations.jsonpairs the pages that say the same thing, keyed by language code, e.g.[{ "en": "/about/", "de": "/de/ueber-uns/" }].
A page's language comes from its URL prefix, and the layout, the head tags, the
footer switcher and the breadcrumbs read it as pageLanguage. Exactly one
language must be marked is_default, and a site that marks none fails the
build. To add a language, add its entry,
put its pages under its home_url, list them in translations.json, and
translate _includes/footer.html and the navigation for it. A page with no
counterpart in a language links that language's home page from the footer.
The template ships one language and no translations, which renders exactly as it did before this existed: no hreflang tags and no switcher.
What is translated is pages. Collection items (products, events, categories,
properties, guide categories) have no language of their own: they are looked up
by slug across the whole collection, and their labels come from
_data/strings.json, which has no language dimension. A translated page that
sits under a collection gets its index URL from the translation groups where a
site has paired one, but the crumb's label, and any category or property crumb
below it, still come from the base language. Translating a collection needs a
language on its items and per-language strings, which is its own change.
Navigation & Layout
- Horizontal or left sidebar navigation
- Sticky mobile nav option
- Two-column layout with sidebar
- Slider component for horizontal scrolling
- Scroll-fade animations (respects
prefers-reduced-motion)
Development
- Nix flakes with direnv support
bin/lint- format with Biomebin/screenshot- automated screenshots- Biome linting
- jscpd duplicate detection
- Knip unused code detection
- 17+ test files with custom runner
- instant.page for link prefetching on hover
Testing template code in a downstream client site
Sites built from this template copy it excluding test/, test-*, images/,
and markdown content, then overlay their own site content under src/. That
missing test/ directory means the suite can't run at all from a checkout in
that state, and even with it copied back in by hand, fixture-driven tests
fail, because the test fixture factory (test/test-site-factory.js) builds
its "template defaults" (config, sample content) from the live checkout
root — which is now your own site content, not the template's.
scripts/stage-hermetic-tests.js handles both problems for you. Point it at
a separate, pristine template checkout and it creates a temporary workspace
containing your downstream template code plus pristine template tests, data,
markdown, and image fixtures. It runs the suite there, removes the workspace,
and forwards the suite's exit status without changing your checkout:
git clone --depth 1 https://github.com/chobbledotcom/chobble-template.git /tmp/chobble-template-upstream
bun scripts/stage-hermetic-tests.js --template /tmp/chobble-template-upstream -- test/integration
This also points the fixture factory at the pristine checkout via the
CHOBBLE_TEMPLATE_FIXTURES_DIR env var, so fixture tests build isolated sites
from template defaults rather than your overrides. bun run test:hermetic -- --template <path> -- <args> runs the same thing via the package.json script.
Deployment
- GitHub Actions workflow for Neocities and Bunny.net
- Forgejo Actions support
- PagesCMS integration for no-code editing
- External ecommerce backend for payment processing
Configuration
_data/config.json- Formspark, Botpoison, ecommerce checkout, map embed, nav options_data/site.json- name, URL, description, social links (14 platforms), opening hours_data/meta.json- language, organisation details for schema.org_data/languages.json- languages the site publishes, and which is the base_data/translations.json- pages that say the same thing in each language_data/strings.json- customisable labels and permalink directories
Want a website based on this template? Clone this repo, or hit me up at Chobble.com.