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>
12 KiB
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— modulegit.dev.alexdunmow.com/block/themes/coffee,go 1.26.4, pinnedblock/core v0.11.1, noreplacedirectives.Makefile— local-onlymake(CGOgo 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— embedsassets/*,schemas/*,presets.json,fonts.json,plugin.mod. ExposesAssets,Schemas,AssetsHandler,ThemePresets,BundledFonts,ThemeCSSManifest.registration.go—var Registration plugin.PluginRegistrationwithCSSManifestwired (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()returnscoffee:default-masterandcoffee:landing-masterper spec §7.helpers.go— content-map readers (getString,getBool,getSlice) and weekday helpers (shortDayName,normaliseDay) for the server-side today-row detection inhours_strip.
System + page templates
- System template
coffeeregistered exactly once. - Page templates:
default(header/main/footer),landing(hero/menu/story/cta/footer),article(header/main/footer),full-width(header/main/footer) —templcomponents intemplate.templ, each rendered viaRender*adapter wrapped withwrap().
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.templrenders a single-column 560px-wide table layout, creamBackground, espressoForeground, inline-SVG doodle divider beneath the body. Hex fallbacks live incoffeeBgColoretc.; the resolvedEmailContext.Colorsoverrides 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}-fallbackfont stacks consumed viavar(--font-heading, var(--coffee-heading-fallback))etc..coffee-paperbody background (inline SVG noise, palette-neutral)..coffee-display,.coffee-body,.coffee-monofont-family hooks..coffee-doodle-underlineSVG underline applied to h2/h3..coffee-dropcapfor article drop-cap and text override..kraft-tagbutton styling with:hover { transform: rotate(-1.2deg); }and:focus-visibleoutline..coffee-torn-topand.coffee-torn-bottommask-imageSVG paths..coffee-card,.coffee-frame,.coffee-pencil-rulesurfaces..coffee-hours-todaytoday-row highlight (border-left accent)..coffee-pricemono numerals.
Fonts policy (per docs/FONTS.md wave-1)
fonts.jsonis literal[].- No woff2 bundled in this pass.
- All
font-familyusage goes through the CSS variables. RECOMMENDED_FONTS.mddocuments 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:
- Standalone module at
/home/alex/src/blockninja/check-safety— the canonical clean run. - CMS backend worktree at
/home/alex/src/blockninja/cms/.worktrees/orchestrator-plugin-builds/backend/cmd/check-safety— also reports coffee-specific checks asOKbut 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) —
coffeestays on the SDK boundary v0.11.1. - Check 3 (Go lint pipeline) — clean for the single coffee module
after dropping the unused
getInthelper. - Check 6 (No hardcoded colours in
.templ/.ninjatpl) — passes; the hex fallbacks inemail_wrapper.templ(coffeeBgColorand 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
- No bundled woff2s. Per wave-1
docs/FONTS.md.fonts.jsonis[],RECOMMENDED_FONTS.mddocuments the Google Fonts picks (Fraunces / Inter / JetBrains Mono). Wave-2 follow-up will bundle and addLICENSES.md. - No bitmap paper-grain texture. The
.coffee-paperbody background uses an inline SVG noise data URI. The UAT §13.1 expectsassets/textures/paper-grain.<ext>at 8–80 KB. Deferred to a screenshot / marketplace pass that ships an actual PNG. mutedForegroundaccessibility tuning. Spec §4 sets themorning-pourlightmutedForegroundat25 20% 40%, which fails the UAT §6 contrast gate againstmutedat 4.5:1. Bumped to25 25% 32%to clear AA. Same treatment applied tokraft-creamanddark-roastlight fallbacks. The HSL family stays in the spec-described "soft brown over cream" range.- Marketplace assets (UAT §12). Screenshots, demo seed JSON, and launch-copy.txt are not produced in this pass.
- Live URL & container deploy (UAT §2 final, §5 final, §6 etc.).
make rebuildand theinstance-coffeecontainer are out of scope here; the build pass only runsmakelocally + safety. - Email wrapper hex fallbacks.
coffeeBgColorand siblings carry hard-coded hex for the case whereEmailContext.Colorsarrives 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. EmailColors.Accentdoes not exist. The spec mentions a terracotta accent for the email body divider. The SDK'stemplates.EmailColorsexposes Primary / Secondary / Background / Foreground / Muted / MutedForeground / Border / Card / CardForeground only — noAccent.coffeeAccentColortherefore reuses Primary when present, with a terracotta hex fallback.hours_strip"today" detection uses the server's local time zone. Spec §15 flagged this as an open question; resolved here by usingtime.Now(). A future pass could thread a clock through context to make this testable without monkey-patching.menu_boardallergen 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