@emailens/engine 0.10.4 → 0.11.1

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.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, I as ImageReport, g as InboxPreview, h as SizeReport, i as TemplateReport, O as OverflowReport, V as VisualReport, j as DeliverabilityReport } from './types-BLR3-Fzo.cjs';
2
- export { k as AccessibilityIssue, B as BaseIssue, l as ClientTruncation, m as DeliverabilityCheck, n as DeliverabilityIssue, o as EstimateOptions, p as ExportScope, q as FixType, r as ImageInfo, s as ImageIssue, t as InboxPreviewIssue, u as InputFormat, v as LinkIssue, w as OverflowIssue, P as PreviewResult, x as Severity, y as SizeIssue, z as SourceLocation, G as SpamIssue, H as TemplateIssue, J as TokenEstimate, K as TokenEstimateWithWarnings, M as VisualIssue, N as estimateAiFixTokens, Q as generateFixPrompt, R as heuristicTokenCount } from './types-BLR3-Fzo.cjs';
3
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DTzVpGgB.cjs';
1
+ import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.cjs';
2
+ export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.cjs';
3
+ import * as cheerio from 'cheerio';
4
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DuZAgJtw.cjs';
4
5
 
5
6
  declare const EMAIL_CLIENTS: EmailClient[];
6
7
  declare function getClient(id: string): EmailClient | undefined;
