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>
281 lines
14 KiB
Markdown
281 lines
14 KiB
Markdown
# 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.
|
||
- `text` — `font-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-master`** — `PageTemplates: ["default", "article"]`.
|
||
Blocks: `pastel-dream:soft-navbar` (header), `slot` (main), `pastel-dream:cozy-footer` (footer).
|
||
2. **`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 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/`.
|
||
|
||
### 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 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
|
||
```
|