Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/pastel-dream. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14 KiB
Pastel Dream — Build Report
Implementation pass status against the spec
(themes/docs/works/pastel-dream.md) and gating UAT
(themes/docs/uat/pastel-dream.md), executed under wave-1 fonts policy
(themes/docs/FONTS.md).
What landed
Identity & module
plugin.modwritten verbatim from spec §"plugin.mod":name = "pastel-dream",kind = "theme",scope = "@themes",display_name = "Pastel Dream"(12 chars),description= 100 chars,categories = ["templates"], 9 tags matching spec,[compatibility].block_core = ">=0.11.0 <0.12.0",version = "0.1.0".go.modmodule pathgit.dev.alexdunmow.com/block/themes/pastel-dream,go 1.26.4,git.dev.alexdunmow.com/block/core v0.11.1, no replace directives, indirect dependencies pinned to the same versions gotham uses.go.sumis fully tidied (42 lines) —go mod tidyexits clean.
Build artefact
pastel-dream.soproduced bymake(local CGO build, no container): size 21,517,984 bytes (≈21 MB) which sits inside the normal range (gotham is ~21 MB, lcars is ~30 MB).- Exported symbol
Registrationconfirmed present vianm -D:git.dev.alexdunmow.com/block/themes/pastel-dream.Registration. go vet ./...exits 0.
System template and page templates (register.go)
RegisterSystemTemplatecalled exactly once withKey: "pastel-dream".- 4 page templates registered per spec §"Page templates":
default— Slots:header, main, footer.landing— Slots:hero, main, cta, footer.article— Slots:header, main, footer.full-width— Slots:header, main, footer.
- All four templates render via
RenderPastelDefault/…Landing/…Article/…FullWidth, each templ-backed intemplate.templ.
Schema loading order
br.LoadSchemasFromFS(Schemas())is called atregister.go:72, strictly before the firstbr.Registeratregister.go:77. UAT §3 line-order check passes.
Theme-owned blocks (6 in total)
Each block has one <key>.go + <key>.templ pair under repo root, one
schemas/<key>.schema.json (draft-07), and exactly one br.Register call
in register.go. All blocks declare Source: "pastel-dream" on
BlockMeta. Standalone signature func(ctx, content) string is used
throughout (no children argument).
| Key | Title | Category | Schema fields |
|---|---|---|---|
soft-navbar |
Soft Navbar | Navigation | logo:text, menuName:menu-select, ctaText:text, ctaHref:link |
watercolor-hero |
Watercolor Hero | Theme | eyebrow:text, headline:richtext, body:richtext, image:media, ctaText:text, ctaHref:link |
affirmation |
Affirmation Strip | Theme | quote:textarea, author:text, palette:select(blush|mint|butter|sky) |
testimonial-soft |
Soft Testimonial | Theme | quote:richtext, name:text, role:text, avatar:media, rating:number |
feature-grid-soft |
Feature Trio | Layout | intro:richtext, items:collection(icon:media, title:text, body:textarea) |
cozy-footer |
Cozy Footer | Navigation | showSignup:select, affirmation:textarea, menuName:menu-select, social:array(link) |
All x-editor values fall within the allowed set
{text, richtext, media, color, select, number, slug, textarea, array, collection, bucket-picker, menu-select, template-select, link}.
Built-in overrides
Four overrides wired via br.RegisterTemplateOverride("pastel-dream", …):
heading— Caveat Brush at H1/H2 (display face), Nunito H3+, soft.brush-underlineaccent.text—font-bodywithline-height: 1.75per spec.button—.pastel-pill(border-radius 9999px, tinted shadow, 2px hover lift,cubic-bezier(.22,1,.36,1)easing).card—.pastel-card(border-radius 20px, blush-tinted shadow,border-color: hsl(var(--border))).
Master pages
DefaultMasterPages() returns exactly two entries:
pastel-dream:default-master—PageTemplates: ["default", "article"]. Blocks:pastel-dream:soft-navbar(header),slot(main),pastel-dream:cozy-footer(footer).pastel-dream:landing-master—PageTemplates: ["landing"]. Blocks:pastel-dream:watercolor-hero(hero),slot(main),pastel-dream:affirmation(cta),pastel-dream:cozy-footer(footer).
slot block Content.slotName matches its slot name in every case.
Presets (presets.json)
Three presets present, ids exactly blush-morning, mint-meadow,
twilight-petal. All 19 tokens declared per preset. Every value matches
^\d+ \d+% \d+%$ (HSL triple, no hsl() wrapper). blush-morning and
mint-meadow carry lightColors; twilight-petal carries darkColors.
Values copied verbatim from spec §"Variants (presets.json)".
Fonts (wave-1 policy)
fonts.json = [](literal). Embed succeeds.- No woff2s bundled.
assets/fonts/web/holds aREADME.txtplaceholder so the directory is non-empty (required by//go:embed assets/*). - CSS variable consumer pattern in use everywhere:
var(--font-heading, "Caveat Brush", …),var(--font-body, "Nunito", …),var(--font-mono, "JetBrains Mono", …). Fallback stacks degrade gracefully when no admin has assigned fonts. RECOMMENDED_FONTS.mdshipped at theme root with Google Fonts picker recommendations for Caveat Brush, Nunito, JetBrains Mono per spec §5.
CSS strategy
--radius-soft: 20px;declared once.@keyframes pastel-shimmerand@keyframes breathedeclared.cubic-bezier(.22,1,.36,1)used in.pastel-pilland feature card transitions..bg-watercolor-blush/.bg-watercolor-mintutility classes provided for hero / footer backdrops.prefers-reduced-motion: reducemedia query disables both animations and the pill hover transform.- Theme CSS is injected via
CSSManifest.InputCSSAppend(readsassets/css/pastel-dream.css). The asset endpoint also serves a mirroredassets/style.cssso direct loads work in case the manifest is bypassed. - No hardcoded hex / rgb / named colors in
*.templ,*.go, orassets/css/for runtime UI — every color resolves throughhsl(var(--token)). The email wrapper is an explicit exception: it carries hex fallbacks because email clients cannot read CSS custom properties. The colors are sourced fromEmailContext.Colorswhen present; the hex fallbacks only fire when no preset is bound to the email (e.g. raw previews). See "Open items / deferred" below.
Email wrapper
tr.RegisterEmailWrapper("pastel-dream", PastelEmailWrapper)called once.- 560 px centered frame, cream paper backdrop, top-right watercolor blob
watermark (inline SVG with
fill = hsl(var(--primary))viapastelEmailPrimary). - Caveat Brush masthead (
'Caveat Brush', 'Sacramento', cursive), Nunito body. - Footer carries an
<a>for the unsubscribe URL token and a one-line Caveat Brush affirmation. - A reusable mint CTA pill style helper (
pastelEmailPillStyle) is exported for body content to consume.
Build output
$ cd /home/alex/src/blockninja/themes/pastel-dream
$ go mod tidy # ← exits 0
$ /home/alex/go/bin/templ generate # ← exits 0, 12 *_templ.go files
$ make # ← exits 0
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o pastel-dream.so .
$ ls -lh pastel-dream.so
-rw-rw-r-- 1 alex alex 21M Jun 6 13:25 pastel-dream.so
$ nm -D pastel-dream.so | grep Registration
000000000140c420 D git.dev.alexdunmow.com/block/themes/pastel-dream.Registration
Safety check
The task brief points at
/home/alex/src/blockninja/backend/go.mod / ./cmd/check-safety. That
backend path does not exist on this system. The canonical check-safety
tool lives at /home/alex/src/blockninja/check-safety/ and reads the CMS
SDK version from /home/alex/src/blockninja/cms/backend/go.mod. I ran it
from that location:
$ cd /home/alex/src/blockninja/check-safety
$ go run . --plugin-dir /home/alex/src/blockninja/themes/pastel-dream
Per-plugin result: PASS
Every check that names pastel-dream reports OK:
=== Check 1: Secret env var reads outside config.Load() ===
OK: No secret env var reads outside config.Load() (1 plugin roots scanned)
OK: pastel-dream
=== Check 2b: Plugin proto ownership ===
OK: pastel-dream
=== Check 2c: Standalone plugin SDK import boundaries ===
OK: Standalone plugin imports and go.mod stay on SDK version v0.11.1
OK: pastel-dream
=== Check 3: Go code compiles and passes go fix, golangci-lint --fix, go vet, and strict lint ===
OK: 1 Go module(s) cleared the Go lint pipeline
=== Check 6: No hardcoded colors in frontend (use theme tokens) ===
OK: No hardcoded colors in .templ files
=== Check 11: No placeholder code; only shipped features ===
=== Check 12: No reinvented utilities (use helpers) ===
OK: pastel-dream
=== Check 17: No TODO markers in production code ===
OK: pastel-dream
=== Check 18: Plugin segmentation (safety-rules.yml) ===
OK: pastel-dream
=== Check 21: Plugin presets.json validation ===
OK: pastel-dream presets valid
Total: 10 OK lines mentioning pastel-dream, zero FAIL: lines that
reference any path inside themes/pastel-dream/.
Whole-run exit code: 1 (non-zero), but not pastel-dream-related
The check-safety binary always exits 1 in this layout because, when
invoked from inside its own source directory, several of its own internal
files trip the same regexes the tool enforces. The three FAILs the tool
prints all live inside the tool's own source tree, not the plugin:
FAIL: 21 placeholder reference(s) found:
check_placeholder.go:51, colors.go:183, comingsoon.go:163-231, main.go:19, …
FAIL: 10 TODO marker(s) found:
check_todos.go:34, todo.go:86, todo.go:115, frontend.go:108, main.go:25
FAIL: 3 hand-rolled HTML sanitization pattern(s):
check_htmlsanitize.go:37-38, htmlsanitize.go:41
None of those paths is in themes/pastel-dream/. The strict
./cmd/check-safety . --plugin-dir <pastel-dream> exit-0 requirement
from the task brief cannot be satisfied from this checkout without
patching check-safety itself (out of scope and outside the theme
directory). The plugin-specific verdict is PASS.
Open items / deferred
These were scoped out of this implementation pass and should be picked up in wave-2 / UAT sign-off:
- Bundled woff2 fonts. Per wave-1 policy (
themes/docs/FONTS.md), this pass shipsfonts.json = []. Spec §"Bundled fonts" lists Caveat Brush, Nunito (300/400/600/700), and JetBrains Mono — these should be sourced (OFL is fine for all three), placed underassets/fonts/web/, declared infonts.json, and licence-attributed inLICENSES.md. The matching UAT §11 checks (woff2 magic, 200 OK on fetch, FOUT ≤200ms,@font-facerule count ≥ 3) become real then. - Marketplace assets (UAT §12). Six 1440×900 PNG screenshots, demo
seed for "Linden & Loom" (4 posts / 3 services / 2 testimonials), and
the launch copy line require a running container, design QA, and the
marketplace folder at
themes/docs/marketplace/pastel-dream/. None of those can land inside the theme directory. - Email wrapper hex fallbacks vs UAT §5 regex. UAT §5 reads:
grep -rE "#[0-9a-fA-F]{3,8}|rgb\(|rgba\(" pastel-dream/*.templ … returns zero matches. The email wrapper carries hex fallbacks for clients that cannot resolve CSS custom properties (Gmail web, Outlook, etc.). At runtimeEmailContext.Colorsis populated from the active preset so the hex values never reach an actual recipient — they are pure no-active-preset fallbacks. The check-safety colors check accepts this pattern (gotham follows the same convention). A future pass could move the fallback table into a hex-free Go file viafmt.Sprintffrom HSL triples; deferred. - Live container deploy.
make rebuildand theinstance-pastel-dreamcontainer live in the host CMS workflow. UAT §2 checks aboutpodman psshowingUp,make logsshowing zero ERROR / panic, and any tests againsthttps://pastel-dream.localdev.blockninjacms.com/require that container, which is out of scope for this build pass. - DOM-based gates (UAT §5 line 7, §6, §7, §8, §10, §13). These need the running URL with Chrome DevTools / Playwright. Computed-style and network-request assertions are deferred to manual UAT.
- Regression gate (UAT §14). Comparing rendered HTML SHA-256 across multi-theme installs and uninstall behaviour against the real CMS DB is out of scope for the build pass.
- Sign-off (UAT §15). Three named reviewers (Designer, Engineer, Marketplace owner) must independently tick boxes. Cannot be self-applied.
Where everything lives
themes/pastel-dream/
├── BUILD_REPORT.md ← this file
├── Makefile ← `make` builds .so locally, `make rebuild` deploys
├── RECOMMENDED_FONTS.md ← wave-1 Google Fonts picker recommendations
├── plugin.mod ← TOML metadata (locked to spec)
├── go.mod / go.sum ← block/core v0.11.1, no replace directives
├── presets.json ← 3 presets, all 19 tokens, HSL triples
├── fonts.json ← []
├── embed.go ← //go:embed wiring
├── registration.go ← exports Registration + ThemeCSSManifest
├── register.go ← Register() + DefaultMasterPages()
├── helpers.go ← getString / getSlice / getBoolish / safeLink
├── template.templ ← 4 page templates (Default, Landing, Article, FullWidth)
├── email_wrapper.templ ← PastelEmailWrapper
├── <block>.go + <block>.templ ← one pair per block + override
├── *_templ.go ← templ-generated; committed
├── schemas/ ← one *.schema.json per block
├── assets/css/pastel-dream.css← injected via CSSManifest.InputCSSAppend
├── assets/style.css ← served at /templates/pastel-dream/style.css
└── assets/fonts/web/ ← reserved for wave-2 bundled woff2s