themes-pastel-dream/BUILD_REPORT.md
Alex Dunmow de55bbebd6 initial: theme plugin pastel-dream
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>
2026-06-06 14:11:41 +08:00

14 KiB
Raw Blame History

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.mod written 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.mod module path git.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.sum is fully tidied (42 lines) — go mod tidy exits clean.

Build artefact

  • pastel-dream.so produced by make (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 Registration confirmed present via nm -D: git.dev.alexdunmow.com/block/themes/pastel-dream.Registration.
  • go vet ./... exits 0.

System template and page templates (register.go)

  • RegisterSystemTemplate called exactly once with Key: "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 in template.templ.

Schema loading order

  • br.LoadSchemasFromFS(Schemas()) is called at register.go:72, strictly before the first br.Register at register.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-underline accent.
  • textfont-body with line-height: 1.75 per 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:

  1. pastel-dream:default-masterPageTemplates: ["default", "article"]. Blocks: pastel-dream:soft-navbar (header), slot (main), pastel-dream:cozy-footer (footer).
  2. pastel-dream:landing-masterPageTemplates: ["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 a README.txt placeholder 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.md shipped 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-shimmer and @keyframes breathe declared.
  • cubic-bezier(.22,1,.36,1) used in .pastel-pill and feature card transitions.
  • .bg-watercolor-blush / .bg-watercolor-mint utility classes provided for hero / footer backdrops.
  • prefers-reduced-motion: reduce media query disables both animations and the pill hover transform.
  • Theme CSS is injected via CSSManifest.InputCSSAppend (reads assets/css/pastel-dream.css). The asset endpoint also serves a mirrored assets/style.css so direct loads work in case the manifest is bypassed.
  • No hardcoded hex / rgb / named colors in *.templ, *.go, or assets/css/ for runtime UI — every color resolves through hsl(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 from EmailContext.Colors when 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)) via pastelEmailPrimary).
  • 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/.

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 ships fonts.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 under assets/fonts/web/, declared in fonts.json, and licence-attributed in LICENSES.md. The matching UAT §11 checks (woff2 magic, 200 OK on fetch, FOUT ≤200ms, @font-face rule 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 runtime EmailContext.Colors is 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 via fmt.Sprintf from HSL triples; deferred.
  • Live container deploy. make rebuild and the instance-pastel-dream container live in the host CMS workflow. UAT §2 checks about podman ps showing Up, make logs showing zero ERROR / panic, and any tests against https://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