@emailens/engine 0.1.0 → 0.2.0

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.js CHANGED
@@ -1,4 +1,6 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
2
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
4
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -14,6 +16,19 @@ var __spreadValues = (a, b) => {
14
16
  }
15
17
  return a;
16
18
  };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
17
32
 
18
33
  // src/clients.ts
19
34
  var EMAIL_CLIENTS = [
@@ -730,6 +745,189 @@ var CSS_SUPPORT = {
730
745
  "superhuman": "unsupported"
731
746
  // Superhuman strips forms for security
732
747
  },
748
+ // --- Text Wrapping ---
749
+ "word-break": {
750
+ "gmail-web": "supported",
751
+ "gmail-android": "supported",
752
+ "gmail-ios": "supported",
753
+ "outlook-web": "supported",
754
+ "outlook-windows": "unsupported",
755
+ // Word engine ignores word-break entirely
756
+ "apple-mail-macos": "supported",
757
+ "apple-mail-ios": "supported",
758
+ "yahoo-mail": "partial",
759
+ // break-all partially works; break-word unreliable
760
+ "samsung-mail": "supported",
761
+ "thunderbird": "supported",
762
+ "hey-mail": "supported",
763
+ "superhuman": "supported"
764
+ },
765
+ "overflow-wrap": {
766
+ "gmail-web": "supported",
767
+ "gmail-android": "supported",
768
+ "gmail-ios": "supported",
769
+ "outlook-web": "supported",
770
+ "outlook-windows": "unsupported",
771
+ // Word engine ignores overflow-wrap
772
+ "apple-mail-macos": "supported",
773
+ "apple-mail-ios": "supported",
774
+ "yahoo-mail": "partial",
775
+ // inconsistent support
776
+ "samsung-mail": "supported",
777
+ "thunderbird": "supported",
778
+ "hey-mail": "supported",
779
+ "superhuman": "supported"
780
+ },
781
+ "white-space": {
782
+ "gmail-web": "supported",
783
+ "gmail-android": "supported",
784
+ "gmail-ios": "supported",
785
+ "outlook-web": "supported",
786
+ "outlook-windows": "partial",
787
+ // only normal and nowrap
788
+ "apple-mail-macos": "supported",
789
+ "apple-mail-ios": "supported",
790
+ "yahoo-mail": "supported",
791
+ "samsung-mail": "supported",
792
+ "thunderbird": "supported",
793
+ "hey-mail": "supported",
794
+ "superhuman": "supported"
795
+ },
796
+ "text-overflow": {
797
+ "gmail-web": "unsupported",
798
+ // Gmail strips overflow, so text-overflow is useless
799
+ "gmail-android": "unsupported",
800
+ "gmail-ios": "unsupported",
801
+ "outlook-web": "supported",
802
+ "outlook-windows": "unsupported",
803
+ "apple-mail-macos": "supported",
804
+ "apple-mail-ios": "supported",
805
+ "yahoo-mail": "partial",
806
+ "samsung-mail": "supported",
807
+ "thunderbird": "supported",
808
+ "hey-mail": "supported",
809
+ "superhuman": "supported"
810
+ },
811
+ // --- Table Layout ---
812
+ "vertical-align": {
813
+ "gmail-web": "supported",
814
+ "gmail-android": "supported",
815
+ "gmail-ios": "supported",
816
+ "outlook-web": "supported",
817
+ "outlook-windows": "partial",
818
+ // only on <td> elements via valign attribute
819
+ "apple-mail-macos": "supported",
820
+ "apple-mail-ios": "supported",
821
+ "yahoo-mail": "supported",
822
+ "samsung-mail": "supported",
823
+ "thunderbird": "supported",
824
+ "hey-mail": "supported",
825
+ "superhuman": "supported"
826
+ },
827
+ "border-spacing": {
828
+ "gmail-web": "supported",
829
+ "gmail-android": "supported",
830
+ "gmail-ios": "supported",
831
+ "outlook-web": "supported",
832
+ "outlook-windows": "unsupported",
833
+ // use cellspacing attribute instead
834
+ "apple-mail-macos": "supported",
835
+ "apple-mail-ios": "supported",
836
+ "yahoo-mail": "supported",
837
+ "samsung-mail": "supported",
838
+ "thunderbird": "supported",
839
+ "hey-mail": "supported",
840
+ "superhuman": "supported"
841
+ },
842
+ // --- Sizing ---
843
+ "min-width": {
844
+ "gmail-web": "supported",
845
+ "gmail-android": "supported",
846
+ "gmail-ios": "supported",
847
+ "outlook-web": "supported",
848
+ "outlook-windows": "unsupported",
849
+ // Word engine ignores min-width
850
+ "apple-mail-macos": "supported",
851
+ "apple-mail-ios": "supported",
852
+ "yahoo-mail": "supported",
853
+ "samsung-mail": "supported",
854
+ "thunderbird": "supported",
855
+ "hey-mail": "supported",
856
+ "superhuman": "supported"
857
+ },
858
+ "min-height": {
859
+ "gmail-web": "supported",
860
+ "gmail-android": "supported",
861
+ "gmail-ios": "supported",
862
+ "outlook-web": "supported",
863
+ "outlook-windows": "unsupported",
864
+ // Word engine ignores min-height
865
+ "apple-mail-macos": "supported",
866
+ "apple-mail-ios": "supported",
867
+ "yahoo-mail": "supported",
868
+ "samsung-mail": "supported",
869
+ "thunderbird": "supported",
870
+ "hey-mail": "supported",
871
+ "superhuman": "supported"
872
+ },
873
+ "max-height": {
874
+ "gmail-web": "supported",
875
+ "gmail-android": "supported",
876
+ "gmail-ios": "supported",
877
+ "outlook-web": "supported",
878
+ "outlook-windows": "unsupported",
879
+ "apple-mail-macos": "supported",
880
+ "apple-mail-ios": "supported",
881
+ "yahoo-mail": "supported",
882
+ "samsung-mail": "supported",
883
+ "thunderbird": "supported",
884
+ "hey-mail": "supported",
885
+ "superhuman": "supported"
886
+ },
887
+ // --- Shadows ---
888
+ "text-shadow": {
889
+ "gmail-web": "unsupported",
890
+ "gmail-android": "unsupported",
891
+ "gmail-ios": "unsupported",
892
+ "outlook-web": "supported",
893
+ "outlook-windows": "unsupported",
894
+ "apple-mail-macos": "supported",
895
+ "apple-mail-ios": "supported",
896
+ "yahoo-mail": "unsupported",
897
+ "samsung-mail": "supported",
898
+ "thunderbird": "supported",
899
+ "hey-mail": "supported",
900
+ "superhuman": "supported"
901
+ },
902
+ // --- Background Sub-properties ---
903
+ "background-size": {
904
+ "gmail-web": "supported",
905
+ "gmail-android": "supported",
906
+ "gmail-ios": "supported",
907
+ "outlook-web": "supported",
908
+ "outlook-windows": "unsupported",
909
+ "apple-mail-macos": "supported",
910
+ "apple-mail-ios": "supported",
911
+ "yahoo-mail": "partial",
912
+ "samsung-mail": "supported",
913
+ "thunderbird": "supported",
914
+ "hey-mail": "supported",
915
+ "superhuman": "supported"
916
+ },
917
+ "background-position": {
918
+ "gmail-web": "supported",
919
+ "gmail-android": "supported",
920
+ "gmail-ios": "supported",
921
+ "outlook-web": "supported",
922
+ "outlook-windows": "unsupported",
923
+ "apple-mail-macos": "supported",
924
+ "apple-mail-ios": "supported",
925
+ "yahoo-mail": "partial",
926
+ "samsung-mail": "supported",
927
+ "thunderbird": "supported",
928
+ "hey-mail": "supported",
929
+ "superhuman": "supported"
930
+ },
733
931
  // --- Misc ---
734
932
  "opacity": {
735
933
  "gmail-web": "unsupported",
@@ -822,6 +1020,8 @@ var OUTLOOK_WORD_UNSUPPORTED = /* @__PURE__ */ new Set([
822
1020
  "text-shadow",
823
1021
  "max-width",
824
1022
  "max-height",
1023
+ "min-width",
1024
+ "min-height",
825
1025
  "float",
826
1026
  "position",
827
1027
  "display",
@@ -834,7 +1034,30 @@ var OUTLOOK_WORD_UNSUPPORTED = /* @__PURE__ */ new Set([
834
1034
  "background-position",
835
1035
  "box-sizing",
836
1036
  "object-fit",
837
- "gap"
1037
+ "gap",
1038
+ "word-break",
1039
+ "overflow-wrap",
1040
+ "text-overflow",
1041
+ "border-spacing"
1042
+ ]);
1043
+ var STRUCTURAL_FIX_PROPERTIES = /* @__PURE__ */ new Set([
1044
+ "display:flex",
1045
+ "display:grid",
1046
+ "word-break",
1047
+ "overflow-wrap",
1048
+ "text-overflow",
1049
+ "position",
1050
+ "float",
1051
+ "gap",
1052
+ "max-width",
1053
+ "border-radius",
1054
+ "background-image",
1055
+ "background-size",
1056
+ "background-position",
1057
+ "<svg>",
1058
+ "<video>",
1059
+ "<form>",
1060
+ "object-fit"
838
1061
  ]);
839
1062
 
840
1063
  // src/fix-snippets.ts
@@ -1340,6 +1563,146 @@ h1 {
1340
1563
  <div style="font-size: 0; max-height: 0; overflow: hidden;
1341
1564
  mso-hide: all;" aria-hidden="true">
1342
1565
  Preheader text
1566
+ </div>`
1567
+ },
1568
+ // ── word-break → table cell wrapping ─────────────────────────────────────
1569
+ "word-break": {
1570
+ language: "html",
1571
+ description: "Wrap long text in a table cell to force line breaks without word-break",
1572
+ before: `<span style="word-break: break-all;">
1573
+ https://example.com/very/long/url?token=abc123def456
1574
+ </span>`,
1575
+ after: `<!-- Table cells force text wrapping in all clients including Outlook -->
1576
+ <table role="presentation" width="100%" cellpadding="0"
1577
+ cellspacing="0" border="0">
1578
+ <tr>
1579
+ <td style="word-break: break-all; overflow-wrap: break-word;
1580
+ word-wrap: break-word;">
1581
+ https://example.com/very/long/url?token=abc123def456
1582
+ </td>
1583
+ </tr>
1584
+ </table>`
1585
+ },
1586
+ "word-break::jsx": {
1587
+ language: "jsx",
1588
+ description: "Wrap long text in a table cell for Outlook-safe word breaking",
1589
+ before: `<span style={{ wordBreak: "break-all" }}>{url}</span>`,
1590
+ after: `{/* Table cells force text wrapping in Outlook and Yahoo */}
1591
+ <table width="100%" cellPadding={0} cellSpacing={0}
1592
+ role="presentation" style={{ borderCollapse: "collapse" }}>
1593
+ <tr>
1594
+ <td style={{
1595
+ wordBreak: "break-all" as const,
1596
+ overflowWrap: "break-word" as const,
1597
+ wordWrap: "break-word" as const,
1598
+ }}>
1599
+ {url}
1600
+ </td>
1601
+ </tr>
1602
+ </table>`
1603
+ },
1604
+ "word-break::mjml": {
1605
+ language: "mjml",
1606
+ description: "MJML renders text in table cells by default \u2014 word-break works via mj-text",
1607
+ before: `<mj-text>
1608
+ <span style="word-break: break-all;">Long URL here</span>
1609
+ </mj-text>`,
1610
+ after: `<!-- mj-text already renders inside a <td>, so add word-break
1611
+ to the mj-text css-class or inline style -->
1612
+ <mj-text css-class="break-words"
1613
+ padding="0">
1614
+ Long URL here
1615
+ </mj-text>
1616
+ <mj-style>
1617
+ .break-words td { word-break: break-all; word-wrap: break-word; }
1618
+ </mj-style>`
1619
+ },
1620
+ // ── overflow-wrap → table cell wrapping ────────────────────────────────
1621
+ "overflow-wrap": {
1622
+ language: "html",
1623
+ description: "Use a table cell to force word wrapping without overflow-wrap",
1624
+ before: `<p style="overflow-wrap: break-word;">
1625
+ https://example.com/very/long/url?token=abc123def456
1626
+ </p>`,
1627
+ after: `<table role="presentation" width="100%" cellpadding="0"
1628
+ cellspacing="0" border="0">
1629
+ <tr>
1630
+ <td style="overflow-wrap: break-word; word-wrap: break-word;
1631
+ word-break: break-all;">
1632
+ https://example.com/very/long/url?token=abc123def456
1633
+ </td>
1634
+ </tr>
1635
+ </table>`
1636
+ },
1637
+ "overflow-wrap::jsx": {
1638
+ language: "jsx",
1639
+ description: "Wrap text in a table cell for Outlook-safe overflow wrapping",
1640
+ before: `<p style={{ overflowWrap: "break-word" }}>{longText}</p>`,
1641
+ after: `<table width="100%" cellPadding={0} cellSpacing={0}
1642
+ role="presentation" style={{ borderCollapse: "collapse" }}>
1643
+ <tr>
1644
+ <td style={{
1645
+ overflowWrap: "break-word" as const,
1646
+ wordWrap: "break-word" as const,
1647
+ wordBreak: "break-all" as const,
1648
+ }}>
1649
+ {longText}
1650
+ </td>
1651
+ </tr>
1652
+ </table>`
1653
+ },
1654
+ // ── text-shadow → border/font-weight alternative ───────────────────────
1655
+ "text-shadow": {
1656
+ language: "css",
1657
+ description: "Use font-weight or border-bottom as alternatives to text-shadow",
1658
+ before: `.glow {
1659
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
1660
+ }`,
1661
+ after: `.glow {
1662
+ /* text-shadow is not supported in Gmail, Outlook, Yahoo.
1663
+ Use font-weight or letter-spacing for emphasis instead. */
1664
+ font-weight: bold;
1665
+ letter-spacing: 0.5px;
1666
+ }`
1667
+ },
1668
+ // ── border-spacing → cellspacing attribute ─────────────────────────────
1669
+ "border-spacing": {
1670
+ language: "html",
1671
+ description: "Use the cellspacing HTML attribute instead of border-spacing CSS",
1672
+ before: `<table style="border-spacing: 8px; border-collapse: separate;">
1673
+ <tr><td>Cell</td></tr>
1674
+ </table>`,
1675
+ after: `<table cellspacing="8" style="border-collapse: separate;">
1676
+ <tr><td>Cell</td></tr>
1677
+ </table>`
1678
+ },
1679
+ // ── min-width → fixed width ────────────────────────────────────────────
1680
+ "min-width": {
1681
+ language: "html",
1682
+ description: "Use a fixed width instead of min-width for Outlook compatibility",
1683
+ before: `<td style="min-width: 200px;">Content</td>`,
1684
+ after: `<!-- Outlook ignores min-width. Use a fixed width or a spacer. -->
1685
+ <td width="200" style="width: 200px;">Content</td>`
1686
+ },
1687
+ // ── min-height → fixed height ──────────────────────────────────────────
1688
+ "min-height": {
1689
+ language: "html",
1690
+ description: "Use a fixed height or spacer instead of min-height",
1691
+ before: `<td style="min-height: 100px;">Content</td>`,
1692
+ after: `<!-- Outlook ignores min-height. Use height or a spacer image. -->
1693
+ <td height="100" style="height: 100px;">Content</td>`
1694
+ },
1695
+ // ── max-height → fixed height ──────────────────────────────────────────
1696
+ "max-height": {
1697
+ language: "html",
1698
+ description: "Outlook ignores max-height \u2014 truncate content server-side",
1699
+ before: `<div style="max-height: 200px; overflow: hidden;">
1700
+ Long content...
1701
+ </div>`,
1702
+ after: `<!-- Outlook ignores max-height. Truncate content server-side. -->
1703
+ <div style="height: 200px;">
1704
+ Shortened content...
1705
+ <a href="https://example.com/full">Read more</a>
1343
1706
  </div>`
1344
1707
  },
1345
1708
  // ── REACT EMAIL (jsx) framework-specific fixes ────────────────────────────
@@ -2074,6 +2437,8 @@ function getClientPrefix(clientId) {
2074
2437
  if (clientId.startsWith("outlook")) return null;
2075
2438
  if (clientId.startsWith("gmail")) return "gmail";
2076
2439
  if (clientId.startsWith("apple-mail")) return "apple";
2440
+ if (clientId === "yahoo-mail") return "yahoo";
2441
+ if (clientId === "samsung-mail") return "samsung";
2077
2442
  return null;
2078
2443
  }
2079
2444
  var SUGGESTION_DATABASE = {
@@ -2177,6 +2542,31 @@ var SUGGESTION_DATABASE = {
2177
2542
  "opacity::jsx": "Use solid colors. Opacity is not supported in many email clients.",
2178
2543
  "opacity::mjml": "Use solid colors. Most email clients don't support opacity.",
2179
2544
  "opacity::maizzle": "Use solid Tailwind color classes instead of opacity.",
2545
+ // ── word-break ──────────────────────────────────────────────────────
2546
+ "word-break": "Wrap long text in a <table><td> to force wrapping in clients that don't support word-break.",
2547
+ "word-break::outlook": "Outlook's Word engine ignores word-break. Place text inside a <td> with a constrained width \u2014 tables always wrap.",
2548
+ "word-break::jsx": "Wrap long text in a <table><tr><td> element. Outlook ignores wordBreak but respects table cell widths.",
2549
+ "word-break::mjml": "mj-text renders inside a <td>, which helps. Add word-wrap: break-word to the td via mj-style.",
2550
+ // ── overflow-wrap ──────────────────────────────────────────────────
2551
+ "overflow-wrap": "Wrap text in a <table><td> to force wrapping. overflow-wrap is ignored by Outlook and unreliable in Yahoo.",
2552
+ "overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths.",
2553
+ // ── white-space ────────────────────────────────────────────────────
2554
+ "white-space": "Outlook only supports 'normal' and 'nowrap'. Use &nbsp; for non-breaking spaces.",
2555
+ // ── text-overflow ──────────────────────────────────────────────────
2556
+ "text-overflow": "text-overflow requires overflow:hidden which is stripped by Gmail. Truncate content server-side.",
2557
+ // ── vertical-align ─────────────────────────────────────────────────
2558
+ "vertical-align": 'Use the valign HTML attribute on <td> elements for Outlook (e.g., valign="top").',
2559
+ // ── border-spacing ─────────────────────────────────────────────────
2560
+ "border-spacing": 'Use the cellspacing HTML attribute instead (e.g., <table cellspacing="8">).',
2561
+ // ── min-width / min-height ─────────────────────────────────────────
2562
+ "min-width": "Outlook ignores min-width. Use a fixed width attribute on <td> or <table>.",
2563
+ "min-height": "Outlook ignores min-height. Use a fixed height or a spacer image.",
2564
+ "max-height": "Outlook ignores max-height. Truncate content server-side or use a fixed height.",
2565
+ // ── text-shadow ────────────────────────────────────────────────────
2566
+ "text-shadow": "text-shadow is stripped by Gmail, Outlook, and Yahoo. Use font-weight for emphasis.",
2567
+ // ── background-size / background-position ──────────────────────────
2568
+ "background-size": "Not supported in many clients. Set image dimensions directly.",
2569
+ "background-position": "Not supported in many clients. Use VML for positioning.",
2180
2570
  // ── Additional properties covered by transform helpers ────────────────
2181
2571
  "overflow": "Content will always be visible. Design accordingly.",
2182
2572
  "visibility": "Remove the element or use display:none as an alternative.",
@@ -2185,9 +2575,6 @@ var SUGGESTION_DATABASE = {
2185
2575
  "transition": "CSS transitions are not supported in email.",
2186
2576
  "box-sizing": "Account for padding in your width calculations (use padding on a nested element).",
2187
2577
  "object-fit": "Use width/height attributes on <img> directly.",
2188
- "max-height": "Use fixed height instead.",
2189
- "background-size": "Not supported in many clients. Set image dimensions directly.",
2190
- "background-position": "Not supported in many clients. Use VML for positioning.",
2191
2578
  "display": "Use tables for layout in email clients."
2192
2579
  };
2193
2580
  function getSuggestion(property, clientId, framework) {
@@ -2329,7 +2716,9 @@ function makeWarning(base, prop, clientId, framework) {
2329
2716
  const sug = getSuggestion(prop, clientId, framework);
2330
2717
  const fix = getCodeFix(prop, clientId, framework);
2331
2718
  const isFallback = framework && ((sug == null ? void 0 : sug.isGenericFallback) || fix && isCodeFixGenericFallback(prop, clientId, framework));
2332
- return __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {});
2719
+ return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {}), {
2720
+ fixType: STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css"
2721
+ });
2333
2722
  }
2334
2723
  function transformGmail(html, clientId, framework) {
2335
2724
  const $ = cheerio.load(html);
@@ -2870,7 +3259,8 @@ function analyzeEmail(html, framework) {
2870
3259
  property: "<style>",
2871
3260
  message: `${client.name} strips <style> blocks. Styles must be inlined.`,
2872
3261
  suggestion: sug.text,
2873
- fix
3262
+ fix,
3263
+ fixType: getFixType("<style>")
2874
3264
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2875
3265
  } else if (support === "partial") {
2876
3266
  const sug = getSuggestion("<style>:partial", client.id, framework);
@@ -2881,7 +3271,8 @@ function analyzeEmail(html, framework) {
2881
3271
  property: "<style>",
2882
3272
  message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
2883
3273
  suggestion: sug.text,
2884
- fix
3274
+ fix,
3275
+ fixType: getFixType("<style>")
2885
3276
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2886
3277
  }
2887
3278
  }
@@ -2898,7 +3289,8 @@ function analyzeEmail(html, framework) {
2898
3289
  property: "<link>",
2899
3290
  message: `${client.name} does not support external stylesheets.`,
2900
3291
  suggestion: sug.text,
2901
- fix
3292
+ fix,
3293
+ fixType: getFixType("<link>")
2902
3294
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<link>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2903
3295
  }
2904
3296
  }
@@ -2915,7 +3307,8 @@ function analyzeEmail(html, framework) {
2915
3307
  property: "<svg>",
2916
3308
  message: `${client.name} does not support inline SVG.`,
2917
3309
  suggestion: sug.text,
2918
- fix
3310
+ fix,
3311
+ fixType: getFixType("<svg>")
2919
3312
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<svg>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2920
3313
  }
2921
3314
  }
@@ -2932,7 +3325,8 @@ function analyzeEmail(html, framework) {
2932
3325
  property: "<video>",
2933
3326
  message: `${client.name} does not support <video> elements.`,
2934
3327
  suggestion: sug.text,
2935
- fix
3328
+ fix,
3329
+ fixType: getFixType("<video>")
2936
3330
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<video>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2937
3331
  }
2938
3332
  }
@@ -2949,7 +3343,8 @@ function analyzeEmail(html, framework) {
2949
3343
  property: "<form>",
2950
3344
  message: `${client.name} strips form elements.`,
2951
3345
  suggestion: sug.text,
2952
- fix
3346
+ fix,
3347
+ fixType: getFixType("<form>")
2953
3348
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<form>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2954
3349
  }
2955
3350
  }
@@ -2994,7 +3389,8 @@ function analyzeEmail(html, framework) {
2994
3389
  property: "@font-face",
2995
3390
  message: `${client.name} does not support web fonts (@font-face).`,
2996
3391
  suggestion: sug.text,
2997
- fix
3392
+ fix,
3393
+ fixType: getFixType("@font-face")
2998
3394
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("@font-face", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
2999
3395
  }
3000
3396
  }
@@ -3011,7 +3407,8 @@ function analyzeEmail(html, framework) {
3011
3407
  property: "@media",
3012
3408
  message: `${client.name} does not support @media queries.`,
3013
3409
  suggestion: sug.text,
3014
- fix
3410
+ fix,
3411
+ fixType: getFixType("@media")
3015
3412
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("@media", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
3016
3413
  }
3017
3414
  }
@@ -3050,7 +3447,8 @@ function analyzeEmail(html, framework) {
3050
3447
  severity: "warning",
3051
3448
  client: client.id,
3052
3449
  property: prop,
3053
- message: `${client.name} does not support "${prop}" in <style> blocks.`
3450
+ message: `${client.name} does not support "${prop}" in <style> blocks.`,
3451
+ fixType: getFixType(prop)
3054
3452
  });
3055
3453
  }
3056
3454
  }
@@ -3064,9 +3462,13 @@ function analyzeEmail(html, framework) {
3064
3462
  warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
3065
3463
  return warnings;
3066
3464
  }
3465
+ function getFixType(prop) {
3466
+ return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
3467
+ }
3067
3468
  function checkPropertySupport(prop, addWarning, framework) {
3068
3469
  const supportData = CSS_SUPPORT[prop];
3069
3470
  if (!supportData) return;
3471
+ const fixType = getFixType(prop);
3070
3472
  for (const client of EMAIL_CLIENTS) {
3071
3473
  const support = supportData[client.id] || "unknown";
3072
3474
  if (support === "unsupported") {
@@ -3078,7 +3480,8 @@ function checkPropertySupport(prop, addWarning, framework) {
3078
3480
  property: prop,
3079
3481
  message: `${client.name} does not support "${prop}".`,
3080
3482
  suggestion: sug.text,
3081
- fix
3483
+ fix,
3484
+ fixType
3082
3485
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
3083
3486
  } else if (support === "partial") {
3084
3487
  const sug = getSuggestion(prop, client.id, framework);
@@ -3089,7 +3492,8 @@ function checkPropertySupport(prop, addWarning, framework) {
3089
3492
  property: prop,
3090
3493
  message: `${client.name} has partial support for "${prop}".`,
3091
3494
  suggestion: sug.text,
3092
- fix
3495
+ fix,
3496
+ fixType
3093
3497
  }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
3094
3498
  }
3095
3499
  }
@@ -3311,8 +3715,13 @@ ${originalHtml}
3311
3715
  `;
3312
3716
  for (const w of group) {
3313
3717
  const clientName = (_f = (_e = getClient(w.client)) == null ? void 0 : _e.name) != null ? _f : w.client;
3718
+ const fixTypeLabel = w.fixType === "structural" ? " [STRUCTURAL]" : "";
3314
3719
  issueSection += `
3315
- - **${w.property}** (${clientName}): ${w.message}`;
3720
+ - **${w.property}** (${clientName})${fixTypeLabel}: ${w.message}`;
3721
+ if (w.fixType === "structural") {
3722
+ issueSection += `
3723
+ - **Fix type: structural** \u2014 CSS-only changes will NOT work. HTML restructuring required.`;
3724
+ }
3316
3725
  if (w.suggestion) {
3317
3726
  issueSection += `
3318
3727
  - Suggestion: ${w.suggestion}`;
@@ -3343,15 +3752,177 @@ ${originalHtml}
3343
3752
  );
3344
3753
  return sections.join("\n\n");
3345
3754
  }
3755
+
3756
+ // src/token-utils.ts
3757
+ var CHARS_PER_TOKEN = 3.5;
3758
+ var OUTPUT_RATIO = 1.3;
3759
+ var DEFAULT_SYSTEM_PROMPT_TOKENS = 250;
3760
+ async function estimateAiFixTokens(options) {
3761
+ const _a = options, {
3762
+ maxInputTokens = 16e3,
3763
+ tokenCounter,
3764
+ systemPromptTokens = DEFAULT_SYSTEM_PROMPT_TOKENS
3765
+ } = _a, rest = __objRest(_a, [
3766
+ "maxInputTokens",
3767
+ "tokenCounter",
3768
+ "systemPromptTokens"
3769
+ ]);
3770
+ const effectiveMaxTokens = maxInputTokens - systemPromptTokens;
3771
+ const { warnings: finalWarnings, truncated, removed } = truncateWarnings(
3772
+ rest.warnings,
3773
+ rest.originalHtml,
3774
+ effectiveMaxTokens,
3775
+ rest.scope,
3776
+ rest.selectedClientId
3777
+ );
3778
+ const prompt = generateFixPrompt(__spreadProps(__spreadValues({}, rest), { warnings: finalWarnings }));
3779
+ const promptChars = prompt.length;
3780
+ let promptTokens;
3781
+ if (tokenCounter) {
3782
+ const count = tokenCounter(prompt);
3783
+ promptTokens = count instanceof Promise ? await count : count;
3784
+ } else {
3785
+ promptTokens = heuristicTokenCount(prompt);
3786
+ }
3787
+ const inputTokens = promptTokens + systemPromptTokens;
3788
+ const estimatedOutputTokens = Math.ceil(
3789
+ heuristicTokenCount(rest.originalHtml) * OUTPUT_RATIO
3790
+ );
3791
+ const structuralCount = finalWarnings.filter(
3792
+ (w) => w.fixType === "structural"
3793
+ ).length;
3794
+ return {
3795
+ inputTokens,
3796
+ estimatedOutputTokens,
3797
+ promptCharacters: promptChars,
3798
+ htmlCharacters: rest.originalHtml.length,
3799
+ warningCount: finalWarnings.length,
3800
+ structuralCount,
3801
+ truncated,
3802
+ warningsRemoved: removed,
3803
+ warnings: finalWarnings
3804
+ };
3805
+ }
3806
+ function heuristicTokenCount(text) {
3807
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
3808
+ }
3809
+ function truncateWarnings(warnings, html, maxTokens, scope, selectedClientId) {
3810
+ const originalCount = warnings.length;
3811
+ const fullPromptEstimate = heuristicTokenCount(html) + heuristicTokenCount(
3812
+ JSON.stringify(warnings)
3813
+ ) + 500;
3814
+ if (fullPromptEstimate <= maxTokens) {
3815
+ return { warnings, truncated: false, removed: 0 };
3816
+ }
3817
+ let result = [...warnings];
3818
+ const seen = /* @__PURE__ */ new Set();
3819
+ result = result.filter((w) => {
3820
+ const key = `${w.property}:${w.severity}`;
3821
+ if (seen.has(key)) return false;
3822
+ seen.add(key);
3823
+ return true;
3824
+ });
3825
+ if (estimateFits(result, html, maxTokens)) {
3826
+ return { warnings: result, truncated: true, removed: originalCount - result.length };
3827
+ }
3828
+ result = result.filter((w) => w.severity !== "info");
3829
+ if (estimateFits(result, html, maxTokens)) {
3830
+ return { warnings: result, truncated: true, removed: originalCount - result.length };
3831
+ }
3832
+ result = result.filter((w) => w.fixType === "structural" || w.severity === "error");
3833
+ if (estimateFits(result, html, maxTokens)) {
3834
+ return { warnings: result, truncated: true, removed: originalCount - result.length };
3835
+ }
3836
+ result = result.map((w) => __spreadProps(__spreadValues({}, w), {
3837
+ fix: w.fix ? __spreadProps(__spreadValues({}, w.fix), {
3838
+ before: w.fix.before.length > 200 ? w.fix.before.slice(0, 200) + "\n/* ... truncated ... */" : w.fix.before,
3839
+ after: w.fix.after.length > 200 ? w.fix.after.slice(0, 200) + "\n/* ... truncated ... */" : w.fix.after
3840
+ }) : void 0
3841
+ }));
3842
+ return { warnings: result, truncated: true, removed: originalCount - result.length };
3843
+ }
3844
+ function estimateFits(warnings, html, maxTokens) {
3845
+ const estimate = heuristicTokenCount(html) + heuristicTokenCount(JSON.stringify(warnings)) + 500;
3846
+ return estimate <= maxTokens;
3847
+ }
3848
+
3849
+ // src/ai-fix.ts
3850
+ async function generateAiFix(options) {
3851
+ const _a = options, { provider, maxInputTokens = 16e3 } = _a, promptOptions = __objRest(_a, ["provider", "maxInputTokens"]);
3852
+ const estimate = await estimateAiFixTokens(__spreadProps(__spreadValues({}, promptOptions), {
3853
+ maxInputTokens
3854
+ }));
3855
+ const truncatedWarnings = estimate.warnings;
3856
+ const prompt = generateFixPrompt(__spreadProps(__spreadValues({}, promptOptions), { warnings: truncatedWarnings }));
3857
+ const structuralCount = countStructuralWarnings(
3858
+ truncatedWarnings,
3859
+ promptOptions.scope,
3860
+ promptOptions.selectedClientId
3861
+ );
3862
+ const _b = estimate, { warnings: _discarded } = _b, tokenEstimate = __objRest(_b, ["warnings"]);
3863
+ const response = await provider(prompt);
3864
+ const code = extractCode(response);
3865
+ return {
3866
+ code,
3867
+ prompt,
3868
+ targetedWarnings: tokenEstimate.warningCount,
3869
+ structuralCount,
3870
+ tokenEstimate
3871
+ };
3872
+ }
3873
+ var 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.
3874
+
3875
+ Rules:
3876
+ - Return ONLY the fixed code inside a single code fence. No explanations before or after.
3877
+ - Preserve all existing content, text, links, and visual design.
3878
+ - For structural issues (fixType: "structural"), you MUST restructure the HTML \u2014 CSS-only changes will not work.
3879
+ - Common structural patterns:
3880
+ - word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width
3881
+ - display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)
3882
+ - border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals
3883
+ - background-image in Outlook \u2192 use VML <v:rect> with <v:fill>
3884
+ - max-width in Outlook \u2192 wrap in <!--[if mso]><table width="N"> conditional
3885
+ - position:absolute \u2192 use <table> cells for layout
3886
+ - <svg> \u2192 replace with <img> pointing to a hosted PNG
3887
+ - For CSS-only issues (fixType: "css"), swap properties or add fallbacks.
3888
+ - Apply ALL fixes from the issues list \u2014 do not skip any.
3889
+ - Use the framework syntax specified (JSX/MJML/Maizzle/HTML).
3890
+ - For JSX: use camelCase style props, React Email components, and proper TypeScript types.
3891
+ - For MJML: use mj-* elements and attributes.
3892
+ - For Maizzle: use Tailwind CSS classes.`;
3893
+ function countStructuralWarnings(warnings, scope, selectedClientId) {
3894
+ const filtered = scope === "current" && selectedClientId ? warnings.filter((w) => w.client === selectedClientId) : warnings;
3895
+ return filtered.filter((w) => w.fixType === "structural").length;
3896
+ }
3897
+ function extractCode(response) {
3898
+ const fencePattern = /```(?:[\w]*)\n([\s\S]*?)```/g;
3899
+ let largest = null;
3900
+ let match;
3901
+ while ((match = fencePattern.exec(response)) !== null) {
3902
+ const content = match[1].trim();
3903
+ if (largest === null || content.length > largest.length) {
3904
+ largest = content;
3905
+ }
3906
+ }
3907
+ if (largest !== null) {
3908
+ return largest;
3909
+ }
3910
+ return response.trim();
3911
+ }
3346
3912
  export {
3913
+ AI_FIX_SYSTEM_PROMPT,
3347
3914
  EMAIL_CLIENTS,
3915
+ STRUCTURAL_FIX_PROPERTIES,
3348
3916
  analyzeEmail,
3349
3917
  diffResults,
3918
+ estimateAiFixTokens,
3919
+ generateAiFix,
3350
3920
  generateCompatibilityScore,
3351
3921
  generateFixPrompt,
3352
3922
  getClient,
3353
3923
  getCodeFix,
3354
3924
  getSuggestion,
3925
+ heuristicTokenCount,
3355
3926
  simulateDarkMode,
3356
3927
  transformForAllClients,
3357
3928
  transformForClient