@samva/markup 0.18.1 → 0.19.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 (54) hide show
  1. package/AGENTS.md +11 -4
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +89 -5
  4. package/dist/assets-BSpgZOSR.js +61 -0
  5. package/dist/assets-k7_v3IOJ.d.ts +39 -0
  6. package/dist/{assets-CS5Xa_mr.d.ts → brand-Bxb6Sm7y.d.ts} +34 -41
  7. package/dist/brand.d.ts +2 -2
  8. package/dist/brand.js +3 -3
  9. package/dist/compatibility.d.ts +15 -5
  10. package/dist/compatibility.js +48 -5
  11. package/dist/compile-C1GWLQ5C.js +490 -0
  12. package/dist/compile-CIeuGmP-.d.ts +98 -0
  13. package/dist/{compiled-template-Bc4Dq9zy.d.ts → compiled-template-CPDHvFlt.d.ts} +157 -6
  14. package/dist/compiler.d.ts +4 -89
  15. package/dist/compiler.js +8 -486
  16. package/dist/components-DNBCz20_.js +3267 -0
  17. package/dist/{css-color-DLM12Osu.js → dark-scheme-BGDCCaUq.js} +12 -1
  18. package/dist/{diagnostic-codes-DZKb0Xcg.js → diagnostic-codes-BCwfUgnF.js} +33 -36
  19. package/dist/diagnostic-model-DJtqbETd.js +36 -0
  20. package/dist/{diagnostics-D4vSLQrO.d.ts → diagnostics-C3TSx6SU.d.ts} +22 -2
  21. package/dist/diagnostics.d.ts +33 -1
  22. package/dist/diagnostics.js +2 -1
  23. package/dist/edit.d.ts +15 -4
  24. package/dist/edit.js +67 -4
  25. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  26. package/dist/email/jsx-runtime.d.ts +1 -1
  27. package/dist/email.d.ts +6 -2
  28. package/dist/email.js +6 -2
  29. package/dist/index.d.ts +1 -1
  30. package/dist/{jsx-runtime-DL8NVF1f.d.ts → jsx-runtime-HHfrVZEf.d.ts} +1 -2
  31. package/dist/markdown.d.ts +69 -0
  32. package/dist/markdown.js +95 -0
  33. package/dist/parse-Bex7QFqj.js +921 -0
  34. package/dist/{preview-rNoJBEIY.d.ts → preview-DRLWgmfg.d.ts} +3 -3
  35. package/dist/profile-DFk4gs7o.js +56 -0
  36. package/dist/{profile-JTvgCD6X.js → project-B6D64SZS.js} +497 -234
  37. package/dist/render.d.ts +8 -8
  38. package/dist/render.js +1 -2
  39. package/dist/{css-B_aVKrDo.js → selectors-CKGuc9MO.js} +158 -4
  40. package/dist/sms.d.ts +1 -1
  41. package/dist/{template-DmNcoH4Q.d.ts → template-4Dse7_OC.d.ts} +1 -1
  42. package/dist/{theme-css-P9F5S7Ic.js → theme-css-BujDuuEA.js} +1 -2
  43. package/dist/whatsapp.d.ts +1 -1
  44. package/generated/contract.json +41 -1
  45. package/generated/reference.md +97 -89
  46. package/package.json +15 -4
  47. package/dist/brand-CIiEqkU7.js +0 -193
  48. package/dist/brand-plugin-Dj9SMpfS.d.ts +0 -33
  49. package/dist/components-C5850-Qg.js +0 -409
  50. package/dist/dark-scheme-CbCwGDWH.js +0 -12
  51. package/dist/html-DJUK2YJF.js +0 -483
  52. package/dist/render-template-DwPDm3Hq.js +0 -1226
  53. package/dist/selectors-CYWCoxZr.js +0 -157
  54. package/dist/source-locations-C8wCsiAy.d.ts +0 -22
@@ -4,7 +4,7 @@ A template is one TSX file that default-exports `defineTemplate({ id, schema, fi
4
4
  from `@samva/markup`, in `templates/` (`emails/` is also recognized). Set
5
5
  `jsxImportSource: "@samva/markup/email"`. `email` is `{ subject, preheader?, body }`; each is a
6
6
  function of the validated input that the compiler reads without running. The compiled template it
7
- produces is what every preview and send renders (compiled template version 1).
7
+ produces is what every preview and send renders (compiled template version 2).
8
8
 
9
9
  ## Static profile
10
10
 
@@ -287,94 +287,102 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
287
287
 
288
288
  ## Diagnostics
289
289
 
290
- | Code | Meaning | Fix |
291
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
292
- | `brand-import-conflict` | The project imports two different brands. | Import one brand specifier everywhere. |
293
- | `brand-unavailable` | The build has no brand to resolve a brand import. | Create or select the brand the import names. |
294
- | `button-dynamic-label` | A Button label is read at render time, so classic Outlook cannot size it. | Give the button a width: <Button width={200}>. |
295
- | `button-outlook-fallback` | Classic Outlook needs a height and a plain string label to draw the button. | Give <Button> a height and a string label. |
296
- | `caniemail/unknown-coverage` | A feature has no client-support data. | Verify it in the target clients. |
297
- | `css-conditional-variable` | A custom property is redefined inside a conditional at-rule. | Keep the override on a class rule. |
298
- | `css-import-unresolved` | A stylesheet @import cannot be resolved. | Import the stylesheet from the template module, or use the theme.css layers. |
299
- | `css-parse-error` | A stylesheet does not parse. | Fix the CSS at the reported location. |
300
- | `css-unsupported-at-rule` | An at-rule is not applied to email output. | Remove it or move the declarations into a class rule. |
301
- | `dangerous-html` | dangerouslySetInnerHTML; there is no raw-HTML escape hatch. | Write the markup as JSX. |
302
- | `duplicate-attribute` | An attribute is written twice. | Keep one. |
303
- | `dynamic-class` | A class name is built at run time, and Tailwind cannot see it. | Write classes literally, or choose between literal lists: input.vip ? "bg-amber-100" : "bg-white". |
304
- | `dynamic-definition` | A definition field is spread or computed instead of written out. | Write each field as name: value. |
305
- | `dynamic-expression` | An expression outside the static profile: a call other than fmt.* and .map, a computed value, or a spread. | Compute the value where the message is sent and bind it from the payload. |
306
- | `dynamic-numeric-prop` | A prop that sizes layout at compile time is not a number literal. | Write the number literally, for example height={44}. |
307
- | `email-contrast` | Text, or a filled button against its surroundings, falls below the contrast ratio in light or dark mode (below 1.5:1 a button is indistinguishable from its surface; WCAG 1.4.11 asks 3:1). | Pair background and text tokens (bg-primary with text-primary-foreground), give the theme's dark-mode @theme a value for the token, or choose colors that reach the required ratio. |
308
- | `email-external-asset` | An image points at another site's URL; a hotlinked image can disappear, be blocked, track recipients or slow the message. | Add the image to the project's files and import it (import logo from './logo.png'), then use the import as the src. |
309
- | `email-font-fallback` | A font stack names a project font without a generic fallback. | End the stack with sans-serif, serif or monospace. |
310
- | `email-font-family` | @font-face declares no font-family. | Add font-family. |
311
- | `email-font-format` | @font-face uses a format other than WOFF2. | Convert the file to .woff2. |
312
- | `email-font-missing` | @font-face names a file the project does not hold. | Add the file; paths resolve from the stylesheet's directory. |
313
- | `email-font-src` | @font-face declares no usable url() src. | Add a url() pointing at a .woff2 file. |
314
- | `event-handler` | An event handler; email clients strip scripting. | Remove the handler; use a link for interaction. |
315
- | `fixture-invalid` | A fixture does not match the schema. | Change the fixture, or the schema, so every fixture is a payload a send could carry. |
316
- | `fixtures-not-static` | A fixture is computed instead of written as data. | Write each fixture as a literal object; an imported asset is allowed. |
317
- | `formatter-arguments` | A formatter got the wrong number of arguments. | Match the formatter's signature in the contract. |
318
- | `hook-call` | A hook was called; templates have no state, effects or context. | Read the input through the function's parameter and send anything else in the payload. |
319
- | `invalid-asset-base` | The asset base is not an https URL or a path. | Use an https URL, an absolute path, or a relative path. |
320
- | `invalid-attribute-value` | An attribute value is not a string or number, or contains control characters. | Pass plain text. |
321
- | `invalid-binding-type` | A binding has the wrong type for where it is used. | Bind a field of the type the position needs, or send it in that type. |
322
- | `invalid-button-number` | A Button size prop is not a finite number. | Pass a finite number. |
323
- | `invalid-channel` | A channel field is not shaped as the channel requires. | Write email: { subject, preheader?, body } with each a function of the input. |
324
- | `invalid-columns-each` | <Columns each> is not a list from the input. | Write each={input.items} with a callback returning <Column>. |
325
- | `invalid-columns-gap` | <Columns gap> is set on a Columns whose cells are repeated with each or rendered conditionally. | Write each Column out unconditionally, or pad the cells instead of using gap. |
326
- | `invalid-columns-per` | <Columns per> is not a positive integer literal. | Write per={2}. |
327
- | `invalid-columns-stack` | <Columns stack> is not a true or false literal. | Write stack={false}, or leave it out to stack on narrow screens. |
328
- | `invalid-content` | An element holds content its content model does not allow. | Move the child to a parent that accepts it. |
329
- | `invalid-fixture-name` | A fixture key is not a single path segment. | Use letters, digits, '.', '_' and '-', starting with a letter or digit. |
330
- | `invalid-locale` | The template's default locale is not a string literal. | Write locale: "en-US". |
331
- | `invalid-number-prop` | A primitive's numeric prop, such as <Email gutter>, is not a finite number in range. | Pass a finite number the message allows, for example gutter={16}. |
332
- | `invalid-preheader` | The preheader is not a string. | Return a string. |
333
- | `invalid-schema` | The schema uses JSON Schema keywords the compiler cannot validate against. | Use only the JSON Schema keywords the package README lists. |
334
- | `invalid-style` | A style prop is not a static property object. | Write style={{ backgroundColor: "#fff" }} with literal values, or use classes. |
335
- | `invalid-style-value` | A style value is not a string or number. | Pass a string or number. |
336
- | `invalid-subject` | The subject is not one line of text. | Return a single-line string. |
337
- | `invalid-template-id` | The template id is not a lowercase kebab-case string literal. | Write id: "order-shipped". |
338
- | `invalid-text` | The plain-text body is not a string. | Return a string. |
339
- | `legacy-definition` | The entry default-exports something other than defineTemplate. | Import { defineTemplate } from "@samva/markup" and default-export defineTemplate({ ... }). |
340
- | `missing-attribute` | An element is missing a required attribute. | Add the attribute, for example alt on img. |
341
- | `missing-body` | The email channel has no body. | Add body: (input) => (<Email>...</Email>). |
342
- | `missing-fixtures` | The template declares no fixtures. | Add fixtures: { default: { ... } } with at least one valid payload. |
343
- | `missing-head` | Styles that cannot be inlined need a <head> to live in. | Wrap the message in <Email>, or write those declarations inline. |
344
- | `missing-schema` | The template declares no input schema. | Add schema: jsonSchema<Input>({ ... }). |
345
- | `missing-subject` | The email channel has no subject. | Add subject: (input) => .... |
346
- | `moved-import` | The file imports an @samva/markup entry that moved. | Import email components from "@samva/markup/email". |
347
- | `multiple-roots` | The body renders nothing or more than one root. | Wrap the body in a single element such as <Email>. |
348
- | `nesting-limit` | The template nests deeper than the compiler reads. | Flatten the structure. |
349
- | `no-channel` | The template declares no channel body. | Add an email body. |
350
- | `no-template` | The entry does not default-export a template. | Default-export defineTemplate({ ... }) imported from @samva/markup. |
351
- | `non-project-import` | The file imports something other than @samva/markup or a project file. | Remove the import, or copy the code you need into a project file. |
352
- | `not-a-function` | A channel field is not a function of the input. | Write (input) => ... in place or declare the function in the project. |
353
- | `preheader-without-shell` | A preheader is set but the body has no <Email> shell to hold it. | Wrap the body in <Email>. |
354
- | `recursive-partial` | A partial renders itself; partials are inlined at compile time. | Flatten the recursion, or repeat the structure with .map. |
355
- | `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from the brand import instead of marking an element as the footer. |
356
- | `reserved-character` | Text contains a private-use character the compiler reserves. | Remove the characters U+E000 to U+E003. |
357
- | `reserved-url` | A URL is the unsubscribe placeholder only BrandFooter can write. | Render <BrandFooter /> instead. |
358
- | `schema-not-static` | The schema cannot be read without running code. | Write the schema as a literal jsonSchema<Input>({ type: 'object', ... }). |
359
- | `statement-in-body` | A body contains a statement (const, if, a loop); a body is one expression. | Use cond && <X />, cond ? <A /> : <B /> and .map, or send the computed value in the payload. |
360
- | `syntax-error` | A project file does not parse as TSX. | Fix the syntax at the reported location. |
361
- | `tailwind-dynamic-class` | A class list is assembled at run time. | Write the classes literally. |
362
- | `too-many-class-variants` | A class list chooses between more literal lists than the compiler resolves. | Split the choice into fewer conditions, or move part of it to a partial. |
363
- | `unguarded-optional` | An optional field is read outside a guard, so a send without it would render a blank. | Guard it with {input.field && ...}, or make the field required. |
364
- | `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email, the brand import, or the project. |
365
- | `unknown-field` | A binding names a field the schema does not declare. | Add the field to the schema, or bind a field it declares (the message suggests the nearest). |
366
- | `unknown-formatter` | fmt.<name> is not a formatter. | Use fmt.money, number, date, time, plural or list. |
367
- | `unmatched-class` | A class produced no styles. | Use a class the theme or Tailwind defines, or write the style inline. |
368
- | `unresolved-import` | An import names no file in the project. | Check the path and the file extension. |
369
- | `unsafe-raw-text` | Raw text content contains markup that could end its element. | Remove the offending sequence. |
370
- | `unsafe-style-value` | A style value contains CSS that could break out of its declaration. | Remove the offending characters. |
371
- | `unsafe-url` | A URL uses a scheme email cannot send safely. | Use an https URL, a mailto: or tel: link, or a bound URL. |
372
- | `unsupported-attribute` | An attribute the element does not support. | Use a supported attribute, or data-* and aria-*. |
373
- | `unsupported-element` | An element outside the supported email vocabulary. | Use an element from the contract's element table. |
374
- | `unsupported-pattern` | A parameter pattern or default value the profile cannot bind. | Use plain names or destructuring without defaults; declare the field required or guard it. |
375
- | `unsupported-style-property` | A style property name is not a CSS property. | Write camelCase (backgroundColor) or a custom property (--brand). |
376
- | `void-element-children` | A void element has children. | Remove the children. |
377
- | `caniemail/<feature>` | 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>. | Use the alternative the message names, such as an email component instead of display: flex. |
290
+ | Code | Meaning | Fix |
291
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
292
+ | `brand-import-conflict` | The project imports two different brands. | Import one brand specifier everywhere. |
293
+ | `brand-unavailable` | The build has no brand to resolve a brand import. | Create or select the brand the import names. |
294
+ | `button-dynamic-label` | A Button label is read at render time, so classic Outlook cannot size it. | Give the button a width: <Button width={200}>. |
295
+ | `button-outlook-fallback` | Classic Outlook needs a height and a plain string label to draw the button. | Give <Button> a height and a string label. |
296
+ | `caniemail/unknown-coverage` | A feature has no client-support data. | Verify it in the target clients. |
297
+ | `css-conditional-variable` | A custom property is redefined inside a conditional at-rule. | Keep the override on a class rule. |
298
+ | `css-import-unresolved` | A stylesheet @import cannot be resolved. | Import the stylesheet from the template module, or use the theme.css layers. |
299
+ | `css-parse-error` | A stylesheet does not parse. | Fix the CSS at the reported location. |
300
+ | `css-unsupported-at-rule` | An at-rule is not applied to email output. | Remove it or move the declarations into a class rule. |
301
+ | `dangerous-html` | dangerouslySetInnerHTML; there is no raw-HTML escape hatch. | Write the markup as JSX. |
302
+ | `duplicate-attribute` | An attribute is written twice. | Keep one. |
303
+ | `dynamic-class` | A class name is built at run time, and Tailwind cannot see it. | Write classes literally, or choose between literal lists: input.vip ? "bg-amber-100" : "bg-white". |
304
+ | `dynamic-definition` | A definition field is spread or computed instead of written out. | Write each field as name: value. |
305
+ | `dynamic-expression` | An expression outside the static profile: a call other than fmt.* and .map, a computed value, or a spread. | Compute the value where the message is sent and bind it from the payload. |
306
+ | `dynamic-numeric-prop` | A prop that sizes layout at compile time is not a number literal. | Write the number literally, for example height={44}. |
307
+ | `email-component-degradation` | A component loses content or behavior when lowered to email. | Compare the email and web previews; provide an email-safe equivalent for content readers need. |
308
+ | `email-contrast` | Text, or a filled button against its surroundings, falls below the contrast ratio in light or dark mode (below 1.5:1 a button is indistinguishable from its surface; WCAG 1.4.11 asks 3:1). | Pair background and text tokens (bg-primary with text-primary-foreground), give the theme's dark-mode @theme a value for the token, or choose colors that reach the required ratio. |
309
+ | `email-external-asset` | An image points at another site's URL; a hotlinked image can disappear, be blocked, track recipients or slow the message. | Add the image to the project's files and import it (import logo from './logo.png'), then use the import as the src. |
310
+ | `email-font-fallback` | A font stack names a project font without a generic fallback. | End the stack with sans-serif, serif or monospace. |
311
+ | `email-font-family` | @font-face declares no font-family. | Add font-family. |
312
+ | `email-font-format` | @font-face uses a format other than WOFF2. | Convert the file to .woff2. |
313
+ | `email-font-missing` | @font-face names a file the project does not hold. | Add the file; paths resolve from the stylesheet's directory. |
314
+ | `email-font-src` | @font-face declares no usable url() src. | Add a url() pointing at a .woff2 file. |
315
+ | `event-handler` | An event handler; email clients strip scripting. | Remove the handler; use a link for interaction. |
316
+ | `fixture-invalid` | A fixture does not match the schema. | Change the fixture, or the schema, so every fixture is a payload a send could carry. |
317
+ | `fixtures-not-static` | A fixture is computed instead of written as data. | Write each fixture as a literal object; an imported asset is allowed. |
318
+ | `formatter-arguments` | A formatter got the wrong number of arguments. | Match the formatter's signature in the contract. |
319
+ | `hook-call` | A hook was called; templates have no state, effects or context. | Read the input through the function's parameter and send anything else in the payload. |
320
+ | `invalid-asset-base` | The asset base is not an https URL or a path. | Use an https URL, an absolute path, or a relative path. |
321
+ | `invalid-attribute-value` | An attribute value is not a string or number, or contains control characters. | Pass plain text. |
322
+ | `invalid-binding-type` | A binding has the wrong type for where it is used. | Bind a field of the type the position needs, or send it in that type. |
323
+ | `invalid-button-number` | A Button size prop is not a finite number. | Pass a finite number. |
324
+ | `invalid-channel` | A channel field is not shaped as the channel requires. | Write email: { subject, preheader?, body } with each a function of the input. |
325
+ | `invalid-columns-each` | <Columns each> is not a list from the input. | Write each={input.items} with a callback returning <Column>. |
326
+ | `invalid-columns-gap` | <Columns gap> is set on a Columns whose cells are repeated with each or rendered conditionally. | Write each Column out unconditionally, or pad the cells instead of using gap. |
327
+ | `invalid-columns-per` | <Columns per> is not a positive integer literal. | Write per={2}. |
328
+ | `invalid-columns-stack` | <Columns stack> is not a true or false literal. | Write stack={false}, or leave it out to stack on narrow screens. |
329
+ | `invalid-content` | An element holds content its content model does not allow. | Move the child to a parent that accepts it. |
330
+ | `invalid-fixture-name` | A fixture key is not a single path segment. | Use letters, digits, '.', '_' and '-', starting with a letter or digit. |
331
+ | `invalid-locale` | The template's default locale is not a string literal. | Write locale: "en-US". |
332
+ | `invalid-number-prop` | A primitive's numeric prop, such as <Email gutter>, is not a finite number in range. | Pass a finite number the message allows, for example gutter={16}. |
333
+ | `invalid-preheader` | The preheader is not a string. | Return a string. |
334
+ | `invalid-schema` | The schema uses JSON Schema keywords the compiler cannot validate against. | Use only the JSON Schema keywords the package README lists. |
335
+ | `invalid-style` | A style prop is not a static property object. | Write style={{ backgroundColor: "#fff" }} with literal values, or use classes. |
336
+ | `invalid-style-value` | A style value is not a string or number. | Pass a string or number. |
337
+ | `invalid-subject` | The subject is not one line of text. | Return a single-line string. |
338
+ | `invalid-template-id` | The template id is not a lowercase kebab-case string literal. | Write id: "order-shipped". |
339
+ | `invalid-text` | The plain-text body is not a string. | Return a string. |
340
+ | `issue-layout` | A newsletter issue layout does not declare only an email channel. | Use an email-only defineTemplate layout with one IssueContent slot. |
341
+ | `issue-slot` | An issue layout does not contain exactly one unconditional content slot. | Import IssueContent from @samva/markup/email and place one <IssueContent /> outside loops and conditions. |
342
+ | `legacy-definition` | The entry default-exports something other than defineTemplate. | Import { defineTemplate } from "@samva/markup" and default-export defineTemplate({ ... }). |
343
+ | `markdown-expression` | An issue contains an expression or spread, which is never evaluated. | Replace expressions with literal prose or quoted static component attributes. |
344
+ | `markdown-frontmatter` | Issue frontmatter is missing or does not match the typed contract. | Provide title, slug and topic strings; use only the documented preheader, seo and gate fields. |
345
+ | `markdown-html` | An issue contains raw HTML rather than a supported SML component. | Use Markdown syntax or a component from the closed SML component set. |
346
+ | `markdown-prop` | A Markdown component has an unknown, missing or invalid literal prop. | Use the component's documented props with quoted static values or bare boolean flags. |
347
+ | `markdown-syntax` | An issue contains syntax outside SML Markdown. | Write Markdown prose and supported SML component tags. |
348
+ | `missing-attribute` | An element is missing a required attribute. | Add the attribute, for example alt on img. |
349
+ | `missing-body` | The email channel has no body. | Add body: (input) => (<Email>...</Email>). |
350
+ | `missing-fixtures` | The template declares no fixtures. | Add fixtures: { default: { ... } } with at least one valid payload. |
351
+ | `missing-head` | Styles that cannot be inlined need a <head> to live in. | Wrap the message in <Email>, or write those declarations inline. |
352
+ | `missing-schema` | The template declares no input schema. | Add schema: jsonSchema<Input>({ ... }). |
353
+ | `missing-subject` | The email channel has no subject. | Add subject: (input) => .... |
354
+ | `moved-import` | The file imports an @samva/markup entry that moved. | Import email components from "@samva/markup/email". |
355
+ | `multiple-roots` | The body renders nothing or more than one root. | Wrap the body in a single element such as <Email>. |
356
+ | `nesting-limit` | The template nests deeper than the compiler reads. | Flatten the structure. |
357
+ | `no-channel` | The template declares no channel body. | Add an email body. |
358
+ | `no-template` | The entry does not default-export a template. | Default-export defineTemplate({ ... }) imported from @samva/markup. |
359
+ | `non-project-import` | The file imports something other than @samva/markup or a project file. | Remove the import, or copy the code you need into a project file. |
360
+ | `not-a-function` | A channel field is not a function of the input. | Write (input) => ... in place or declare the function in the project. |
361
+ | `preheader-without-shell` | A preheader is set but the body has no <Email> shell to hold it. | Wrap the body in <Email>. |
362
+ | `recursive-partial` | A partial renders itself; partials are inlined at compile time. | Flatten the recursion, or repeat the structure with .map. |
363
+ | `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from the brand import instead of marking an element as the footer. |
364
+ | `reserved-character` | Text contains a private-use character the compiler reserves. | Remove the characters U+E000 to U+E003. |
365
+ | `reserved-url` | A URL is the unsubscribe placeholder only BrandFooter can write. | Render <BrandFooter /> instead. |
366
+ | `schema-not-static` | The schema cannot be read without running code. | Write the schema as a literal jsonSchema<Input>({ type: 'object', ... }). |
367
+ | `statement-in-body` | A body contains a statement (const, if, a loop); a body is one expression. | Use cond && <X />, cond ? <A /> : <B /> and .map, or send the computed value in the payload. |
368
+ | `syntax-error` | A project file does not parse as TSX. | Fix the syntax at the reported location. |
369
+ | `tailwind-dynamic-class` | A class list is assembled at run time. | Write the classes literally. |
370
+ | `too-many-class-variants` | A class list chooses between more literal lists than the compiler resolves. | Split the choice into fewer conditions, or move part of it to a partial. |
371
+ | `unguarded-optional` | An optional field is read outside a guard, so a send without it would render a blank. | Guard it with {input.field && ...}, or make the field required. |
372
+ | `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email, the brand import, or the project. |
373
+ | `unknown-field` | A binding names a field the schema does not declare. | Add the field to the schema, or bind a field it declares (the message suggests the nearest). |
374
+ | `unknown-formatter` | fmt.<name> is not a formatter. | Use fmt.money, number, date, time, plural or list. |
375
+ | `unmatched-class` | A class produced no styles. | Use a class the theme or Tailwind defines, or write the style inline. |
376
+ | `unresolved-import` | An import names no file in the project. | Check the path and the file extension. |
377
+ | `unsafe-raw-text` | Raw text content contains markup that could end its element. | Remove the offending sequence. |
378
+ | `unsafe-style-value` | A style value contains CSS that could break out of its declaration. | Remove the offending characters. |
379
+ | `unsafe-url` | A URL uses a scheme email cannot send safely. | Use an https URL, a mailto: or tel: link, or a bound URL. |
380
+ | `unsupported-attribute` | An attribute the element does not support. | Use a supported attribute, or data-* and aria-*. |
381
+ | `unsupported-element` | An element outside the supported email vocabulary. | Use an element from the contract's element table. |
382
+ | `unsupported-pattern` | A parameter pattern or default value the profile cannot bind. | Use plain names or destructuring without defaults; declare the field required or guard it. |
383
+ | `unsupported-style-property` | A style property name is not a CSS property. | Write camelCase (backgroundColor) or a custom property (--brand). |
384
+ | `void-element-children` | A void element has children. | Remove the children. |
385
+ | `caniemail/<feature>` | 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>. | Use the alternative the message names, such as an email component instead of display: flex. |
378
386
 
379
387
  ## Upgrades
380
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/markup",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "Type-safe authoring and compilation primitives for Samva templates",
5
5
  "keywords": [
6
6
  "email",
@@ -98,7 +98,11 @@
98
98
  },
99
99
  "./contract.json": "./generated/contract.json",
100
100
  "./reference.md": "./generated/reference.md",
101
- "./package.json": "./package.json"
101
+ "./package.json": "./package.json",
102
+ "./markdown": {
103
+ "types": "./dist/markdown.d.ts",
104
+ "import": "./dist/markdown.js"
105
+ }
102
106
  },
103
107
  "publishConfig": {
104
108
  "access": "public"
@@ -119,12 +123,19 @@
119
123
  "better-result": "3.0.1",
120
124
  "caniemail": "2.0.2",
121
125
  "css-tree": "3.2.1",
122
- "tailwindcss": "^4.3.3"
126
+ "mdast-util-from-markdown": "^2.1.0",
127
+ "mdast-util-mdx-expression": "^2.0.1",
128
+ "mdast-util-mdx-jsx": "^3.2.0",
129
+ "micromark-extension-mdx-expression": "^3.0.1",
130
+ "micromark-extension-mdx-jsx": "^3.0.2",
131
+ "tailwindcss": "^4.3.3",
132
+ "yaml": "^2.9.1"
123
133
  },
124
134
  "devDependencies": {
125
- "@css-inline/css-inline-wasm": "0.15.0",
135
+ "@css-inline/css-inline-wasm": "0.23.1",
126
136
  "@effect/vitest": "4.0.0",
127
137
  "@types/css-tree": "3.2.0",
138
+ "@types/mdast": "^4.0.4",
128
139
  "@types/node": "^26.6.2",
129
140
  "effect": "4.0.0",
130
141
  "tsdown": "^0.22.13",