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>
239 lines
12 KiB
Markdown
239 lines
12 KiB
Markdown
# 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
|
||
```
|