@samva/markup 0.5.0 → 0.7.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 (85) hide show
  1. package/AGENTS.md +80 -45
  2. package/README.md +1 -1
  3. package/dist/{ast-BLybiDxp.d.ts → ast-BrDtApwS.d.ts} +2 -2
  4. package/dist/ast.d.ts +1 -1
  5. package/dist/ast.js +1 -1
  6. package/dist/{catalog-Db0UcCTg.d.ts → catalog-60eaYC7b.d.ts} +2 -2
  7. package/dist/{checks-CmmITP6j.js → checks-DBweSIPN.js} +12 -14
  8. package/dist/checks.d.ts +1 -1
  9. package/dist/checks.js +1 -1
  10. package/dist/classes.d.ts +1 -1
  11. package/dist/compile-sms.d.ts +3 -3
  12. package/dist/compile-whatsapp.d.ts +3 -3
  13. package/dist/compile.d.ts +2 -2
  14. package/dist/compiler.d.ts +7 -4
  15. package/dist/compiler.js +67 -46
  16. package/dist/components.d.ts +5 -5
  17. package/dist/conditionals.js +55 -23
  18. package/dist/{corpus-CM7rKX41.d.ts → corpus-BjYMqOoY.d.ts} +2 -2
  19. package/dist/{css-DoNc7CiC.d.ts → css-COcFOT1y.d.ts} +3 -3
  20. package/dist/{diagnostics-eFM9A_TU.d.ts → diagnostics-BMYtBCQk.d.ts} +2 -2
  21. package/dist/{diagnostics-CqepLcpI.d.ts → diagnostics-OcQKQYSY.d.ts} +4 -2
  22. package/dist/diagnostics.d.ts +2 -2
  23. package/dist/diagnostics.js +10 -5
  24. package/dist/{elements-ChJULZlh.d.ts → elements-BzjjnoCS.d.ts} +3 -3
  25. package/dist/email/components.d.ts +1 -1
  26. package/dist/email/components.js +40 -10
  27. package/dist/email/diagnostics.d.ts +1 -1
  28. package/dist/email/elements.d.ts +1 -1
  29. package/dist/email/html.d.ts +2 -2
  30. package/dist/email/html.js +2 -1
  31. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  32. package/dist/email/jsx-runtime.d.ts +1 -1
  33. package/dist/email/render.d.ts +4 -4
  34. package/dist/email/render.js +1 -1
  35. package/dist/email/selection.d.ts +1 -1
  36. package/dist/email/source-edit.d.ts +1 -1
  37. package/dist/email/stylesheets.d.ts +1 -1
  38. package/dist/email/text.d.ts +1 -1
  39. package/dist/emit.d.ts +2 -2
  40. package/dist/emit.js +2 -2
  41. package/dist/footer.d.ts +3 -3
  42. package/dist/footer.js +9 -6
  43. package/dist/jsx-dev-runtime.d.ts +2 -2
  44. package/dist/{jsx-runtime-BI4gI19E.d.ts → jsx-runtime-I-1nCtl2.d.ts} +3 -3
  45. package/dist/jsx-runtime.d.ts +1 -1
  46. package/dist/lexer.js +17 -5
  47. package/dist/model.d.ts +1 -1
  48. package/dist/ops-2aiiYO7m.js +630 -0
  49. package/dist/ops.d.ts +2 -2
  50. package/dist/ops.js +1 -479
  51. package/dist/{parser-b_Kp5MT6.d.ts → parser-t29cHLVL.d.ts} +4 -4
  52. package/dist/parser.d.ts +1 -1
  53. package/dist/parser.js +2 -3
  54. package/dist/{predicate-DwXzmqIY.js → predicate-BI6uRRM-.js} +14 -4
  55. package/dist/{predicate-Bmvgw7Ao.d.ts → predicate-M-05Nd7R.d.ts} +2 -2
  56. package/dist/predicate.d.ts +1 -1
  57. package/dist/predicate.js +1 -1
  58. package/dist/presets/category-mapping.d.ts +1 -1
  59. package/dist/presets/corpus.d.ts +1 -1
  60. package/dist/presets/public-gallery.d.ts +2 -2
  61. package/dist/presets/public-gallery.js +10 -2
  62. package/dist/presets/public-handoff.d.ts +2 -2
  63. package/dist/presets/public-preview-artifacts.d.ts +3 -1
  64. package/dist/presets/public-preview-artifacts.js +1 -0
  65. package/dist/presets/showcase.d.ts +1 -1
  66. package/dist/presets.d.ts +1 -1
  67. package/dist/printer.d.ts +1 -1
  68. package/dist/{render-T6IFPnP4.js → render-aaJBnsXK.js} +33 -16
  69. package/dist/render-template.d.ts +5 -5
  70. package/dist/render-template.js +1 -1
  71. package/dist/{showcase-dvJMn-Lo.d.ts → showcase-DGZAWn4Z.d.ts} +2 -2
  72. package/dist/{source-locations-D6Te6Gy1.d.ts → source-locations-DrCM82Ym.d.ts} +2 -2
  73. package/dist/source-locations.d.ts +1 -1
  74. package/dist/{template-G-QPcfmn.d.ts → template-DWftqh4x.d.ts} +2 -2
  75. package/dist/template.d.ts +1 -1
  76. package/dist/theme-css.d.ts +1 -1
  77. package/dist/theme.js +25 -2
  78. package/dist/tsx-source-instrumentation.d.ts +9 -0
  79. package/dist/tsx-source-instrumentation.js +20 -2
  80. package/dist/tsx-tracer.d.ts +1 -1
  81. package/dist/tsx-tracer.js +3 -3
  82. package/dist/{variables-BnKs1iqb.d.ts → variables-J_L3qxRa.d.ts} +2 -2
  83. package/dist/variables.d.ts +1 -1
  84. package/dist/variables.js +1 -1
  85. package/package.json +4 -4
