Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
# renderer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The renderer is the bridge between the `EmailTemplate<TProps>` contract and
a final `{ html, text, subject }` triple ready to hand to an SMTP / SES / SendGrid
client. It calls `template.component(merged_props)` to obtain a `VNode`,
walks the tree through `renderVNode`, wraps the output in a full
`<!doctype html>` document with the head boilerplate every email client
expects, and (by default) derives a plain-text fallback from the HTML body
via tag-stripping.
Every render emits OTel telemetry — `email_templates.render_total` counter
tagged by `template` and `outcome`, plus a duration histogram. Errors raised
by the component are caught and re-thrown as `EmailTemplatesError` with the
`RENDER_FAILURE` code unless they were already a typed error from this
package (those propagate unchanged).
## Features
- `renderToHtml(template, props, options?)` → `{ html, text, subject }`.
- `wrapInDocument(body, options?)` — applies the doctype + head boilerplate.
- `resolveSubject(template, props)` — pure helper for subject derivation.
- `escapeHtml(input)` / `stripHtml(html)` — public utilities reused by
upstream callers.
- Injectable `theme` and `Clock` via render options.
- Optional `minify: true` collapses whitespace between tags.
- Optional `plainText: false` skips text-body generation.
- MSO conditional comment (`<!--[if mso]>...<![endif]-->`) for Outlook
PPI normalisation lives inside `wrapInDocument`.
## Usage: Basic
```ts
import { renderToHtml } from "@suaveplan/email-templates/renderer";
import { welcomeTemplate } from "@suaveplan/email-templates/templates";
const { html, text, subject } = renderToHtml(welcomeTemplate, {
recipientName: "Jane",
ctaUrl: "https://app.example.com",
});
```
## Usage: Advanced
Inject a fake clock for deterministic footer year rendering and pre-build a
tenant-specific theme:
```ts
import { renderToHtml } from "@suaveplan/email-templates/renderer";
import { createTheme } from "@suaveplan/email-templates/theme";
import { welcomeTemplate } from "@suaveplan/email-templates/templates";
const theme = createTheme({
colors: { primary: "#ff6600" },
branding: { companyName: "Acme" },
});
const result = renderToHtml(
welcomeTemplate,
{ recipientName: "Jane", ctaUrl: "https://acme.test" },
{
theme,
clock: { now: () => 1735689600000 }, // 2025-01-01T00:00:00Z
previewText: "Welcome aboard, Jane!",
minify: true,
},
);
```
## API Reference
| Symbol | Kind | Description |
| ----------------- | -------- | ---------------------------------------------------- |
| `renderToHtml` | function | Render a template to `{ html, text, subject }`. |
| `wrapInDocument` | function | Wrap a body fragment in a full HTML document. |
| `resolveSubject` | function | Resolve `template.subject` (literal or function). |
| `escapeHtml` | function | Escape text for HTML inclusion. |
| `stripHtml` | function | Tag-strip + entity-decode for plain-text fallback. |
Types: `EmailTemplate<TProps>`, `RenderOptions`, `RenderResult`,
`HtmlDocumentOptions`.
## Implementation Notes
The renderer is **not** idempotent across `Date.now()` calls — render
duration is recorded as `Date.now() - start`. Components, however, never
read wall-clock time directly: the renderer threads an injected `Clock`
(falling back to a real-`Date.now()` clock) into the merged props bag, so
template-level determinism is preserved.
`stripHtml` is a regex pass — it does NOT round-trip nested HTML entities
or HTML5 ambiguous markup. Email body content is expected to be the
output of `renderVNode` (well-formed) and is reduced to a coarse
plain-text representation suitable for clients that fall back to the
text part. Production-grade plain-text rendering (line wrapping, link
extraction, list bullets) is the consumer's responsibility.
Errors raised inside `template.component` are caught, the `error` outcome
counter is bumped, and the original cause is preserved on the
`EmailTemplatesError`. Typed errors already raised by this package are
re-thrown without re-wrapping so their original `code` survives the
boundary intact.