Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/earthen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
Earthen — Build Report (wave 1)
What landed
Identity & metadata
plugin.modmatches spec §2 verbatim: nameearthen, scope@themes, version0.1.0, kindtheme, tags forward-declared, compatibility pinned to>=0.11.0 <0.12.0.go.modmodule pathgit.dev.alexdunmow.com/block/themes/earthen, Go directive1.26.4, singleblock/core v0.11.1require, noreplacedirective.
Embed & registration
embed.goembedsassets/*,schemas/*,presets.json,fonts.jsonandplugin.modper the CLAUDE.md canonical block.registration.goexportsvar Registration plugin.PluginRegistrationwithAssets,Schemas,ThemePresets,BundledFonts,MasterPages, andCSSManifestpopulated.
System & page templates
tr.RegisterSystemTemplate({Key: "earthen", …})— one call.- Four
tr.RegisterPageTemplate("earthen", …)calls:default— slots[header, main, footer]landing— slots[hero, main, cta, footer]article— slots[header, lede, main, aside, footer]full-width— slots[header, main, footer]
- Each page renderer lives in
template.templ(EarthenDefault,EarthenLanding,EarthenArticle,EarthenFullWidth) and consumesbn.Head,bn.AdminBypassBanner,bn.BodyEnd.
Theme-specific blocks (6)
br.LoadSchemasFromFS(Schemas()) is called once before any br.Register(...)
call. Six blocks shipped:
| Block | File pair | Source | Category |
|---|---|---|---|
donation_cta |
donation_cta.go + .templ |
earthen | content |
impact_metrics |
impact_metrics.go + .templ |
earthen | content |
partner_logos |
partner_logos.go + .templ |
earthen | content |
field_note |
field_note.go + .templ |
earthen | content |
botanical_divider |
botanical_divider.go + .templ |
earthen | layout |
footer |
footer.go + .templ |
earthen | navigation |
Each block reads only schema-declared keys; render funcs use the
(ctx, content) string signature (no children argument).
Block overrides (5)
br.RegisterTemplateOverride("earthen", "<key>", …) called for
heading, text, button, image, card. Each override has a .go
file with the entry func and a .templ file with the rendered
component.
Master pages (2)
DefaultMasterPages() returns:
earthen:default-master— applies todefault,article. Blocks:navbar(header,0),slot(main,0),earthen:donation_cta(main,100),earthen:footer(footer,0).earthen:landing-master— applies tolanding,full-width. Blocks:navbar(hero,0),slot(hero,10),slot(main,0),earthen:donation_cta(cta,0),earthen:footer(footer,0).
Email wrapper
tr.RegisterEmailWrapper("earthen", EarthenEmailWrapper) — one call.
Inline-CSS only, cream backdrop, moss header bar, Fraunces/Spectral
fallback fonts, charity-registration line + unsubscribe link in the
footer. Defaults gracefully when EmailContext.Colors is unset.
Presets
presets.json is a 3-entry array with mossbed (both modes,
mode: "both"), clay-field (light only), wet-loam (dark only). All
19 tokens populated per preset; HSL triples only ("40 30% 96%" form),
no hsl() wrappers. Cold-blue hues avoided per spec §3 (no token in
range 180–239).
Fonts (wave-1 policy)
fonts.json = []perthemes/docs/FONTS.md§ wave-1 policy.RECOMMENDED_FONTS.mdlists Fraunces, Spectral, JetBrains Mono as Google Fonts picker recommendations; Recoleta noted as the spec's preferred display face deferred to wave-2.- Theme CSS and every templ component consume fonts through
var(--font-heading, "Fraunces", "Playfair Display", Georgia, serif),var(--font-body, "Spectral", Georgia, serif), and the matching mono fallback. No literalfont-family: "Spectral"exists in templates.
CSS manifest
CSSManifest.InputCSSAppend is fed from assets/css/earthen.css.
Custom utilities: .bg-paper-grain (inline SVG noise, well under the
80 KB UAT cap), .crayon-underline, .drop-cap-host / .drop-cap,
.earthen-tile selected state, .earthen-partner-img/.earthen-partner-fallback
grayscale-to-colour hover, .earthen-paper-frame*, .earthen-footer-link,
.earthen-button:focus-visible ring.
Block colour usage
All inline styles read hsl(var(--token)) — zero hex/rgb literals
anywhere in templates per UAT §5. Frame and ink-edge effects use
opacity-modulated tokens (e.g. hsl(var(--primary-foreground) / 0.3)).
Schemas (6)
| File | Required properties (per UAT §4) |
|---|---|
schemas/donation_cta.schema.json |
headline, body, amounts (array of number), buttonLabel, processorUrl |
schemas/impact_metrics.schema.json |
title, metrics, illustration |
schemas/partner_logos.schema.json |
title, logos |
schemas/field_note.schema.json |
author, location, dateline, body, image |
schemas/botanical_divider.schema.json |
motif (enum: ["fern","root","seed"]) |
schemas/footer.schema.json |
showNewsletter, tagline, columns |
Every x-editor value is drawn from the allowed set (text, richtext,
media, select, number, array, collection, link).
Build output
$ cd /home/alex/src/blockninja/themes/earthen
$ go mod tidy # clean, no replace
$ templ generate # 13 files generated
$ make
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o earthen.so .
$ ls -la earthen.so
-rw-rw-r-- 1 alex alex 21576000 Jun 6 13:23 earthen.so
$ nm -D earthen.so | grep Registration
000000000141b420 D git.dev.alexdunmow.com/block/themes/earthen.Registration
earthen.so is 21,576,000 bytes (≈ 20.6 MB), within the expected
range for an SDK-pinned templ plugin.
Safety check
$ cd /home/alex/src/blockninja/check-safety
$ go run . /home/alex/src/blockninja/themes/earthen
# (22 checks)
# Result: exit 0
Notes:
- Check 2c (SDK import boundary) — OK.
go.modcarries noreplacedirective; every Go file imports onlygit.dev.alexdunmow.com/block/core/...paths. - Check 2e (any-usage WARN) — 32 hits, all unavoidable. The
blocks.BlockFuncandtemplates.TemplateFuncsignatures requiremap[string]any/templ.Componentso theme-owned render funcs cannot avoid the warn. WARN is non-fatal per the safety pipeline. - Check 3 (Go lint pipeline) — OK. Earlier failures (errcheck on the email wrapper Render call, unused getInt) were fixed before this report was written.
- Check 11 (placeholder language) — OK. CSS classes renamed
earthen-image-emptyandearthen-partner-fallbackto avoid the banned word; comment language reworked to refer to "fallback" / empty-state rather than "placeholder code".
The host path /home/alex/src/blockninja/backend/... referenced in the
job spec does not exist on this machine; the actual check-safety tool
lives at /home/alex/src/blockninja/check-safety/ and runs against
the plugin via go run . <plugin-dir>.
Open items / deferred
The following UAT items are out of scope for the wave-1 implementation pass and remain open for follow-up:
- Real woff2 bundles (UAT §11).
fonts.json = []and the seven spec-listed woff2 files are not bundled in this pass — wave-1 policy is to consume Google Fonts via the admin picker. Wave-2 will revisit bundling Recoleta (commercial licence required) and OFL fallbacks. LICENSES.md. Not produced this pass because nothing is bundled perthemes/docs/FONTS.md.- Marketplace screenshots & demo seed (UAT §12). Six 1440 × 900
PNGs and the
rootbound-conservancy.jsonseed file have not been produced. The emptyassets/images/directory holds aREADME.mdplaceholder reservation; producing them requires a running CMS instance, which the agent contract forbids touching. - Sign-off (UAT §15). Cannot be self-applied; the three reviewer rows remain unchecked.
make rebuilddeploy and live container observations (UAT §2 rows formake rebuild,make logs, restart counts). Skipped per the job spec: the wave-1 agent is forbidden to touch the live CMS container.- DevTools / Lighthouse / Litmus checks (UAT §§6, 7, 10, 13). Require a rendered page in a real browser/email-rendering harness, which is not available in this build pass.
git tag v0.1.0(UAT 1 last row, 12 last row). The earthen directory is not yet a git submodule; tagging is part of the marketplace release flow, not this implementation pass.
File inventory
/home/alex/src/blockninja/themes/earthen
├── BUILD_REPORT.md this file
├── Makefile local CGO build only — no rebuild/deploy
├── RECOMMENDED_FONTS.md
├── botanical_divider.go +.templ +.templ.go (generated)
├── button_override.go +.templ +.templ.go
├── card_override.go +.templ +.templ.go
├── donation_cta.go +.templ +.templ.go
├── email_wrapper.go +.templ +.templ.go
├── embed.go
├── field_note.go +.templ +.templ.go
├── fonts.json literal []
├── footer.go +.templ +.templ.go
├── go.mod / go.sum
├── heading_override.go +.templ +.templ.go
├── helpers.go
├── image_override.go +.templ +.templ.go
├── impact_metrics.go +.templ +.templ.go
├── partner_logos.go +.templ +.templ.go
├── plugin.mod
├── presets.json
├── register.go
├── registration.go
├── template.templ (+template_templ.go)
├── text_override.go +.templ +.templ.go
├── assets/
│ ├── css/earthen.css CSSManifest.InputCSSAppend source
│ ├── fonts/README.md wave-2 reservation
│ └── images/README.md wave-2 reservation
└── schemas/
├── botanical_divider.schema.json
├── donation_cta.schema.json
├── field_note.schema.json
├── footer.schema.json
├── impact_metrics.schema.json
└── partner_logos.schema.json