From e97a41cc2e1dfbc845408ec9e17d56f3827ee51a Mon Sep 17 00:00:00 2001 From: Ivo Date: Thu, 15 Oct 2026 11:21:28 +0200 Subject: [PATCH 1/6] feat(preview): add @unlayer/elements-preview, a local preview for Elements templates `elements-preview dev` previews a folder of templates in the browser with live reload, rendering them with the project's own Elements and React (Vite SSR with the project as root, so its imports, aliases and React resolve as in the app). The UI shows desktop and phone widths, dark mode as Gmail and Apple Mail apply it, the code, an Editor view that opens the design in the Unlayer editor, and a checks drawer: render problems, email-client support with source lines (caniemail), and links and images. Commands: dev, export (HTML, plain text, design JSON), build and start (a static preview to share), check (--json, exit codes for agents and CI), screenshot (desktop, phone, dark PNGs with the system browser) and render (--json). Copy for AI turns a template and its problems into a prompt. Binds to localhost and checks Host and Origin. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) --- CLAUDE.md | 1 + README.md | 3 + packages/preview/.gitignore | 1 + packages/preview/README.md | 317 ++++ packages/preview/THIRD_PARTY_NOTICES.md | 33 + packages/preview/package.json | 73 + packages/preview/shared/darkMode.ts | 223 +++ packages/preview/shared/findings.ts | 198 ++ packages/preview/src/bin.ts | 18 + packages/preview/src/cli.ts | 447 +++++ packages/preview/src/index.ts | 25 + packages/preview/src/server/browser.ts | 106 ++ packages/preview/src/server/build.ts | 152 ++ packages/preview/src/server/check.ts | 197 ++ packages/preview/src/server/checks.ts | 43 + packages/preview/src/server/compat.ts | 279 +++ packages/preview/src/server/dev-server.ts | 200 ++ packages/preview/src/server/discover.ts | 119 ++ packages/preview/src/server/errors.ts | 124 ++ packages/preview/src/server/export.ts | 102 + packages/preview/src/server/http.ts | 117 ++ packages/preview/src/server/links.ts | 268 +++ packages/preview/src/server/loader.ts | 307 +++ packages/preview/src/server/paths.ts | 7 + packages/preview/src/server/project.ts | 88 + packages/preview/src/server/render.ts | 116 ++ packages/preview/src/server/screenshot.ts | 278 +++ packages/preview/test/build.test.ts | 99 + packages/preview/test/check.test.ts | 105 ++ packages/preview/test/dev-server.test.ts | 211 +++ packages/preview/test/export.test.ts | 57 + .../basic/emails/_components/footer.tsx | 5 + .../fixtures/basic/emails/billing/receipt.jsx | 16 + .../test/fixtures/basic/emails/helpers.ts | 1 + .../test/fixtures/basic/emails/landing.tsx | 13 + .../test/fixtures/basic/emails/lib/format.ts | 1 + .../test/fixtures/basic/emails/plain.js | 9 + .../fixtures/basic/emails/static/logo.png | 2 + .../test/fixtures/basic/emails/welcome.tsx | 25 + .../preview/test/fixtures/basic/package.json | 1 + .../preview/test/fixtures/basic/tsconfig.json | 1 + packages/preview/test/helpers.ts | 17 + packages/preview/test/links.test.ts | 113 ++ packages/preview/test/render.test.ts | 144 ++ packages/preview/test/screenshot.test.ts | 109 ++ packages/preview/test/unit.test.ts | 411 ++++ packages/preview/tsconfig.json | 10 + packages/preview/tsup.config.ts | 17 + packages/preview/ui/App.tsx | 239 +++ packages/preview/ui/api.ts | 135 ++ packages/preview/ui/components/CodeView.tsx | 91 + packages/preview/ui/components/EditorView.tsx | 63 + packages/preview/ui/components/ErrorView.tsx | 21 + .../preview/ui/components/NoTemplates.tsx | 40 + .../preview/ui/components/PreviewFrame.tsx | 100 + .../preview/ui/components/PropsEditor.tsx | 90 + packages/preview/ui/components/Sidebar.tsx | 135 ++ packages/preview/ui/components/Tip.tsx | 24 + packages/preview/ui/components/aiPrompt.ts | 63 + .../ui/components/checks/ChecksDrawer.tsx | 42 + .../ui/components/checks/ClientChecks.tsx | 124 ++ .../ui/components/checks/LinkChecks.tsx | 91 + .../ui/components/checks/RenderingChecks.tsx | 33 + .../preview/ui/components/checks/Section.tsx | 42 + .../preview/ui/components/checks/summary.ts | 56 + packages/preview/ui/components/files.ts | 34 + packages/preview/ui/components/highlight.ts | 29 + packages/preview/ui/components/icons.tsx | 151 ++ .../ui/components/topbar/ChecksButton.tsx | 18 + .../ui/components/topbar/DarkToggle.tsx | 67 + .../ui/components/topbar/DeviceToggle.tsx | 25 + .../ui/components/topbar/ExportMenu.tsx | 93 + .../ui/components/topbar/TemplateTitle.tsx | 58 + .../preview/ui/components/topbar/TopBar.tsx | 50 + .../ui/components/topbar/ViewSwitch.tsx | 27 + packages/preview/ui/editor/prepareDesign.ts | 44 + packages/preview/ui/editor/staticUrls.ts | 58 + packages/preview/ui/editor/unlayerEmbed.ts | 54 + .../preview/ui/editor/useUnlayerEditor.ts | 114 ++ packages/preview/ui/hooks/useCheck.ts | 71 + packages/preview/ui/hooks/useDismiss.ts | 26 + packages/preview/ui/hooks/useLiveChanges.ts | 38 + .../preview/ui/hooks/useSelectedTemplate.ts | 35 + packages/preview/ui/hooks/useShortcuts.ts | 26 + packages/preview/ui/hooks/useStoredState.ts | 32 + packages/preview/ui/index.html | 13 + packages/preview/ui/main.tsx | 10 + packages/preview/ui/styles.css | 1646 +++++++++++++++++ packages/preview/vite.ui.config.ts | 16 + packages/preview/vitest.config.ts | 10 + pnpm-lock.yaml | 1179 +++++++++++- 91 files changed, 10434 insertions(+), 88 deletions(-) create mode 100644 packages/preview/.gitignore create mode 100644 packages/preview/README.md create mode 100644 packages/preview/THIRD_PARTY_NOTICES.md create mode 100644 packages/preview/package.json create mode 100644 packages/preview/shared/darkMode.ts create mode 100644 packages/preview/shared/findings.ts create mode 100644 packages/preview/src/bin.ts create mode 100644 packages/preview/src/cli.ts create mode 100644 packages/preview/src/index.ts create mode 100644 packages/preview/src/server/browser.ts create mode 100644 packages/preview/src/server/build.ts create mode 100644 packages/preview/src/server/check.ts create mode 100644 packages/preview/src/server/checks.ts create mode 100644 packages/preview/src/server/compat.ts create mode 100644 packages/preview/src/server/dev-server.ts create mode 100644 packages/preview/src/server/discover.ts create mode 100644 packages/preview/src/server/errors.ts create mode 100644 packages/preview/src/server/export.ts create mode 100644 packages/preview/src/server/http.ts create mode 100644 packages/preview/src/server/links.ts create mode 100644 packages/preview/src/server/loader.ts create mode 100644 packages/preview/src/server/paths.ts create mode 100644 packages/preview/src/server/project.ts create mode 100644 packages/preview/src/server/render.ts create mode 100644 packages/preview/src/server/screenshot.ts create mode 100644 packages/preview/test/build.test.ts create mode 100644 packages/preview/test/check.test.ts create mode 100644 packages/preview/test/dev-server.test.ts create mode 100644 packages/preview/test/export.test.ts create mode 100644 packages/preview/test/fixtures/basic/emails/_components/footer.tsx create mode 100644 packages/preview/test/fixtures/basic/emails/billing/receipt.jsx create mode 100644 packages/preview/test/fixtures/basic/emails/helpers.ts create mode 100644 packages/preview/test/fixtures/basic/emails/landing.tsx create mode 100644 packages/preview/test/fixtures/basic/emails/lib/format.ts create mode 100644 packages/preview/test/fixtures/basic/emails/plain.js create mode 100644 packages/preview/test/fixtures/basic/emails/static/logo.png create mode 100644 packages/preview/test/fixtures/basic/emails/welcome.tsx create mode 100644 packages/preview/test/fixtures/basic/package.json create mode 100644 packages/preview/test/fixtures/basic/tsconfig.json create mode 100644 packages/preview/test/helpers.ts create mode 100644 packages/preview/test/links.test.ts create mode 100644 packages/preview/test/render.test.ts create mode 100644 packages/preview/test/screenshot.test.ts create mode 100644 packages/preview/test/unit.test.ts create mode 100644 packages/preview/tsconfig.json create mode 100644 packages/preview/tsup.config.ts create mode 100644 packages/preview/ui/App.tsx create mode 100644 packages/preview/ui/api.ts create mode 100644 packages/preview/ui/components/CodeView.tsx create mode 100644 packages/preview/ui/components/EditorView.tsx create mode 100644 packages/preview/ui/components/ErrorView.tsx create mode 100644 packages/preview/ui/components/NoTemplates.tsx create mode 100644 packages/preview/ui/components/PreviewFrame.tsx create mode 100644 packages/preview/ui/components/PropsEditor.tsx create mode 100644 packages/preview/ui/components/Sidebar.tsx create mode 100644 packages/preview/ui/components/Tip.tsx create mode 100644 packages/preview/ui/components/aiPrompt.ts create mode 100644 packages/preview/ui/components/checks/ChecksDrawer.tsx create mode 100644 packages/preview/ui/components/checks/ClientChecks.tsx create mode 100644 packages/preview/ui/components/checks/LinkChecks.tsx create mode 100644 packages/preview/ui/components/checks/RenderingChecks.tsx create mode 100644 packages/preview/ui/components/checks/Section.tsx create mode 100644 packages/preview/ui/components/checks/summary.ts create mode 100644 packages/preview/ui/components/files.ts create mode 100644 packages/preview/ui/components/highlight.ts create mode 100644 packages/preview/ui/components/icons.tsx create mode 100644 packages/preview/ui/components/topbar/ChecksButton.tsx create mode 100644 packages/preview/ui/components/topbar/DarkToggle.tsx create mode 100644 packages/preview/ui/components/topbar/DeviceToggle.tsx create mode 100644 packages/preview/ui/components/topbar/ExportMenu.tsx create mode 100644 packages/preview/ui/components/topbar/TemplateTitle.tsx create mode 100644 packages/preview/ui/components/topbar/TopBar.tsx create mode 100644 packages/preview/ui/components/topbar/ViewSwitch.tsx create mode 100644 packages/preview/ui/editor/prepareDesign.ts create mode 100644 packages/preview/ui/editor/staticUrls.ts create mode 100644 packages/preview/ui/editor/unlayerEmbed.ts create mode 100644 packages/preview/ui/editor/useUnlayerEditor.ts create mode 100644 packages/preview/ui/hooks/useCheck.ts create mode 100644 packages/preview/ui/hooks/useDismiss.ts create mode 100644 packages/preview/ui/hooks/useLiveChanges.ts create mode 100644 packages/preview/ui/hooks/useSelectedTemplate.ts create mode 100644 packages/preview/ui/hooks/useShortcuts.ts create mode 100644 packages/preview/ui/hooks/useStoredState.ts create mode 100644 packages/preview/ui/index.html create mode 100644 packages/preview/ui/main.tsx create mode 100644 packages/preview/ui/styles.css create mode 100644 packages/preview/vite.ui.config.ts create mode 100644 packages/preview/vitest.config.ts diff --git a/CLAUDE.md b/CLAUDE.md index 5d6d7ade..bae904e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,6 +31,7 @@ cd packages/react && pnpm storybook # Storybook dev server | `@unlayer/from-react-email` | `packages/from-react-email` | No (private, bundled into migrate; API at `@unlayer/migrate/react-email`) | React Email → Elements converter (codemod, runtime with merge tags, check against the original); fidelity benchmark in `bench/`, results in `FIDELITY.md` | | `@unlayer/migrate` | `packages/migrate` | Yes (npm) | React Email converter API at `@unlayer/migrate/react-email`; `npx @unlayer/migrate` CLI: migrate React Email templates, check them, write a report; `compare` for templates migrated by hand | | `@unlayer/convert-core` | `packages/convert-core` | No (private, bundled into the converters) | Elements tree, conversion report, TSX printer and content check that converters share | +| `@unlayer/elements-preview` | `packages/preview` | Yes (npm) | `elements-preview dev` / `export` / `build` / `start` / `check` / `screenshot` (the Unlayer CLI's `unlayer elements …`): a Vite SSR loader renders the project's templates with the project's own Elements and React; preview app in `ui/` (built to `dist/ui`, also served as a static build); caniemail compatibility check that separates the template's CSS from Elements' own; link and image checks; emulated dark mode. `shared/` holds what the app and the server both use: the findings' levels (`check` and the Checks drawer) and the dark mode (`screenshot --dark` loads `dist/dark-mode.js` into the page). `screenshot` drives an installed Chrome/Chromium/Edge/Brave with `puppeteer-core` | ### Component Hierarchy (strict) diff --git a/README.md b/README.md index 03adc8df..9811291d 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ const html = renderToHtml(); **Try it in your browser:** the [Elements Playground](https://examples.unlayer.com/examples/elements-playground/) renders Elements code as an email, page, or document with nothing to install. Write plain JSX, export the HTML or design JSON, and open the result in the Unlayer builder. +**Preview your templates while you edit them:** [`@unlayer/elements-preview`](./packages/preview) lists the templates in your `emails` folder, renders them with your project's Elements as you save, and checks them against email clients. `npm install -D @unlayer/elements-preview && npx elements-preview dev`, or `unlayer elements dev` with the [Unlayer CLI](https://github.com/unlayer/cli). + ## Email Client Compatibility Notes ### Targeted Clients @@ -267,6 +269,7 @@ For the full API reference, component props, design patterns, and common mistake |---------|-------------|-----------| | [`@unlayer/react-elements`](./packages/react) | React components and renderers | [![npm](https://img.shields.io/npm/v/@unlayer/react-elements.svg)](https://www.npmjs.com/package/@unlayer/react-elements) | | [`@unlayer/migrate`](./packages/migrate) | Migrate React Email templates to Elements, checked against the originals | Not yet | +| [`@unlayer/elements-preview`](./packages/preview) | Preview templates in the browser as you edit them, check email client support, export to HTML | Not yet | | [`@unlayer/from-react-email`](./packages/from-react-email) | The React Email → Elements converter, as a library | Private (bundled into `@unlayer/migrate`, API at `@unlayer/migrate/react-email`) | | [`@unlayer/convert-core`](./packages/convert-core) | Shared model for converters (Elements tree, report, TSX printer, layout check) | Internal | | [`@unlayer-internal/shared-elements`](./packages/shared) | Framework-agnostic shared logic | Internal | diff --git a/packages/preview/.gitignore b/packages/preview/.gitignore new file mode 100644 index 00000000..acf3bb67 --- /dev/null +++ b/packages/preview/.gitignore @@ -0,0 +1 @@ +test/.tmp/ diff --git a/packages/preview/README.md b/packages/preview/README.md new file mode 100644 index 00000000..0a4e7bca --- /dev/null +++ b/packages/preview/README.md @@ -0,0 +1,317 @@ +# @unlayer/elements-preview + +Preview your [Unlayer Elements](https://github.com/unlayer/elements) templates in the browser while you edit them, check them against email clients (in the browser, or from a terminal, CI or a coding agent), take screenshots, and export them to HTML. + +```bash +npm install -D @unlayer/elements-preview +npx elements-preview dev +``` + +Open http://localhost:3000. Every template in `./emails` is listed, rendered with your project's own Elements and React, and updated as you save. + +With the [Unlayer CLI](https://github.com/unlayer/cli), the same commands are `unlayer elements dev`, `check`, `screenshot`, `export`, `build` and `start`, and `unlayer elements init` sets a project up with starter templates and an `AGENTS.md` section for coding agents. `unlayer elements push` uses `render` to create and update the templates in an Unlayer project. + +## Templates + +A template is a `.tsx`, `.jsx` or `.js` file with an `export default` component (or element). These are the same rules as React Email's preview, so an `emails` folder moved from it works as it is: + +- Folders starting with `_` aren't listed: put shared components in `_components/`. +- Files in `static/` are served at `/static/`: ``. Host them elsewhere for the emails you send. +- `PreviewProps` on the component are the props it previews and exports with: + +```tsx +import { Email, Row, Column, Heading, Button } from "@unlayer/react-elements"; + +export default function Welcome({ name }: { name: string }) { + return ( + + + + Welcome, {name} + + + + + ); +} + +Welcome.PreviewProps = { name: "Ada" }; +``` + +TypeScript, JSX, your `tsconfig.json` path aliases (`@/components/...`) and imports of your other files all work as in your project. + +## The preview + +The top bar has the template's name (its render time and whether it's live are in the tooltip), the views in the middle, and on the right the preview's width and dark mode, **Checks** and **Export**. + +- **Preview**: the template as an email, page or document, at desktop or phone width. Drag the frame's edges for any width (it shows the width as you drag); double-click an edge to go back. +- **Dark mode** (the sun and moon): what email clients do to an email's colors for readers in dark mode. By default every color flips, as in the Gmail apps and Outlook.com; the arrow next to it switches to light colors only, as in Apple Mail and Outlook on phones. It catches unreadable text and logos that vanish. +- **Editor**: the template's design JSON in the [Unlayer editor](https://unlayer.com), to check how it opens there. Each render loads it again; changes made in the editor aren't saved to your file. The editor comes from `editor.unlayer.com`, so this view needs an internet connection (the others don't). Images from `static/` are copied into the design it gets, since the editor's page can't load them from localhost. It opens without an Unlayer project; pass `--project-id` (or set `UNLAYER_PROJECT_ID`) to open it in yours, with your project's settings. +- **Code**: the template's source, the HTML, the plain text and the design JSON, highlighted, to copy or download. +- **Checks**: the button says "All clear", or how many things there are to fix (something broken) or to check, in the color of the worst. It opens a drawer with three sections: + - **Rendering**: what Elements and React report while rendering (a column too many, a block left out), and a design JSON (the template then won't open in the Unlayer editor) or plain text that can't be made. A template that doesn't compile or throws shows its error, with the file, line and code, in place of the preview; the preview keeps running and recovers when you fix the file. + - **Email clients**: the HTML and CSS the email uses that Gmail, Outlook, Apple Mail, Yahoo Mail, Samsung Email or Thunderbird don't support, from [caniemail.com](https://www.caniemail.com), with the block it's on and the template lines it likely comes from. Elements' own email HTML uses some CSS on purpose, with fallbacks for the clients that ignore it; those are listed apart, as handled by Elements. + - **Links & images**: links that go nowhere, relative or `http:` URLs, links to localhost, images without alt text, SVG and `data:` images most clients block, `/static/` images to host before sending, and HTML over Gmail's 102 KB clipping limit. Each URL is requested, so broken links (404), hosts that don't exist, non-images and large images show too. +- **Props** (the drawer's other tab): render with other props than `PreviewProps`, without changing the file. +- **Export**: download the HTML, plain text or design JSON (it opens in the Unlayer builder with `loadDesign()`), copy the HTML, or **Copy for AI**: the template, its problems, compatibility and link findings as one prompt to paste into an AI assistant. + +`elements-preview check` runs the same checks from a terminal, and `elements-preview screenshot --dark` takes the same dark mode. + +Keyboard shortcuts (also in the tooltips): / filters the templates, 1 2 3 switch to Preview, Editor and Code, D switches between desktop and phone width, T turns dark mode on and off, and . opens and closes the checks. + +The preview follows your system's light or dark appearance. + +## Commands + +### `elements-preview dev` + +| Option | Default | | +| --- | --- | --- | +| `--dir ` | `emails` | The templates folder | +| `--port ` | `3000` | The port. When it's taken, the next free one is used, unless you set it. | +| `--open` | | Open the preview in your browser | +| `--project-id ` | `UNLAYER_PROJECT_ID`, or none | The Unlayer project the Editor view opens designs in | + +The server listens on localhost only and answers only requests addressed to localhost. + +### `elements-preview export` + +Renders every template with its `PreviewProps` to `out/.html` (`emails/billing/receipt.tsx` → `out/billing/receipt.html`), and copies `static/`. Existing files with the same names are replaced; nothing else in the folder is deleted. + +| Option | Default | | +| --- | --- | --- | +| `--dir ` | `emails` | The templates folder | +| `--outDir ` | `out` | Where to write the files | +| `--pretty` | | Format the HTML for reading | +| `--plainText` | | Write plain text instead of HTML | +| `--design` | | Also write each template's design JSON (`.design.json`) for the Unlayer builder | +| `--extension ` | `html` | The file extension, e.g. `blade.php` (`txt` with `--plainText`) | + +It exits with 1 when a template fails, after exporting the others. Export is for backends that can't run JavaScript, or platforms that only take HTML; when your code can call Elements, render at send time with the real data instead (`renderToHtml`). + +### `elements-preview check` + +Renders templates with their `PreviewProps` and checks them as the preview's **Checks** do, without a browser: for a terminal, a CI job or a coding agent that just edited a template. + +```bash +npx elements-preview check # every template +npx elements-preview check welcome.tsx --json # one, as JSON +``` + +For each template it reports render errors (with the file and line), what Elements and React warn about, a design JSON that can't be made (the template won't open in the Unlayer editor) or plain text that can't, the CSS and HTML the template brings in that email clients don't support (with the template lines it likely comes from), links and images, and HTML over Gmail's clipping size. The levels are the preview's: **to fix** is something broken (a render error, a block Elements left out, no design JSON, a link that goes nowhere or answers 404, a `javascript:` or relative URL); **to check** is worth a look (warnings, compatibility, placeholder or `http:` URLs, Gmail clipping); notes are for information. + +``` +✗ showcase/needs-fixes.tsx (email, 10 KB): 3 to fix, 5 to check, 1 note + fix Paragraph 1 · “terms” → # · This link goes nowhere: give it a URL. + fix Button 1 · “Click me” → javascript:alert(1) · Email clients don't run javascript: links. + check Html 1 · line 13 · `display:flex` isn't supported in Outlook (2 clients) and only partly in Gmail (1 client): remove it or add a fallback. +✓ receipt.tsx (email, 16 KB): 1 note + +Checked 2 templates: 3 to fix, 5 to check. (2 notes: --json lists them) +``` + +Name templates by their path in the templates folder (`welcome.tsx`) or from the working directory (`emails/welcome.tsx`), with or without the extension, or name a folder of them (`billing`). With none, it checks every template. + +| Option | Default | | +| --- | --- | --- | +| `--dir ` | `emails` | The templates folder | +| `--json` | | Print the report as JSON (below) | +| `--strict` | | Fail on what's to check too, not only on what's to fix | +| `--offline` | | Don't request the links' and images' URLs; check the HTML only | + +| Exit code | | +| --- | --- | +| `0` | Nothing to fix (with `--strict`, nothing to check either) | +| `1` | Something to fix (with `--strict`, or to check) | +| `2` | The check couldn't run: an option, folder or template that isn't right, or Elements not installed. With `--json`, stderr has `{"error": "…"}` | + +#### The JSON report + +```json +{ + "ok": false, + "strict": false, + "network": true, + "templates": [ + { + "path": "showcase/needs-fixes.tsx", + "file": "emails/showcase/needs-fixes.tsx", + "ok": false, + "mode": "email", + "bytes": 10342, + "toFix": [ + { + "kind": "link", + "level": "error", + "message": "Email clients don't run javascript: links.", + "block": "Button 1", + "url": "javascript:alert(1)", + "label": "Click me" + } + ], + "toCheck": [ + { + "kind": "client", + "level": "warning", + "message": "`display:flex` isn't supported in Outlook (2 clients) and only partly in Gmail (1 client): remove it or add a fallback.", + "block": "Html 1", + "line": 13, + "lines": [13], + "url": "https://www.caniemail.com/features/css-display-flex/", + "feature": "display:flex", + "support": "none", + "clients": [{ "client": "outlook.windows", "label": "Outlook (Windows)", "support": "none" }] + } + ], + "notes": [] + } + ], + "summary": { "templates": 1, "toFix": 3, "toCheck": 5, "notes": 1 } +} +``` + +- `ok`: what the exit code says (`true` is 0). Each template's `ok` is the same for it. +- `templates`: worst first. `path` is in the templates folder, `file` from the working directory. `mode` is `email`, `web` (a `Page`) or `document`, and `bytes` the size of the HTML; both are `null` when the template doesn't render. +- `toFix`, `toCheck` and `notes` hold findings of `level` `error`, `warning` and `info`. Each has a `kind` and a `message`, and when they apply: + +| Field | | +| --- | --- | +| `kind` | `render` (doesn't compile or throws, or Elements left a block out), `warning` (from Elements or React), `log` (other console output), `design` (no design JSON), `text` (no plain text), `client` (email client compatibility), `link`, `image`, `size` (Gmail clipping) | +| `block` | The Elements block: `Button 1`, `Html 2` (a `client` finding lists every block, comma-separated) | +| `file`, `line`, `column`, `frame` | A `render` error's file (from the working directory), position and surrounding code | +| `line`, `lines` | A `client` finding's first template line, and every line that likely brings it in | +| `url`, `label`, `status` | A link or image: its URL, its text or alt text, and the HTTP status it answered with. For a `client` finding, `url` is its caniemail.com page | +| `feature`, `support`, `clients` | A `client` finding: caniemail's name for it, `none` or `partial`, and each client with its support | + +New fields may be added; existing ones keep their meaning. + +### `elements-preview screenshot` + +Saves full-length PNGs of templates rendered with their `PreviewProps`, to look at the layout without opening the preview: `screenshots/