# 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 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
```