@@ -13,7 +14,7 @@ interface ParseOptions {
13
14
  /**
14
15
  * Record source positions so issues carry a `loc`. Costs a little parse time
15
16
  * (parse5 tracks a location record per node and per attribute), so it is
16
- * opt-in for callers that need to point at the source editors, CI
17
+ * opt-in for callers that need to point at the source: editors, CI
17
18
  * annotations, agents.
18
19
  */
19
20
  positions?: boolean;
@@ -24,7 +25,7 @@ interface ParseOptions {
24
25
  * for all target email clients.
25
26
  *
26
27
  * The `framework` parameter controls which fix snippets are attached
27
- * to warnings it does NOT change which warnings fire. Analysis always
28
+ * to warnings; it does NOT change which warnings fire. Analysis always
28
29
  * runs on compiled HTML (what email clients actually receive). Fix
29
30
  * snippets reference source-level constructs so users know how to
30
31
  * modify their framework source code.
@@ -128,7 +129,7 @@ interface GenerateAiFixOptions extends ExportPromptOptions {
128
129
  * to build a structured prompt, then delegates to an LLM for context-aware
129
130
  * structural fixes that static snippets cannot handle.
130
131
  *
131
- * The engine stays provider-agnostic consumers pass their own `AiProvider`
132
+ * The engine stays provider-agnostic: consumers pass their own `AiProvider`
132
133
  * callback (Anthropic SDK, Vercel AI SDK, OpenRouter, etc.).
133
134
  *
134
135
  * @example
@@ -166,10 +167,10 @@ declare function generateAiFix(options: GenerateAiFixOptions): Promise<AiFixResu
166
167
  * System prompt for the AI fix provider. Consumers should pass this as
167
168
  * the `system` parameter to their LLM call for best results.
168
169
  */
169
- declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer specializing in cross-client HTML email compatibility. You fix emails to render correctly across all email clients.\n\nRules:\n- Return ONLY the fixed code inside a single code fence. No explanations before or after.\n- Preserve all existing content, text, links, and visual design.\n- For structural issues (fixType: \"structural\"), you MUST restructure the HTML \u2014 CSS-only changes will not work.\n- Common structural patterns:\n - word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width\n - display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)\n - border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals\n - background-image in Outlook \u2192 use VML <v:rect> with <v:fill>\n - max-width in Outlook \u2192 wrap in <!--[if mso]><table width=\"N\"> conditional\n - position:absolute \u2192 use <table> cells for layout\n - <svg> \u2192 replace with <img> pointing to a hosted PNG\n- For CSS-only issues (fixType: \"css\"), swap properties or add fallbacks.\n- For \"Layout & Visual Issues\", apply the provided Fix directly: add the background-color fallback beneath a gradient/image, append a web-safe font to the stack, and constrain fixed widths wider than the frame to width:100% with max-width.\n- Apply ALL fixes from the issues list \u2014 do not skip any.\n- Use the framework syntax specified (JSX/MJML/Maizzle/HTML).\n- For JSX: use camelCase style props, React Email components, and proper TypeScript types.\n- For MJML: use mj-* elements and attributes.\n- For Maizzle: use Tailwind CSS classes.";
170
+ declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer specializing in cross-client HTML email compatibility. You fix emails to render correctly across all email clients.\n\nRules:\n- Return ONLY the fixed code inside a single code fence. No explanations before or after.\n- Preserve all existing content, text, links, and visual design.\n- For structural issues (fixType: \"structural\"), you MUST restructure the HTML; CSS-only changes will not work.\n- Common structural patterns:\n - word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width\n - display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)\n - border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals\n - background-image in Outlook \u2192 use VML <v:rect> with <v:fill>\n - max-width in Outlook \u2192 wrap in <!--[if mso]><table width=\"N\"> conditional\n - position:absolute \u2192 use <table> cells for layout\n - <svg> \u2192 replace with <img> pointing to a hosted PNG\n- For CSS-only issues (fixType: \"css\"), swap properties or add fallbacks.\n- For \"Layout & Visual Issues\", apply the provided Fix directly: add the background-color fallback beneath a gradient/image, append a web-safe font to the stack, and constrain fixed widths wider than the frame to width:100% with max-width.\n- Apply ALL fixes from the issues list: do not skip any.\n- Use the framework syntax specified (JSX/MJML/Maizzle/HTML).\n- For JSX: use camelCase style props, React Email components, and proper TypeScript types.\n- For MJML: use mj-* elements and attributes.\n- For Maizzle: use Tailwind CSS classes.";
170
171
 
171
172
  /**
172
- * CSS/HTML feature support matrix auto-generated from caniemail.com.
173
+ * CSS/HTML feature support matrix, auto-generated from caniemail.com.
173
174
  * Last synced: 2026-07-28
174
175
  * caniemail last updated: 2026-07-20 09:56:37 +0000
175
176
  *
@@ -186,11 +187,11 @@ declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer speciali
186
187
  * - hey-mail: caniemail.com (WebKit-based)
187
188
  * - superhuman: Manual overrides (Chromium/Blink-based, best-effort estimates)
188
189
  *
189
- * DO NOT EDIT regenerate with: bun run sync:caniemail
190
+ * DO NOT EDIT; regenerate with: bun run sync:caniemail
190
191
  */
191
192
  declare const CSS_SUPPORT: Record<string, Record<string, SupportLevel>>;
192
193
  /**
193
- * Caveat notes per (feature, client) from caniemail the "why" behind a
194
+ * Caveat notes per (feature, client) from caniemail: the "why" behind a
194
195
  * partial/buggy/unsupported rating. Sparse: only cells with a note appear.
195
196
  */
196
197
  declare const CSS_SUPPORT_NOTES: Record<string, Record<string, string[]>>;
@@ -212,10 +213,10 @@ declare const CSS_FUNCTION_FEATURES: readonly ["calc", "clamp", "conic-gradient"
212
213
  * caniemail's page for each feature, for a diagnostic's `codeDescription`.
213
214
  *
214
215
  * 250 of 255 features. The rest have no caniemail entry of
215
- * their own and get no link, which is the honest outcome a link to a 404
216
+ * their own and get no link, which is the honest outcome: a link to a 404
216
217
  * is worse than none.
217
218
  *
218
- * DO NOT EDIT regenerate with: bun run sync:feature-urls
219
+ * DO NOT EDIT; regenerate with: bun run sync:feature-urls
219
220
  */
220
221
  declare const FEATURE_URLS: Record<string, string>;
221
222
  /** Where to read about a rule, when there is somewhere. */
@@ -239,7 +240,7 @@ declare const VALUE_CAVEAT_PROPS: ReadonlySet<string>;
239
240
  * applies if either does.
240
241
  *
241
242
  * Returns true (report it) for a property that isn't value-gated, and for one
242
- * where we never saw a value an at-rule, a pseudo-class, a detected CSS
243
+ * where we never saw a value: an at-rule, a pseudo-class, a detected CSS
243
244
  * function.
244
245
  */
245
246
  declare function caveatApplies(prop: string, values: readonly string[] | undefined, notes: string[] | undefined): boolean;
@@ -267,6 +268,20 @@ declare function analyzeSpam(html: string, options?: SpamAnalysisOptions): SpamR
267
268
  */
268
269
  declare function validateLinks(html: string, options?: ParseOptions): LinkReport;
269
270
 
271
+ /**
272
+ * The render a cascade is being resolved for.
273
+ *
274
+ * An email has more than one correct answer: the same CSS produces a different
275
+ * palette on a phone and in dark mode, and a contrast bug can live in only one
276
+ * of them. Resolving against an explicit context is what lets the same
277
+ * machinery grade all three.
278
+ */
279
+ interface RenderContext {
280
+ /** Viewport width in px, for width media queries. */
281
+ width: number;
282
+ /** Whether `prefers-color-scheme: dark` matches. */
283
+ dark: boolean;
284
+ }
270
285
  /**
271
286
  * Audit an HTML email for accessibility issues.
272
287
  *
@@ -275,6 +290,43 @@ declare function validateLinks(html: string, options?: ParseOptions): LinkReport
275
290
  * color contrast. Returns a 0–100 score and detailed issues.
276
291
  */
277
292
  declare function checkAccessibility(html: string, options?: ParseOptions): AccessibilityReport;
293
+ /**
294
+ * Contrast failures inside the email's own `@media (prefers-color-scheme: dark)`
295
+ * styles.
296
+ *
297
+ * This is the other half of dark mode, and the half the author actually
298
+ * controls: Apple Mail, Superhuman, Thunderbird and friends do not invert
299
+ * anything, they just apply the dark block as written. A dark background
300
+ * paired with body text that was never re-coloured is invisible there, and no
301
+ * amount of inversion analysis finds it; the bug is in the CSS, not in what
302
+ * the client does to it.
303
+ *
304
+ * Free to run: same DOM, resolved against a dark render, so positions stay
305
+ * valid. Emails with no dark block resolve identically to the light pass and
306
+ * report nothing.
307
+ */
308
+ declare function checkDarkStylesContrastFromDom($: cheerio.CheerioAPI, lightIssues?: AccessibilityIssue[]): AccessibilityIssue[];
309
+ /**
310
+ * Contrast failures that only appear at mobile width. See
311
+ * {@link checkMobileContrastFromDom}.
312
+ */
313
+ declare function checkMobileContrast(html: string, options?: ParseOptions): AccessibilityIssue[];
314
+ /**
315
+ * Contrast failures an email has *only* in dark mode.
316
+ *
317
+ * Clients that invert colours do it selectively, so a palette that passes in
318
+ * light mode can collapse to unreadable once inverted, mid-tone text left
319
+ * alone on a background repainted near-black. This runs the ordinary contrast
320
+ * check over the simulated dark render and reports what is new there.
321
+ *
322
+ * Pass `lightIssues` when the light-mode report is already in hand (the audit
323
+ * has it) to skip re-running the light pass.
324
+ *
325
+ * ponytail: deduped by the element description, so two genuinely identical
326
+ * elements collapse into one report. Track them individually only if the
327
+ * per-element count turns out to matter.
328
+ */
329
+ declare function checkDarkModeContrast(html: string, lightIssues?: AccessibilityIssue[]): AccessibilityIssue[];
278
330
 
279
331
  /**
280
332
  * Analyze images in an HTML email for best practices.
@@ -320,6 +372,24 @@ declare function checkTemplateVariables(html: string, options?: ParseOptions): T
320
372
  */
321
373
  declare function checkOverflow(html: string, options?: ParseOptions): OverflowReport;
322
374
 
375
+ /**
376
+ * Check hand-written VML for the mistakes Outlook punishes.
377
+ *
378
+ * This is the one part of an email the DOM analyzers structurally cannot see:
379
+ * VML lives inside `<!--[if mso]>` conditional comments, so to every HTML
380
+ * parser it is a comment node, and to a headless-Chromium screenshot it does
381
+ * not exist at all. An email can therefore lint clean and preview perfectly
382
+ * while the branch Outlook actually renders is broken.
383
+ *
384
+ * ponytail: a tag-sequence checker, not a VML renderer. It validates structure
385
+ * and attribute values, which is where the reported breakages cluster. It does
386
+ * not resolve geometry, so it can say Outlook will misplace a nested shape but
387
+ * not where the shape lands.
388
+ */
389
+ declare function checkVml(html: string, options?: {
390
+ positions?: boolean;
391
+ }): VmlReport;
392
+
323
393
  /**
324
394
  * Detect probable visual bugs in a stylized email: background images/gradients
325
395
  * with no colour fallback, and font stacks with no web-safe fallback. Each
@@ -327,12 +397,18 @@ declare function checkOverflow(html: string, options?: ParseOptions): OverflowRe
327
397
  */
328
398
  declare function checkVisual(html: string, options?: ParseOptions): VisualReport;
329
399
 
400
+ /**
401
+ * Report design drift in an HTML email: near-identical colours, and properties
402
+ * carrying more distinct values than a design system would.
403
+ */
404
+ declare function checkDesignConsistency(html: string, options?: ParseOptions): DesignReport;
405
+
330
406
  interface AuditOptions extends ParseOptions {
331
407
  framework?: Framework;
332
408
  /** Options for spam analysis */
333
409
  spam?: SpamAnalysisOptions;
334
410
  /** Skip specific checks */
335
- skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual">;
411
+ skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design" | "vml">;
336
412
  }
337
413
  interface AuditReport {
338
414
  compatibility: {
@@ -353,6 +429,14 @@ interface AuditReport {
353
429
  templateVariables: TemplateReport;
354
430
  overflow: OverflowReport;
355
431
  visual: VisualReport;
432
+ /** Structural faults in Outlook-only VML, which lives inside conditional comments. */
433
+ vml: VmlReport;
434
+ /** Contrast failures present only once a client inverts colours. */
435
+ darkContrast: AccessibilityIssue[];
436
+ /** Contrast failures present only below the email's mobile breakpoint. */
437
+ mobileContrast: AccessibilityIssue[];
438
+ /** Design drift: near-identical colours, and properties with no system left. */
439
+ design: DesignReport;
356
440
  }
357
441
  /**
358
442
  * Run all email analysis checks in a single call.
@@ -453,7 +537,7 @@ interface EmailSession {
453
537
  * Simulate dark mode for a specific client.
454
538
  *
455
539
  * Creates an isolated DOM copy per call (simulation mutates the DOM).
456
- * Operates on the **original** HTML if you need dark mode on
540
+ * Operates on the **original** HTML, if you need dark mode on
457
541
  * already-transformed HTML, use the standalone `simulateDarkMode()` instead.
458
542
  */
459
543
  simulateDarkMode(clientId: string): {
@@ -466,7 +550,7 @@ interface EmailSession {
466
550
  * DOM across all read-only analysis operations.
467
551
  *
468
552
  * Use this when you need to call multiple analysis functions on the
469
- * same HTML it eliminates redundant `cheerio.load()` calls.
553
+ * same HTML: it eliminates redundant `cheerio.load()` calls.
470
554
  *
471
555
  * @example
472
556
  * ```typescript
@@ -548,6 +632,21 @@ declare function wcagGrade(ratio: number): WcagGrade;
548
632
  * Returns the flattened [r, g, b] as 0-255 values.
549
633
  */
550
634
  declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [number, number, number];
635
+ /**
636
+ * Convert sRGB to OKLab.
637
+ *
638
+ * OKLab is built so that straight-line distance between two points matches how
639
+ * different the colours look, which sRGB emphatically does not: `#333` and
640
+ * `#343434` are far apart in RGB terms and identical to a reader.
641
+ */
642
+ declare function rgbToOklab(rgba: RGBA): [number, number, number];
643
+ /**
644
+ * Perceptual distance between two colours, as OKLab delta-E.
645
+ *
646
+ * Roughly: under ~0.02 the two are the same colour to a reader, and around
647
+ * ~0.1 they are clearly different.
648
+ */
649
+ declare function colorDistance(a: RGBA, b: RGBA): number;
551
650
 
552
651
  /**
553
652
  * Downlevel modern CSS in HTML emails to maximize email client compatibility.
@@ -575,4 +674,4 @@ declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [n
575
674
  */
576
675
  declare function downlevelCSS(html: string): string;
577
676
 
578
- export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkOverflow, checkSize, checkTemplateVariables, checkVisual, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
677
+ export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, VmlReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, checkVml, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, I as ImageReport, g as InboxPreview, h as SizeReport, i as TemplateReport, O as OverflowReport, V as VisualReport, j as DeliverabilityReport } from './types-BLR3-Fzo.js';
2
- export { k as AccessibilityIssue, B as BaseIssue, l as ClientTruncation, m as DeliverabilityCheck, n as DeliverabilityIssue, o as EstimateOptions, p as ExportScope, q as FixType, r as ImageInfo, s as ImageIssue, t as InboxPreviewIssue, u as InputFormat, v as LinkIssue, w as OverflowIssue, P as PreviewResult, x as Severity, y as SizeIssue, z as SourceLocation, G as SpamIssue, H as TemplateIssue, J as TokenEstimate, K as TokenEstimateWithWarnings, M as VisualIssue, N as estimateAiFixTokens, Q as generateFixPrompt, R as heuristicTokenCount } from './types-BLR3-Fzo.js';
3
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DwBWB2kr.js';
1
+ import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.js';
2
+ export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.js';
3
+ import * as cheerio from 'cheerio';
4
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-BROBcC8u.js';
4
5
 
5
6
  declare const EMAIL_CLIENTS: EmailClient[];
6
7
  declare function getClient(id: string): EmailClient | undefined;
@@ -13,7 +14,7 @@ interface ParseOptions {
13
14
  /**
14
15
  * Record source positions so issues carry a `loc`. Costs a little parse time
15
16
  * (parse5 tracks a location record per node and per attribute), so it is
16
- * opt-in for callers that need to point at the source editors, CI
17
+ * opt-in for callers that need to point at the source: editors, CI
17
18
  * annotations, agents.
18
19
  */
19
20
  positions?: boolean;
@@ -24,7 +25,7 @@ interface ParseOptions {
24
25
  * for all target email clients.
25
26
  *
26
27
  * The `framework` parameter controls which fix snippets are attached
27
- * to warnings it does NOT change which warnings fire. Analysis always
28
+ * to warnings; it does NOT change which warnings fire. Analysis always
28
29
  * runs on compiled HTML (what email clients actually receive). Fix
29
30
  * snippets reference source-level constructs so users know how to
30
31
  * modify their framework source code.
@@ -128,7 +129,7 @@ interface GenerateAiFixOptions extends ExportPromptOptions {
128
129
  * to build a structured prompt, then delegates to an LLM for context-aware
129
130
  * structural fixes that static snippets cannot handle.
130
131
  *
131
- * The engine stays provider-agnostic consumers pass their own `AiProvider`
132
+ * The engine stays provider-agnostic: consumers pass their own `AiProvider`
132
133
  * callback (Anthropic SDK, Vercel AI SDK, OpenRouter, etc.).
133
134
  *
134
135
  * @example
@@ -166,10 +167,10 @@ declare function generateAiFix(options: GenerateAiFixOptions): Promise<AiFixResu
166
167
  * System prompt for the AI fix provider. Consumers should pass this as
167
168
  * the `system` parameter to their LLM call for best results.
168
169
  */
169
- declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer specializing in cross-client HTML email compatibility. You fix emails to render correctly across all email clients.\n\nRules:\n- Return ONLY the fixed code inside a single code fence. No explanations before or after.\n- Preserve all existing content, text, links, and visual design.\n- For structural issues (fixType: \"structural\"), you MUST restructure the HTML \u2014 CSS-only changes will not work.\n- Common structural patterns:\n - word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width\n - display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)\n - border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals\n - background-image in Outlook \u2192 use VML <v:rect> with <v:fill>\n - max-width in Outlook \u2192 wrap in <!--[if mso]><table width=\"N\"> conditional\n - position:absolute \u2192 use <table> cells for layout\n - <svg> \u2192 replace with <img> pointing to a hosted PNG\n- For CSS-only issues (fixType: \"css\"), swap properties or add fallbacks.\n- For \"Layout & Visual Issues\", apply the provided Fix directly: add the background-color fallback beneath a gradient/image, append a web-safe font to the stack, and constrain fixed widths wider than the frame to width:100% with max-width.\n- Apply ALL fixes from the issues list \u2014 do not skip any.\n- Use the framework syntax specified (JSX/MJML/Maizzle/HTML).\n- For JSX: use camelCase style props, React Email components, and proper TypeScript types.\n- For MJML: use mj-* elements and attributes.\n- For Maizzle: use Tailwind CSS classes.";
170
+ declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer specializing in cross-client HTML email compatibility. You fix emails to render correctly across all email clients.\n\nRules:\n- Return ONLY the fixed code inside a single code fence. No explanations before or after.\n- Preserve all existing content, text, links, and visual design.\n- For structural issues (fixType: \"structural\"), you MUST restructure the HTML; CSS-only changes will not work.\n- Common structural patterns:\n - word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width\n - display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)\n - border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals\n - background-image in Outlook \u2192 use VML <v:rect> with <v:fill>\n - max-width in Outlook \u2192 wrap in <!--[if mso]><table width=\"N\"> conditional\n - position:absolute \u2192 use <table> cells for layout\n - <svg> \u2192 replace with <img> pointing to a hosted PNG\n- For CSS-only issues (fixType: \"css\"), swap properties or add fallbacks.\n- For \"Layout & Visual Issues\", apply the provided Fix directly: add the background-color fallback beneath a gradient/image, append a web-safe font to the stack, and constrain fixed widths wider than the frame to width:100% with max-width.\n- Apply ALL fixes from the issues list: do not skip any.\n- Use the framework syntax specified (JSX/MJML/Maizzle/HTML).\n- For JSX: use camelCase style props, React Email components, and proper TypeScript types.\n- For MJML: use mj-* elements and attributes.\n- For Maizzle: use Tailwind CSS classes.";
170
171
 
171
172
  /**
172
- * CSS/HTML feature support matrix auto-generated from caniemail.com.
173
+ * CSS/HTML feature support matrix, auto-generated from caniemail.com.
173
174
  * Last synced: 2026-07-28
174
175
  * caniemail last updated: 2026-07-20 09:56:37 +0000
175
176
  *
@@ -186,11 +187,11 @@ declare const AI_FIX_SYSTEM_PROMPT = "You are an expert email developer speciali
186
187
  * - hey-mail: caniemail.com (WebKit-based)
187
188
  * - superhuman: Manual overrides (Chromium/Blink-based, best-effort estimates)
188
189
  *
189
- * DO NOT EDIT regenerate with: bun run sync:caniemail
190
+ * DO NOT EDIT; regenerate with: bun run sync:caniemail
190
191
  */
191
192
  declare const CSS_SUPPORT: Record<string, Record<string, SupportLevel>>;
192
193
  /**
193
- * Caveat notes per (feature, client) from caniemail the "why" behind a
194
+ * Caveat notes per (feature, client) from caniemail: the "why" behind a
194
195
  * partial/buggy/unsupported rating. Sparse: only cells with a note appear.
195
196
  */
196
197
  declare const CSS_SUPPORT_NOTES: Record<string, Record<string, string[]>>;
@@ -212,10 +213,10 @@ declare const CSS_FUNCTION_FEATURES: readonly ["calc", "clamp", "conic-gradient"
212
213
  * caniemail's page for each feature, for a diagnostic's `codeDescription`.
213
214
  *
214
215
  * 250 of 255 features. The rest have no caniemail entry of
215
- * their own and get no link, which is the honest outcome a link to a 404
216
+ * their own and get no link, which is the honest outcome: a link to a 404
216
217
  * is worse than none.
217
218
  *
218
- * DO NOT EDIT regenerate with: bun run sync:feature-urls
219
+ * DO NOT EDIT; regenerate with: bun run sync:feature-urls
219
220
  */
220
221
  declare const FEATURE_URLS: Record<string, string>;
221
222
  /** Where to read about a rule, when there is somewhere. */
@@ -239,7 +240,7 @@ declare const VALUE_CAVEAT_PROPS: ReadonlySet<string>;
239
240
  * applies if either does.
240
241
  *
241
242
  * Returns true (report it) for a property that isn't value-gated, and for one
242
- * where we never saw a value an at-rule, a pseudo-class, a detected CSS
243
+ * where we never saw a value: an at-rule, a pseudo-class, a detected CSS
243
244
  * function.
244
245
  */
245
246
  declare function caveatApplies(prop: string, values: readonly string[] | undefined, notes: string[] | undefined): boolean;
@@ -267,6 +268,20 @@ declare function analyzeSpam(html: string, options?: SpamAnalysisOptions): SpamR
267
268
  */
268
269
  declare function validateLinks(html: string, options?: ParseOptions): LinkReport;
269
270
 
271
+ /**
272
+ * The render a cascade is being resolved for.
273
+ *
274
+ * An email has more than one correct answer: the same CSS produces a different
275
+ * palette on a phone and in dark mode, and a contrast bug can live in only one
276
+ * of them. Resolving against an explicit context is what lets the same
277
+ * machinery grade all three.
278
+ */
279
+ interface RenderContext {
280
+ /** Viewport width in px, for width media queries. */
281
+ width: number;
282
+ /** Whether `prefers-color-scheme: dark` matches. */
283
+ dark: boolean;
284
+ }
270
285
  /**
271
286
  * Audit an HTML email for accessibility issues.
272
287
  *
@@ -275,6 +290,43 @@ declare function validateLinks(html: string, options?: ParseOptions): LinkReport
275
290
  * color contrast. Returns a 0–100 score and detailed issues.
276
291
  */
277
292
  declare function checkAccessibility(html: string, options?: ParseOptions): AccessibilityReport;
293
+ /**
294
+ * Contrast failures inside the email's own `@media (prefers-color-scheme: dark)`
295
+ * styles.
296
+ *
297
+ * This is the other half of dark mode, and the half the author actually
298
+ * controls: Apple Mail, Superhuman, Thunderbird and friends do not invert
299
+ * anything, they just apply the dark block as written. A dark background
300
+ * paired with body text that was never re-coloured is invisible there, and no
301
+ * amount of inversion analysis finds it; the bug is in the CSS, not in what
302
+ * the client does to it.
303
+ *
304
+ * Free to run: same DOM, resolved against a dark render, so positions stay
305
+ * valid. Emails with no dark block resolve identically to the light pass and
306
+ * report nothing.
307
+ */
308
+ declare function checkDarkStylesContrastFromDom($: cheerio.CheerioAPI, lightIssues?: AccessibilityIssue[]): AccessibilityIssue[];
309
+ /**
310
+ * Contrast failures that only appear at mobile width. See
311
+ * {@link checkMobileContrastFromDom}.
312
+ */
313
+ declare function checkMobileContrast(html: string, options?: ParseOptions): AccessibilityIssue[];
314
+ /**
315
+ * Contrast failures an email has *only* in dark mode.
316
+ *
317
+ * Clients that invert colours do it selectively, so a palette that passes in
318
+ * light mode can collapse to unreadable once inverted, mid-tone text left
319
+ * alone on a background repainted near-black. This runs the ordinary contrast
320
+ * check over the simulated dark render and reports what is new there.
321
+ *
322
+ * Pass `lightIssues` when the light-mode report is already in hand (the audit
323
+ * has it) to skip re-running the light pass.
324
+ *
325
+ * ponytail: deduped by the element description, so two genuinely identical
326
+ * elements collapse into one report. Track them individually only if the
327
+ * per-element count turns out to matter.
328
+ */
329
+ declare function checkDarkModeContrast(html: string, lightIssues?: AccessibilityIssue[]): AccessibilityIssue[];
278
330
 
279
331
  /**
280
332
  * Analyze images in an HTML email for best practices.
@@ -320,6 +372,24 @@ declare function checkTemplateVariables(html: string, options?: ParseOptions): T
320
372
  */
321
373
  declare function checkOverflow(html: string, options?: ParseOptions): OverflowReport;
322
374
 
375
+ /**
376
+ * Check hand-written VML for the mistakes Outlook punishes.
377
+ *
378
+ * This is the one part of an email the DOM analyzers structurally cannot see:
379
+ * VML lives inside `<!--[if mso]>` conditional comments, so to every HTML
380
+ * parser it is a comment node, and to a headless-Chromium screenshot it does
381
+ * not exist at all. An email can therefore lint clean and preview perfectly
382
+ * while the branch Outlook actually renders is broken.
383
+ *
384
+ * ponytail: a tag-sequence checker, not a VML renderer. It validates structure
385
+ * and attribute values, which is where the reported breakages cluster. It does
386
+ * not resolve geometry, so it can say Outlook will misplace a nested shape but
387
+ * not where the shape lands.
388
+ */
389
+ declare function checkVml(html: string, options?: {
390
+ positions?: boolean;
391
+ }): VmlReport;
392
+
323
393
  /**
324
394
  * Detect probable visual bugs in a stylized email: background images/gradients
325
395
  * with no colour fallback, and font stacks with no web-safe fallback. Each
@@ -327,12 +397,18 @@ declare function checkOverflow(html: string, options?: ParseOptions): OverflowRe
327
397
  */
328
398
  declare function checkVisual(html: string, options?: ParseOptions): VisualReport;
329
399
 
400
+ /**
401
+ * Report design drift in an HTML email: near-identical colours, and properties
402
+ * carrying more distinct values than a design system would.
403
+ */
404
+ declare function checkDesignConsistency(html: string, options?: ParseOptions): DesignReport;
405
+
330
406
  interface AuditOptions extends ParseOptions {
331
407
  framework?: Framework;
332
408
  /** Options for spam analysis */
333
409
  spam?: SpamAnalysisOptions;
334
410
  /** Skip specific checks */
335
- skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual">;
411
+ skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design" | "vml">;
336
412
  }
337
413
  interface AuditReport {
338
414
  compatibility: {
@@ -353,6 +429,14 @@ interface AuditReport {
353
429
  templateVariables: TemplateReport;
354
430
  overflow: OverflowReport;
355
431
  visual: VisualReport;
432
+ /** Structural faults in Outlook-only VML, which lives inside conditional comments. */
433
+ vml: VmlReport;
434
+ /** Contrast failures present only once a client inverts colours. */
435
+ darkContrast: AccessibilityIssue[];
436
+ /** Contrast failures present only below the email's mobile breakpoint. */
437
+ mobileContrast: AccessibilityIssue[];
438
+ /** Design drift: near-identical colours, and properties with no system left. */
439
+ design: DesignReport;
356
440
  }
357
441
  /**
358
442
  * Run all email analysis checks in a single call.
@@ -453,7 +537,7 @@ interface EmailSession {
453
537
  * Simulate dark mode for a specific client.
454
538
  *
455
539
  * Creates an isolated DOM copy per call (simulation mutates the DOM).
456
- * Operates on the **original** HTML if you need dark mode on
540
+ * Operates on the **original** HTML, if you need dark mode on
457
541
  * already-transformed HTML, use the standalone `simulateDarkMode()` instead.
458
542
  */
459
543
  simulateDarkMode(clientId: string): {
@@ -466,7 +550,7 @@ interface EmailSession {
466
550
  * DOM across all read-only analysis operations.
467
551
  *
468
552
  * Use this when you need to call multiple analysis functions on the
469
- * same HTML it eliminates redundant `cheerio.load()` calls.
553
+ * same HTML: it eliminates redundant `cheerio.load()` calls.
470
554
  *
471
555
  * @example
472
556
  * ```typescript
@@ -548,6 +632,21 @@ declare function wcagGrade(ratio: number): WcagGrade;
548
632
  * Returns the flattened [r, g, b] as 0-255 values.
549
633
  */
550
634
  declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [number, number, number];
635
+ /**
636
+ * Convert sRGB to OKLab.
637
+ *
638
+ * OKLab is built so that straight-line distance between two points matches how
639
+ * different the colours look, which sRGB emphatically does not: `#333` and
640
+ * `#343434` are far apart in RGB terms and identical to a reader.
641
+ */
642
+ declare function rgbToOklab(rgba: RGBA): [number, number, number];
643
+ /**
644
+ * Perceptual distance between two colours, as OKLab delta-E.
645
+ *
646
+ * Roughly: under ~0.02 the two are the same colour to a reader, and around
647
+ * ~0.1 they are clearly different.
648
+ */
649
+ declare function colorDistance(a: RGBA, b: RGBA): number;
551
650
 
552
651
  /**
553
652
  * Downlevel modern CSS in HTML emails to maximize email client compatibility.
@@ -575,4 +674,4 @@ declare function alphaBlend(fg: RGBA, bgR: number, bgG: number, bgB: number): [n
575
674
  */
576
675
  declare function downlevelCSS(html: string): string;
577
676
 
578
- export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkOverflow, checkSize, checkTemplateVariables, checkVisual, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
677
+ export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, VmlReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, checkVml, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };