@samva/markup 0.7.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 (176) hide show
  1. package/AGENTS.md +104 -37
  2. package/README.md +188 -52
  3. package/dist/assets-C5duwTya.d.ts +40 -0
  4. package/dist/brand-B-0bN_Ir.d.ts +88 -0
  5. package/dist/{render-aaJBnsXK.js → cascade-t6kxlgEI.js} +130 -160
  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 +19 -249
  12. package/dist/compiler.js +10 -1408
  13. package/dist/{css-COcFOT1y.d.ts → css-BVQKICKC.d.ts} +30 -4
  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 -0
  21. package/dist/email/assets.js +63 -0
  22. package/dist/email/brand-footer.d.ts +34 -0
  23. package/dist/email/brand-footer.js +63 -0
  24. package/dist/email/brand.d.ts +2 -0
  25. package/dist/email/brand.js +148 -0
  26. package/dist/email/components.d.ts +21 -10
  27. package/dist/email/components.js +46 -11
  28. package/dist/email/diagnostics.d.ts +2 -2
  29. package/dist/email/diagnostics.js +2 -1
  30. package/dist/email/elements.d.ts +1 -1
  31. package/dist/email/html.d.ts +2 -42
  32. package/dist/email/html.js +56 -2
  33. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  34. package/dist/email/jsx-runtime.d.ts +1 -1
  35. package/dist/email/render.d.ts +10 -13
  36. package/dist/email/render.js +163 -6
  37. package/dist/email/text.d.ts +1 -1
  38. package/dist/fmt.d.ts +32 -0
  39. package/dist/fmt.js +174 -0
  40. package/dist/html-BJepOwZ4.d.ts +56 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/ir-aaqNE987.d.ts +252 -0
  44. package/dist/ir.d.ts +2 -0
  45. package/dist/ir.js +7 -0
  46. package/dist/preview-Ekdg1dXG.d.ts +100 -0
  47. package/dist/preview.d.ts +2 -0
  48. package/dist/preview.js +160 -0
  49. package/dist/profile-BH-OtdDA.js +3670 -0
  50. package/dist/render-ir.d.ts +19 -0
  51. package/dist/render-ir.js +826 -0
  52. package/dist/sms-segments.d.ts +13 -2
  53. package/dist/sms.d.ts +16 -0
  54. package/dist/sms.js +12 -0
  55. package/dist/{email/source-edit.d.ts → source-edit.d.ts} +55 -6
  56. package/dist/source-edit.js +343 -0
  57. package/dist/source-locations-C8wCsiAy.d.ts +23 -0
  58. package/dist/source-locations.d.ts +2 -2
  59. package/dist/source-locations.js +3 -89
  60. package/dist/static.d.ts +57 -0
  61. package/dist/static.js +69 -0
  62. package/dist/tailwind-CMYpZf1v.js +614 -0
  63. package/dist/template-_QE_rQND.d.ts +65 -0
  64. package/dist/template.d.ts +2 -2
  65. package/dist/template.js +15 -10
  66. package/dist/theme-css-hFqcPSar.js +587 -0
  67. package/dist/theme-css.d.ts +57 -16
  68. package/dist/theme-css.js +2 -509
  69. package/dist/theme-imports-lvBw3AD1.js +109 -0
  70. package/dist/transform-Ct2OMkFN.d.ts +153 -0
  71. package/dist/unsubscribe-link-CoesnQDU.js +26 -0
  72. package/dist/whatsapp.d.ts +57 -0
  73. package/dist/whatsapp.js +22 -0
  74. package/generated/contract.json +1782 -0
  75. package/generated/reference.md +353 -0
  76. package/package.json +56 -154
  77. package/samva-brand.d.ts +14 -0
  78. package/skills/sml/SKILL.md +123 -0
  79. package/dist/ast-BrDtApwS.d.ts +0 -107
  80. package/dist/ast.d.ts +0 -2
  81. package/dist/ast.js +0 -124
  82. package/dist/candidate.d.ts +0 -29
  83. package/dist/candidate.js +0 -88
  84. package/dist/catalog-60eaYC7b.d.ts +0 -356
  85. package/dist/checks-DBweSIPN.js +0 -58954
  86. package/dist/checks.d.ts +0 -33
  87. package/dist/checks.js +0 -2
  88. package/dist/classes.d.ts +0 -27
  89. package/dist/classes.js +0 -184
  90. package/dist/compile-sms.d.ts +0 -28
  91. package/dist/compile-sms.js +0 -44
  92. package/dist/compile-whatsapp.d.ts +0 -56
  93. package/dist/compile-whatsapp.js +0 -186
  94. package/dist/compile.d.ts +0 -18
  95. package/dist/compile.js +0 -29
  96. package/dist/components.d.ts +0 -289
  97. package/dist/components.js +0 -106
  98. package/dist/conditionals.d.ts +0 -10
  99. package/dist/conditionals.js +0 -74
  100. package/dist/corpus-BjYMqOoY.d.ts +0 -106
  101. package/dist/diagnostics-OcQKQYSY.d.ts +0 -49
  102. package/dist/email/selection.d.ts +0 -91
  103. package/dist/email/selection.js +0 -91
  104. package/dist/email/source-edit.js +0 -185
  105. package/dist/email/stylesheets.d.ts +0 -15
  106. package/dist/email/stylesheets.js +0 -45
  107. package/dist/emit.d.ts +0 -51
  108. package/dist/emit.js +0 -151
  109. package/dist/footer.d.ts +0 -66
  110. package/dist/footer.js +0 -93
  111. package/dist/interpolate.d.ts +0 -24
  112. package/dist/interpolate.js +0 -43
  113. package/dist/jsx-dev-runtime.d.ts +0 -11
  114. package/dist/jsx-dev-runtime.js +0 -15
  115. package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
  116. package/dist/jsx-runtime.d.ts +0 -2
  117. package/dist/jsx-runtime.js +0 -121
  118. package/dist/lexer.d.ts +0 -60
  119. package/dist/lexer.js +0 -281
  120. package/dist/model.d.ts +0 -24
  121. package/dist/model.js +0 -77
  122. package/dist/ops-2aiiYO7m.js +0 -630
  123. package/dist/ops.d.ts +0 -114
  124. package/dist/ops.js +0 -2
  125. package/dist/parser-t29cHLVL.d.ts +0 -396
  126. package/dist/parser.d.ts +0 -2
  127. package/dist/parser.js +0 -859
  128. package/dist/predicate-BI6uRRM-.js +0 -549
  129. package/dist/predicate-M-05Nd7R.d.ts +0 -56
  130. package/dist/predicate.d.ts +0 -2
  131. package/dist/predicate.js +0 -2
  132. package/dist/presets/category-mapping.d.ts +0 -13
  133. package/dist/presets/category-mapping.js +0 -49
  134. package/dist/presets/corpus.d.ts +0 -2
  135. package/dist/presets/corpus.js +0 -194
  136. package/dist/presets/public-gallery.d.ts +0 -266
  137. package/dist/presets/public-gallery.js +0 -916
  138. package/dist/presets/public-handoff.d.ts +0 -164
  139. package/dist/presets/public-handoff.js +0 -361
  140. package/dist/presets/public-preview-artifacts.d.ts +0 -43
  141. package/dist/presets/public-preview-artifacts.js +0 -104
  142. package/dist/presets/public-preview.d.ts +0 -61
  143. package/dist/presets/public-preview.js +0 -83
  144. package/dist/presets/showcase.d.ts +0 -2
  145. package/dist/presets/showcase.js +0 -627
  146. package/dist/presets/sources.d.ts +0 -38
  147. package/dist/presets/sources.js +0 -6083
  148. package/dist/presets.d.ts +0 -2
  149. package/dist/presets.js +0 -881
  150. package/dist/printer.d.ts +0 -6
  151. package/dist/printer.js +0 -122
  152. package/dist/registry.d.ts +0 -42
  153. package/dist/registry.js +0 -646
  154. package/dist/render-template.d.ts +0 -45
  155. package/dist/render-template.js +0 -76
  156. package/dist/showcase-DGZAWn4Z.d.ts +0 -456
  157. package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
  158. package/dist/source-locations-DrCM82Ym.d.ts +0 -67
  159. package/dist/template-DWftqh4x.d.ts +0 -60
  160. package/dist/theme-6ApjjiBo.d.ts +0 -116
  161. package/dist/theme.d.ts +0 -2
  162. package/dist/theme.js +0 -118
  163. package/dist/tokens.d.ts +0 -6
  164. package/dist/tokens.js +0 -417
  165. package/dist/tsx-source-instrumentation.d.ts +0 -30
  166. package/dist/tsx-source-instrumentation.js +0 -115
  167. package/dist/tsx-tracer.d.ts +0 -112
  168. package/dist/tsx-tracer.js +0 -985
  169. package/dist/variables-J_L3qxRa.d.ts +0 -77
  170. package/dist/variables.d.ts +0 -2
  171. package/dist/variables.js +0 -141
  172. package/dist/version-BITr58kk.js +0 -96
  173. package/generated/sml-agent-contract.json +0 -709
  174. package/generated/sml-email-agent-contract.json +0 -615
  175. package/generated/sml-email-language-reference.md +0 -94
  176. package/generated/sml-language-reference.md +0 -195
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/markup",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Type-safe authoring and compilation primitives for Samva templates",
5
5
  "keywords": [
6
6
  "email",
@@ -26,7 +26,9 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "!dist/**/*.map",
29
+ "samva-brand.d.ts",
29
30
  "generated",
31
+ "skills",
30
32
  "package.json",
31
33
  "README.md",
32
34
  "AGENTS.md",
@@ -35,171 +37,60 @@
35
37
  "type": "module",
36
38
  "sideEffects": false,
37
39
  "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js"
43
+ },
38
44
  "./package.json": "./package.json",
