# 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.blockninja.dev/` 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.
