themes-coffee/BUILD_REPORT.md
Alex Dunmow 11c6c8c63e initial: theme plugin coffee
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/coffee.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 14:11:22 +08:00

239 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Coffee — Build Report (v0.1.0 pass)
## What landed
### Plugin shell
- `plugin.mod``name=coffee`, `kind=theme`, `scope=@themes`, `version=0.1.0`,
`categories=["templates"]`, `tags=["warm","hospitality","cafe","bakery","food","handcraft","organic","artisan","menu"]`,
`[compatibility].block_core = ">=0.11.0 <0.12.0"`.
- `go.mod` — module `git.dev.alexdunmow.com/block/themes/coffee`,
`go 1.26.4`, pinned `block/core v0.11.1`, no `replace` directives.
- `Makefile` — local-only `make` (CGO `go build -buildmode=plugin`),
`make clean`, `make templ`, plus the full container-deploy targets copied
from gotham (rebuild / backend / build-so / sync-migrations / etc.).
- `embed.go` — embeds `assets/*`, `schemas/*`, `presets.json`, `fonts.json`,
`plugin.mod`. Exposes `Assets`, `Schemas`, `AssetsHandler`, `ThemePresets`,
`BundledFonts`, `ThemeCSSManifest`.
- `registration.go``var Registration plugin.PluginRegistration` with
`CSSManifest` wired (theme needs paper texture, torn-edge, kraft-tag CSS
injected into the host Tailwind input).
- `register.go` — entry point. Order: system template → 4 page templates →
`br.LoadSchemasFromFS` → 6 theme block registrations → 4 built-in
overrides → email wrapper.
- `master_pages.go``DefaultMasterPages()` returns `coffee:default-master`
and `coffee:landing-master` per spec §7.
- `helpers.go` — content-map readers (`getString`, `getBool`, `getSlice`)
and weekday helpers (`shortDayName`, `normaliseDay`) for the
server-side today-row detection in `hours_strip`.
### System + page templates
- System template `coffee` registered exactly once.
- Page templates: `default` (header/main/footer), `landing`
(hero/menu/story/cta/footer), `article` (header/main/footer),
`full-width` (header/main/footer) — `templ` components in
`template.templ`, each rendered via `Render*` adapter wrapped with `wrap()`.
### Theme blocks (Source: `coffee`)
| Key | File pair | Schema | Notes |
|---|---|---|---|
| `menu_board` | `menu_board.{go,templ}` | `menu_board.schema.json` | Sectioned kraft-paper card; collection of items with price, note, allergens. |
| `hours_strip` | `hours_strip.{go,templ}` | `hours_strip.schema.json` | Today row flagged server-side via `time.Now().Weekday()``is-today today` classes. |
| `location_card` | `location_card.{go,templ}` | `location_card.schema.json` | Doodled SVG pin overlay; falls back to placeholder when map image absent. |
| `featured_pour` | `featured_pour.{go,templ}` | `featured_pour.schema.json` | Hero card with "Featured" badge, rich-text tasting notes, mono price. |
| `footer` | `footer.{go,templ}` | `footer.schema.json` | Torn-edge top border via `.coffee-torn-top` mask; newsletter caption. |
| `doodle_divider` | `doodle_divider.{go,templ}` | `doodle_divider.schema.json` | Inline SVG; four motifs (beans/croissant/cup/leaf), each visibly distinct. |
All 6 schemas use draft-07 and an allowed `x-editor` from
`{text, richtext, media, color, select, number, slug, textarea, array, collection, bucket-picker, menu-select, template-select, link}`.
Schema property names match each block's `content["..."]` reads.
### Built-in block overrides (active only when Coffee is the system template)
| Built-in | Coffee replacement | Aesthetic |
|---|---|---|
| `heading` | `CoffeeHeadingBlock` (`heading_override.{go,templ}`) | Fraunces display via `var(--font-heading)`, doodle underline on h2/h3. |
| `text` | `CoffeeTextBlock` (`text_override.{go,templ}`) | `.coffee-dropcap` first-letter floats large in `--primary`. |
| `button` | `CoffeeButtonBlock` (`button_override.{go,templ}`) | `.kraft-tag` class with `rotate(-1.2deg)` on hover, `:focus-visible` ring at 2px. |
| `image` | `CoffeeImageBlock` (`image_override.{go,templ}`) | Torn-edge bottom frame, handwritten italic caption. |
### Email wrapper
- `coffee/email_wrapper.templ` renders a single-column 560px-wide table
layout, cream `Background`, espresso `Foreground`, inline-SVG doodle
divider beneath the body. Hex fallbacks live in `coffeeBgColor` etc.;
the resolved `EmailContext.Colors` overrides them per email.
- Registered via `tr.RegisterEmailWrapper("coffee", CoffeeEmailWrapper)`.
### Presets (`presets.json`)
Three presets, all 19 tokens populated for both `lightColors` and `darkColors`:
- `morning-pour` (mode: light) — cream paper, espresso ink, terracotta accent.
- `dark-roast` (mode: dark) — espresso base, copper primary, terracotta accent.
- `kraft-cream` (mode: light) — warmer ivory paper, deeper terracotta.
All colour values are HSL triple strings (no `hsl()` wrapper). Token values
copied from spec §4 verbatim where given. Two derived values were tuned
for accessibility (noted under "Open items").
### CSS Manifest
`assets/style.css` is injected into the host Tailwind input via
`CSSManifest.InputCSSAppend`. Provides:
- `--coffee-{heading,body,mono}-fallback` font stacks consumed via
`var(--font-heading, var(--coffee-heading-fallback))` etc.
- `.coffee-paper` body background (inline SVG noise, palette-neutral).
- `.coffee-display`, `.coffee-body`, `.coffee-mono` font-family hooks.
- `.coffee-doodle-underline` SVG underline applied to h2/h3.
- `.coffee-dropcap` for article drop-cap and text override.
- `.kraft-tag` button styling with `:hover { transform: rotate(-1.2deg); }`
and `:focus-visible` outline.
- `.coffee-torn-top` and `.coffee-torn-bottom` `mask-image` SVG paths.
- `.coffee-card`, `.coffee-frame`, `.coffee-pencil-rule` surfaces.
- `.coffee-hours-today` today-row highlight (border-left accent).
- `.coffee-price` mono numerals.
### Fonts policy (per `docs/FONTS.md` wave-1)
- `fonts.json` is literal `[]`.
- No woff2 bundled in this pass.
- All `font-family` usage goes through the CSS variables.
- `RECOMMENDED_FONTS.md` documents Fraunces / Inter / JetBrains Mono as
Google Fonts picker recommendations.
## Build output
```
$ cd /home/alex/src/blockninja/themes/coffee
$ go mod tidy
(no output)
$ /home/alex/go/bin/templ generate
(✓) Complete [ updates=12 duration=8.143585ms ]
$ make
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o coffee.so .
$ ls -la coffee.so
-rw-rw-r-- 1 alex alex 21520096 ./coffee.so (≈ 20.5 MiB)
```
Zero compiler warnings, zero `WARN` lines. The .so is in the same size
range as the gotham reference (`gotham.so` is 20.2 MiB).
## Safety check
The user-supplied invocation references `/home/alex/src/blockninja/backend`
which does not exist on this host. The two available check-safety
locations are:
1. Standalone module at `/home/alex/src/blockninja/check-safety` — the
canonical clean run.
2. CMS backend worktree at
`/home/alex/src/blockninja/cms/.worktrees/orchestrator-plugin-builds/backend/cmd/check-safety`
also reports coffee-specific checks as `OK` but has pre-existing
failures in the CMS backend itself unrelated to this plugin
(`templ.ResolveAttributeValue`, Tailwind v4 frontend lints).
Used invocation:
```
$ cd /home/alex/src/blockninja/check-safety
$ go run . /home/alex/src/blockninja/themes/coffee \
--plugin-dir /home/alex/src/blockninja/themes/coffee
... 22 checks ...
=== Check 22: No hand-rolled HTML sanitization (use bluemonday) ===
OK: No hand-rolled HTML sanitization detected
exit=0
```
All 22 checks PASS or SKIP. Notable:
- Check 2c (Standalone plugin SDK import boundaries) — `coffee` stays on
the SDK boundary v0.11.1.
- Check 3 (Go lint pipeline) — clean for the single coffee module
after dropping the unused `getInt` helper.
- Check 6 (No hardcoded colours in `.templ` / `.ninjatpl`) — passes; the
hex fallbacks in `email_wrapper.templ` (`coffeeBgColor` and siblings)
are the email-client compatibility fallbacks the check tolerates,
matching the same pattern gotham ships.
- Check 21 (preset validation) — all three presets parse against the
theme.Theme schema.
## Open items / deferred
1. **No bundled woff2s.** Per wave-1 `docs/FONTS.md`. `fonts.json` is `[]`,
`RECOMMENDED_FONTS.md` documents the Google Fonts picks (Fraunces /
Inter / JetBrains Mono). Wave-2 follow-up will bundle and add
`LICENSES.md`.
2. **No bitmap paper-grain texture.** The `.coffee-paper` body background
uses an inline SVG noise data URI. The UAT §13.1 expects
`assets/textures/paper-grain.<ext>` at 880 KB. Deferred to a screenshot
/ marketplace pass that ships an actual PNG.
3. **`mutedForeground` accessibility tuning.** Spec §4 sets the
`morning-pour` light `mutedForeground` at `25 20% 40%`, which fails
the UAT §6 contrast gate against `muted` at 4.5:1. Bumped to
`25 25% 32%` to clear AA. Same treatment applied to `kraft-cream`
and `dark-roast` light fallbacks. The HSL family stays in the
spec-described "soft brown over cream" range.
4. **Marketplace assets (UAT §12).** Screenshots, demo seed JSON, and
launch-copy.txt are not produced in this pass.
5. **Live URL & container deploy (UAT §2 final, §5 final, §6 etc.).**
`make rebuild` and the `instance-coffee` container are out of scope
here; the build pass only runs `make` locally + safety.
6. **Email wrapper hex fallbacks.** `coffeeBgColor` and siblings carry
hard-coded hex for the case where `EmailContext.Colors` arrives
empty. This mirrors the gotham reference pattern; email clients
cannot consume CSS variables, so the resolved palette is delivered
per-email and the hex strings only ever land in the no-colour
fallback path. Visual gate UAT §5 last bullet treats this as a
deliberate exception.
7. **`EmailColors.Accent` does not exist.** The spec mentions a
terracotta accent for the email body divider. The SDK's
`templates.EmailColors` exposes Primary / Secondary / Background /
Foreground / Muted / MutedForeground / Border / Card / CardForeground
only — no `Accent`. `coffeeAccentColor` therefore reuses Primary
when present, with a terracotta hex fallback.
8. **`hours_strip` "today" detection uses the server's local time zone.**
Spec §15 flagged this as an open question; resolved here by using
`time.Now()`. A future pass could thread a clock through context to
make this testable without monkey-patching.
9. **`menu_board` allergen field is free-text.** Per spec §15. Structured
allergen taxonomy deferred.
## File map
```
coffee/
├── BUILD_REPORT.md ← this file
├── Makefile
├── RECOMMENDED_FONTS.md
├── assets/
│ ├── style.css ← injected via CSSManifest
│ └── textures/
│ └── README.txt ← placeholder; paper-grain inlined in CSS for v0.1.0
├── coffee.so ← 20.5 MiB build artefact
├── embed.go
├── plugin.mod
├── go.mod
├── go.sum
├── presets.json ← morning-pour, dark-roast, kraft-cream
├── fonts.json ← [] (wave-1 policy)
├── registration.go ← exports var Registration
├── register.go ← Register(tr, br) wires everything
├── master_pages.go ← DefaultMasterPages()
├── helpers.go ← getString/getBool/getSlice + day helpers
├── template.templ ← 4 page templates (Coffee, CoffeeLanding, CoffeeArticle, CoffeeFullWidth)
├── menu_board.{go,templ}
├── hours_strip.{go,templ}
├── location_card.{go,templ}
├── featured_pour.{go,templ}
├── footer.{go,templ}
├── doodle_divider.{go,templ}
├── heading_override.{go,templ}
├── text_override.{go,templ}
├── button_override.{go,templ}
├── image_override.{go,templ}
├── email_wrapper.templ
├── *_templ.go ← templ-generated companions (committed)
└── schemas/
├── menu_board.schema.json
├── hours_strip.schema.json
├── location_card.schema.json
├── featured_pour.schema.json
├── footer.schema.json
└── doodle_divider.schema.json
```