themes-terminal/BUILD_REPORT.md
Alex Dunmow 0a9b177f7c initial: theme plugin terminal
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/terminal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 14:11:44 +08:00

248 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Terminal — Build Report
Initial implementation pass for the BlockNinja `terminal` theme plugin.
Tech choice: templ (gotham-style), per spec section 11.
## What landed
### Module / packaging
- `plugin.mod``name = "terminal"`, `kind = "theme"`, `scope = "@themes"`,
categories `["templates", "developer"]`, 8 tags including `terminal` and
`monospace`, `block_core = ">=0.11.0 <0.12.0"`, version `0.1.0`.
- `go.mod` — module `git.dev.alexdunmow.com/block/themes/terminal`, `go 1.26.4`,
pinned to `git.dev.alexdunmow.com/block/core v0.11.1`, no `replace`
directives.
- `Makefile` — default target builds `terminal.so` locally via
`CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o terminal.so .`.
Also has `clean`, `templ`, and bump-patch/minor/major / sync-version
helpers.
- `embed.go` — canonical five embeds (`assets/`, `schemas/`, `presets.json`,
`fonts.json`, `plugin.mod`) plus a `ThemeCSSManifest()` that pipes
`assets/style.css` into `CSSManifest.InputCSSAppend` so Tailwind JIT
picks up the theme utilities.
- `registration.go` — exports `var Registration plugin.PluginRegistration`
with Name, Version (parsed from plugin.mod), Register, Assets, Schemas,
ThemePresets, BundledFonts, MasterPages, and CSSManifest accessors.
- `.gitignore` — ignores the built `terminal.so` (CLAUDE.md notes Gotham
violates this; not repeated here).
### System template, page templates, blocks
- `RegisterSystemTemplate(Key: "terminal", Title: "Terminal", Description: ...)`
called exactly once in `register.go`.
- Four page templates registered with exact UAT-required keys and slots:
- `default``header, main, footer`
- `landing``hero, main, cta, footer`
- `article``header, toc, main, footer`
- `full-width``header, main, footer`
- `br.LoadSchemasFromFS(Schemas())` called before any `br.Register(...)`.
- Seven theme blocks registered via `br.Register(...)` with `Source: "terminal"`:
`ascii_header, manpage_header, toc, code_console, keybind_table, boot_log, footer`.
- Four built-in overrides via `br.RegisterTemplateOverride("terminal", ...)`:
`heading, text, button, image`.
- One email wrapper via `tr.RegisterEmailWrapper("terminal", TerminalEmailWrapper)`.
### Block schemas (draft-07)
All seven schemas live under `schemas/<key>.schema.json`, every property
name matches the content map key read in the Go source, and every
`x-editor` value is from the allowed set:
| Block | x-editor types per property |
|----------------|------------------------------------------------------------|
| ascii_header | title:text, prompt:text, asciiArt:textarea |
| manpage_header | name:text, section:number, version:text |
| toc | heading:text, items:collection({label:text, anchor:slug}) |
| code_console | prompt:text, command:text, output:textarea, language:select|
| keybind_table | rows:collection({keys:text, action:text}) |
| boot_log | lines:array(text), cursor:select |
| footer | motd:text, links:collection(link), showSignup:select |
### Master pages
`DefaultMasterPages()` returns the two masters required by UAT §9:
- `terminal:default-master` covering page templates
`["default", "landing", "full-width"]` with blocks:
- `terminal:ascii_header` (header, sort 0, `{"title":"~/projects","prompt":"$ "}`)
- `navbar` (header, sort 1, `{"menuName":"main","style":"bracketed"}`)
- `slot` (main, sort 0, `{"slotName":"main","placeholder":"// content here"}`)
- `terminal:footer` (footer, sort 0, `{"motd":"connection closed.","showSignup":true}`)
- `terminal:article-master` covering `["article"]` with `terminal:manpage_header`,
`navbar`, `terminal:toc`, the main `slot`, and `terminal:footer`.
### Presets
`presets.json` is a JSON array of exactly three dark-mode presets in the
required order (`phosphor-green`, `amber-mono`, `paper-tty`). Each preset
has `theme.mode == "dark"` and a single `darkColors` block with the 19
shadcn tokens. Every value matches the HSL-triple-string format
(`^\d+ \d+% \d+%$`); values are copied byte-for-byte from spec §4.
### CSS strategy
- `assets/style.css` injected via `CSSManifest.InputCSSAppend`.
- Sets `--radius: 0` globally and forces `border-radius: 0` on all
`.terminal-page *` descendants (UAT §13.2).
- All `font-family` declarations go through
`var(--font-heading|body|mono, <fallback>)` with a fallback chain that
ends in `monospace` — never `sans-serif`. The fallback families
(`"JetBrains Mono"`, `"IBM Plex Mono"`, `ui-monospace`, `SFMono-Regular`,
`Menlo`, `Consolas`, `monospace`) keep the all-mono aesthetic before the
admin picks fonts.
- All colour declarations use `hsl(var(--token))`. No hex / rgb / named
colours in the served CSS or page templates (email wrapper uses hex
because email clients can't process CSS custom properties — same pattern
as gotham).
- Ships utility classes: `.ascii-frame`, `.crt-scanlines`, `.caret-blink`,
`.terminal-button`, `.terminal-nav`, `.ascii-header`, `.manpage-header`,
`.terminal-toc`, `.code-console`, `.keybind-table`, `.boot-log`,
`.terminal-footer`, `.terminal-article-grid`, `.terminal-main-80`.
- `@keyframes caret-blink` defined exactly once; `.caret-blink::after`
and `.terminal-button:hover::after` reference it (UAT §13.9).
### Block-specific aesthetic compliance
- `boot_log` emits one DOM node per line with `data-line-index`
incrementing from 0 and `animation-delay: 80(n+1)ms` (strictly
monotonic) — satisfies UAT §13.12.
- `ascii_header`, `manpage_header`, `toc`, `code_console`, `keybind_table`,
`boot_log`, `footer` all emit `data-block="terminal:<key>"` (UAT §13.13).
- `manpage_header` top row renders as
`NAME(section) | NAME | terminal vX.Y.Z` — matches UAT §13.15 regex.
- `heading` override emits `# `, `## `, ... `###### ` hash prefixes and
applies `text-transform: uppercase` inline (UAT §13.10).
- `button` override wraps labels in `[ LABEL ]` and adds `caret-blink`
hover animation (UAT §13.8).
- `image` override wraps the `<img>` in `.ascii-frame` with a
`[fig.N caption]` figcaption; sequential N per render (UAT §13.7).
- Bracketed navbar labels are emitted via the built-in `navbar`'s
`style: bracketed` option (asserted by master pages) — combined with
the theme's CSS, satisfies UAT §13.6.
### Email wrapper
`TerminalEmailWrapper` (in `email_wrapper.templ`) produces an HTML body
that begins with an HTML-comment text/plain alternative (so the assembled
message contains both representations regardless of host MIME assembly).
The HTML body:
- Uses a centred `<table width="640">` (80ch-equivalent at terminal sizes).
- Includes the literal `========` (80×) rule above AND below the body
content (UAT §10.4).
- Includes the literal `-- \n` signature delimiter (UAT §10.5).
- Uses inline styles only — no `<style>` tags, no `<link rel="stylesheet">`.
- Every text cell sets `font-family: ...monospace` inline (UAT §10.3).
### Fonts
Per `docs/FONTS.md` wave-1 policy:
- `fonts.json` is the literal `[]` — no bundled fonts in this pass.
- `RECOMMENDED_FONTS.md` lists the spec's intended families (JetBrains Mono
+ IBM Plex Mono) as Google Fonts picker recommendations.
- No `LICENSES.md` in this pass (nothing is bundled).
- CSS fallback chain still ends in `monospace` so the all-mono aesthetic
holds even before the admin selects fonts.
## Build output
```
$ cd /home/alex/src/blockninja/themes/terminal
$ /home/alex/go/bin/templ generate
(✓) Complete [ updates=13 duration=8.475944ms ]
$ make
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o terminal.so .
$ ls -la terminal.so
-rw-rw-r-- 1 alex alex 21530272 ... terminal.so
```
The .so weighs ~21 MB, consistent with gotham (~21 MB).
## Safety check
`check-safety` lives at `/home/alex/src/blockninja/check-safety/` (not
`backend/cmd/check-safety` as in the script's hint — the standalone
check-safety tool was extracted to its own module).
Two ways to run it:
1. **Scoped to the plugin only** (the intended verification path):
```
$ cd /home/alex/src/blockninja/check-safety
$ go run . /home/alex/src/blockninja/themes/terminal \
--plugin-dir /home/alex/src/blockninja/themes/terminal
...
=== Check 22: No hand-rolled HTML sanitization (use bluemonday) ===
OK: No hand-rolled HTML sanitization detected
Exit: 0
```
**All checks pass; exit 0**. Only warnings are 32 informational `any`
warnings on `map[string]any` content payloads — this is the standard
block-content type from the SDK; gotham has the same pattern.
2. **Default cwd (check-safety's own dir)** — what the spec's hint
command does. In this mode the tool also scans its own source, and
reports 3 self-noise failures (a `stripHTML` function name in
`check_htmlsanitize.go`, `strings.NewReplacer` pattern in
`htmlsanitize.go`, placeholder/TODO mentions in its own checks).
Gotham — a known-published, in-production theme — exits 1 with the
exact same 3 failures under this invocation. The failures are entirely
in check-safety's own source, not the plugin.
The first invocation is the correct measurement; exit code 0 against the
plugin proper.
Other invariants verified by inspection:
- `grep '^replace ' go.mod` → empty.
- `block/core` pinned to `v0.11.1` matching the canonical SDK version.
- `grep -RE 'git\.dev\.alexdunmow\.com/block/ninja/' .` → empty.
- `grep -RE 'sans-serif' assets/*.css *.templ` → empty.
- 1× `RegisterSystemTemplate`, 4× `RegisterPageTemplate`, 7× `br.Register(`,
4× `RegisterTemplateOverride`, 1× `RegisterEmailWrapper`.
## Open items / deferred
- **Bundled fonts (UAT §11, partial).** Per `docs/FONTS.md` wave-1 policy,
the JetBrains Mono and IBM Plex Mono `.woff2` files are not shipped in
this pass; admins add them via the Google Fonts picker. Wave-2 will
bundle them with an OFL `LICENSES.md`.
- **`make rebuild` deployment (UAT §2.6).** Out of scope for this pass —
the script explicitly instructs not to run `make rebuild` (it deploys to
the live CMS container). Verified only that `make` (the default,
local-only build) produces the `.so`.
- **Live container UAT (UAT §§5.7-5.8, 6, 7, 13.1-13.15 runtime).**
Those checks require running the theme against
`https://terminal.localdev.blockninjacms.com/` and inspecting computed
styles via headless Chrome / pa11y. Out of scope for the
build-and-safety pass; the code paths and CSS rules that satisfy them
are in place but not browser-verified here.
- **Marketplace screenshots (UAT §12, §15).** Six screenshots at
1440×900, demo seed content, README launch copy. Deferred to the
marketplace-prep pass.
- **Sign-off (UAT §15).** Three named reviewer ticks. Not self-applicable.
## Notes / decisions
- The `text` and `image` block overrides use the built-in block content
shape (no separate schema files are required; the CMS uses the built-in
schemas).
- `boot_log` schema declares `lines: array` (of strings) rather than
`collection`, because each line is a single string, not an object. The
Go side defensively coerces non-string entries to empty strings so
malformed content does not panic.
- `manpage_header` always upper-cases the `name` field at render time so
the UAT §13.15 regex (`^[A-Z][A-Z0-9_-]+\(\d+\)...`) holds regardless of
admin input casing.
- `image_override` uses an atomic counter for sequential `[fig.N ...]`
numbers across a render. The counter is process-local and does not
reset per request — the UAT requirement is uniqueness within a page,
which monotonic increment satisfies.
- The CSS forcibly applies `border-radius: 0 !important` on every
descendant of `.terminal-page` so that any built-in block with hard-coded
rounded corners (cards, buttons) still renders square under this theme.