diff --git a/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md b/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md new file mode 100644 index 0000000..4ebac1c --- /dev/null +++ b/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md @@ -0,0 +1,705 @@ +# Email Templates: Brand + Hygiene Pass — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Reskin all 10 `email/` templates to the Westgate brand, eliminate ~390 lines/file of duplication by wiring up real shared `header.html`/`footer.html` partials via Benchpress `IMPORT`, and close template-level inbox-hygiene gaps (non-empty ``, preheader preview text). + +**Architecture:** Each template collapses from ~400 lines to ~40: `IMPORT emails/partials/header.html` → preheader + body → `IMPORT emails/partials/footer.html`. The shared head/letterhead/footer carry the brand. The body keeps only this template's content and loops. Palette is applied as literal inline hex (no `var()` — Outlook/Gmail strip it); `scss/westgate/_tokens.scss` is the value source, copied as literals. + +**Tech Stack:** Benchpress templating (`{var}`, `{{{ if }}}`, `[[ns:key]]`, `<!-- IMPORT … -->`), HTML email (table layout, inline CSS, MSO conditional comments), NodeBB language JSON files, a tiny dependency-free Node assertion script for regression. + +## Global Constraints + +Copied verbatim from the spec — every task implicitly includes these: + +- **No CSS custom properties.** `var(--…)` is stripped by Outlook/Gmail. All brand color/spacing is **literal hex, inline on each element**. +- **Keep table layout, inline CSS, MSO conditional comments.** No flex/grid/modern CSS refactor. The shared `<style>` block holds only what *must* be in a stylesheet: `:hover` states and `@media` queries. +- **No hardcoded user-facing copy.** Every visible string stays a `[[email:…]]` key. Strings with no existing key (preheaders) get a new key added to the theme language file. +- **Web fonts barely load in email** (Cinzel/Jost only in Apple/iOS Mail). The serif/sans *fallback* stacks carry the look everywhere else. +- **Markup must stay balanced and build cleanly** (`./nodebb build` from the NodeBB root where this theme is installed). +- Reuse the current feature branch (`email-brand-hygiene-spec`). Never commit to `main`. + +### Brand palette — literal hex (the single source of truth for every task) + +| Slot | Old value | New value | Token | +|------|-----------|-----------|-------| +| Page background | `#f6f6f6` | `#ece6da` | adapted neutral | +| Letterhead band | — | `#18141d` | `--wg-panel` | +| Header/footer hairline | — | `#c2a35a` | `--wg-gold` | +| Card background | `#ffffff` | `#fbf7ef` | adapted parchment | +| H1 / greeting | `#333333` | `#1a1418` | ink | +| Sub-heading | `#aaaaaa` | `#9a9086` | `--wg-text-muted` | +| Body text | `#555555` | `#3a3340` | ink | +| Button bg | `#222222` | `#2a1222` | `--wg-plum` | +| Button border | — | `#c2a35a` | `--wg-gold` | +| Button text | `#ffffff` | `#f4ecd8` | parchment | +| Button hover | `#555555` | `#3a1830` | `--wg-plum-soft` | +| Links | default | `#a8893f` | `--wg-gold-soft` | +| Footer text | `#888888` | `#9a9086` | `--wg-text-muted` | + +### Font stacks — literal, inline + +- **Headings:** `Cinzel, Georgia, 'Times New Roman', serif` +- **Body / UI:** `Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif` + +Every existing `-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol` stack becomes the **Body** stack, except `<h1>` greeting/heading elements which become the **Headings** stack. + +--- + +## Decisions & deviations from the spec (review before executing) + +Two points the spec leaves open; resolved here, flag to the user if either is wrong: + +1. **`<title>` is `{site_title}`, not per-email.** Benchpress `IMPORT` is a static include with no argument passing, so a *shared* `header.html` cannot receive a per-template title. Using `{site_title}` makes the title **non-empty and graceful everywhere** (the actual hygiene gap), with zero fragile per-template wiring. Per-message specificity lives in the **preheader**, which *is* per-template and trivial. This satisfies the spec's "populate per email / falls back gracefully" intent without a brittle mechanism. If true per-email titles are required, the alternative is keeping `<head>` out of the partial — reject unless asked. +2. **Preheaders need a theme language file.** The theme currently ships no `languages/` dir and no `languages` key in `plugin.json`. New preheader copy can't be hardcoded (Global Constraint), so Task 5 creates `languages/en-GB/email.json` and registers it. Titles need no key (decision 1). + +--- + +## File structure + +``` +email/ + partials/ + header.html # MODIFY: doctype, <head>, brand <style>, letterhead band, open container, title={site_title} + footer.html # MODIFY: gold hairline, reskinned unsubscribe block, closing tags + post-queue-body.html # MODIFY: reskinned (links/text colors) + reset.html # MODIFY: thin body — IMPORT header + body + IMPORT footer + preheader (worked example) + welcome.html # MODIFY: same transform + verify-email.html # MODIFY + reset_notify.html # MODIFY + registration_accepted.html # MODIFY + invitation.html # MODIFY + banned.html # MODIFY + test.html # MODIFY + notification.html # MODIFY: keeps body, IMPORTs post-queue-body when applicable + digest.html # MODIFY: keeps loops; reskinned + README.md # MODIFY: partials now real; document IMPORT + literal-hex constraint +languages/ + en-GB/email.json # CREATE: preheader keys +plugin.json # MODIFY: add "languages": "languages" +scripts/check-emails.js # CREATE: dependency-free regression guard +``` + +--- + +### Task 1: Reskin shared `header.html` (letterhead band, palette, title, styles) + +**Files:** +- Modify: `email/partials/header.html` + +**Interfaces:** +- Produces: an `IMPORT`-able partial that opens `<html><head>…</head><body>…<center>…<div class="email-container">` and renders the dark letterhead band with `{logo.src}`. Every template `IMPORT`s it first. It does **not** open the body content table — templates do that. +- Consumes (Benchpress params, already provided by NodeBB): `{site_title}`, `{logo.src}`, `{logo.width}`, `{{{ if rtl }}}`, `{{{ if logo.src }}}`. + +- [x] **Step 1: Replace the empty title with `{site_title}`** + +In `email/partials/header.html`, change line 17 from: + +```html + <title> +``` + +to: + +```html + {site_title} +``` + +- [x] **Step 2: Add the guarded webfont link** (low-risk progressive enhancement) + +Replace the empty webfont placeholder block (currently lines 30–32): + +```html + + + +``` + +with: + +```html + + + +``` + +- [x] **Step 3: Update the button hover color in the progressive-enhancement ` + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ {{{ if logo.src }}} + {site_title} + {{{ else }}} + {site_title} + {{{ end }}} +
 
+