@samva/markup 0.8.0 → 0.9.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.
Files changed (171) hide show
  1. package/AGENTS.md +90 -37
  2. package/README.md +188 -52
  3. package/dist/assets-C5duwTya.d.ts +40 -0
  4. package/dist/{brand-DcoVkh_S.d.ts → brand-B-0bN_Ir.d.ts} +2 -2
  5. package/dist/{render-vAv_4r9v.js → cascade-t6kxlgEI.js} +93 -280
  6. package/dist/channel-edit.d.ts +121 -0
  7. package/dist/channel-edit.js +609 -0
  8. package/dist/channel-specs-B6M_HlaV.js +29 -0
  9. package/dist/channel-specs-C2feJwuP.d.ts +13 -0
  10. package/dist/compatibility-DbISoFIq.js +589 -0
  11. package/dist/compiler.d.ts +10 -257
  12. package/dist/compiler.js +10 -730
  13. package/dist/{css-CS2jLID6.d.ts → css-BVQKICKC.d.ts} +3 -3
  14. package/dist/diagnostic-codes.d.ts +403 -0
  15. package/dist/diagnostic-codes.js +399 -0
  16. package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
  17. package/dist/diagnostics.d.ts +31 -2
  18. package/dist/diagnostics.js +1 -19
  19. package/dist/{elements-BzjjnoCS.d.ts → elements-cqldpaJt.d.ts} +3 -3
  20. package/dist/email/assets.d.ts +2 -40
  21. package/dist/email/brand-footer.d.ts +2 -2
  22. package/dist/email/brand-footer.js +4 -3
  23. package/dist/email/brand.d.ts +1 -1
  24. package/dist/email/brand.js +1 -1
  25. package/dist/email/components.d.ts +21 -10
  26. package/dist/email/components.js +46 -11
  27. package/dist/email/diagnostics.d.ts +2 -2
  28. package/dist/email/diagnostics.js +2 -1
  29. package/dist/email/elements.d.ts +1 -1
  30. package/dist/email/html.d.ts +2 -2
  31. package/dist/email/html.js +45 -2
  32. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  33. package/dist/email/jsx-runtime.d.ts +1 -1
  34. package/dist/email/render.d.ts +8 -18
  35. package/dist/email/render.js +163 -3
  36. package/dist/email/text.d.ts +1 -1
  37. package/dist/fmt.d.ts +32 -0
  38. package/dist/fmt.js +174 -0
  39. package/dist/{html-Bl9BAdqc.d.ts → html-BJepOwZ4.d.ts} +19 -5
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +2 -0
  42. package/dist/ir-aaqNE987.d.ts +252 -0
  43. package/dist/ir.d.ts +2 -0
  44. package/dist/ir.js +7 -0
  45. package/dist/preview-Ekdg1dXG.d.ts +100 -0
  46. package/dist/preview.d.ts +2 -0
  47. package/dist/preview.js +160 -0
  48. package/dist/profile-BH-OtdDA.js +3670 -0
  49. package/dist/render-ir.d.ts +19 -0
  50. package/dist/render-ir.js +826 -0
  51. package/dist/sms-segments.d.ts +13 -2
  52. package/dist/sms.d.ts +16 -0
  53. package/dist/sms.js +12 -0
  54. package/dist/{email/source-edit.d.ts → source-edit.d.ts} +55 -6
  55. package/dist/source-edit.js +343 -0
  56. package/dist/source-locations-C8wCsiAy.d.ts +23 -0
  57. package/dist/source-locations.d.ts +2 -2
  58. package/dist/source-locations.js +3 -89
  59. package/dist/static.d.ts +57 -0
  60. package/dist/static.js +69 -0
  61. package/dist/tailwind-CMYpZf1v.js +614 -0
  62. package/dist/template-_QE_rQND.d.ts +65 -0
  63. package/dist/template.d.ts +2 -2
  64. package/dist/template.js +15 -10
  65. package/dist/theme-css.d.ts +1 -1
  66. package/dist/theme-imports-lvBw3AD1.js +109 -0
  67. package/dist/transform-Ct2OMkFN.d.ts +153 -0
  68. package/dist/whatsapp.d.ts +57 -0
  69. package/dist/whatsapp.js +22 -0
  70. package/generated/contract.json +1782 -0
  71. package/generated/reference.md +353 -0
  72. package/package.json +41 -151
  73. package/skills/sml/SKILL.md +123 -0
  74. package/dist/ast-BrDtApwS.d.ts +0 -107
  75. package/dist/ast.d.ts +0 -2
  76. package/dist/ast.js +0 -124
  77. package/dist/candidate.d.ts +0 -29
  78. package/dist/candidate.js +0 -88
  79. package/dist/catalog-CKuOooB8.d.ts +0 -354
  80. package/dist/checks-D73Xizbw.js +0 -59366
  81. package/dist/checks.d.ts +0 -33
  82. package/dist/checks.js +0 -2
  83. package/dist/classes.d.ts +0 -26
  84. package/dist/classes.js +0 -184
  85. package/dist/compile-sms.d.ts +0 -28
  86. package/dist/compile-sms.js +0 -44
  87. package/dist/compile-whatsapp.d.ts +0 -56
  88. package/dist/compile-whatsapp.js +0 -186
  89. package/dist/compile.d.ts +0 -18
  90. package/dist/compile.js +0 -29
  91. package/dist/components.d.ts +0 -289
  92. package/dist/components.js +0 -106
  93. package/dist/conditionals.d.ts +0 -10
  94. package/dist/conditionals.js +0 -74
  95. package/dist/corpus-CPPfgAHN.d.ts +0 -106
  96. package/dist/diagnostics-OcQKQYSY.d.ts +0 -49
  97. package/dist/email/selection.d.ts +0 -2
  98. package/dist/email/selection.js +0 -91
  99. package/dist/email/source-edit.js +0 -185
  100. package/dist/email/stylesheets.d.ts +0 -15
  101. package/dist/email/stylesheets.js +0 -48
  102. package/dist/emit.d.ts +0 -51
  103. package/dist/emit.js +0 -151
  104. package/dist/interpolate.d.ts +0 -24
  105. package/dist/interpolate.js +0 -43
  106. package/dist/jsx-dev-runtime.d.ts +0 -11
  107. package/dist/jsx-dev-runtime.js +0 -15
  108. package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
  109. package/dist/jsx-runtime.d.ts +0 -2
  110. package/dist/jsx-runtime.js +0 -121
  111. package/dist/lexer.d.ts +0 -60
  112. package/dist/lexer.js +0 -281
  113. package/dist/model.d.ts +0 -24
  114. package/dist/model.js +0 -77
  115. package/dist/ops-BvwqHl7V.js +0 -630
  116. package/dist/ops.d.ts +0 -114
  117. package/dist/ops.js +0 -2
  118. package/dist/parser-t29cHLVL.d.ts +0 -396
  119. package/dist/parser.d.ts +0 -2
  120. package/dist/parser.js +0 -859
  121. package/dist/predicate-BI6uRRM-.js +0 -549
  122. package/dist/predicate-M-05Nd7R.d.ts +0 -56
  123. package/dist/predicate.d.ts +0 -2
  124. package/dist/predicate.js +0 -2
  125. package/dist/printer.d.ts +0 -6
  126. package/dist/printer.js +0 -122
  127. package/dist/registry-Dcp6QEK_.d.ts +0 -73
  128. package/dist/registry.d.ts +0 -2
  129. package/dist/registry.js +0 -646
  130. package/dist/render-template.d.ts +0 -45
  131. package/dist/render-template.js +0 -76
  132. package/dist/selection-CCArx9lU.d.ts +0 -91
  133. package/dist/showcase-egkusQBf.d.ts +0 -415
  134. package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
  135. package/dist/source-locations-DrCM82Ym.d.ts +0 -67
  136. package/dist/starters/category-mapping.d.ts +0 -13
  137. package/dist/starters/category-mapping.js +0 -49
  138. package/dist/starters/corpus.d.ts +0 -2
  139. package/dist/starters/corpus.js +0 -194
  140. package/dist/starters/library.d.ts +0 -18
  141. package/dist/starters/library.js +0 -692
  142. package/dist/starters/public-gallery.d.ts +0 -266
  143. package/dist/starters/public-gallery.js +0 -916
  144. package/dist/starters/public-handoff.d.ts +0 -164
  145. package/dist/starters/public-handoff.js +0 -361
  146. package/dist/starters/public-preview-artifacts.d.ts +0 -43
  147. package/dist/starters/public-preview-artifacts.js +0 -104
  148. package/dist/starters/public-preview.d.ts +0 -61
  149. package/dist/starters/public-preview.js +0 -83
  150. package/dist/starters/showcase.d.ts +0 -2
  151. package/dist/starters/showcase.js +0 -335
  152. package/dist/starters/sources.d.ts +0 -45
  153. package/dist/starters/sources.js +0 -7762
  154. package/dist/starters/theme.d.ts +0 -45
  155. package/dist/starters/theme.js +0 -77
  156. package/dist/starters.d.ts +0 -2
  157. package/dist/starters.js +0 -874
  158. package/dist/tailwind-BYq7Lo6u.js +0 -727
  159. package/dist/template-DWftqh4x.d.ts +0 -60
  160. package/dist/tsx-source-instrumentation.d.ts +0 -30
  161. package/dist/tsx-source-instrumentation.js +0 -115
  162. package/dist/tsx-tracer.d.ts +0 -112
  163. package/dist/tsx-tracer.js +0 -985
  164. package/dist/variables-J_L3qxRa.d.ts +0 -77
  165. package/dist/variables.d.ts +0 -2
  166. package/dist/variables.js +0 -141
  167. package/dist/version-BITr58kk.js +0 -96
  168. package/generated/sml-agent-contract.json +0 -709
  169. package/generated/sml-email-agent-contract.json +0 -615
  170. package/generated/sml-email-language-reference.md +0 -94
  171. package/generated/sml-language-reference.md +0 -195
