@hegemonart/get-design-done 1.34.1 → 1.34.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ # Litmus — Connection Specification
2
+
3
+ This file is the connection specification for Litmus within the get-design-done pipeline. It lives in `connections/` alongside other connection specs. See the connection index for the full connection capability matrix (the litmus row is added at the 34.2 closeout).
4
+
5
+ ---
6
+
7
+ Litmus is the **verify stage's cross-client email render-test** for the `email` project type. It renders an email's HTML across the **top-20 email clients** (Outlook's Word engine, the various Gmail modes, Apple Mail, Yahoo/AOL, Proton, etc.) and returns per-client screenshots. Its pipeline role: after the `email-executor` generates an email (MJML source + derived HTML), the verify stage uses Litmus — **when available** — to surface per-client rendering breakage that the static validator cannot see, and narrates the result in plain English.
8
+
9
+ **Key relationship to the static validator:** Litmus is the *rendered* complement to the *static* checker `scripts/lib/email/validate-email-html.cjs`. The static validator deterministically checks constraint **classes** (table layout, inline styles, MSO comments, color-scheme — `EM-LAYOUT/STYLE/MSO/DARK`); Litmus checks **rendered pixels** across real clients. Litmus is **optional** — its absence is a quality reduction, not a blocking error (D-03).
10
+
11
+ ---
12
+
13
+ ## Setup
14
+
15
+ **Prerequisites:**
16
+
17
+ - A Litmus account at [litmus.com](https://www.litmus.com) — paid (a trial is available)
18
+ - API access enabled on the account (the Litmus Email Previews / Instant API), or the Litmus CLI
19
+
20
+ **Account and token:**
21
+
22
+ 1. Create a Litmus account and enable API access in the account settings
23
+ 2. Copy the API key / token from the account's API settings page
24
+ 3. Set the environment variable — **NEVER commit the token to git or to a tracked `.env` file:**
25
+
26
+ ```bash
27
+ export LITMUS_API_KEY=<your-token>
28
+ ```
29
+
30
+ Add this to your shell profile or CI environment secrets. `LITMUS_API_KEY` grants access to your Litmus account (create previews, consume render quota) — treat it like a password: never commit it (not in source files, not in `.env`, not in configuration files), never log it in CI output, and rotate it if it is exposed.
31
+
32
+ **Verification:**
33
+
34
+ ```bash
35
+ test -n "${LITMUS_API_KEY}" && echo "litmus token present" || echo "litmus token absent"
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Why Litmus is useful
41
+
42
+ Email rendering breakage is invisible to both code review and the static validator. An email's HTML can pass every static class-check (`validateEmailHtml` returns `ok: true`) and still render broken in a specific client: Outlook's Word engine collapses a layout that lacked a ghost table, Outlook.com inverts colors in dark mode and produces unreadable low-contrast text, the strict Gmail IMAP mode strips a `<style>` block the static heuristic tolerated, a retina image overflows on a mobile client.
43
+
44
+ The static validator checks constraint **classes**; it cannot render. Litmus renders the **actual** email across real clients and returns a screenshot per client, so per-client breakage surfaces as a visible image rather than a user report weeks later.
45
+
46
+ ---
47
+
48
+ ## When to use Litmus
49
+
50
+ **Verify stage:** After the `email-executor` generates the email, run Litmus — when available — to capture cross-client screenshots and check for per-client rendering breakage. The verify stage narrates the per-client delta and notes it in `DESIGN-VERIFICATION.md`.
51
+
52
+ Litmus is **not** used at generation time. The `email-executor` needs no Litmus account, no network, and no render service to produce the MJML + HTML — generation is gated by the static validator only (D-04/D-10).
53
+
54
+ ---
55
+
56
+ ## CLI / API Commands (Bash, not MCP tools)
57
+
58
+ Litmus is consumed via its HTTP API (or the Litmus CLI), not an MCP. All interactions are via Bash.
59
+
60
+ | Interaction | Auth | Returns | Pipeline use |
61
+ |---|---|---|---|
62
+ | Create an Email Preview (POST the HTML) | `LITMUS_API_KEY` | A preview id + per-client screenshot URLs | verify: cross-client render evidence |
63
+ | Poll the preview's client results | `LITMUS_API_KEY` | Per-client screenshot status (ready / processing) | verify: delta narration |
64
+
65
+ Write the rendered results to `.design/litmus-results.json` for the verify stage to narrate, mirroring the chromatic-results.json pattern. Exact endpoint shapes are documented at the Litmus API reference; this connection only requires the token-present probe + the degrade contract below.
66
+
67
+ ---
68
+
69
+ ## Availability Probe
70
+
71
+ Litmus is consumed via API/CLI — the probe is Bash-based, not ToolSearch-based.
72
+
73
+ **Step L1 — CLI/API presence:**
74
+
75
+ ```bash
76
+ command -v litmus 2>/dev/null || test -n "${LITMUS_API_KEY}"
77
+ ```
78
+
79
+ - A Litmus CLI is found, OR an API key is present → proceed to Step L2
80
+ - Neither → `litmus: not_configured` (skip all Litmus steps)
81
+
82
+ **Step L2 — Token check:**
83
+
84
+ ```bash
85
+ test -n "${LITMUS_API_KEY}"
86
+ ```
87
+
88
+ - Non-empty → `litmus: available`
89
+ - Empty → `litmus: unavailable` (no API key to authenticate render requests)
90
+
91
+ **Write litmus status to `.design/STATE.md` `<connections>` after probing.**
92
+
93
+ ---
94
+
95
+ ## Fallback Behavior
96
+
97
+ **verify stage:**
98
+
99
+ - `litmus: unavailable` → skip cross-client render-testing; **degrade** to the static validator `scripts/lib/email/validate-email-html.cjs` (the `EM-LAYOUT/STYLE/MSO/DARK` class-checks), then a **code-only** structural audit of the HTML; note in `DESIGN-VERIFICATION.md`: "Cross-client render-test skipped — LITMUS_API_KEY not set; verified via the static email-HTML validator + a code-only structural audit."
100
+ - `litmus: not_configured` → same as unavailable; note: "Cross-client render-test skipped — Litmus not configured; verified via the static validator + a code-only audit. (Alternative: Email-on-Acid.)"
101
+
102
+ **Graceful degradation required:** the pipeline must continue when Litmus is unavailable. Missing cross-client render data is a **quality reduction, not a blocking error** (D-03 — the email render-test is an enhancement, never hard-required, mirroring the 34.1 simulator connections). The static validator is always available and is the deterministic floor; Litmus is the rendered ceiling on top of it.
103
+
104
+ ---
105
+
106
+ ## STATE.md Integration
107
+
108
+ Every stage that probes Litmus writes the result to `.design/STATE.md` under the `<connections>` section:
109
+
110
+ ```xml
111
+ <connections>
112
+ figma: available
113
+ litmus: unavailable
114
+ </connections>
115
+ ```
116
+
117
+ **Status values:**
118
+
119
+ | Value | Meaning |
120
+ |---|---|
121
+ | `available` | CLI/API reachable AND `LITMUS_API_KEY` is set |
122
+ | `unavailable` | CLI/API present but `LITMUS_API_KEY` is empty |
123
+ | `not_configured` | No Litmus CLI and no API key — Litmus not set up |
124
+
125
+ ---
126
+
127
+ ## Alternative render-test services
128
+
129
+ Litmus is **not** the only cross-client render-test. If you prefer a different provider, the connection's contract (probe → available/unavailable/not_configured; degrade to the static validator when absent) is identical — only the token name and endpoint change:
130
+
131
+ - **Email on Acid** — the documented primary alternative; comparable cross-client previews and an API. Swap `LITMUS_API_KEY` for the Email-on-Acid API key and keep the same degrade behavior.
132
+ - **Putsmail** / **Mailtrap** — lighter options for sending an email to your own inboxes for manual spot-checks (not full cross-client screenshot matrices, but useful when no render-test account exists).
133
+
134
+ In every case the **static validator remains the deterministic floor**; the render-test service is the optional rendered enhancement that degrades gracefully when absent.
@@ -0,0 +1,113 @@
1
+ # Print-Renderer — Connection Specification
2
+
3
+ This file is the connection specification for the print-render within the get-design-done pipeline. It lives in `connections/` alongside the other connection specs (the closest analog is `connections/preview.md`, the headless-Chrome visual-truth connection). See the connection index for the full connection capability matrix (the print-renderer row is added at the 34.3 closeout).
4
+
5
+ ---
6
+
7
+ The print-render is the **verify stage's RENDERED proof for the `print` project type**. It renders the `pdf-executor`'s **Paged.js-compatible print HTML/CSS** to a **paginated PDF / page image** — the print analog of `preview.md`'s browser screenshots. Its pipeline role: after the `pdf-executor` generates the print HTML/CSS (validated by the static checker), the verify stage uses the print-render — **when available** — to surface print breakage the static validator cannot see (actual pagination, bleed placement, font embedding in the PDF, raster resolution), and narrates the result in plain English.
8
+
9
+ **Key relationship to the static validator:** the print-render is the *rendered* complement to the *static* checker `scripts/lib/print/validate-print-css.cjs`. The static validator deterministically checks constraint **classes** (`@page` present, bleed/crop-marks signal, CMYK awareness, font-embed, 300dpi — `PR-PAGE/BLEED/CMYK/FONT/DPI`); the print-render checks the **rendered output** — what actually paginates and rasterizes. The print-render is **optional** — its absence is a quality reduction, not a blocking error (D-03).
10
+
11
+ ---
12
+
13
+ ## Setup
14
+
15
+ **Prerequisites:**
16
+
17
+ - **PRIMARY — Paged.js via headless Chrome:** the same headless-Chrome family `preview.md` uses. Paged.js consumes the `@page`/bleed print CSS and paginates in headless Chrome, producing a paginated PDF. No bundled dependency is required to *probe*; the render is opt-in — like the Preview MCP, prefer a built-in / no-external-package path where possible, and where a tool IS needed it is opt-in (never installed by the pipeline).
18
+ - **ALTERNATIVE — PDFKit for Chrome-less runtimes:** where no headless Chrome is available, PDFKit constructs the page box programmatically (`new PDFDocument({ size, margins })`), embeds fonts (`doc.registerFont(...)`), and places the bleed. This is the documented Chrome-less render path.
19
+
20
+ Per **D-02 / D-10** there is **NO bundled `pdfkit`/`paged`/`puppeteer`/`playwright` dependency** and **no live render in the default `npm test`** — the print-render is an optional, opt-in enhancement the maintainer wires up in the verify stage when a renderer is present.
21
+
22
+ **Verification:**
23
+
24
+ ```bash
25
+ command -v chromium 2>/dev/null || command -v chrome 2>/dev/null || command -v node 2>/dev/null
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Why the print-render is useful
31
+
32
+ Print rendering breakage is invisible to both code review and the static validator. A print stylesheet can pass every static class-check (`validatePrintCss` returns `ok: true`) and still render broken: a table splits badly across a page boundary because a `break-inside: avoid` was missing, the bleed box does not actually extend past the trim, an `@font-face` fails to embed and the RIP substitutes a metrically-different face (reflowing the layout), a raster image that *claimed* 300dpi is upscaled and pixelates.
33
+
34
+ The static validator checks constraint **classes**; it cannot render. The print-render renders the **actual** document to a paginated PDF / page image, so pagination, bleed placement, font embedding, and raster resolution surface as visible output rather than a press reject weeks later.
35
+
36
+ ---
37
+
38
+ ## When to use the print-render
39
+
40
+ **Verify stage:** After the `pdf-executor` generates the print HTML/CSS, run the print-render — when available — to capture a paginated PDF / page proof and check for rendered breakage. The verify stage narrates the delta and notes it in `DESIGN-VERIFICATION.md`.
41
+
42
+ The print-render is **not** used at generation time. The `pdf-executor` needs no headless Chrome, no Paged.js runtime, no PDFKit, and no network to produce the print HTML/CSS — generation is gated by the static validator only (D-04/D-10).
43
+
44
+ ---
45
+
46
+ ## Availability Probe
47
+
48
+ The print-render is consumed via a CLI/binary presence check (headless Chrome / PDFKit), not ToolSearch.
49
+
50
+ **Step PR1 — renderer presence:**
51
+
52
+ ```bash
53
+ command -v chromium 2>/dev/null || command -v chrome 2>/dev/null
54
+ ```
55
+
56
+ - A headless-Chrome binary (for Paged.js) OR a PDFKit-capable runtime is found → proceed to Step PR2
57
+ - Neither → `print-renderer: not_configured` (skip all print-render steps)
58
+
59
+ **Step PR2 — render capability check:**
60
+
61
+ - A renderer is present and invokable → `print-renderer: available`
62
+ - Present but not invokable (missing flag, sandbox error) → `print-renderer: unavailable`
63
+
64
+ **Write the print-renderer status to `.design/STATE.md` `<connections>` immediately after probing** — the three-value schema `preview.md` uses (`available` / `unavailable` / `not_configured`).
65
+
66
+ ---
67
+
68
+ ## Fallback Behavior
69
+
70
+ When the print-render is `not_configured` or `unavailable`, the verify stage degrades gracefully — no error is raised.
71
+
72
+ **verify stage (`skills/verify/SKILL.md` + `agents/design-verifier.md`):**
73
+
74
+ - `print-renderer: unavailable` → skip the rendered PDF/page proof; **degrade** to the static validator `scripts/lib/print/validate-print-css.cjs` (the `PR-PAGE/BLEED/CMYK/FONT/DPI` class-checks), then a **code-only** structural audit of the print HTML/CSS; note in `DESIGN-VERIFICATION.md`: "Print render-test skipped — no headless Chrome / PDFKit; verified via the static print-CSS validator + a code-only structural audit."
75
+ - `print-renderer: not_configured` → same as unavailable; note: "Print render-test skipped — print-render not configured; verified via the static validator + a code-only audit. (Alternative: PDFKit on a Chrome-less runtime.)"
76
+
77
+ **Graceful degradation required:** the pipeline must continue when the print-render is unavailable. Missing rendered-PDF data is a **quality reduction, not a blocking error** (D-03 — the print-render is an enhancement, never hard-required, mirroring the 34.1 simulator / 34.2 Litmus connections). The static validator is always available and is the deterministic floor; the print-render is the rendered ceiling on top of it. Neither stage appends a `<blocker>` for a missing print-render connection. If a `must_have` explicitly requires rendered-PDF evidence, THEN append a blocker.
78
+
79
+ ---
80
+
81
+ ## STATE.md Integration
82
+
83
+ Every stage that probes the print-render writes the result to `.design/STATE.md` under the `<connections>` section:
84
+
85
+ ```xml
86
+ <connections>
87
+ figma: available
88
+ preview: available
89
+ print-renderer: not_configured
90
+ </connections>
91
+ ```
92
+
93
+ **Status values:**
94
+
95
+ | Value | Meaning |
96
+ |---|---|
97
+ | `available` | A headless-Chrome (Paged.js) or PDFKit renderer is present AND invokable |
98
+ | `unavailable` | A renderer binary is present but the render call errored (missing flag, sandbox) |
99
+ | `not_configured` | No headless Chrome and no PDFKit runtime — the print-render is not set up |
100
+
101
+ The verify stage re-probes at stage entry (renderer availability can change between sessions). If STATE.md already carries a `print-renderer:` status from a prior stage in the SAME session, that status can be trusted for the rest of that session.
102
+
103
+ ---
104
+
105
+ ## Caveats and Pitfalls
106
+
107
+ 1. **The print-render is an enhancement, not a requirement.** Its absence degrades to the static validator + a code-only audit and never blocks the pipeline (D-03). Do not gate a print build on a rendered PDF.
108
+
109
+ 2. **Physical-unit / DPI gotchas.** The page is a physical object — `mm`/`pt`/`in` have fixed physical sizes, `px` does not across RIPs. A render at the wrong DPI (72/96 screen vs 300 print) misrepresents raster sharpness; render at the document's declared output resolution. Bleed (~3mm) and crop marks live in the trim waste — confirm the rendered PDF actually carries them.
110
+
111
+ 3. **No bundled render dependency.** Do NOT add `pdfkit`/`paged`/`puppeteer`/`playwright` to `package.json` to make this connection work — the render is opt-in and the maintainer supplies the renderer. The default `npm test` stays hermetic (D-10).
112
+
113
+ 4. **Do NOT edit the connection index here.** The print-renderer's Active-Connections entry and Capability-Matrix row are added by the 34.3 closeout plan, not by this spec (the 34.1/34.2 disjointness pattern).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hegemonart/get-design-done",
3
- "version": "1.34.1",
3
+ "version": "1.34.3",
4
4
  "description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
5
5
  "author": "Hegemon",
6
6
  "homepage": "https://github.com/hegemonart/get-design-done",
@@ -0,0 +1,219 @@
1
+ # Email Design — Constraint Catalogue
2
+
3
+ This reference is the **email-constraint catalogue**: the hard email-client rules an
4
+ email template MUST honor. Email is a *constrained* HTML/CSS surface — the modern
5
+ HTML/CSS the web executor emits (flexbox, grid, `<style>` sheets, `position`) is
6
+ dropped or mis-rendered by most email clients. This file is the **authority** that the
7
+ email-executor (Phase 34.2-02) generates against and the design-verifier's email branch
8
+ (34.2-03) audits against; neither re-derives these rules. The deterministic subset of
9
+ this catalogue is checked by [`scripts/lib/email/validate-email-html.cjs`](../scripts/lib/email/validate-email-html.cjs),
10
+ whose emitted `rule` ids are the constraint-ids defined below (the spec is the authority).
11
+
12
+ It is the sibling of [`reference/platforms.md`](./platforms.md). The two files have
13
+ distinct jobs and must not be confused:
14
+
15
+ | File | Job |
16
+ | --- | --- |
17
+ | `reference/platforms.md` (Phase 19) | Interaction **conventions** — navigation, safe areas, gestures, native typography, haptics. *Behavioral* knowledge for native/web screens. |
18
+ | `reference/email-design.md` (Phase 34.2, this file) | The email **constraint catalogue** — table-based layout, inline styles, MSO conditional comments, dark-mode `color-scheme`, ~600px max-width, image/alt rules, and top-20-client quirks. *Structural* knowledge an email template implements. |
19
+
20
+ Per **D-02** there is **no `mjml` runtime dependency**: MJML source is the agent's
21
+ canonical artifact and the HTML is derived by the agent, not a build step. Per **D-03**
22
+ email verify is this catalogue's *static* validator plus an *optional* Litmus /
23
+ Email-on-Acid render-test connection that degrades to the static validator when absent.
24
+ Per **D-10** the static checks are deterministic (same HTML string → same result), with
25
+ no network and no `mjml`, so the default `npm test` stays green on any machine.
26
+
27
+ Each constraint carries a **rule-id** (`EM-<CLASS>-NN`). Section 8 marks exactly which
28
+ ids the static validator asserts versus which are render-tested guidance only.
29
+
30
+ ---
31
+
32
+ ## 1. Purpose
33
+
34
+ Phase 19 shipped platform *references*; Phase 23 shipped the token engine; Phase 34.1
35
+ added native generators. Email is the remaining untouched product surface, and its
36
+ constraints are unlike both web and native: a fifteen-year-old rendering engine
37
+ (Outlook/Word), aggressive `<head>` stripping (Gmail), and a long tail of per-client
38
+ quirks. Instead of each email being authored from memory, the constraints live once
39
+ here (the catalogue) and once in the validator (the statically-checkable subset), and
40
+ the executor + verifier consume them. This file is the single SC#9-email authority.
41
+
42
+ The catalogue is **prose + tables**, not an implementation. Illustrative snippets are
43
+ kept to 2–3 lines. The implementation is `validate-email-html.cjs`.
44
+
45
+ ---
46
+
47
+ ## 2. Layout — tables, not flexbox/grid/position
48
+
49
+ Email layout uses nested `role="presentation"` **tables**, never the modern CSS box
50
+ primitives. `display:flex`, `display:grid`, and `position:absolute|fixed|sticky` are
51
+ dropped or mis-rendered by Outlook (Word engine), older Gmail, and many mobile clients,
52
+ collapsing a layout to a single column or off-screen elements.
53
+
54
+ | Rule-id | Constraint |
55
+ | --- | --- |
56
+ | **EM-LAYOUT-01** | Layout is built from `role="presentation"` tables. The forbidden modern primitives — `display:flex`, `display:grid`, `position:absolute`, `position:fixed` (and `position:sticky`) — MUST NOT appear in any `style`. *(Statically checkable: their presence is flagged.)* |
57
+ | EM-LAYOUT-02 | Body content sits in a fixed-/max-width container of ~**600px** (the safe width across the desktop preview pane and most mobile clients). Wider tables clip in Outlook and force horizontal scroll on mobile. |
58
+ | EM-LAYOUT-03 | Use `cellpadding="0" cellspacing="0" border="0"` on layout tables and prefer cell `padding` over margins (margins are inconsistently honored). |
59
+ | EM-LAYOUT-04 | Single primary column on mobile; multi-column desktop layouts degrade to stacked rows. Do not rely on `float` for columns (use side-by-side `<td>`s or `align`). |
60
+ | EM-LAYOUT-05 | Set explicit `width` on tables/cells; never assume an intrinsic content width. Outlook ignores CSS `max-width` on many elements — pair it with a `<!--[if mso]>` ghost table (see §4). |
61
+
62
+ ```html
63
+ <table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0"
64
+ style="max-width:600px;margin:0 auto;"><tr><td>…</td></tr></table>
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 3. Styling — inline, not a `<style>` block
70
+
71
+ Visual styling lives in **inline `style="…"` attributes** on each element, NOT in a
72
+ `<head><style>` sheet. Gmail (and several webmail clients) strip or heavily limit
73
+ `<head>` styles, so any rule that *must* apply is inlined. A `<style>` block is
74
+ **tolerated only** for progressive enhancement that degrades gracefully — chiefly
75
+ `@media` queries for responsive/dark-mode — never as the primary styling mechanism.
76
+
77
+ | Rule-id | Constraint |
78
+ | --- | --- |
79
+ | **EM-STYLE-01** | Core/visual styling is inline. A `<style>` block used as the PRIMARY styling mechanism (non-`@media` rules, or a large sheet) is forbidden because Gmail strips it. *(Statically checkable: a non-trivial `<style>` block carrying non-`@media` rules is flagged; a small `@media`-only block is tolerated.)* |
80
+ | EM-STYLE-02 | Avoid CSS shorthand that clients mangle (`background` shorthand, unitless line-heights in some clients); prefer longhand (`background-color`). |
81
+ | EM-STYLE-03 | No external stylesheets (`<link rel="stylesheet">`) and no `@import` — clients strip them. |
82
+ | EM-STYLE-04 | A small `@media`-only `<style>` for responsive breakpoints / dark-mode is the one accepted `<style>` use; it must enhance, not be required for, a readable layout. |
83
+
84
+ ---
85
+
86
+ ## 4. Outlook / MSO conditional comments
87
+
88
+ Outlook on Windows renders with Microsoft Word's HTML engine. It needs **MSO
89
+ conditional comments** to apply Outlook-specific fallbacks (ghost tables for centering
90
+ and width, VML for background images and bulletproof buttons) and to hide modern markup
91
+ it would mangle.
92
+
93
+ | Rule-id | Constraint |
94
+ | --- | --- |
95
+ | **EM-MSO-01** | A full email document MUST include at least one MSO conditional comment — `<!--[if mso]> … <![endif]-->` and/or `<!--[if !mso]><!--> … <!--<![endif]-->`. *(Statically checkable: absence in a full-email document is flagged.)* |
96
+ | EM-MSO-02 | Use `<!--[if mso]>` **ghost tables** to give Outlook an explicit fixed width and centering that it would otherwise drop from CSS `max-width`/`margin:auto`. |
97
+ | EM-MSO-03 | Use **VML** (`<v:roundrect>`, `<v:fill>`) inside `<!--[if mso]>` for rounded "bulletproof" buttons and background images, since Outlook ignores `border-radius` and CSS `background-image`. |
98
+ | EM-MSO-04 | Add the MSO DPI/namespace head fixes (`o:OfficeDocumentSettings`, `xmlns:v`/`xmlns:o`, `mso-line-height-rule:exactly`) to stabilize spacing and image scaling. |
99
+
100
+ ```html
101
+ <!--[if mso]><table role="presentation" width="600"><tr><td><![endif]-->
102
+ <!-- modern markup here -->
103
+ <!--[if mso]></td></tr></table><![endif]-->
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 5. Dark mode
109
+
110
+ Clients invert or remap colors in dark mode inconsistently (Outlook.com fully inverts;
111
+ Apple Mail and Gmail partially; some not at all). Declare a **`color-scheme`** signal so
112
+ clients keep the intended palette, and use `prefers-color-scheme` for explicit dark
113
+ overrides.
114
+
115
+ | Rule-id | Constraint |
116
+ | --- | --- |
117
+ | **EM-DARK-01** | Declare a `color-scheme` signal: a `<meta name="color-scheme" content="light dark">` and/or a `color-scheme: light dark;` CSS declaration and/or a `@media (prefers-color-scheme: dark)` block. At least one MUST be present. *(Statically checkable: total absence of any color-scheme signal is flagged. A `<meta name="color-scheme">` alone satisfies it — decoupled from any `<style>` block.)* |
118
+ | EM-DARK-02 | Pair `color-scheme` with `supported-color-schemes` (meta) for Apple Mail / Outlook. |
119
+ | EM-DARK-03 | Beware forced color inversion: set explicit `background-color` AND `color` on text containers so an inverting client cannot produce unreadable low-contrast pairs. |
120
+ | EM-DARK-04 | Provide dark-mode-friendly logos (transparent PNG or a `prefers-color-scheme` image swap) so a dark background does not hide a dark logo. |
121
+
122
+ ---
123
+
124
+ ## 6. Images & accessibility
125
+
126
+ Images are often blocked by default and must degrade gracefully; accessibility rules
127
+ prevent broken layouts and unreadable content. *(Guidance — not all statically
128
+ asserted; see §8.)*
129
+
130
+ | Rule-id | Constraint |
131
+ | --- | --- |
132
+ | EM-IMG-01 | Every `<img>` carries explicit `width` and `height` attributes (Outlook needs them; prevents reflow when images load). |
133
+ | EM-IMG-02 | Every `<img>` carries meaningful `alt` text (shown when images are blocked) and `display:block` to avoid the baseline gap below images. |
134
+ | EM-IMG-03 | Serve retina images at 2× and constrain with `width`/`height` so they render crisp without breaking the layout. |
135
+ | EM-IMG-04 | Buttons are **bulletproof** (table/`<a>` with padding + VML fallback for Outlook), never an image-only CTA that vanishes when images are blocked. |
136
+ | EM-A11Y-01 | Set the document `lang`, a real `<title>`, and a logical heading/reading order; ensure WCAG-AA text contrast that survives dark-mode inversion. |
137
+
138
+ ---
139
+
140
+ ## 7. Top-20 client quirks
141
+
142
+ The top-20-by-market-share email clients and their headline quirks. *(Catalogue —
143
+ most are render-tested via the optional Litmus / Email-on-Acid connection (34.2-02),
144
+ NOT statically checkable; see §8.)*
145
+
146
+ | Rule-id | Client | Headline quirk |
147
+ | --- | --- | --- |
148
+ | EM-CLIENT-01 | Apple Mail (iOS) | Most standards-compliant; honors `<style>` + media queries; auto-scales text — set `meta viewport`. |
149
+ | EM-CLIENT-02 | Apple Mail (macOS) | WebKit-based, robust; respects `prefers-color-scheme`; watch auto-link of dates/addresses. |
150
+ | EM-CLIENT-03 | Gmail (web) | **Strips `<head>` styles** beyond a limited `<style>`; clips messages over ~102KB ("[Message clipped]"); requires inline styles. |
151
+ | EM-CLIENT-04 | Gmail (mobile app, default account) | Supports a `<style>` block + media queries; same ~102KB clip; no `:hover` reliability. |
152
+ | EM-CLIENT-05 | Gmail (mobile, non-default / IMAP "GANGA") | **Strips `<style>` entirely** — only inline styles survive; the strictest Gmail mode. |
153
+ | EM-CLIENT-06 | Outlook 2016–2021 (Windows) | **Word engine**: no flexbox/grid/`position`, ignores `max-width`/`border-radius`/CSS `background-image`; needs ghost tables + VML + `mso-line-height-rule:exactly`. |
154
+ | EM-CLIENT-07 | Outlook (Microsoft 365, Windows) | Word engine like 2016/2019; DPI scaling bugs — set explicit image `width`/`height`. |
155
+ | EM-CLIENT-08 | Outlook.com (web) | Different (better) engine than desktop; **aggressive dark-mode color inversion**; rewrites some colors. |
156
+ | EM-CLIENT-09 | Outlook (macOS) | WebKit-based (unlike Windows); far more capable; still test buttons/spacing. |
157
+ | EM-CLIENT-10 | Outlook (mobile, iOS/Android) | Largely fine; respects media queries; watch link color overrides. |
158
+ | EM-CLIENT-11 | Yahoo Mail | Supports media queries; strips/rewrites some `class`/`id`; avoid unsupported pseudo-classes. |
159
+ | EM-CLIENT-12 | AOL Mail | Shares Yahoo's engine; similar class/id handling. |
160
+ | EM-CLIENT-13 | Samsung Mail (Android) | Webview-based; media-query support varies by version; test stacking. |
161
+ | EM-CLIENT-14 | Android default / Gmail-for-non-Gmail | Inline-only safe mode; treat like the strict Gmail IMAP mode. |
162
+ | EM-CLIENT-15 | Thunderbird | Gecko-based, capable; honors most CSS; test dark theme. |
163
+ | EM-CLIENT-16 | Windows 10/11 Mail | EdgeHTML/Chromium-ish; generally capable; watch padding. |
164
+ | EM-CLIENT-17 | Proton Mail | Sanitizes aggressively; strips remote content until approved; inline-safe. |
165
+ | EM-CLIENT-18 | Fastmail | Standards-friendly; respects media queries and dark mode. |
166
+ | EM-CLIENT-19 | Zoho Mail | Reasonable CSS support; test buttons + dark mode. |
167
+ | EM-CLIENT-20 | GMX / Web.de | EU webmail; limited CSS; lean on inline styles + tables. |
168
+
169
+ ---
170
+
171
+ ## 8. Statically-checkable vs render-tested
172
+
173
+ This table is the **contract** the validator's `rule` ids map to. The four rule-ids
174
+ below are the deterministic subset that `scripts/lib/email/validate-email-html.cjs`
175
+ asserts via regex/string analysis of the supplied HTML string. Every other rule-id in
176
+ this catalogue is **render-tested guidance** — verified by the optional Litmus /
177
+ Email-on-Acid render-test connection (34.2-02), never asserted by the static validator.
178
+
179
+ | Rule-id | Check | Statically checked by the validator? | How verified otherwise |
180
+ | --- | --- | --- | --- |
181
+ | **EM-LAYOUT-01** | No `display:flex` / `display:grid` / `position:absolute\|fixed` in any `style` | **YES** — presence flagged | — |
182
+ | **EM-STYLE-01** | No `<style>` block as the PRIMARY styling mechanism (non-`@media` rules / large sheet); inline styling expected | **YES** — a non-trivial `<style>` block flagged; a small `@media`-only block tolerated | — |
183
+ | **EM-MSO-01** | An MSO conditional comment (`<!--[if mso]>` / `<!--[if !mso]>`) is present in a full-email document | **YES** — absence flagged | — |
184
+ | **EM-DARK-01** | A `color-scheme` signal is present (meta `color-scheme` and/or CSS `color-scheme` and/or `prefers-color-scheme`) | **YES** — total absence flagged (a `<meta name="color-scheme">` alone satisfies it) | — |
185
+ | EM-LAYOUT-02..05 | ~600px width, cellpadding, single-column, explicit widths | No | Render test (Litmus) |
186
+ | EM-STYLE-02..04 | Longhand props, no external CSS, tolerated `@media` | No | Render test |
187
+ | EM-MSO-02..04 | Ghost tables, VML, DPI/namespace head | No | Render test (Outlook) |
188
+ | EM-DARK-02..04 | `supported-color-schemes`, explicit bg+color, dark logos | No | Render test (dark mode) |
189
+ | EM-IMG-01..04, EM-A11Y-01 | width/height, alt, display:block, bulletproof buttons, lang/contrast | No | Render test + a11y review |
190
+ | EM-CLIENT-01..20 | Per-client headline quirks | No | Render test (cross-client screenshots) |
191
+
192
+ Notes on the four statically-checked rules:
193
+
194
+ - **EM-STYLE-01 heuristic (deterministic).** A `<style>…</style>` block is flagged as the
195
+ primary styling mechanism when, after removing `@media { … }` groups and `@font-face`
196
+ from the block, residual CSS rules remain **or** the block exceeds a generous size
197
+ threshold. A small dark-mode/responsive `@media`-only block (per EM-STYLE-04) is
198
+ therefore tolerated and does NOT trip the check.
199
+ - **EM-DARK-01 is decoupled from `<style>`.** A `<meta name="color-scheme" content="…">`
200
+ in `<head>` satisfies EM-DARK-01 on its own, so a fully-inline email with only a
201
+ color-scheme meta (and no `<style>` at all) passes both EM-STYLE-01 and EM-DARK-01.
202
+ - **EM-MSO-01 fires only on a full email.** A document is treated as a full email when it
203
+ has `<html>`/`<body>` or a layout `<table>`; a bare fragment is not flagged for a
204
+ missing MSO comment.
205
+
206
+ ---
207
+
208
+ ## 9. Cross-references
209
+
210
+ - [`reference/platforms.md`](./platforms.md) — the interaction-conventions sibling
211
+ (navigation, safe areas, gestures). The email-executor reads **this** file for the
212
+ email constraints, that file for general platform behavior.
213
+ - [`scripts/lib/email/validate-email-html.cjs`](../scripts/lib/email/validate-email-html.cjs)
214
+ — the deterministic static validator that asserts the §8 subset; its `rule` ids are
215
+ the constraint-ids defined here.
216
+ - [`reference/registry.json`](./registry.json) — this catalogue is registered as the
217
+ `email-design` entry (type `heuristic`, phase `34.2`) so the registry round-trip test
218
+ (`test/suite/reference-registry.test.cjs`) stays green (D-05, the 33.5-01 / 34.1-01
219
+ lesson).