39
- "./ast": {
40
- "types": "./dist/ast.d.ts",
41
- "import": "./dist/ast.js"
45
+ "./static": {
46
+ "types": "./dist/static.d.ts",
47
+ "import": "./dist/static.js"
48
+ },
49
+ "./ir": {
50
+ "types": "./dist/ir.d.ts",
51
+ "import": "./dist/ir.js"
52
+ },
53
+ "./render-ir": {
54
+ "types": "./dist/render-ir.d.ts",
55
+ "import": "./dist/render-ir.js"
42
56
  },
43
- "./candidate": {
44
- "types": "./dist/candidate.d.ts",
45
- "import": "./dist/candidate.js"
57
+ "./sms": {
58
+ "types": "./dist/sms.d.ts",
59
+ "import": "./dist/sms.js"
46
60
  },
47
- "./classes": {
48
- "types": "./dist/classes.d.ts",
49
- "import": "./dist/classes.js"
61
+ "./whatsapp": {
62
+ "types": "./dist/whatsapp.d.ts",
63
+ "import": "./dist/whatsapp.js"
50
64
  },
51
- "./components": {
52
- "types": "./dist/components.d.ts",
53
- "import": "./dist/components.js"
65
+ "./fmt": {
66
+ "types": "./dist/fmt.d.ts",
67
+ "import": "./dist/fmt.js"
54
68
  },
55
69
  "./compiler": {
56
70
  "types": "./dist/compiler.d.ts",
57
71
  "import": "./dist/compiler.js"
58
72
  },
59
- "./compile": {
60
- "types": "./dist/compile.d.ts",
61
- "import": "./dist/compile.js"
62
- },
63
- "./compile-sms": {
64
- "types": "./dist/compile-sms.d.ts",
65
- "import": "./dist/compile-sms.js"
66
- },
67
- "./compile-whatsapp": {
68
- "types": "./dist/compile-whatsapp.d.ts",
69
- "import": "./dist/compile-whatsapp.js"
70
- },
71
- "./interpolate": {
72
- "types": "./dist/interpolate.d.ts",
73
- "import": "./dist/interpolate.js"
74
- },
75
- "./model": {
76
- "types": "./dist/model.d.ts",
77
- "import": "./dist/model.js"
78
- },
79
- "./ops": {
80
- "types": "./dist/ops.d.ts",
81
- "import": "./dist/ops.js"
82
- },
83
73
  "./sms-segments": {
84
74
  "types": "./dist/sms-segments.d.ts",
85
75
  "import": "./dist/sms-segments.js"
86
76
  },
87
- "./checks": {
88
- "types": "./dist/checks.d.ts",
89
- "import": "./dist/checks.js"
90
- },
91
- "./footer": {
92
- "types": "./dist/footer.d.ts",
93
- "import": "./dist/footer.js"
94
- },
95
- "./conditionals": {
96
- "types": "./dist/conditionals.d.ts",
97
- "import": "./dist/conditionals.js"
77
+ "./diagnostic-codes": {
78
+ "types": "./dist/diagnostic-codes.d.ts",
79
+ "import": "./dist/diagnostic-codes.js"
98
80
  },
99
81
  "./diagnostics": {
100
82
  "types": "./dist/diagnostics.d.ts",
101
83
  "import": "./dist/diagnostics.js"
102
84
  },
103
- "./emit": {
104
- "types": "./dist/emit.d.ts",
105
- "import": "./dist/emit.js"
106
- },
107
- "./jsx-runtime": {
108
- "types": "./dist/jsx-runtime.d.ts",
109
- "import": "./dist/jsx-runtime.js"
110
- },
111
- "./jsx-dev-runtime": {
112
- "types": "./dist/jsx-dev-runtime.d.ts",
113
- "import": "./dist/jsx-dev-runtime.js"
114
- },
115
- "./lexer": {
116
- "types": "./dist/lexer.d.ts",
117
- "import": "./dist/lexer.js"
118
- },
119
- "./parser": {
120
- "types": "./dist/parser.d.ts",
121
- "import": "./dist/parser.js"
122
- },
123
- "./predicate": {
124
- "types": "./dist/predicate.d.ts",
125
- "import": "./dist/predicate.js"
126
- },
127
- "./presets": {
128
- "types": "./dist/presets.d.ts",
129
- "import": "./dist/presets.js"
130
- },
131
- "./presets/category-mapping": {
132
- "types": "./dist/presets/category-mapping.d.ts",
133
- "import": "./dist/presets/category-mapping.js"
134
- },
135
- "./presets/corpus": {
136
- "types": "./dist/presets/corpus.d.ts",
137
- "import": "./dist/presets/corpus.js"
138
- },
139
- "./presets/public-gallery": {
140
- "types": "./dist/presets/public-gallery.d.ts",
141
- "import": "./dist/presets/public-gallery.js"
142
- },
143
- "./presets/public-handoff": {
144
- "types": "./dist/presets/public-handoff.d.ts",
145
- "import": "./dist/presets/public-handoff.js"
146
- },
147
- "./presets/public-preview": {
148
- "types": "./dist/presets/public-preview.d.ts",
149
- "import": "./dist/presets/public-preview.js"
150
- },
151
- "./presets/public-preview-artifacts": {
152
- "types": "./dist/presets/public-preview-artifacts.d.ts",
153
- "import": "./dist/presets/public-preview-artifacts.js"
154
- },
155
- "./presets/showcase": {
156
- "types": "./dist/presets/showcase.d.ts",
157
- "import": "./dist/presets/showcase.js"
158
- },
159
- "./presets/sources": {
160
- "types": "./dist/presets/sources.d.ts",
161
- "import": "./dist/presets/sources.js"
162
- },
163
- "./printer": {
164
- "types": "./dist/printer.d.ts",
165
- "import": "./dist/printer.js"
166
- },
167
- "./registry": {
168
- "types": "./dist/registry.d.ts",
169
- "import": "./dist/registry.js"
170
- },
171
- "./render-template": {
172
- "types": "./dist/render-template.d.ts",
173
- "import": "./dist/render-template.js"
174
- },
175
85
  "./source-locations": {
176
86
  "types": "./dist/source-locations.d.ts",
177
87
  "import": "./dist/source-locations.js"
178
88
  },
179
- "./theme": {
180
- "types": "./dist/theme.d.ts",
181
- "import": "./dist/theme.js"
182
- },
183
89
  "./theme-css": {
184
90
  "types": "./dist/theme-css.d.ts",
185
91
  "import": "./dist/theme-css.js"
186
92
  },
187
- "./tsx-tracer": {
188
- "types": "./dist/tsx-tracer.d.ts",
189
- "import": "./dist/tsx-tracer.js"
190
- },
191
- "./tsx-source-instrumentation": {
192
- "types": "./dist/tsx-source-instrumentation.d.ts",
193
- "import": "./dist/tsx-source-instrumentation.js"
194
- },
195
- "./tokens": {
196
- "types": "./dist/tokens.d.ts",
197
- "import": "./dist/tokens.js"
198
- },
199
- "./variables": {
200
- "types": "./dist/variables.d.ts",
201
- "import": "./dist/variables.js"
202
- },
93
+ "./contract.json": "./generated/contract.json",
203
94
  "./generated/*": "./generated/*",
204
95
  "./input-schema": {
205
96
  "types": "./dist/input-schema.d.ts",
@@ -233,13 +124,21 @@
233
124
  "types": "./dist/email/diagnostics.d.ts",
234
125
  "import": "./dist/email/diagnostics.js"
235
126
  },
236
- "./email/source-edit": {
237
- "types": "./dist/email/source-edit.d.ts",
238
- "import": "./dist/email/source-edit.js"
127
+ "./email/assets": {
128
+ "types": "./dist/email/assets.d.ts",
129
+ "import": "./dist/email/assets.js"
239
130
  },
240
- "./email/selection": {
241
- "types": "./dist/email/selection.d.ts",
242
- "import": "./dist/email/selection.js"
131
+ "./channel-edit": {
132
+ "types": "./dist/channel-edit.d.ts",
133
+ "import": "./dist/channel-edit.js"
134
+ },
135
+ "./source-edit": {
136
+ "types": "./dist/source-edit.d.ts",
137
+ "import": "./dist/source-edit.js"
138
+ },
139
+ "./preview": {
140
+ "types": "./dist/preview.d.ts",
141
+ "import": "./dist/preview.js"
243
142
  },
244
143
  "./email/html": {
245
144
  "types": "./dist/email/html.d.ts",
@@ -249,9 +148,16 @@
249
148
  "types": "./dist/email/text.d.ts",
250
149
  "import": "./dist/email/text.js"
251
150
  },
252
- "./email/stylesheets": {
253
- "types": "./dist/email/stylesheets.d.ts",
254
- "import": "./dist/email/stylesheets.js"
151
+ "./samva-brand": {
152
+ "types": "./samva-brand.d.ts"
153
+ },
154
+ "./email/brand-footer": {
155
+ "types": "./dist/email/brand-footer.d.ts",
156
+ "import": "./dist/email/brand-footer.js"
157
+ },
158
+ "./email/brand": {
159
+ "types": "./dist/email/brand.d.ts",
160
+ "import": "./dist/email/brand.js"
255
161
  }
256
162
  },
257
163
  "publishConfig": {
@@ -260,14 +166,10 @@
260
166
  "scripts": {
261
167
  "build": "tsdown",
262
168
  "prepack": "bun run build",
263
- "codegen:presets": "bun run ../vite/scripts/codegen-presets.ts && oxfmt presets/sources.gen.ts",
264
- "import:public-handoff": "bun run ../vite/scripts/import-public-template-handoff.ts",
265
169
  "corpus:render": "bun run scripts/render-compatibility-corpus.ts",
266
170
  "codegen:tailwind": "bun run scripts/codegen-tailwind.ts && oxfmt src/email/tailwind-stylesheets.gen.ts",
267
- "codegen:tokens": "bun run scripts/codegen-tokens.ts && oxfmt src/tokens.ts",
268
- "codegen:sml-reference": "bun run scripts/generate-sml-reference.ts",
171
+ "codegen:contract": "bun run scripts/generate-contract.ts",
269
172
  "codegen:docs": "bun run scripts/codegen-docs.ts",
270
- "refresh-caniemail": "bun run scripts/refresh-caniemail.ts",
271
173
  "test": "vitest run --root ../.. --project packages packages/markup/tests",
272
174
  "typecheck": "tsc --noEmit && tsc --noEmit -p tests/consumer/tsconfig.json"
273
175
  },
@@ -0,0 +1,14 @@
1
+ // The `samva:brand` module a template imports its brand components from. The
2
+ // compiler resolves the specifier to a generated module bound to the build's
3
+ // brand; this declaration is what a project's own `tsc` sees. Add
4
+ // `"types": ["@samva/markup/samva-brand"]` to the project's tsconfig.
5
+
6
+ declare module "samva:brand" {
7
+ export const BrandLogo: import("@samva/markup/email/brand").BrandComponents["BrandLogo"];
8
+ export const BrandFooter: import("@samva/markup/email/brand").BrandComponents["BrandFooter"];
9
+ }
10
+
11
+ declare module "samva:brand/*" {
12
+ export const BrandLogo: import("@samva/markup/email/brand").BrandComponents["BrandLogo"];
13
+ export const BrandFooter: import("@samva/markup/email/brand").BrandComponents["BrandFooter"];
14
+ }
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: sml
3
+ description: Author, check, render and repair Samva email templates written as static-profile TSX (defineTemplate). Use when creating or editing a file under templates/ or emails/, fixing template diagnostics, or previewing a template.
4
+ ---
5
+
6
+ # Authoring Samva templates
7
+
8
+ A template is one `.tsx` file that default-exports `defineTemplate` from `@samva/markup`. It is
9
+ ordinary JSX, HTML and Tailwind written in a static profile: the compiler reads it without running
10
+ it and produces a JSON IR. Every preview and every send renders that IR against the input, so
11
+ nothing you write executes at send time.
12
+
13
+ ## The loop
14
+
15
+ Write, check, render, look, repair. Repeat until the check is clean.
16
+
17
+ ```bash
18
+ samva templates check # compile every template, print diagnostics with fixes
19
+ samva templates render --fixture no-tracking # subject, preheader, text and HTML for one fixture
20
+ samva templates snapshot # desktop and mobile PNGs, light and dark
21
+ samva templates dev # local editor with every fixture rendered
22
+ ```
23
+
24
+ Add `--json` for diagnostics as data: each has a `code`, a location, a severity and a `fix`. Apply
25
+ the fix the diagnostic names rather than rewriting around it. Without the CLI, call
26
+ `compileTemplate` from `@samva/markup/static` and `renderIr` from `@samva/markup/render-ir`.
27
+
28
+ ## Upgrades
29
+
30
+ When a diagnostic has an `upgrade` field in `@samva/markup/contract.json`, follow that recipe
31
+ for every affected entry while preserving project edits and entry paths. The Upgrades section of
32
+ `generated/reference.md` renders the same recipes. Check every converted entry and render its
33
+ fixtures before saving the project.
34
+
35
+ ## Shape
36
+
37
+ ```tsx
38
+ import { defineTemplate } from "@samva/markup";
39
+ import { Email } from "@samva/markup/email/components";
40
+ import { fmt } from "@samva/markup/fmt";
41
+ import { jsonSchema } from "@samva/markup/input-schema";
42
+
43
+ export default defineTemplate({
44
+ id: "order-shipped",
45
+ schema: jsonSchema<{ name: string; total: number; currency: string; trackingUrl?: string }>({
46
+ type: "object",
47
+ properties: {
48
+ name: { type: "string" },
49
+ total: { type: "number" },
50
+ currency: { type: "string" },
51
+ trackingUrl: { type: "string" },
52
+ },
53
+ required: ["name", "total", "currency"],
54
+ additionalProperties: false,
55
+ }),
56
+ fixtures: {
57
+ default: { name: "Ada", total: 84, currency: "EUR", trackingUrl: "https://example.com/t/1" },
58
+ "no-tracking": { name: "Ada", total: 84, currency: "EUR" },
59
+ },
60
+ email: {
61
+ subject: (input) => `${input.name}, your order shipped`,
62
+ body: (input) => (
63
+ <Email>
64
+ <h1 className="text-2xl font-semibold">Hi {input.name}, it shipped.</h1>
65
+ <p>Total {fmt.money(input.total, input.currency)}</p>
66
+ {input.trackingUrl && <a href={input.trackingUrl}>Track package</a>}
67
+ </Email>
68
+ ),
69
+ },
70
+ });
71
+ ```
72
+
73
+ - `id` is lowercase kebab-case and is the template's public handle.
74
+ - Put templates in `templates/` (`emails/` also works). Import only `@samva/markup` and files in
75
+ the project; there are no dependencies to install.
76
+ - `subject` and `preheader` belong to the `email` channel, never to `<Email>`.
77
+ - Declare at least one fixture, and one per branch: default, missing optional fields, many items.
78
+
79
+ ## The static profile
80
+
81
+ Use these forms; they are what models already write for React.
82
+
83
+ | Form | Example |
84
+ | ------------------- | --------------------------------------------------------------------------- |
85
+ | Text and attributes | `{input.name}`, `href={input.trackingUrl}`, template strings |
86
+ | Condition | `{input.trackingUrl && <A />}`, `{input.vip ? <A /> : <B />}` |
87
+ | Comparison | `===`, `!==`, `<`, `>`, `!`, `&&`, `\|\|`, `.length` |
88
+ | Loop | `input.items.map((item) => <Row />)` |
89
+ | Filter and count | `input.items.filter((item) => item.done).length` |
90
+ | Grid | `<Columns each={input.deals} per={2}>{(deal) => <Column />}</Columns>` |
91
+ | Formatter | `fmt.money`, `fmt.number`, `fmt.date`, `fmt.time`, `fmt.plural`, `fmt.list` |
92
+ | Partial | any project component, inlined at compile time |
93
+ | Conditional class | `className={input.vip ? "bg-amber-100" : "bg-white"}` |
94
+
95
+ Anything else is a diagnostic: other calls (`reduce`, `Math.floor`, `Date.now`), statements in a
96
+ body (`const`, `if`, loops), imports outside the project, hooks, event handlers,
97
+ `dangerouslySetInnerHTML`, and class names built at run time. There is no escape hatch. When the
98
+ profile cannot express something, compute it where the message is sent and add a field to the
99
+ schema.
100
+
101
+ ## Data
102
+
103
+ - The schema is a literal `jsonSchema<Input>({ ... })` or any Standard JSON Schema converter. It
104
+ validates every send and drives the binding checker: `input.ordr` fails with a suggestion.
105
+ - Guard optional fields before binding them: `{input.trackingUrl && ...}` (`unguarded-optional`).
106
+ - Every fixture must validate against the schema (`fixture-invalid`). Nothing is coerced and no
107
+ defaults are inserted.
108
+ - Locale and time zone are send options; `fmt` reads them, and an explicit locale argument wins.
109
+
110
+ ## Styling and components
111
+
112
+ Write HTML for content and Tailwind classes for style. Components exist only where an email client
113
+ needs something HTML cannot say: `Email`, `Section`, `Columns`/`Column`, `Button`, `Image`,
114
+ `Spacer`, `Divider`, and `BrandLogo`/`BrandFooter` from `samva:brand`. `theme.css` layers
115
+ `starter.css`, then `@import "samva:brand";`, then the project's own `@theme`. Fonts are `.woff2`
116
+ files declared with `@font-face`, and a font stack ends in a generic family.
117
+
118
+ ## References
119
+
120
+ - `contract.json` (`@samva/markup/contract.json`): components with props, profile forms,
121
+ formatters, elements and every diagnostic code, generated from the compiler.
122
+ - `generated/reference.md`: the same contract as a readable reference.
123
+ - `AGENTS.md`: the package guide, including what goes wrong and why.
@@ -1,107 +0,0 @@
1
- import { a as Span } from "./diagnostics-OcQKQYSY.js";
2
- //#region src/ast.d.ts
3
- declare const BLOCK_TAGS: readonly ["Email", "Subject", "Preview", "Container", "Section", "Row", "Column", "Heading", "Text", "Button", "Image", "Divider", "Spacer", "List", "ListItem", "If", "Else"];
4
- declare const INLINE_TAGS: readonly ["strong", "em", "a"];
5
- /** Email-only helper tags scoped by their parent primitive. */
6
- declare const AUXILIARY_TAGS: readonly ["Source"];
7
- /** Structured-channel tags: the sms/whatsapp vocabulary beyond shared If/Else. */
8
- declare const CHANNEL_TAGS: readonly ["Sms", "WhatsApp", "Header", "Body", "Footer", "Buttons"];
9
- declare const SML_TAGS: readonly ["Email", "Subject", "Preview", "Container", "Section", "Row", "Column", "Heading", "Text", "Button", "Image", "Divider", "Spacer", "List", "ListItem", "If", "Else", "strong", "em", "a", "Source", "Sms", "WhatsApp", "Header", "Body", "Footer", "Buttons"];
10
- /**
11
- * JSX component name → the SML tag that component emits. TSX authors write
12
- * capitalized component names, and the inline tags are lowercase in SML, so
13
- * `<Strong>` emits `strong` and `<A>` emits `a`. The table is derived from
14
- * `SML_TAGS` rather than written out, so a new tag is mapped the moment it
15
- * joins the vocabulary. `components.ts` is held to it by the component test
16
- * suite, and `tsx-tracer.ts` resolves authored JSX names through it.
17
- */
18
- declare const SML_TAG_BY_COMPONENT_NAME: Readonly<Record<string, SmlTag>>;
19
- /** The SML tag a JSX component name emits, or `undefined` when it is not a Samva primitive. */
20
- declare const smlTagForComponentName: (name: string) => SmlTag | undefined;
21
- /** The JSX component name that emits an SML tag, for printing SML back into TSX. */
22
- declare const componentNameForSmlTag: (tag: SmlTag) => string;
23
- type BlockTag = (typeof BLOCK_TAGS)[number];
24
- type InlineTag = (typeof INLINE_TAGS)[number];
25
- type AuxiliaryTag = (typeof AUXILIARY_TAGS)[number];
26
- type ChannelTag = (typeof CHANNEL_TAGS)[number];
27
- type SmlTag = (typeof SML_TAGS)[number];
28
- /** Channels supported by the public SML document model. */
29
- type Channel = "email" | "sms" | "whatsapp";
30
- /** Resolved primary width for email documents that omit `<Email width={…}>`. */
31
- declare const DEFAULT_EMAIL_WIDTH = 600;
32
- /** Root tag → channel. The root tag IS the channel declaration. */
33
- declare const CHANNEL_BY_ROOT_TAG: {
34
- readonly Email: "email";
35
- readonly Sms: "sms";
36
- readonly WhatsApp: "whatsapp";
37
- };
38
- type RootTag = keyof typeof CHANNEL_BY_ROOT_TAG;
39
- declare const ROOT_TAGS: ReadonlyArray<RootTag>;
40
- declare const isRootTag: (tag: string) => tag is RootTag;
41
- /** The channel a parsed document targets, read off its root tag. */
42
- declare const documentChannel: (document: SmlDocument) => Channel;
43
- type AttributeValue = {
44
- readonly kind: "string";
45
- readonly value: string;
46
- } | {
47
- readonly kind: "number";
48
- readonly value: number;
49
- };
50
- interface Attribute {
51
- readonly name: string;
52
- readonly value: AttributeValue;
53
- /** Span of the whole `name="value"` pair. */
54
- readonly span: Span;
55
- /** Span of the value contents (inside quotes/braces) — class diagnostics point here. */
56
- readonly valueSpan: Span;
57
- }
58
- interface TextNode {
59
- readonly kind: "text";
60
- readonly id: string;
61
- readonly value: string;
62
- readonly span: Span;
63
- }
64
- interface InterpolationNode {
65
- readonly kind: "interpolation";
66
- readonly id: string;
67
- readonly name: string;
68
- /** {{name}} vs ${name} — both today's syntaxes, preserved through print. */
69
- readonly syntax: "curly" | "dollar";
70
- readonly span: Span;
71
- }
72
- interface ElementNode {
73
- readonly kind: "element";
74
- readonly id: string;
75
- readonly tag: SmlTag;
76
- readonly attributes: ReadonlyArray<Attribute>;
77
- readonly children: ReadonlyArray<SmlNode>;
78
- readonly span: Span;
79
- }
80
- type SmlNode = ElementNode | TextNode | InterpolationNode;
81
- interface SmlDocument {
82
- readonly kind: "document";
83
- /** Always a root element: `<Email>`, `<Sms>`, or `<WhatsApp>`. */
84
- readonly root: ElementNode;
85
- }
86
- declare const attributeValue: (node: ElementNode, name: string) => AttributeValue | undefined;
87
- declare const stringAttribute: (node: ElementNode, name: string) => string | undefined;
88
- declare const numberAttribute: (node: ElementNode, name: string) => number | undefined;
89
- /**
90
- * The editor-only display label (navigator rename) — a universal attribute the
91
- * compiler never reads, so it never reaches HTML. Absent when unset.
92
- */
93
- declare const nodeLabel: (node: ElementNode) => string | undefined;
94
- /**
95
- * The editor-only visibility flag. A `hidden="true"` node stays in the source
96
- * (and the navigator, dimmed) but is dropped from every compile — preview and
97
- * send alike. Any other value / absence means visible.
98
- */
99
- declare const isHiddenNode: (node: ElementNode) => boolean;
100
- /**
101
- * All variable references in the document: `{{name}}` / `${name}` in children
102
- * and attribute values, plus names referenced by `<If test="...">` predicates.
103
- */
104
- declare const collectVariableReferences: (document: SmlDocument) => ReadonlySet<string>;
105
- //#endregion
106
- export { nodeLabel as A, TextNode as C, documentChannel as D, componentNameForSmlTag as E, smlTagForComponentName as M, stringAttribute as N, isHiddenNode as O, SmlTag as S, collectVariableReferences as T, RootTag as _, BLOCK_TAGS as a, SmlDocument as b, CHANNEL_TAGS as c, DEFAULT_EMAIL_WIDTH as d, ElementNode as f, ROOT_TAGS as g, InterpolationNode as h, AuxiliaryTag as i, numberAttribute as j, isRootTag as k, Channel as l, InlineTag as m, Attribute as n, BlockTag as o, INLINE_TAGS as p, AttributeValue as r, CHANNEL_BY_ROOT_TAG as s, AUXILIARY_TAGS as t, ChannelTag as u, SML_TAGS as v, attributeValue as w, SmlNode as x, SML_TAG_BY_COMPONENT_NAME as y };
107
- //# sourceMappingURL=ast-BrDtApwS.d.ts.map
package/dist/ast.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { A as nodeLabel, C as TextNode, D as documentChannel, E as componentNameForSmlTag, M as smlTagForComponentName, N as stringAttribute, O as isHiddenNode, S as SmlTag, T as collectVariableReferences, _ as RootTag, a as BLOCK_TAGS, b as SmlDocument, c as CHANNEL_TAGS, d as DEFAULT_EMAIL_WIDTH, f as ElementNode, g as ROOT_TAGS, h as InterpolationNode, i as AuxiliaryTag, j as numberAttribute, k as isRootTag, l as Channel, m as InlineTag, n as Attribute, o as BlockTag, p as INLINE_TAGS, r as AttributeValue, s as CHANNEL_BY_ROOT_TAG, t as AUXILIARY_TAGS, u as ChannelTag, v as SML_TAGS, w as attributeValue, x as SmlNode, y as SML_TAG_BY_COMPONENT_NAME } from "./ast-BrDtApwS.js";
2
- export { AUXILIARY_TAGS, Attribute, AttributeValue, AuxiliaryTag, BLOCK_TAGS, BlockTag, CHANNEL_BY_ROOT_TAG, CHANNEL_TAGS, Channel, ChannelTag, DEFAULT_EMAIL_WIDTH, ElementNode, INLINE_TAGS, InlineTag, InterpolationNode, ROOT_TAGS, RootTag, SML_TAGS, SML_TAG_BY_COMPONENT_NAME, SmlDocument, SmlNode, SmlTag, TextNode, attributeValue, collectVariableReferences, componentNameForSmlTag, documentChannel, isHiddenNode, isRootTag, nodeLabel, numberAttribute, smlTagForComponentName, stringAttribute };
package/dist/ast.js DELETED
@@ -1,124 +0,0 @@
1
- import { i as parsePredicate, n as collectPredicateVariables } from "./predicate-BI6uRRM-.js";
2
- import { Result } from "better-result";
3
- //#region src/ast.ts
4
- const BLOCK_TAGS = [
5
- "Email",
6
- "Subject",
7
- "Preview",
8
- "Container",
9
- "Section",
10
- "Row",
11
- "Column",
12
- "Heading",
13
- "Text",
14
- "Button",
15
- "Image",
16
- "Divider",
17
- "Spacer",
18
- "List",
19
- "ListItem",
20
- "If",
21
- "Else"
22
- ];
23
- const INLINE_TAGS = [
24
- "strong",
25
- "em",
26
- "a"
27
- ];
28
- /** Email-only helper tags scoped by their parent primitive. */
29
- const AUXILIARY_TAGS = ["Source"];
30
- /** Structured-channel tags: the sms/whatsapp vocabulary beyond shared If/Else. */
31
- const CHANNEL_TAGS = [
32
- "Sms",
33
- "WhatsApp",
34
- "Header",
35
- "Body",
36
- "Footer",
37
- "Buttons"
38
- ];
39
- const SML_TAGS = [
40
- ...BLOCK_TAGS,
41
- ...INLINE_TAGS,
42
- ...AUXILIARY_TAGS,
43
- ...CHANNEL_TAGS
44
- ];
45
- /**
46
- * JSX component name → the SML tag that component emits. TSX authors write
47
- * capitalized component names, and the inline tags are lowercase in SML, so
48
- * `<Strong>` emits `strong` and `<A>` emits `a`. The table is derived from
49
- * `SML_TAGS` rather than written out, so a new tag is mapped the moment it
50
- * joins the vocabulary. `components.ts` is held to it by the component test
51
- * suite, and `tsx-tracer.ts` resolves authored JSX names through it.
52
- */
53
- const SML_TAG_BY_COMPONENT_NAME = Object.freeze(Object.fromEntries(SML_TAGS.map((tag) => [`${tag[0].toUpperCase()}${tag.slice(1)}`, tag])));
54
- /** The SML tag a JSX component name emits, or `undefined` when it is not a Samva primitive. */
55
- const smlTagForComponentName = (name) => SML_TAG_BY_COMPONENT_NAME[name];
56
- /** The JSX component name that emits an SML tag, for printing SML back into TSX. */
57
- const componentNameForSmlTag = (tag) => `${tag[0].toUpperCase()}${tag.slice(1)}`;
58
- /** Resolved primary width for email documents that omit `<Email width={…}>`. */
59
- const DEFAULT_EMAIL_WIDTH = 600;
60
- /** Root tag → channel. The root tag IS the channel declaration. */
61
- const CHANNEL_BY_ROOT_TAG = {
62
- Email: "email",
63
- Sms: "sms",
64
- WhatsApp: "whatsapp"
65
- };
66
- const ROOT_TAGS = Object.keys(CHANNEL_BY_ROOT_TAG);
67
- const isRootTag = (tag) => tag in CHANNEL_BY_ROOT_TAG;
68
- /** The channel a parsed document targets, read off its root tag. */
69
- const documentChannel = (document) => CHANNEL_BY_ROOT_TAG[document.root.tag];
70
- const attributeValue = (node, name) => node.attributes.find((attribute) => attribute.name === name)?.value;
71
- const stringAttribute = (node, name) => {
72
- const value = attributeValue(node, name);
73
- return value?.kind === "string" ? value.value : void 0;
74
- };
75
- const numberAttribute = (node, name) => {
76
- const value = attributeValue(node, name);
77
- if (value === void 0) return void 0;
78
- return value.kind === "number" ? value.value : Number.parseInt(value.value, 10);
79
- };
80
- /**
81
- * The editor-only display label (navigator rename) — a universal attribute the
82
- * compiler never reads, so it never reaches HTML. Absent when unset.
83
- */
84
- const nodeLabel = (node) => stringAttribute(node, "label");
85
- /**
86
- * The editor-only visibility flag. A `hidden="true"` node stays in the source
87
- * (and the navigator, dimmed) but is dropped from every compile — preview and
88
- * send alike. Any other value / absence means visible.
89
- */
90
- const isHiddenNode = (node) => stringAttribute(node, "hidden") === "true";
91
- /**
92
- * All variable references in the document: `{{name}}` / `${name}` in children
93
- * and attribute values, plus names referenced by `<If test="...">` predicates.
94
- */
95
- const collectVariableReferences = (document) => {
96
- const names = /* @__PURE__ */ new Set();
97
- const attributePattern = /\$\{\s*\/?([\w.]+)\s*\}|\{\{\s*([\w.]+)\s*\}\}/g;
98
- const visit = (node) => {
99
- if (node.kind === "interpolation") {
100
- names.add(node.name);
101
- return;
102
- }
103
- if (node.kind === "text") return;
104
- for (const attribute of node.attributes) {
105
- if (attribute.value.kind !== "string") continue;
106
- if (node.tag === "If" && attribute.name === "test") {
107
- const parsed = parsePredicate(attribute.value.value);
108
- if (Result.isOk(parsed)) for (const name of collectPredicateVariables(parsed.value)) names.add(name);
109
- continue;
110
- }
111
- for (const match of attribute.value.value.matchAll(attributePattern)) {
112
- const name = match[1] ?? match[2];
113
- if (name !== void 0) names.add(name);
114
- }
115
- }
116
- for (const child of node.children) visit(child);
117
- };
118
- visit(document.root);
119
- return names;
120
- };
121
- //#endregion
122
- export { AUXILIARY_TAGS, BLOCK_TAGS, CHANNEL_BY_ROOT_TAG, CHANNEL_TAGS, DEFAULT_EMAIL_WIDTH, INLINE_TAGS, ROOT_TAGS, SML_TAGS, SML_TAG_BY_COMPONENT_NAME, attributeValue, collectVariableReferences, componentNameForSmlTag, documentChannel, isHiddenNode, isRootTag, nodeLabel, numberAttribute, smlTagForComponentName, stringAttribute };
123
-
124
- //# sourceMappingURL=ast.js.map