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

12 KiB
Raw Permalink Blame History

Coffee — Build Report (v0.1.0 pass)

What landed

Plugin shell

  • plugin.modname=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.govar 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.goDefaultMasterPages() 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