# 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.` at 8–80 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 ```