@@ -0,0 +1,1782 @@
1
+ {
2
+ "contract": 1,
3
+ "irVersion": 1,
4
+ "template": {
5
+ "extension": ".tsx",
6
+ "directories": ["templates", "emails"],
7
+ "jsxImportSource": "@samva/markup/email",
8
+ "definition": {
9
+ "import": "@samva/markup",
10
+ "name": "defineTemplate"
11
+ },
12
+ "fields": ["id", "schema", "fixtures", "locale?", "email?", "sms?", "whatsapp?"],
13
+ "idPattern": "^(?=.{1,100}$)[a-z0-9]+(?:-[a-z0-9]+)*$",
14
+ "emailBody": "{ subject, preheader?, body }, each a function of the validated input",
15
+ "schema": "Any Standard JSON Schema converter, or a literal jsonSchema<Input>({ ... }); portable JSON Schema 2020-12. It validates every send and drives the binding checker. No coercion and no inserted defaults.",
16
+ "fixtures": "Named payloads that must validate. At least one. Each is a preview and a publish check.",
17
+ "execution": "Nothing runs at send time. A template compiles to IR and the send path renders the IR against the input. Locale and time zone are send options."
18
+ },
19
+ "profile": {
20
+ "forms": [
21
+ {
22
+ "form": "Text binding",
23
+ "example": "{input.name}",
24
+ "compilesTo": "bind"
25
+ },
26
+ {
27
+ "form": "Attribute binding",
28
+ "example": "{input.trackingUrl && <a href={input.trackingUrl}>Track</a>}",
29
+ "compilesTo": "bind on the attribute"
30
+ },
31
+ {
32
+ "form": "Template string",
33
+ "example": "`Order ${input.orderId}`",
34
+ "compilesTo": "concat"
35
+ },
36
+ {
37
+ "form": "Conditional",
38
+ "example": "{input.trackingUrl && <p>Track it</p>}",
39
+ "compilesTo": "if"
40
+ },
41
+ {
42
+ "form": "Either/or",
43
+ "example": "{input.vip ? <p>Gold</p> : <p>Standard</p>}",
44
+ "compilesTo": "if / else"
45
+ },
46
+ {
47
+ "form": "Conditions",
48
+ "example": "{input.items.length > 1 && input.total !== 0 && <p>Many</p>}",
49
+ "compilesTo": "a predicate (===, !==, <, >, !, &&, ||, .length)"
50
+ },
51
+ {
52
+ "form": "Loop",
53
+ "example": "{input.items.map((item, index) => <li key={index}>{item.title}</li>)}",
54
+ "compilesTo": "each"
55
+ },
56
+ {
57
+ "form": "Filtered loop or count",
58
+ "example": "{input.items.filter((item) => item.qty > 1).length}",
59
+ "compilesTo": "each with a where predicate, or a count"
60
+ },
61
+ {
62
+ "form": "Grid",
63
+ "example": "<Columns each={input.items} per={2}>{(item) => <Column><p>{item.title}</p></Column>}</Columns>",
64
+ "compilesTo": "each with a chunk size, one row per chunk"
65
+ },
66
+ {
67
+ "form": "Formatter",
68
+ "example": "{fmt.money(input.total, input.currency)}",
69
+ "compilesTo": "format"
70
+ },
71
+ {
72
+ "form": "Arithmetic",
73
+ "example": "{fmt.money(input.total * 2, input.currency)}",
74
+ "compilesTo": "an expression node, inside a formatter or condition"
75
+ },
76
+ {
77
+ "form": "Conditional class",
78
+ "example": "<p className={input.vip ? \"bg-amber-100\" : \"bg-white\"}>Hi</p>",
79
+ "compilesTo": "if on the attribute"
80
+ }
81
+ ],
82
+ "rejected": [
83
+ {
84
+ "form": "A call other than fmt.* and .map",
85
+ "example": "{input.name.toUpperCase()}",
86
+ "code": "dynamic-expression"
87
+ },
88
+ {
89
+ "form": "A statement in a body",
90
+ "example": "(input) => { const label = input.name; return <Email>{label}</Email>; }",
91
+ "code": "statement-in-body",
92
+ "scope": "body"
93
+ },
94
+ {
95
+ "form": "A hook",
96
+ "example": "{useState(0)}",
97
+ "code": "hook-call"
98
+ },
99
+ {
100
+ "form": "An event handler",
101
+ "example": "<a href=\"/x\" onClick={() => 1}>x</a>",
102
+ "code": "event-handler"
103
+ },
104
+ {
105
+ "form": "A class name built at run time",
106
+ "example": "<p className={`text-${input.name}`}>x</p>",
107
+ "code": "dynamic-class"
108
+ }
109
+ ]
110
+ },
111
+ "formatters": [
112
+ {
113
+ "name": "fmt.money",
114
+ "signature": "money(amount: number, currency: string, locale?: string): string"
115
+ },
116
+ {
117
+ "name": "fmt.number",
118
+ "signature": "number(value: number, options?: Intl.NumberFormatOptions, locale?: string): string"
119
+ },
120
+ {
121
+ "name": "fmt.date",
122
+ "signature": "date(iso: string, style?: DateStyle, locale?: string): string"
123
+ },
124
+ {
125
+ "name": "fmt.time",
126
+ "signature": "time(iso: string, style?: DateStyle, locale?: string): string"
127
+ },
128
+ {
129
+ "name": "fmt.plural",
130
+ "signature": "plural(count: number, forms: PluralForms, locale?: string): string"
131
+ },
132
+ {
133
+ "name": "fmt.list",
134
+ "signature": "list(values: readonly string[], locale?: string): string"
135
+ }
136
+ ],
137
+ "components": [
138
+ {
139
+ "name": "Email",
140
+ "import": "@samva/markup/email/components",
141
+ "props": [
142
+ {
143
+ "name": "className",
144
+ "type": "string",
145
+ "optional": true,
146
+ "description": "CSS classes applied to the primitive's content element."
147
+ },
148
+ {
149
+ "name": "lang",
150
+ "type": "string",
151
+ "optional": true
152
+ },
153
+ {
154
+ "name": "dir",
155
+ "type": "\"ltr\" | \"rtl\"",
156
+ "optional": true
157
+ },
158
+ {
159
+ "name": "title",
160
+ "type": "string",
161
+ "optional": true,
162
+ "description": "Document title; several clients use it as the fallback preview line."
163
+ },
164
+ {
165
+ "name": "backgroundColor",
166
+ "type": "string",
167
+ "optional": true
168
+ },
169
+ {
170
+ "name": "style",
171
+ "type": "EmailStyle",
172
+ "optional": true,
173
+ "description": "Applied to the content cell, where Yahoo and AOL keep body styling."
174
+ },
175
+ {
176
+ "name": "children",
177
+ "type": "EmailChild",
178
+ "optional": true
179
+ }
180
+ ]
181
+ },
182
+ {
183
+ "name": "Section",
184
+ "import": "@samva/markup/email/components",
185
+ "props": [
186
+ {
187
+ "name": "className",
188
+ "type": "string",
189
+ "optional": true,
190
+ "description": "CSS classes applied to the primitive's content element."
191
+ },
192
+ {
193
+ "name": "align",
194
+ "type": "\"left\" | \"center\" | \"right\"",
195
+ "optional": true
196
+ },
197
+ {
198
+ "name": "width",
199
+ "type": "number | string",
200
+ "optional": true
201
+ },
202
+ {
203
+ "name": "bgcolor",
204
+ "type": "string",
205
+ "optional": true
206
+ },
207
+ {
208
+ "name": "style",
209
+ "type": "EmailStyle",
210
+ "optional": true,
211
+ "description": "Applied to the content cell so padding survives Outlook and Klaviyo."
212
+ },
213
+ {
214
+ "name": "tableStyle",
215
+ "type": "EmailStyle",
216
+ "optional": true
217
+ },
218
+ {
219
+ "name": "children",
220
+ "type": "EmailChild",
221
+ "optional": true
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ "name": "Columns",
227
+ "import": "@samva/markup/email/components",
228
+ "props": [
229
+ {
230
+ "name": "className",
231
+ "type": "string",
232
+ "optional": true,
233
+ "description": "CSS classes applied to the primitive's content element."
234
+ },
235
+ {
236
+ "name": "align",
237
+ "type": "\"left\" | \"center\" | \"right\"",
238
+ "optional": true
239
+ },
240
+ {
241
+ "name": "width",
242
+ "type": "number | string",
243
+ "optional": true
244
+ },
245
+ {
246
+ "name": "style",
247
+ "type": "EmailStyle",
248
+ "optional": true
249
+ },
250
+ {
251
+ "name": "each",
252
+ "type": "readonly Item[]",
253
+ "optional": true,
254
+ "description": "Repeat the cells for each item: `children` is then a function from an item (and its index in the row) to a `Column`. Without `per` all items share one row."
255
+ },
256
+ {
257
+ "name": "per",
258
+ "type": "number",
259
+ "optional": true,
260
+ "description": "With `each`, the cells in a row; each chunk of items is its own `Columns` table."
261
+ },
262
+ {
263
+ "name": "children",
264
+ "type": "EmailChild | ((item: Item, index: number) => EmailChild)",
265
+ "optional": true,
266
+ "description": "`Column` elements, or with `each` a function that returns one."
267
+ }
268
+ ]
269
+ },
270
+ {
271
+ "name": "Column",
272
+ "import": "@samva/markup/email/components",
273
+ "props": [
274
+ {
275
+ "name": "className",
276
+ "type": "string",
277
+ "optional": true,
278
+ "description": "CSS classes applied to the primitive's content element."
279
+ },
280
+ {
281
+ "name": "align",
282
+ "type": "\"left\" | \"center\" | \"right\"",
283
+ "optional": true
284
+ },
285
+ {
286
+ "name": "valign",
287
+ "type": "\"top\" | \"middle\" | \"bottom\"",
288
+ "optional": true
289
+ },
290
+ {
291
+ "name": "width",
292
+ "type": "number | string",
293
+ "optional": true
294
+ },
295
+ {
296
+ "name": "style",
297
+ "type": "EmailStyle",
298
+ "optional": true
299
+ },
300
+ {
301
+ "name": "children",
302
+ "type": "EmailChild",
303
+ "optional": true
304
+ }
305
+ ]
306
+ },
307
+ {
308
+ "name": "Button",
309
+ "import": "@samva/markup/email/components",
310
+ "props": [
311
+ {
312
+ "name": "className",
313
+ "type": "string",
314
+ "optional": true,
315
+ "description": "CSS classes applied to the primitive's content element."
316
+ },
317
+ {
318
+ "name": "href",
319
+ "type": "string",
320
+ "optional": false
321
+ },
322
+ {
323
+ "name": "width",
324
+ "type": "number",
325
+ "optional": true,
326
+ "description": "Pixel width of the box. Classic Outlook draws the VML shape at a fixed width, so without this it is estimated from the label and `paddingX`, and the anchor sizes to its label."
327
+ },
328
+ {
329
+ "name": "height",
330
+ "type": "number",
331
+ "optional": true,
332
+ "description": "Pixel height; classic Outlook's VML fallback needs it."
333
+ },
334
+ {
335
+ "name": "paddingX",
336
+ "type": "number",
337
+ "optional": true,
338
+ "description": "Horizontal padding on each side of the label when no `width` fixes the box."
339
+ },
340
+ {
341
+ "name": "backgroundColor",
342
+ "type": "string",
343
+ "optional": true
344
+ },
345
+ {
346
+ "name": "color",
347
+ "type": "string",
348
+ "optional": true
349
+ },
350
+ {
351
+ "name": "borderRadius",
352
+ "type": "number",
353
+ "optional": true
354
+ },
355
+ {
356
+ "name": "borderColor",
357
+ "type": "string",
358
+ "optional": true
359
+ },
360
+ {
361
+ "name": "borderWidth",
362
+ "type": "number",
363
+ "optional": true
364
+ },
365
+ {
366
+ "name": "fontSize",
367
+ "type": "number",
368
+ "optional": true
369
+ },
370
+ {
371
+ "name": "fontFamily",
372
+ "type": "string",
373
+ "optional": true
374
+ },
375
+ {
376
+ "name": "align",
377
+ "type": "\"left\" | \"center\" | \"right\"",
378
+ "optional": true
379
+ },
380
+ {
381
+ "name": "style",
382
+ "type": "EmailStyle",
383
+ "optional": true
384
+ },
385
+ {
386
+ "name": "children",
387
+ "type": "EmailChild",
388
+ "optional": true,
389
+ "description": "The label. A string label is required for the classic Outlook fallback."
390
+ }
391
+ ]
392
+ },
393
+ {
394
+ "name": "Spacer",
395
+ "import": "@samva/markup/email/components",
396
+ "props": [
397
+ {
398
+ "name": "height",
399
+ "type": "number",
400
+ "optional": false
401
+ }
402
+ ]
403
+ },
404
+ {
405
+ "name": "Divider",
406
+ "import": "@samva/markup/email/components",
407
+ "props": [
408
+ {
409
+ "name": "className",
410
+ "type": "string",
411
+ "optional": true,
412
+ "description": "CSS classes applied to the primitive's content element."
413
+ },
414
+ {
415
+ "name": "color",
416
+ "type": "string",
417
+ "optional": true
418
+ },
419
+ {
420
+ "name": "thickness",
421
+ "type": "number",
422
+ "optional": true
423
+ },
424
+ {
425
+ "name": "style",
426
+ "type": "EmailStyle",
427
+ "optional": true
428
+ }
429
+ ]
430
+ },
431
+ {
432
+ "name": "Image",
433
+ "import": "@samva/markup/email/components",
434
+ "props": [
435
+ {
436
+ "name": "className",
437
+ "type": "string",
438
+ "optional": true,
439
+ "description": "CSS classes applied to the primitive's content element."
440
+ },
441
+ {
442
+ "name": "src",
443
+ "type": "string",
444
+ "optional": false
445
+ },
446
+ {
447
+ "name": "alt",
448
+ "type": "string",
449
+ "optional": false,
450
+ "description": "Required. Use an empty string only for images that carry no meaning."
451
+ },
452
+ {
453
+ "name": "width",
454
+ "type": "number | string",
455
+ "optional": true
456
+ },
457
+ {
458
+ "name": "height",
459
+ "type": "number | string",
460
+ "optional": true
461
+ },
462
+ {
463
+ "name": "align",
464
+ "type": "\"left\" | \"center\" | \"right\"",
465
+ "optional": true
466
+ },
467
+ {
468
+ "name": "style",
469
+ "type": "EmailStyle",
470
+ "optional": true
471
+ }
472
+ ]
473
+ },
474
+ {
475
+ "name": "Link",
476
+ "import": "@samva/markup/email/components",
477
+ "props": [
478
+ {
479
+ "name": "className",
480
+ "type": "string",
481
+ "optional": true,
482
+ "description": "CSS classes applied to the primitive's content element."
483
+ },
484
+ {
485
+ "name": "href",
486
+ "type": "string",
487
+ "optional": false
488
+ },
489
+ {
490
+ "name": "target",
491
+ "type": "string",
492
+ "optional": true
493
+ },
494
+ {
495
+ "name": "rel",
496
+ "type": "string",
497
+ "optional": true
498
+ },
499
+ {
500
+ "name": "style",
501
+ "type": "EmailStyle",
502
+ "optional": true
503
+ },
504
+ {
505
+ "name": "children",
506
+ "type": "EmailChild",
507
+ "optional": true
508
+ }
509
+ ]
510
+ },
511
+ {
512
+ "name": "BrandLogo",
513
+ "import": "samva:brand",
514
+ "props": [
515
+ {
516
+ "name": "width",
517
+ "type": "number",
518
+ "optional": true,
519
+ "description": "Rendered width in pixels; the height follows the image."
520
+ },
521
+ {
522
+ "name": "alt",
523
+ "type": "string",
524
+ "optional": true,
525
+ "description": "Defaults to the brand footer's company name."
526
+ },
527
+ {
528
+ "name": "align",
529
+ "type": "\"left\" | \"center\" | \"right\"",
530
+ "optional": true
531
+ },
532
+ {
533
+ "name": "className",
534
+ "type": "string",
535
+ "optional": true
536
+ }
537
+ ]
538
+ },
539
+ {
540
+ "name": "BrandFooter",
541
+ "import": "samva:brand",
542
+ "props": [
543
+ {
544
+ "name": "unsubscribeLabel",
545
+ "type": "string",
546
+ "optional": true,
547
+ "description": "Text of the unsubscribe link."
548
+ },
549
+ {
550
+ "name": "className",
551
+ "type": "string",
552
+ "optional": true
553
+ }
554
+ ]
555
+ }
556
+ ],
557
+ "channelComponents": [
558
+ {
559
+ "name": "Sms",
560
+ "import": "@samva/markup/sms",
561
+ "props": [
562
+ {
563
+ "name": "category",
564
+ "type": "SmsCategory",
565
+ "optional": true
566
+ },
567
+ {
568
+ "name": "children",
569
+ "type": "EmailChild",
570
+ "optional": true
571
+ }
572
+ ]
573
+ },
574
+ {
575
+ "name": "WhatsApp",
576
+ "import": "@samva/markup/whatsapp",
577
+ "props": [
578
+ {
579
+ "name": "name",
580
+ "type": "string",
581
+ "optional": true
582
+ },
583
+ {
584
+ "name": "language",
585
+ "type": "string",
586
+ "optional": false,
587
+ "description": "The Meta language code, for example `en_US`."
588
+ },
589
+ {
590
+ "name": "category",
591
+ "type": "WhatsAppCategory",
592
+ "optional": false
593
+ },
594
+ {
595
+ "name": "children",
596
+ "type": "EmailChild",
597
+ "optional": true
598
+ }
599
+ ]
600
+ },
601
+ {
602
+ "name": "WhatsApp.Header",
603
+ "import": "@samva/markup/whatsapp",
604
+ "props": [
605
+ {
606
+ "name": "type",
607
+ "type": "WhatsAppHeaderType",
608
+ "optional": true,
609
+ "description": "`text` by default; for `image`, `video` and `document` the child is the media URL."
610
+ },
611
+ {
612
+ "name": "children",
613
+ "type": "EmailChild",
614
+ "optional": true
615
+ }
616
+ ]
617
+ },
618
+ {
619
+ "name": "WhatsApp.Body",
620
+ "import": "@samva/markup/whatsapp",
621
+ "props": [
622
+ {
623
+ "name": "children",
624
+ "type": "EmailChild",
625
+ "optional": true
626
+ }
627
+ ]
628
+ },
629
+ {
630
+ "name": "WhatsApp.Footer",
631
+ "import": "@samva/markup/whatsapp",
632
+ "props": [
633
+ {
634
+ "name": "children",
635
+ "type": "EmailChild",
636
+ "optional": true
637
+ }
638
+ ]
639
+ },
640
+ {
641
+ "name": "WhatsApp.Buttons",
642
+ "import": "@samva/markup/whatsapp",
643
+ "props": [
644
+ {
645
+ "name": "children",
646
+ "type": "EmailChild",
647
+ "optional": true
648
+ }
649
+ ]
650
+ },
651
+ {
652
+ "name": "WhatsApp.UrlButton",
653
+ "import": "@samva/markup/whatsapp",
654
+ "props": [
655
+ {
656
+ "name": "url",
657
+ "type": "string",
658
+ "optional": false
659
+ },
660
+ {
661
+ "name": "children",
662
+ "type": "EmailChild",
663
+ "optional": true,
664
+ "description": "The button label."
665
+ }
666
+ ]
667
+ },
668
+ {
669
+ "name": "WhatsApp.PhoneButton",
670
+ "import": "@samva/markup/whatsapp",
671
+ "props": [
672
+ {
673
+ "name": "phone",
674
+ "type": "string",
675
+ "optional": false
676
+ },
677
+ {
678
+ "name": "children",
679
+ "type": "EmailChild",
680
+ "optional": true,
681
+ "description": "The button label."
682
+ }
683
+ ]
684
+ },
685
+ {
686
+ "name": "WhatsApp.QuickReplyButton",
687
+ "import": "@samva/markup/whatsapp",
688
+ "props": [
689
+ {
690
+ "name": "children",
691
+ "type": "EmailChild",
692
+ "optional": true,
693
+ "description": "The button label."
694
+ }
695
+ ]
696
+ },
697
+ {
698
+ "name": "WhatsApp.CopyCodeButton",
699
+ "import": "@samva/markup/whatsapp",
700
+ "props": [
701
+ {
702
+ "name": "children",
703
+ "type": "EmailChild",
704
+ "optional": true,
705
+ "description": "The code the button copies."
706
+ }
707
+ ]
708
+ }
709
+ ],
710
+ "elements": {
711
+ "html": {
712
+ "content": "flow",
713
+ "layout": "document",
714
+ "attributes": {
715
+ "xmlns": {
716
+ "type": "string"
717
+ },
718
+ "xmlns:o": {
719
+ "type": "string"
720
+ },
721
+ "xmlns:v": {
722
+ "type": "string"
723
+ },
724
+ "xmlns:w": {
725
+ "type": "string"
726
+ }
727
+ },
728
+ "childElements": ["head", "body"]
729
+ },
730
+ "head": {
731
+ "content": "flow",
732
+ "layout": "metadata",
733
+ "attributes": {},
734
+ "childElements": ["title", "meta", "style"]
735
+ },
736
+ "title": {
737
+ "content": "text",
738
+ "layout": "metadata",
739
+ "attributes": {}
740
+ },
741
+ "style": {
742
+ "content": "raw",
743
+ "layout": "metadata",
744
+ "attributes": {
745
+ "type": {
746
+ "type": "string"
747
+ }
748
+ }
749
+ },
750
+ "meta": {
751
+ "content": "void",
752
+ "layout": "metadata",
753
+ "attributes": {
754
+ "name": {
755
+ "type": "string"
756
+ },
757
+ "content": {
758
+ "type": "string"
759
+ },
760
+ "charset": {
761
+ "type": "string"
762
+ },
763
+ "http-equiv": {
764
+ "type": "string"
765
+ }
766
+ }
767
+ },
768
+ "body": {
769
+ "content": "flow",
770
+ "layout": "block",
771
+ "attributes": {
772
+ "bgcolor": {
773
+ "type": "string"
774
+ }
775
+ }
776
+ },
777
+ "div": {
778
+ "content": "flow",
779
+ "layout": "block",
780
+ "attributes": {
781
+ "align": {
782
+ "type": "string",
783
+ "values": ["left", "center", "right"]
784
+ }
785
+ }
786
+ },
787
+ "section": {
788
+ "content": "flow",
789
+ "layout": "block",
790
+ "attributes": {
791
+ "align": {
792
+ "type": "string",
793
+ "values": ["left", "center", "right"]
794
+ }
795
+ }
796
+ },
797
+ "article": {
798
+ "content": "flow",
799
+ "layout": "block",
800
+ "attributes": {
801
+ "align": {
802
+ "type": "string",
803
+ "values": ["left", "center", "right"]
804
+ }
805
+ }
806
+ },
807
+ "header": {
808
+ "content": "flow",
809
+ "layout": "block",
810
+ "attributes": {
811
+ "align": {
812
+ "type": "string",
813
+ "values": ["left", "center", "right"]
814
+ }
815
+ }
816
+ },
817
+ "footer": {
818
+ "content": "flow",
819
+ "layout": "block",
820
+ "attributes": {
821
+ "align": {
822
+ "type": "string",
823
+ "values": ["left", "center", "right"]
824
+ }
825
+ }
826
+ },
827
+ "main": {
828
+ "content": "flow",
829
+ "layout": "block",
830
+ "attributes": {
831
+ "align": {
832
+ "type": "string",
833
+ "values": ["left", "center", "right"]
834
+ }
835
+ }
836
+ },
837
+ "center": {
838
+ "content": "flow",
839
+ "layout": "block",
840
+ "attributes": {}
841
+ },
842
+ "p": {
843
+ "content": "flow",
844
+ "layout": "block",
845
+ "attributes": {
846
+ "align": {
847
+ "type": "string",
848
+ "values": ["left", "center", "right"]
849
+ }
850
+ }
851
+ },
852
+ "blockquote": {
853
+ "content": "flow",
854
+ "layout": "block",
855
+ "attributes": {
856
+ "align": {
857
+ "type": "string",
858
+ "values": ["left", "center", "right"]
859
+ }
860
+ }
861
+ },
862
+ "pre": {
863
+ "content": "flow",
864
+ "layout": "preformatted",
865
+ "attributes": {}
866
+ },
867
+ "h1": {
868
+ "content": "flow",
869
+ "layout": "block",
870
+ "attributes": {
871
+ "align": {
872
+ "type": "string",
873
+ "values": ["left", "center", "right"]
874
+ }
875
+ }
876
+ },
877
+ "h2": {
878
+ "content": "flow",
879
+ "layout": "block",
880
+ "attributes": {
881
+ "align": {
882
+ "type": "string",
883
+ "values": ["left", "center", "right"]
884
+ }
885
+ }
886
+ },
887
+ "h3": {
888
+ "content": "flow",
889
+ "layout": "block",
890
+ "attributes": {
891
+ "align": {
892
+ "type": "string",
893
+ "values": ["left", "center", "right"]
894
+ }
895
+ }
896
+ },
897
+ "h4": {
898
+ "content": "flow",
899
+ "layout": "block",
900
+ "attributes": {
901
+ "align": {
902
+ "type": "string",
903
+ "values": ["left", "center", "right"]
904
+ }
905
+ }
906
+ },
907
+ "h5": {
908
+ "content": "flow",
909
+ "layout": "block",
910
+ "attributes": {
911
+ "align": {
912
+ "type": "string",
913
+ "values": ["left", "center", "right"]
914
+ }
915
+ }
916
+ },
917
+ "h6": {
918
+ "content": "flow",
919
+ "layout": "block",
920
+ "attributes": {
921
+ "align": {
922
+ "type": "string",
923
+ "values": ["left", "center", "right"]
924
+ }
925
+ }
926
+ },
927
+ "span": {
928
+ "content": "flow",
929
+ "layout": "inline",
930
+ "attributes": {}
931
+ },
932
+ "strong": {
933
+ "content": "flow",
934
+ "layout": "inline",
935
+ "attributes": {}
936
+ },
937
+ "b": {
938
+ "content": "flow",
939
+ "layout": "inline",
940
+ "attributes": {}
941
+ },
942
+ "em": {
943
+ "content": "flow",
944
+ "layout": "inline",
945
+ "attributes": {}
946
+ },
947
+ "i": {
948
+ "content": "flow",
949
+ "layout": "inline",
950
+ "attributes": {}
951
+ },
952
+ "u": {
953
+ "content": "flow",
954
+ "layout": "inline",
955
+ "attributes": {}
956
+ },
957
+ "s": {
958
+ "content": "flow",
959
+ "layout": "inline",
960
+ "attributes": {}
961
+ },
962
+ "small": {
963
+ "content": "flow",
964
+ "layout": "inline",
965
+ "attributes": {}
966
+ },
967
+ "code": {
968
+ "content": "flow",
969
+ "layout": "inline",
970
+ "attributes": {}
971
+ },
972
+ "a": {
973
+ "content": "flow",
974
+ "layout": "link",
975
+ "attributes": {
976
+ "href": {
977
+ "type": "url",
978
+ "required": true
979
+ },
980
+ "target": {
981
+ "type": "string"
982
+ },
983
+ "rel": {
984
+ "type": "string"
985
+ },
986
+ "name": {
987
+ "type": "string"
988
+ }
989
+ }
990
+ },
991
+ "img": {
992
+ "content": "void",
993
+ "layout": "image",
994
+ "attributes": {
995
+ "src": {
996
+ "type": "url",
997
+ "required": true
998
+ },
999
+ "alt": {
1000
+ "type": "string",
1001
+ "required": true
1002
+ },
1003
+ "width": {
1004
+ "type": "measure"
1005
+ },
1006
+ "height": {
1007
+ "type": "measure"
1008
+ },
1009
+ "border": {
1010
+ "type": "measure"
1011
+ },
1012
+ "align": {
1013
+ "type": "string",
1014
+ "values": ["left", "center", "right"]
1015
+ }
1016
+ }
1017
+ },
1018
+ "table": {
1019
+ "content": "flow",
1020
+ "layout": "table",
1021
+ "attributes": {
1022
+ "width": {
1023
+ "type": "measure"
1024
+ },
1025
+ "height": {
1026
+ "type": "measure"
1027
+ },
1028
+ "border": {
1029
+ "type": "measure"
1030
+ },
1031
+ "cellpadding": {
1032
+ "type": "measure"
1033
+ },
1034
+ "cellspacing": {
1035
+ "type": "measure"
1036
+ },
1037
+ "align": {
1038
+ "type": "string",
1039
+ "values": ["left", "center", "right"]
1040
+ },
1041
+ "bgcolor": {
1042
+ "type": "string"
1043
+ }
1044
+ },
1045
+ "childElements": ["caption", "colgroup", "thead", "tbody", "tfoot", "tr"]
1046
+ },
1047
+ "caption": {
1048
+ "content": "flow",
1049
+ "layout": "block",
1050
+ "attributes": {
1051
+ "align": {
1052
+ "type": "string",
1053
+ "values": ["left", "center", "right"]
1054
+ }
1055
+ }
1056
+ },
1057
+ "colgroup": {
1058
+ "content": "flow",
1059
+ "layout": "table-section",
1060
+ "attributes": {
1061
+ "span": {
1062
+ "type": "number"
1063
+ },
1064
+ "width": {
1065
+ "type": "measure"
1066
+ }
1067
+ },
1068
+ "childElements": ["col"]
1069
+ },
1070
+ "col": {
1071
+ "content": "void",
1072
+ "layout": "table-section",
1073
+ "attributes": {
1074
+ "span": {
1075
+ "type": "number"
1076
+ },
1077
+ "width": {
1078
+ "type": "measure"
1079
+ }
1080
+ }
1081
+ },
1082
+ "thead": {
1083
+ "content": "flow",
1084
+ "layout": "table-section",
1085
+ "attributes": {
1086
+ "align": {
1087
+ "type": "string",
1088
+ "values": ["left", "center", "right"]
1089
+ },
1090
+ "valign": {
1091
+ "type": "string",
1092
+ "values": ["top", "middle", "bottom"]
1093
+ },
1094
+ "bgcolor": {
1095
+ "type": "string"
1096
+ }
1097
+ },
1098
+ "childElements": ["tr"]
1099
+ },
1100
+ "tbody": {
1101
+ "content": "flow",
1102
+ "layout": "table-section",
1103
+ "attributes": {
1104
+ "align": {
1105
+ "type": "string",
1106
+ "values": ["left", "center", "right"]
1107
+ },
1108
+ "valign": {
1109
+ "type": "string",
1110
+ "values": ["top", "middle", "bottom"]
1111
+ },
1112
+ "bgcolor": {
1113
+ "type": "string"
1114
+ }
1115
+ },
1116
+ "childElements": ["tr"]
1117
+ },
1118
+ "tfoot": {
1119
+ "content": "flow",
1120
+ "layout": "table-section",
1121
+ "attributes": {
1122
+ "align": {
1123
+ "type": "string",
1124
+ "values": ["left", "center", "right"]
1125
+ },
1126
+ "valign": {
1127
+ "type": "string",
1128
+ "values": ["top", "middle", "bottom"]
1129
+ },
1130
+ "bgcolor": {
1131
+ "type": "string"
1132
+ }
1133
+ },
1134
+ "childElements": ["tr"]
1135
+ },
1136
+ "tr": {
1137
+ "content": "flow",
1138
+ "layout": "row",
1139
+ "attributes": {
1140
+ "align": {
1141
+ "type": "string",
1142
+ "values": ["left", "center", "right"]
1143
+ },
1144
+ "valign": {
1145
+ "type": "string",
1146
+ "values": ["top", "middle", "bottom"]
1147
+ },
1148
+ "bgcolor": {
1149
+ "type": "string"
1150
+ },
1151
+ "height": {
1152
+ "type": "measure"
1153
+ }
1154
+ },
1155
+ "childElements": ["td", "th"]
1156
+ },
1157
+ "td": {
1158
+ "content": "flow",
1159
+ "layout": "cell",
1160
+ "attributes": {
1161
+ "align": {
1162
+ "type": "string",
1163
+ "values": ["left", "center", "right"]
1164
+ },
1165
+ "valign": {
1166
+ "type": "string",
1167
+ "values": ["top", "middle", "bottom"]
1168
+ },
1169
+ "width": {
1170
+ "type": "measure"
1171
+ },
1172
+ "height": {
1173
+ "type": "measure"
1174
+ },
1175
+ "bgcolor": {
1176
+ "type": "string"
1177
+ },
1178
+ "colspan": {
1179
+ "type": "number"
1180
+ },
1181
+ "rowspan": {
1182
+ "type": "number"
1183
+ }
1184
+ }
1185
+ },
1186
+ "th": {
1187
+ "content": "flow",
1188
+ "layout": "cell",
1189
+ "attributes": {
1190
+ "align": {
1191
+ "type": "string",
1192
+ "values": ["left", "center", "right"]
1193
+ },
1194
+ "valign": {
1195
+ "type": "string",
1196
+ "values": ["top", "middle", "bottom"]
1197
+ },
1198
+ "width": {
1199
+ "type": "measure"
1200
+ },
1201
+ "height": {
1202
+ "type": "measure"
1203
+ },
1204
+ "bgcolor": {
1205
+ "type": "string"
1206
+ },
1207
+ "colspan": {
1208
+ "type": "number"
1209
+ },
1210
+ "rowspan": {
1211
+ "type": "number"
1212
+ }
1213
+ }
1214
+ },
1215
+ "ul": {
1216
+ "content": "flow",
1217
+ "layout": "list",
1218
+ "attributes": {},
1219
+ "childElements": ["li"]
1220
+ },
1221
+ "ol": {
1222
+ "content": "flow",
1223
+ "layout": "list",
1224
+ "attributes": {
1225
+ "start": {
1226
+ "type": "number"
1227
+ },
1228
+ "type": {
1229
+ "type": "string"
1230
+ }
1231
+ },
1232
+ "childElements": ["li"]
1233
+ },
1234
+ "li": {
1235
+ "content": "flow",
1236
+ "layout": "list-item",
1237
+ "attributes": {}
1238
+ },
1239
+ "br": {
1240
+ "content": "void",
1241
+ "layout": "break",
1242
+ "attributes": {}
1243
+ },
1244
+ "hr": {
1245
+ "content": "void",
1246
+ "layout": "rule",
1247
+ "attributes": {
1248
+ "width": {
1249
+ "type": "measure"
1250
+ },
1251
+ "size": {
1252
+ "type": "number"
1253
+ },
1254
+ "align": {
1255
+ "type": "string",
1256
+ "values": ["left", "center", "right"]
1257
+ }
1258
+ }
1259
+ }
1260
+ },
1261
+ "globalAttributes": {
1262
+ "id": {
1263
+ "type": "string"
1264
+ },
1265
+ "title": {
1266
+ "type": "string"
1267
+ },
1268
+ "lang": {
1269
+ "type": "string"
1270
+ },
1271
+ "dir": {
1272
+ "type": "string",
1273
+ "values": ["ltr", "rtl", "auto"]
1274
+ },
1275
+ "role": {
1276
+ "type": "string"
1277
+ }
1278
+ },
1279
+ "styles": {
1280
+ "dialect": "Tailwind classes plus theme.css tokens. Classes must be literal.",
1281
+ "themeLayers": [
1282
+ "@import \"./starter.css\";",
1283
+ "@import \"samva:brand\";",
1284
+ "the project's own @theme"
1285
+ ],
1286
+ "fonts": "WOFF2 files in the project through @font-face; a stack must end in a generic family."
1287
+ },
1288
+ "channels": {
1289
+ "sms": {
1290
+ "categories": ["transactional", "promotional", "service_implicit"],
1291
+ "attributes": ["category"]
1292
+ },
1293
+ "whatsapp": {
1294
+ "categories": ["marketing", "utility", "authentication"],
1295
+ "attributes": ["name", "language", "category"],
1296
+ "headerTypes": ["text", "image", "video", "document"]
1297
+ }
1298
+ },
1299
+ "diagnostics": [
1300
+ {
1301
+ "code": "brand-import-conflict",
1302
+ "summary": "The project imports two different brands.",
1303
+ "fix": "Import one brand specifier everywhere."
1304
+ },
1305
+ {
1306
+ "code": "brand-unavailable",
1307
+ "summary": "The build has no brand to resolve a samva:brand import.",
1308
+ "fix": "Create or select the brand the import names."
1309
+ },
1310
+ {
1311
+ "code": "button-dynamic-label",
1312
+ "summary": "A Button label is read at render time, so classic Outlook cannot size it.",
1313
+ "fix": "Give the button a width: <Button width={200}>."
1314
+ },
1315
+ {
1316
+ "code": "button-outlook-fallback",
1317
+ "summary": "Classic Outlook needs a height and a plain string label to draw the button.",
1318
+ "fix": "Give <Button> a height and a string label."
1319
+ },
1320
+ {
1321
+ "code": "caniemail/unknown-coverage",
1322
+ "summary": "A feature has no client-support data.",
1323
+ "fix": "Verify it in the target clients."
1324
+ },
1325
+ {
1326
+ "code": "css-conditional-variable",
1327
+ "summary": "A custom property is redefined inside a conditional at-rule.",
1328
+ "fix": "Keep the override on a class rule."
1329
+ },
1330
+ {
1331
+ "code": "css-import-unresolved",
1332
+ "summary": "A stylesheet @import cannot be resolved.",
1333
+ "fix": "Import the stylesheet from the template module, or use the theme.css layers."
1334
+ },
1335
+ {
1336
+ "code": "css-parse-error",
1337
+ "summary": "A stylesheet does not parse.",
1338
+ "fix": "Fix the CSS at the reported location."
1339
+ },
1340
+ {
1341
+ "code": "css-unsupported-at-rule",
1342
+ "summary": "An at-rule is not applied to email output.",
1343
+ "fix": "Remove it or move the declarations into a class rule."
1344
+ },
1345
+ {
1346
+ "code": "dangerous-html",
1347
+ "summary": "dangerouslySetInnerHTML; there is no raw-HTML escape hatch.",
1348
+ "fix": "Write the markup as JSX."
1349
+ },
1350
+ {
1351
+ "code": "duplicate-attribute",
1352
+ "summary": "An attribute is written twice.",
1353
+ "fix": "Keep one."
1354
+ },
1355
+ {
1356
+ "code": "dynamic-class",
1357
+ "summary": "A class name is built at run time, and Tailwind cannot see it.",
1358
+ "fix": "Write classes literally, or choose between literal lists: input.vip ? \"bg-amber-100\" : \"bg-white\"."
1359
+ },
1360
+ {
1361
+ "code": "dynamic-definition",
1362
+ "summary": "A definition field is spread or computed instead of written out.",
1363
+ "fix": "Write each field as name: value."
1364
+ },
1365
+ {
1366
+ "code": "dynamic-expression",
1367
+ "summary": "An expression outside the static profile: a call other than fmt.* and .map, a computed value, or a spread.",
1368
+ "fix": "Compute the value where the message is sent and bind it from the payload."
1369
+ },
1370
+ {
1371
+ "code": "dynamic-numeric-prop",
1372
+ "summary": "A prop that sizes layout at compile time is not a number literal.",
1373
+ "fix": "Write the number literally, for example height={44}."
1374
+ },
1375
+ {
1376
+ "code": "email-font-fallback",
1377
+ "summary": "A font stack names a project font without a generic fallback.",
1378
+ "fix": "End the stack with sans-serif, serif or monospace."
1379
+ },
1380
+ {
1381
+ "code": "email-font-family",
1382
+ "summary": "@font-face declares no font-family.",
1383
+ "fix": "Add font-family."
1384
+ },
1385
+ {
1386
+ "code": "email-font-format",
1387
+ "summary": "@font-face uses a format other than WOFF2.",
1388
+ "fix": "Convert the file to .woff2."
1389
+ },
1390
+ {
1391
+ "code": "email-font-missing",
1392
+ "summary": "@font-face names a file the project does not hold.",
1393
+ "fix": "Add the file; paths resolve from the stylesheet's directory."
1394
+ },
1395
+ {
1396
+ "code": "email-font-src",
1397
+ "summary": "@font-face declares no usable url() src.",
1398
+ "fix": "Add a url() pointing at a .woff2 file."
1399
+ },
1400
+ {
1401
+ "code": "event-handler",
1402
+ "summary": "An event handler; email clients strip scripting.",
1403
+ "fix": "Remove the handler; use a link for interaction."
1404
+ },
1405
+ {
1406
+ "code": "fixture-invalid",
1407
+ "summary": "A fixture does not match the schema.",
1408
+ "fix": "Change the fixture, or the schema, so every fixture is a payload a send could carry."
1409
+ },
1410
+ {
1411
+ "code": "fixtures-not-static",
1412
+ "summary": "A fixture is computed instead of written as data.",
1413
+ "fix": "Write each fixture as a literal object; an imported asset is allowed."
1414
+ },
1415
+ {
1416
+ "code": "formatter-arguments",
1417
+ "summary": "A formatter got the wrong number of arguments.",
1418
+ "fix": "Match the formatter's signature in the contract."
1419
+ },
1420
+ {
1421
+ "code": "hook-call",
1422
+ "summary": "A hook was called; templates have no state, effects or context.",
1423
+ "fix": "Read the input through the function's parameter and send anything else in the payload."
1424
+ },
1425
+ {
1426
+ "code": "invalid-asset-base",
1427
+ "summary": "The asset base is not an https URL or a path.",
1428
+ "fix": "Use an https URL, an absolute path, or a relative path."
1429
+ },
1430
+ {
1431
+ "code": "invalid-attribute",
1432
+ "summary": "A channel component attribute has a value the channel does not accept.",
1433
+ "fix": "Use one of the listed values.",
1434
+ "channelOnly": true
1435
+ },
1436
+ {
1437
+ "code": "invalid-attribute-value",
1438
+ "summary": "An attribute value is not a string or number, or contains control characters.",
1439
+ "fix": "Pass plain text."
1440
+ },
1441
+ {
1442
+ "code": "invalid-binding-type",
1443
+ "summary": "A binding has the wrong type for where it is used.",
1444
+ "fix": "Bind a field of the type the position needs, or send it in that type."
1445
+ },
1446
+ {
1447
+ "code": "invalid-button-number",
1448
+ "summary": "A Button size prop is not a finite number.",
1449
+ "fix": "Pass a finite number."
1450
+ },
1451
+ {
1452
+ "code": "invalid-channel",
1453
+ "summary": "A channel field is not shaped as the channel requires.",
1454
+ "fix": "Write email: { subject, preheader?, body } with each a function of the input."
1455
+ },
1456
+ {
1457
+ "code": "invalid-columns-each",
1458
+ "summary": "<Columns each> is not a list from the input.",
1459
+ "fix": "Write each={input.items} with a callback returning <Column>."
1460
+ },
1461
+ {
1462
+ "code": "invalid-columns-per",
1463
+ "summary": "<Columns per> is not a positive integer literal.",
1464
+ "fix": "Write per={2}."
1465
+ },
1466
+ {
1467
+ "code": "invalid-content",
1468
+ "summary": "An element holds content its content model does not allow.",
1469
+ "fix": "Move the child to a parent that accepts it."
1470
+ },
1471
+ {
1472
+ "code": "invalid-fixture-name",
1473
+ "summary": "A fixture key is not a single path segment.",
1474
+ "fix": "Use letters, digits, '.', '_' and '-', starting with a letter or digit."
1475
+ },
1476
+ {
1477
+ "code": "invalid-locale",
1478
+ "summary": "The template's default locale is not a string literal.",
1479
+ "fix": "Write locale: \"en-US\"."
1480
+ },
1481
+ {
1482
+ "code": "invalid-preheader",
1483
+ "summary": "The preheader is not a string.",
1484
+ "fix": "Return a string."
1485
+ },
1486
+ {
1487
+ "code": "invalid-schema",
1488
+ "summary": "The schema uses JSON Schema keywords the compiler cannot validate against.",
1489
+ "fix": "Use only the JSON Schema keywords the package README lists."
1490
+ },
1491
+ {
1492
+ "code": "invalid-style",
1493
+ "summary": "A style prop is not a static property object.",
1494
+ "fix": "Write style={{ backgroundColor: \"#fff\" }} with literal values, or use classes."
1495
+ },
1496
+ {
1497
+ "code": "invalid-style-value",
1498
+ "summary": "A style value is not a string or number.",
1499
+ "fix": "Pass a string or number."
1500
+ },
1501
+ {
1502
+ "code": "invalid-subject",
1503
+ "summary": "The subject is not one line of text.",
1504
+ "fix": "Return a single-line string."
1505
+ },
1506
+ {
1507
+ "code": "invalid-template-id",
1508
+ "summary": "The template id is not a lowercase kebab-case string literal.",
1509
+ "fix": "Write id: \"order-shipped\"."
1510
+ },
1511
+ {
1512
+ "code": "invalid-text",
1513
+ "summary": "The plain-text body is not a string.",
1514
+ "fix": "Return a string."
1515
+ },
1516
+ {
1517
+ "code": "legacy-definition",
1518
+ "summary": "The entry default-exports something other than defineTemplate.",
1519
+ "fix": "Import { defineTemplate } from \"@samva/markup\" and default-export defineTemplate({ ... }).",
1520
+ "upgrade": "Read the entry and its imports without executing them. For a default-exported defineEmail definition, import defineTemplate from \"@samva/markup\" and replace the definition call.\n\nPreserve the entry path, template id, schema, named fixtures, content, design, bindings, conditions, project partials, assets, theme and brand imports. Replace render(input) returning { subject, preheader?, body } with email: { subject: (input) => ..., preheader: (input) => ..., body: (input) => ... }, retaining its parameter bindings and each returned expression; omit preheader when absent.\n\nInline module constants only when they are literal strings, numbers or booleans and their bindings are unambiguous; never substitute a fixture value for an input binding. Keep each channel function a single static-profile expression, and keep subject and preheader off <Email>.\n\nHelper calls, `Math.*`, general arithmetic, render statements, spreads, computed properties and runtime clock or seed have no direct conversion. Rewrite with static-profile bindings, conditions, .map, .filter, project partials or `fmt.*` where equivalent; arithmetic is allowed only in formatter arguments and conditions. Otherwise compute the value in the sender, declare a payload field in the schema and every fixture, and explain the required sender change. Never guess time, randomness or computed data.\n\nExplicit text has no email-channel field: plain text is derived from the body. If deriving it changes intended content, or another construct cannot be preserved, explain the tradeoff and ask rather than silently discard it. Do not drop fixtures, weaken validation or hide diagnostics.\n\nConvert every affected entry in the project at its existing path, including emails/ entries. Compile each entry, render every declared fixture and inspect the affected output. Resolve errors before requesting one saved version for the whole project, approved by the user; do not publish."
1521
+ },
1522
+ {
1523
+ "code": "missing-attribute",
1524
+ "summary": "An element is missing a required attribute.",
1525
+ "fix": "Add the attribute, for example alt on img."
1526
+ },
1527
+ {
1528
+ "code": "missing-body",
1529
+ "summary": "The email channel has no body.",
1530
+ "fix": "Add body: (input) => (<Email>...</Email>)."
1531
+ },
1532
+ {
1533
+ "code": "missing-fixtures",
1534
+ "summary": "The template declares no fixtures.",
1535
+ "fix": "Add fixtures: { default: { ... } } with at least one valid payload."
1536
+ },
1537
+ {
1538
+ "code": "missing-head",
1539
+ "summary": "Styles that cannot be inlined need a <head> to live in.",
1540
+ "fix": "Wrap the message in <Email>, or write those declarations inline."
1541
+ },
1542
+ {
1543
+ "code": "missing-schema",
1544
+ "summary": "The template declares no input schema.",
1545
+ "fix": "Add schema: jsonSchema<Input>({ ... })."
1546
+ },
1547
+ {
1548
+ "code": "missing-subject",
1549
+ "summary": "The email channel has no subject.",
1550
+ "fix": "Add subject: (input) => ...."
1551
+ },
1552
+ {
1553
+ "code": "multiple-roots",
1554
+ "summary": "The body renders nothing or more than one root.",
1555
+ "fix": "Wrap the body in a single element such as <Email>."
1556
+ },
1557
+ {
1558
+ "code": "nesting-limit",
1559
+ "summary": "The template nests deeper than the compiler reads.",
1560
+ "fix": "Flatten the structure."
1561
+ },
1562
+ {
1563
+ "code": "no-channel",
1564
+ "summary": "The template declares no channel body.",
1565
+ "fix": "Add an email body."
1566
+ },
1567
+ {
1568
+ "code": "no-template",
1569
+ "summary": "The entry does not default-export a template.",
1570
+ "fix": "Default-export defineTemplate({ ... }) imported from @samva/markup."
1571
+ },
1572
+ {
1573
+ "code": "non-project-import",
1574
+ "summary": "The file imports something other than @samva/markup or a project file.",
1575
+ "fix": "Remove the import, or copy the code you need into a project file."
1576
+ },
1577
+ {
1578
+ "code": "not-a-function",
1579
+ "summary": "A channel field is not a function of the input.",
1580
+ "fix": "Write (input) => ... in place or declare the function in the project."
1581
+ },
1582
+ {
1583
+ "code": "preheader-without-shell",
1584
+ "summary": "A preheader is set but the body has no <Email> shell to hold it.",
1585
+ "fix": "Wrap the body in <Email>."
1586
+ },
1587
+ {
1588
+ "code": "recursive-partial",
1589
+ "summary": "A partial renders itself; partials are inlined at compile time.",
1590
+ "fix": "Flatten the recursion, or repeat the structure with .map."
1591
+ },
1592
+ {
1593
+ "code": "reserved-attribute",
1594
+ "summary": "An attribute is written only by a brand component.",
1595
+ "fix": "Render BrandFooter from samva:brand instead of marking an element as the footer."
1596
+ },
1597
+ {
1598
+ "code": "reserved-character",
1599
+ "summary": "Text contains a private-use character the compiler reserves.",
1600
+ "fix": "Remove the characters U+E000 to U+E003."
1601
+ },
1602
+ {
1603
+ "code": "reserved-url",
1604
+ "summary": "A URL is the unsubscribe placeholder only BrandFooter can write.",
1605
+ "fix": "Render <BrandFooter /> instead."
1606
+ },
1607
+ {
1608
+ "code": "schema-not-static",
1609
+ "summary": "The schema cannot be read without running code.",
1610
+ "fix": "Write the schema as a literal jsonSchema<Input>({ type: 'object', ... })."
1611
+ },
1612
+ {
1613
+ "code": "sms-markup",
1614
+ "summary": "An SMS body contains an element; SMS is plain text.",
1615
+ "fix": "Write text and bindings, with a newline for a line break.",
1616
+ "channelOnly": true
1617
+ },
1618
+ {
1619
+ "code": "statement-in-body",
1620
+ "summary": "A body contains a statement (const, if, a loop); a body is one expression.",
1621
+ "fix": "Use cond && <X />, cond ? <A /> : <B /> and .map, or send the computed value in the payload."
1622
+ },
1623
+ {
1624
+ "code": "syntax-error",
1625
+ "summary": "A project file does not parse as TSX.",
1626
+ "fix": "Fix the syntax at the reported location."
1627
+ },
1628
+ {
1629
+ "code": "tailwind-dynamic-class",
1630
+ "summary": "A class list is assembled at run time.",
1631
+ "fix": "Write the classes literally."
1632
+ },
1633
+ {
1634
+ "code": "too-many-class-variants",
1635
+ "summary": "A class list chooses between more literal lists than the compiler resolves.",
1636
+ "fix": "Split the choice into fewer conditions, or move part of it to a partial."
1637
+ },
1638
+ {
1639
+ "code": "unguarded-optional",
1640
+ "summary": "An optional field is read outside a guard, so a send without it would render a blank.",
1641
+ "fix": "Guard it with {input.field && ...}, or make the field required."
1642
+ },
1643
+ {
1644
+ "code": "unknown-attribute",
1645
+ "summary": "A channel component attribute the component does not have.",
1646
+ "fix": "Use one of the listed attributes.",
1647
+ "channelOnly": true
1648
+ },
1649
+ {
1650
+ "code": "unknown-component",
1651
+ "summary": "A component the profile cannot read.",
1652
+ "fix": "Import components from @samva/markup/email/components, samva:brand, or the project."
1653
+ },
1654
+ {
1655
+ "code": "unknown-field",
1656
+ "summary": "A binding names a field the schema does not declare.",
1657
+ "fix": "Add the field to the schema, or bind a field it declares (the message suggests the nearest)."
1658
+ },
1659
+ {
1660
+ "code": "unknown-formatter",
1661
+ "summary": "fmt.<name> is not a formatter.",
1662
+ "fix": "Use fmt.money, number, date, time, plural or list."
1663
+ },
1664
+ {
1665
+ "code": "unmatched-class",
1666
+ "summary": "A class produced no styles.",
1667
+ "fix": "Use a class the theme or Tailwind defines, or write the style inline."
1668
+ },
1669
+ {
1670
+ "code": "unresolved-import",
1671
+ "summary": "An import names no file in the project.",
1672
+ "fix": "Check the path and the file extension."
1673
+ },
1674
+ {
1675
+ "code": "unsafe-raw-text",
1676
+ "summary": "Raw text content contains markup that could end its element.",
1677
+ "fix": "Remove the offending sequence."
1678
+ },
1679
+ {
1680
+ "code": "unsafe-style-value",
1681
+ "summary": "A style value contains CSS that could break out of its declaration.",
1682
+ "fix": "Remove the offending characters."
1683
+ },
1684
+ {
1685
+ "code": "unsafe-url",
1686
+ "summary": "A URL uses a scheme email cannot send safely.",
1687
+ "fix": "Use an https URL, a mailto: or tel: link, or a bound URL."
1688
+ },
1689
+ {
1690
+ "code": "unsupported-attribute",
1691
+ "summary": "An attribute the element does not support.",
1692
+ "fix": "Use a supported attribute, or data-* and aria-*."
1693
+ },
1694
+ {
1695
+ "code": "unsupported-element",
1696
+ "summary": "An element outside the supported email vocabulary.",
1697
+ "fix": "Use an element from the contract's element table."
1698
+ },
1699
+ {
1700
+ "code": "unsupported-pattern",
1701
+ "summary": "A parameter pattern or default value the profile cannot bind.",
1702
+ "fix": "Use plain names or destructuring without defaults; declare the field required or guard it."
1703
+ },
1704
+ {
1705
+ "code": "unsupported-style-property",
1706
+ "summary": "A style property name is not a CSS property.",
1707
+ "fix": "Write camelCase (backgroundColor) or a custom property (--brand)."
1708
+ },
1709
+ {
1710
+ "code": "void-element-children",
1711
+ "summary": "A void element has children.",
1712
+ "fix": "Remove the children."
1713
+ },
1714
+ {
1715
+ "code": "whatsapp-body-required",
1716
+ "summary": "A WhatsApp template has no body.",
1717
+ "fix": "Add <WhatsApp.Body>...</WhatsApp.Body>.",
1718
+ "channelOnly": true
1719
+ },
1720
+ {
1721
+ "code": "whatsapp-button-limit",
1722
+ "summary": "The conditions can produce more buttons of a kind than Meta allows.",
1723
+ "fix": "Remove buttons, or split the template.",
1724
+ "channelOnly": true
1725
+ },
1726
+ {
1727
+ "code": "whatsapp-button-text",
1728
+ "summary": "A WhatsApp button has no label or value between its tags.",
1729
+ "fix": "Write the label or value as text between the tags.",
1730
+ "channelOnly": true
1731
+ },
1732
+ {
1733
+ "code": "whatsapp-conditional",
1734
+ "summary": "A WhatsApp part branches or contains elements; Meta registers its text once.",
1735
+ "fix": "Choose the wording in the payload, or write separate templates.",
1736
+ "channelOnly": true
1737
+ },
1738
+ {
1739
+ "code": "whatsapp-header-media",
1740
+ "summary": "A WhatsApp media header is not exactly one URL.",
1741
+ "fix": "Write the URL as the only child: a literal or one binding.",
1742
+ "channelOnly": true
1743
+ },
1744
+ {
1745
+ "code": "whatsapp-header-variables",
1746
+ "summary": "A WhatsApp header text takes more than one variable.",
1747
+ "fix": "Keep one binding in the header and move the rest to the body.",
1748
+ "channelOnly": true
1749
+ },
1750
+ {
1751
+ "code": "whatsapp-length",
1752
+ "summary": "A WhatsApp part exceeds Meta's length limit.",
1753
+ "fix": "Shorten the text.",
1754
+ "channelOnly": true
1755
+ },
1756
+ {
1757
+ "code": "whatsapp-structure",
1758
+ "summary": "WhatsApp content sits outside Header, Body, Footer or Buttons.",
1759
+ "fix": "Move it into <WhatsApp.Body>.",
1760
+ "channelOnly": true
1761
+ },
1762
+ {
1763
+ "code": "wrong-channel",
1764
+ "summary": "A channel body does not return its own channel component.",
1765
+ "fix": "Return one element imported from the channel package.",
1766
+ "channelOnly": true
1767
+ }
1768
+ ],
1769
+ "diagnosticFamilies": [
1770
+ {
1771
+ "prefix": "caniemail/",
1772
+ "summary": "A CSS property, HTML element or attribute that a target email client ignores or partly supports (from the caniemail.com data), reported as caniemail/<feature>.",
1773
+ "fix": "Use the alternative the message names, such as an email component instead of display: flex."
1774
+ }
1775
+ ],
1776
+ "qualityBar": [
1777
+ "Run the check until it reports no errors, then render every fixture.",
1778
+ "Cover every branch with a fixture: default, missing optional fields, many items.",
1779
+ "Look at the render at desktop and mobile widths before finishing.",
1780
+ "Compute anything the profile cannot express where the message is sent and add it to the schema."
1781
+ ]
1782
+ }