package/dist/ops.js CHANGED
@@ -1,480 +1,2 @@
1
- import { documentChannel } from "./ast.js";
2
- import { CHANNEL_TAG_SPECS, allowedChildren, parseSml } from "./parser.js";
3
- import { compileSml } from "./compile.js";
4
- import { a as isBreakingCompatibilityCheck, r as checkSmlCompatibility } from "./checks-CmmITP6j.js";
5
- import { printSml } from "./printer.js";
6
- import { Result, TaggedError } from "better-result";
7
- //#region src/ops.ts
8
- var SmlOpError = class extends TaggedError("SmlOpError") {
9
- constructor(args) {
10
- super({
11
- diagnostics: args.diagnostics,
12
- message: args.diagnostics.map((diagnostic) => diagnostic.message).join("\n")
13
- });
14
- }
15
- };
16
- const zeroSpan = {
17
- start: 0,
18
- end: 0
19
- };
20
- const diagnostic = (code, message, span = zeroSpan) => ({
21
- code,
22
- message,
23
- span
24
- });
25
- const cloneAttribute = (attribute) => ({ ...attribute });
26
- /**
27
- * Deep-copy a subtree, blanking every id to "". A duplicated node must not
28
- * alias the original's object graph or share its ids: batch ids resolve
29
- * against the pre-batch snapshot, and mapElement transforms every node whose
30
- * id matches — so two live copies sharing an id would both be rewritten by a
31
- * later op. Blank ids make the clone unaddressable (like any freshly created
32
- * node) until the next reprint reassigns positional ids.
33
- */
34
- const cloneSubtree = (node) => {
35
- if (node.kind === "element") return {
36
- ...node,
37
- id: "",
38
- attributes: node.attributes.map(cloneAttribute),
39
- children: node.children.map(cloneSubtree)
40
- };
41
- return {
42
- ...node,
43
- id: ""
44
- };
45
- };
46
- const makeAttribute = (name, value) => ({
47
- name,
48
- value,
49
- span: zeroSpan,
50
- valueSpan: zeroSpan
51
- });
52
- const classAttr = (value) => makeAttribute("class", {
53
- kind: "string",
54
- value
55
- });
56
- const sourceRevision = (source) => {
57
- let hash = 2166136261;
58
- for (let index = 0; index < source.length; index++) {
59
- hash ^= source.charCodeAt(index);
60
- hash = Math.imul(hash, 16777619);
61
- }
62
- return `sml:${(hash >>> 0).toString(16).padStart(8, "0")}`;
63
- };
64
- const normalizeSml = (source) => {
65
- const parsed = parseSml(source);
66
- if (Result.isError(parsed)) return Result.err(parsed.error);
67
- return Result.ok(printSml(parsed.value));
68
- };
69
- const findElement = (node, nodeId) => {
70
- if (node.kind !== "element") return null;
71
- if (node.id === nodeId) return node;
72
- for (const child of node.children) {
73
- const found = findElement(child, nodeId);
74
- if (found !== null) return found;
75
- }
76
- return null;
77
- };
78
- const mapElement = (node, targetId, transform) => {
79
- if (node.kind !== "element") return node;
80
- if (node.id === targetId) return transform(node);
81
- return {
82
- ...node,
83
- children: node.children.map((child) => mapElement(child, targetId, transform))
84
- };
85
- };
86
- const transformElement = (document, targetId, transform) => {
87
- if (findElement(document.root, targetId) === null) return Result.err([diagnostic("op-target-not-found", `node "${targetId}" was not found in the current source`)]);
88
- return Result.ok({
89
- ...document,
90
- root: mapElement(document.root, targetId, transform)
91
- });
92
- };
93
- const setElementAttribute = (element, name, value) => {
94
- const rest = element.attributes.filter((attribute) => attribute.name !== name);
95
- if (value === null || value.kind === "string" && value.value === "") return {
96
- ...element,
97
- attributes: rest
98
- };
99
- const next = makeAttribute(name, value);
100
- const attributes = element.attributes.some((attribute) => attribute.name === name) ? element.attributes.map((attribute) => attribute.name === name ? next : cloneAttribute(attribute)) : [...element.attributes, next];
101
- return {
102
- ...element,
103
- attributes
104
- };
105
- };
106
- const setImageSchemeSource = (document, nodeId, scheme, value) => transformElement(document, nodeId, (element) => {
107
- if (element.tag !== "Image") return element;
108
- const src = value?.kind === "string" ? value.value : "";
109
- const children = element.children.filter((child) => !(child.kind === "element" && child.tag === "Source" && child.attributes.some((attribute) => attribute.name === "scheme" && attribute.value.kind === "string" && attribute.value.value === scheme)));
110
- if (src === "") return {
111
- ...element,
112
- children
113
- };
114
- const sourceNode = {
115
- kind: "element",
116
- id: "",
117
- tag: "Source",
118
- attributes: [makeAttribute("scheme", {
119
- kind: "string",
120
- value: scheme
121
- }), makeAttribute("src", {
122
- kind: "string",
123
- value: src
124
- })],
125
- children: [],
126
- span: zeroSpan
127
- };
128
- return {
129
- ...element,
130
- children: [...children, sourceNode]
131
- };
132
- });
133
- const setProp = (document, op) => {
134
- if (op.name === "src:dark") return setImageSchemeSource(document, op.nodeId, "dark", op.value);
135
- return transformElement(document, op.nodeId, (element) => {
136
- const value = op.name === "class" && op.value?.kind === "string" ? {
137
- kind: "string",
138
- value: op.value.value.trim().replace(/\s+/g, " ")
139
- } : op.value;
140
- return setElementAttribute(element, op.name, value);
141
- });
142
- };
143
- const setChildren = (document, op) => transformElement(document, op.nodeId, (element) => ({
144
- ...element,
145
- children: [...op.children]
146
- }));
147
- const makeColumn = () => ({
148
- kind: "element",
149
- id: "",
150
- tag: "Column",
151
- attributes: [classAttr("px-8")],
152
- children: [{
153
- kind: "element",
154
- id: "",
155
- tag: "Text",
156
- attributes: [classAttr("text-base text-gray-700")],
157
- children: [{
158
- kind: "text",
159
- id: "",
160
- value: "Column",
161
- span: zeroSpan
162
- }],
163
- span: zeroSpan
164
- }],
165
- span: zeroSpan
166
- });
167
- const makeBlock = (tag) => {
168
- const base = (attributes, children) => ({
169
- kind: "element",
170
- id: "",
171
- tag,
172
- attributes,
173
- children,
174
- span: zeroSpan
175
- });
176
- const text = (value) => ({
177
- kind: "text",
178
- id: "",
179
- value,
180
- span: zeroSpan
181
- });
182
- switch (tag) {
183
- case "Heading": return base([makeAttribute("level", {
184
- kind: "number",
185
- value: 2
186
- }), classAttr("text-xl text-gray-900")], [text("Heading")]);
187
- case "Text": return base([classAttr("text-base text-gray-700")], [text("Add your text here.")]);
188
- case "Button": return base([makeAttribute("href", {
189
- kind: "string",
190
- value: "https://example.com"
191
- }), classAttr("bg-blue-600 text-white px-20 py-12 rounded-md")], [text("Button")]);
192
- case "Image": return base([
193
- makeAttribute("src", {
194
- kind: "string",
195
- value: "https://placehold.co/600x200"
196
- }),
197
- makeAttribute("alt", {
198
- kind: "string",
199
- value: ""
200
- }),
201
- makeAttribute("width", {
202
- kind: "number",
203
- value: 600
204
- })
205
- ], []);
206
- case "Divider": return base([classAttr("border-gray-200")], []);
207
- case "Spacer": return base([makeAttribute("height", {
208
- kind: "number",
209
- value: 24
210
- })], []);
211
- case "Section": return base([classAttr("py-16")], [makeBlock("Text")]);
212
- case "Container": return base([classAttr("bg-white p-24")], [makeBlock("Text")]);
213
- case "Row": return base([], [makeColumn(), makeColumn()]);
214
- default: return base([], []);
215
- }
216
- };
217
- const elementChildIndexToNodeIndex = (children, insertionIndex) => {
218
- if (insertionIndex <= 0) {
219
- const firstElement = children.findIndex((child) => child.kind === "element");
220
- return firstElement === -1 ? children.length : firstElement;
221
- }
222
- let elementIndex = 0;
223
- for (let index = 0; index < children.length; index++) {
224
- if (children[index]?.kind !== "element") continue;
225
- if (elementIndex === insertionIndex) return index;
226
- elementIndex++;
227
- }
228
- return children.length;
229
- };
230
- const insertChildren = (document, childrenToInsert, referenceId, position) => {
231
- if (childrenToInsert.length === 0) return Result.err([diagnostic("op-empty-insert", "insert operation produced no element nodes")]);
232
- if (referenceId === null || referenceId === document.root.id) {
233
- const children = position === "before" ? [...childrenToInsert, ...document.root.children] : [...document.root.children, ...childrenToInsert];
234
- return Result.ok({
235
- ...document,
236
- root: {
237
- ...document.root,
238
- children
239
- }
240
- });
241
- }
242
- if (findElement(document.root, referenceId) === null) return Result.err([diagnostic("op-target-not-found", `node "${referenceId}" was not found in the current source`)]);
243
- if (position === "inside") return transformElement(document, referenceId, (element) => ({
244
- ...element,
245
- children: [...element.children, ...childrenToInsert]
246
- }));
247
- const insertInParent = (node) => {
248
- if (node.kind !== "element") return node;
249
- const index = node.children.findIndex((child) => child.kind === "element" && child.id === referenceId);
250
- if (index >= 0) {
251
- const at = position === "after" ? index + 1 : index;
252
- return {
253
- ...node,
254
- children: [
255
- ...node.children.slice(0, at),
256
- ...childrenToInsert,
257
- ...node.children.slice(at)
258
- ]
259
- };
260
- }
261
- return {
262
- ...node,
263
- children: node.children.map(insertInParent)
264
- };
265
- };
266
- return Result.ok({
267
- ...document,
268
- root: insertInParent(document.root)
269
- });
270
- };
271
- const insertBlock = (document, op) => insertChildren(document, [makeBlock(op.tag)], op.referenceId, op.position);
272
- const insertSnippet = (document, op) => {
273
- const rootTag = document.root.tag;
274
- const parsed = parseSml(`<${rootTag}>${op.snippet}</${rootTag}>`);
275
- if (Result.isError(parsed)) return Result.err(parsed.error.diagnostics);
276
- const children = parsed.value.root.children.filter((child) => child.kind === "element");
277
- return insertChildren(document, children, op.referenceId, op.position);
278
- };
279
- const removeNodeById = (document, nodeId) => {
280
- if (document.root.id === nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be removed")]);
281
- if (findElement(document.root, nodeId) === null) return Result.err([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
282
- const prune = (node) => {
283
- if (node.kind !== "element") return node;
284
- return {
285
- ...node,
286
- children: node.children.filter((child) => !(child.kind === "element" && child.id === nodeId)).map(prune)
287
- };
288
- };
289
- return Result.ok({
290
- ...document,
291
- root: prune(document.root)
292
- });
293
- };
294
- const isDescendant = (ancestor, nodeId) => ancestor.children.some((child) => {
295
- if (child.kind !== "element") return false;
296
- return child.id === nodeId || isDescendant(child, nodeId);
297
- });
298
- const removeElement = (node, nodeId) => {
299
- let removed = null;
300
- const children = [];
301
- for (const child of node.children) {
302
- if (child.kind === "element" && child.id === nodeId) {
303
- removed = child;
304
- continue;
305
- }
306
- if (child.kind === "element") {
307
- const result = removeElement(child, nodeId);
308
- if (result.removed !== null) removed = result.removed;
309
- children.push(result.node);
310
- } else children.push(child);
311
- }
312
- return {
313
- node: {
314
- ...node,
315
- children
316
- },
317
- removed
318
- };
319
- };
320
- const insertIntoParent = (node, targetParentId, insertionIndex, moved) => {
321
- if (node.id === targetParentId) {
322
- const at = elementChildIndexToNodeIndex(node.children, insertionIndex);
323
- return {
324
- ...node,
325
- children: [
326
- ...node.children.slice(0, at),
327
- moved,
328
- ...node.children.slice(at)
329
- ]
330
- };
331
- }
332
- return {
333
- ...node,
334
- children: node.children.map((child) => child.kind === "element" ? insertIntoParent(child, targetParentId, insertionIndex, moved) : child)
335
- };
336
- };
337
- const moveNode = (document, op) => {
338
- if (document.root.id === op.nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be moved")]);
339
- const moved = findElement(document.root, op.nodeId);
340
- if (moved === null) return Result.err([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
341
- const parent = findElement(document.root, op.targetParentId);
342
- if (parent === null) return Result.err([diagnostic("op-target-not-found", `target parent "${op.targetParentId}" was not found in the current source`)]);
343
- if (moved.id === parent.id || isDescendant(moved, parent.id)) return Result.err([diagnostic("op-invalid-reparent", "a node cannot be moved into itself or its descendants")]);
344
- const channel = documentChannel(document);
345
- const childModel = CHANNEL_TAG_SPECS[channel][parent.tag]?.content;
346
- if (!(childModel === void 0 ? [] : allowedChildren(childModel)).includes(moved.tag)) return Result.err([diagnostic("op-invalid-reparent", `<${moved.tag}> is not allowed directly inside <${parent.tag}>`, parent.span)]);
347
- const { node: rootWithoutMoved, removed } = removeElement(document.root, op.nodeId);
348
- if (removed === null) return Result.err([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
349
- return Result.ok({
350
- ...document,
351
- root: insertIntoParent(rootWithoutMoved, op.targetParentId, op.insertionIndex, removed)
352
- });
353
- };
354
- const duplicateNode = (document, nodeId) => {
355
- if (document.root.id === nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be duplicated")]);
356
- if (findElement(document.root, nodeId) === null) return Result.err([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
357
- const insertClone = (node) => {
358
- if (node.kind !== "element") return node;
359
- const index = node.children.findIndex((child) => child.kind === "element" && child.id === nodeId);
360
- if (index >= 0) {
361
- const clone = cloneSubtree(node.children[index]);
362
- return {
363
- ...node,
364
- children: [
365
- ...node.children.slice(0, index + 1),
366
- clone,
367
- ...node.children.slice(index + 1)
368
- ]
369
- };
370
- }
371
- return {
372
- ...node,
373
- children: node.children.map(insertClone)
374
- };
375
- };
376
- return Result.ok({
377
- ...document,
378
- root: insertClone(document.root)
379
- });
380
- };
381
- const mutate = (document, op) => {
382
- switch (op.type) {
383
- case "setProp": return setProp(document, op);
384
- case "setChildren": return setChildren(document, op);
385
- case "insertBlock": return insertBlock(document, op);
386
- case "insertSnippet": return insertSnippet(document, op);
387
- case "moveNode": return moveNode(document, op);
388
- case "duplicateNode": return duplicateNode(document, op.nodeId);
389
- case "removeNode": return removeNodeById(document, op.nodeId);
390
- }
391
- };
392
- const preflight = (source) => {
393
- const reparsed = parseSml(source);
394
- if (Result.isError(reparsed)) return Result.err(reparsed.error.diagnostics);
395
- const compiled = compileSml(reparsed.value);
396
- if (Result.isError(compiled)) return Result.err(compiled.error.diagnostics);
397
- const compatibilityErrors = checkSmlCompatibility({ source }).filter(isBreakingCompatibilityCheck).map((message) => diagnostic("compatibility-error", message));
398
- if (compatibilityErrors.length > 0) return Result.err(compatibilityErrors);
399
- return Result.ok(reparsed.value);
400
- };
401
- const failedResult = (index, op, diagnostics) => ({
402
- index,
403
- status: "failed",
404
- op,
405
- diagnostics: [...diagnostics]
406
- });
407
- /**
408
- * Apply a batch of ops to SML source, returning the rewritten source, its new
409
- * revision, and a per-op result.
410
- *
411
- * Id contract: every id in the batch resolves against the id-space of the
412
- * source the caller read (the `expectedRevision` snapshot). Ids are positional
413
- * (`n0..nN` in document order) and are NOT rewritten between ops, so:
414
- *
415
- * - An op cannot target a node created earlier in the same batch — freshly
416
- * inserted/duplicated nodes carry a blank id until the next reprint reassigns
417
- * positional ids, and are unaddressable within the batch (fails with
418
- * `op-target-not-found`).
419
- * - After `duplicateNode`, the duplicated id still addresses the ORIGINAL (the
420
- * first match in document order); the clone is a blank-id copy.
421
- * - Structural ops (insert/remove/move) do not shift the ids of the nodes that
422
- * survive, so a later op targeting a surviving sibling lands on the intended
423
- * node regardless of what earlier ops did.
424
- *
425
- * Each op is still preflighted (reprint, reparse, compile, compatibility check)
426
- * as an independent validity gate; a failing op is recorded as `failed` and
427
- * does not advance the working document.
428
- */
429
- const applyOps = (source, ops, options) => {
430
- const parsed = parseSml(source);
431
- if (Result.isError(parsed)) return Result.err(new SmlOpError({ diagnostics: parsed.error.diagnostics }));
432
- const currentRevision = String(options?.currentRevision ?? sourceRevision(source));
433
- if (options?.expectedRevision !== void 0 && String(options.expectedRevision) !== currentRevision) {
434
- const stale = diagnostic("stale-revision", `source revision changed from ${String(options.expectedRevision)} to ${currentRevision}`);
435
- return Result.ok({
436
- source,
437
- revision: currentRevision,
438
- diagnostics: [stale],
439
- results: ops.map((op, index) => failedResult(index, op, [stale]))
440
- });
441
- }
442
- let currentSource = source;
443
- let currentDocument = parsed.value;
444
- const results = [];
445
- const diagnostics = [];
446
- ops.forEach((op, index) => {
447
- const mutated = mutate(currentDocument, op);
448
- if (Result.isError(mutated)) {
449
- results.push(failedResult(index, op, mutated.error));
450
- diagnostics.push(...mutated.error);
451
- return;
452
- }
453
- const candidateSource = printSml(mutated.value);
454
- const checked = preflight(candidateSource);
455
- if (Result.isError(checked)) {
456
- results.push(failedResult(index, op, checked.error));
457
- diagnostics.push(...checked.error);
458
- return;
459
- }
460
- currentSource = candidateSource;
461
- currentDocument = mutated.value;
462
- results.push({
463
- index,
464
- status: "applied",
465
- op,
466
- diagnostics: []
467
- });
468
- });
469
- return Result.ok({
470
- source: currentSource,
471
- revision: sourceRevision(currentSource),
472
- diagnostics,
473
- results
474
- });
475
- };
476
- const applyOp = (source, op, options) => applyOps(source, [op], options);
477
- //#endregion
1
+ import { a as sourceRevision, i as normalizeSml, n as applyOp, r as applyOps, t as SmlOpError } from "./ops-2aiiYO7m.js";
478
2
  export { SmlOpError, applyOp, applyOps, normalizeSml, sourceRevision };
479
-
480
- //# sourceMappingURL=ops.js.map
@@ -1,5 +1,5 @@
1
- import { i as SmlParseError } from "./diagnostics-CqepLcpI.js";
2
- import { S as SmlTag, b as SmlDocument, l as Channel } from "./ast-BLybiDxp.js";
1
+ import { i as SmlParseError } from "./diagnostics-OcQKQYSY.js";
2
+ import { S as SmlTag, b as SmlDocument, l as Channel } from "./ast-BrDtApwS.js";
3
3
  import { Result } from "better-result";
4
4
  //#region src/limits.d.ts
5
5
  /** Hard ceilings shared by SML documents and standalone predicate parsing. */
@@ -312,7 +312,7 @@ declare const WHATSAPP_TAG_SPECS: {
312
312
  readonly category: {
313
313
  readonly type: "string";
314
314
  readonly required: true;
315
- readonly values: readonly ["marketing", "utility", "authentication", "service"];
315
+ readonly values: readonly ["marketing", "utility", "authentication"];
316
316
  };
317
317
  };
318
318
  readonly content: "whatsapp";
@@ -393,4 +393,4 @@ declare const allowedChildren: (model: ContentModel) => ReadonlyArray<SmlTag>;
393
393
  declare const parseSml: (source: string) => Result<SmlDocument, SmlParseError>;
394
394
  //#endregion
395
395
  export { SMS_TAG_SPECS as a, UNIVERSAL_ATTRIBUTE_TAGS as c, parseSml as d, SML_PARSER_LIMITS as f, EMAIL_TAG_SPECS as i, WHATSAPP_TAG_SPECS as l, CHANNEL_TAG_SPECS as n, TagSpec as o, ContentModel as r, UNIVERSAL_ATTRIBUTES as s, AttributeSpec as t, allowedChildren as u };
396
- //# sourceMappingURL=parser-b_Kp5MT6.d.ts.map
396
+ //# sourceMappingURL=parser-t29cHLVL.d.ts.map
package/dist/parser.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-b_Kp5MT6.js";
1
+ import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-t29cHLVL.js";
2
2
  export { AttributeSpec, CHANNEL_TAG_SPECS, ContentModel, EMAIL_TAG_SPECS, SML_PARSER_LIMITS, SMS_TAG_SPECS, TagSpec, UNIVERSAL_ATTRIBUTES, UNIVERSAL_ATTRIBUTE_TAGS, WHATSAPP_TAG_SPECS, allowedChildren, parseSml };
package/dist/parser.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SmlParseError } from "./diagnostics.js";
2
- import { a as printPredicate, i as parsePredicate, o as SML_PARSER_LIMITS } from "./predicate-DwXzmqIY.js";
2
+ import { a as printPredicate, i as parsePredicate, o as SML_PARSER_LIMITS } from "./predicate-BI6uRRM-.js";
3
3
  import { CHANNEL_BY_ROOT_TAG, ROOT_TAGS, SML_TAGS, isRootTag } from "./ast.js";
4
4
  import { TokenKind, createLexer } from "./lexer.js";
5
5
  import { Result } from "better-result";
@@ -20,8 +20,7 @@ const SMS_CATEGORIES = [
20
20
  const WHATSAPP_CATEGORIES = [
21
21
  "marketing",
22
22
  "utility",
23
- "authentication",
24
- "service"
23
+ "authentication"
25
24
  ];
26
25
  const WHATSAPP_HEADER_TYPES = [
27
26
  "text",
@@ -139,10 +139,13 @@ const tokenize = (source, baseOffset) => {
139
139
  index++;
140
140
  while (index < length && isDigit(source.charCodeAt(index))) index++;
141
141
  }
142
+ const raw = source.slice(start, index);
143
+ const value = Number.parseFloat(raw);
144
+ if (!Number.isFinite(value)) return failAt("predicate-number-out-of-range", "number literal is outside the finite numeric range", start, index);
142
145
  tokens.push({
143
146
  kind: "number",
144
- text: source.slice(start, index),
145
- number: Number.parseFloat(source.slice(start, index)),
147
+ text: raw,
148
+ number: value,
146
149
  span: {
147
150
  start: baseOffset + start,
148
151
  end: baseOffset + index
@@ -449,7 +452,14 @@ const precedence = (node) => {
449
452
  case "name": return 4;
450
453
  }
451
454
  };
452
- const printLiteral = (literal) => literal.kind === "string" ? `'${literal.value}'` : String(literal.value);
455
+ const printLiteral = (literal) => {
456
+ if (literal.kind === "number") {
457
+ if (Number.isFinite(literal.value)) return String(literal.value);
458
+ throw new TypeError(`Number ${String(literal.value)} cannot be represented in a predicate: number literals must be finite`);
459
+ }
460
+ if (literal.value.includes("'") || literal.value.includes("--")) throw new TypeError(`String ${JSON.stringify(literal.value)} cannot be represented in a predicate: string literals cannot contain ' or --`);
461
+ return `'${literal.value}'`;
462
+ };
453
463
  /** Canonical form: single space around operators/keywords, minimal parens. */
454
464
  const printPredicate = (ast) => {
455
465
  const frames = [{
@@ -536,4 +546,4 @@ const collectPredicateVariables = (ast) => {
536
546
  //#endregion
537
547
  export { printPredicate as a, parsePredicate as i, collectPredicateVariables as n, SML_PARSER_LIMITS as o, evaluatePredicate as r, PREDICATE_PARSER_LIMITS as t };
538
548
 
539
- //# sourceMappingURL=predicate-DwXzmqIY.js.map
549
+ //# sourceMappingURL=predicate-BI6uRRM-.js.map
@@ -1,4 +1,4 @@
1
- import { a as Span, t as Diagnostic } from "./diagnostics-CqepLcpI.js";
1
+ import { a as Span, t as Diagnostic } from "./diagnostics-OcQKQYSY.js";
2
2
  import { Result } from "better-result";
3
3
  //#region src/predicate.d.ts
4
4
  /** Standalone predicates inherit the same source, nesting, and node ceilings as SML. */
@@ -53,4 +53,4 @@ declare const printPredicate: (ast: PredicateAst) => string;
53
53
  declare const collectPredicateVariables: (ast: PredicateAst) => Set<string>;
54
54
  //#endregion
55
55
  export { collectPredicateVariables as a, printPredicate as c, PredicateLiteral as i, PREDICATE_PARSER_LIMITS as n, evaluatePredicate as o, PredicateAst as r, parsePredicate as s, ComparisonOp as t };
56
- //# sourceMappingURL=predicate-Bmvgw7Ao.d.ts.map
56
+ //# sourceMappingURL=predicate-M-05Nd7R.d.ts.map
@@ -1,2 +1,2 @@
1
- import { a as collectPredicateVariables, c as printPredicate, i as PredicateLiteral, n as PREDICATE_PARSER_LIMITS, o as evaluatePredicate, r as PredicateAst, s as parsePredicate, t as ComparisonOp } from "./predicate-Bmvgw7Ao.js";
1
+ import { a as collectPredicateVariables, c as printPredicate, i as PredicateLiteral, n as PREDICATE_PARSER_LIMITS, o as evaluatePredicate, r as PredicateAst, s as parsePredicate, t as ComparisonOp } from "./predicate-M-05Nd7R.js";
2
2
  export { ComparisonOp, PREDICATE_PARSER_LIMITS, PredicateAst, PredicateLiteral, collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
package/dist/predicate.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as printPredicate, i as parsePredicate, n as collectPredicateVariables, r as evaluatePredicate, t as PREDICATE_PARSER_LIMITS } from "./predicate-DwXzmqIY.js";
1
+ import { a as printPredicate, i as parsePredicate, n as collectPredicateVariables, r as evaluatePredicate, t as PREDICATE_PARSER_LIMITS } from "./predicate-BI6uRRM-.js";
2
2
  export { PREDICATE_PARSER_LIMITS, collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
@@ -1,4 +1,4 @@
1
- import { r as PresetCategory } from "../catalog-Db0UcCTg.js";
1
+ import { r as PresetCategory } from "../catalog-60eaYC7b.js";
2
2
  import { TemplateCollectionId } from "./public-gallery.js";
3
3
  //#region presets/category-mapping.d.ts
4
4
  declare const LAB_DIMENSIONS: readonly ["newsletter", "receipt", "security", "product-update", "onboarding", "promotion", "shipping", "social-notification"];
@@ -1,2 +1,2 @@
1
- import { a as PublicCorpusRegistrationIssue, c as TemplateFacetQuery, d as matchesTemplateFacets, f as ownedPresetSourcePath, h as templateFacetOptions, i as PublicCorpusRegistration, l as TemplateFacetSet, m as registerPublicCorpusEntry, n as InspirationReference, o as TemplateCorpusKind, p as ownedShowcasePresetSources, r as OwnedEditablePreset, s as TemplateCorpusRecord, t as BrandRecreationPreset, u as brandRecreationProvenance } from "../corpus-CM7rKX41.js";
1
+ import { a as PublicCorpusRegistrationIssue, c as TemplateFacetQuery, d as matchesTemplateFacets, f as ownedPresetSourcePath, h as templateFacetOptions, i as PublicCorpusRegistration, l as TemplateFacetSet, m as registerPublicCorpusEntry, n as InspirationReference, o as TemplateCorpusKind, p as ownedShowcasePresetSources, r as OwnedEditablePreset, s as TemplateCorpusRecord, t as BrandRecreationPreset, u as brandRecreationProvenance } from "../corpus-BjYMqOoY.js";
2
2
  export { BrandRecreationPreset, InspirationReference, OwnedEditablePreset, PublicCorpusRegistration, PublicCorpusRegistrationIssue, TemplateCorpusKind, TemplateCorpusRecord, TemplateFacetQuery, TemplateFacetSet, brandRecreationProvenance, matchesTemplateFacets, ownedPresetSourcePath, ownedShowcasePresetSources, registerPublicCorpusEntry, templateFacetOptions };
@@ -1,5 +1,5 @@
1
- import { t as EmailPresetId } from "../catalog-Db0UcCTg.js";
2
- import { c as TemplateFacetQuery, l as TemplateFacetSet, r as OwnedEditablePreset, t as BrandRecreationPreset } from "../corpus-CM7rKX41.js";
1
+ import { t as EmailPresetId } from "../catalog-60eaYC7b.js";
2
+ import { c as TemplateFacetQuery, l as TemplateFacetSet, r as OwnedEditablePreset, t as BrandRecreationPreset } from "../corpus-BjYMqOoY.js";
3
3
  //#region presets/public-gallery.d.ts
4
4
  declare const templateUseCases: readonly ["account-access", "commerce", "product-communication", "lifecycle", "publishing-campaigns"];
5
5
  declare const templateCollectionFamilies: readonly ["category", "industry", "stage", "style"];
@@ -362,7 +362,11 @@ const publicTemplates = [
362
362
  "style:minimal",
363
363
  "style:single-cta"
364
364
  ],
365
- relatedTemplates: ["otp", "password-reset"]
365
+ relatedTemplates: [
366
+ "otp",
367
+ "password-reset",
368
+ "welcome"
369
+ ]
366
370
  }),
367
371
  template({
368
372
  presetId: "otp",
@@ -375,7 +379,11 @@ const publicTemplates = [
375
379
  "stage:activation",
376
380
  "style:minimal"
377
381
  ],
378
- relatedTemplates: ["email-verification", "password-reset"]
382
+ relatedTemplates: [
383
+ "email-verification",
384
+ "password-reset",
385
+ "linear-login-code"
386
+ ]
379
387
  }),
380
388
  template({
381
389
  presetId: "password-reset",
@@ -1,5 +1,5 @@
1
- import { l as Channel } from "../ast-BLybiDxp.js";
2
- import { r as PresetCategory } from "../catalog-Db0UcCTg.js";
1
+ import { l as Channel } from "../ast-BrDtApwS.js";
2
+ import { r as PresetCategory } from "../catalog-60eaYC7b.js";
3
3
  import { TemplateCollectionId, TemplateUseCase } from "./public-gallery.js";
4
4
  import { PublicTemplatePreview } from "./public-preview.js";
5
5
  import { PresetProject } from "./sources.js";
@@ -1,4 +1,4 @@
1
- import { t as EmailPresetId } from "../catalog-Db0UcCTg.js";
1
+ import { t as EmailPresetId } from "../catalog-60eaYC7b.js";
2
2
  import { PublicTemplate } from "./public-gallery.js";
3
3
  import { PreviewQuality } from "./public-preview.js";
4
4
  import { Result } from "better-result";
@@ -23,6 +23,8 @@ interface PublicTemplatePreviewArtifact {
23
23
  readonly quality: PreviewQuality;
24
24
  readonly source: string;
25
25
  readonly html: string;
26
+ /** The delivered plain-text alternative of the same executed fixture. */
27
+ readonly text: string;
26
28
  readonly assets: ReadonlyArray<string>;
27
29
  readonly renders: ReadonlyArray<PublicTemplateRenderInput>;
28
30
  }
@@ -80,6 +80,7 @@ const buildPublicTemplatePreviewArtifact = (template) => {
80
80
  quality: preview.quality,
81
81
  source,
82
82
  html: fixture.html,
83
+ text: fixture.text,
83
84
  assets: [...assets],
84
85
  renders: renderInputs(template.slug, fixture.html)
85
86
  });