@samva/markup 0.1.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 (124) hide show
  1. package/AGENTS.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +41 -0
  4. package/dist/ast-DAhnAvQN.d.ts +120 -0
  5. package/dist/ast.d.ts +2 -0
  6. package/dist/ast.js +159 -0
  7. package/dist/ast.js.map +1 -0
  8. package/dist/candidate.d.ts +29 -0
  9. package/dist/candidate.js +88 -0
  10. package/dist/candidate.js.map +1 -0
  11. package/dist/checks-CW0nFj5n.js +58922 -0
  12. package/dist/checks-CW0nFj5n.js.map +1 -0
  13. package/dist/checks.d.ts +26 -0
  14. package/dist/checks.js +2 -0
  15. package/dist/classes.d.ts +27 -0
  16. package/dist/classes.js +184 -0
  17. package/dist/classes.js.map +1 -0
  18. package/dist/compile-sms.d.ts +28 -0
  19. package/dist/compile-sms.js +44 -0
  20. package/dist/compile-sms.js.map +1 -0
  21. package/dist/compile-whatsapp.d.ts +56 -0
  22. package/dist/compile-whatsapp.js +186 -0
  23. package/dist/compile-whatsapp.js.map +1 -0
  24. package/dist/compile.d.ts +93 -0
  25. package/dist/compile.js +938 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/components.d.ts +289 -0
  28. package/dist/components.js +106 -0
  29. package/dist/components.js.map +1 -0
  30. package/dist/conditionals.d.ts +10 -0
  31. package/dist/conditionals.js +42 -0
  32. package/dist/conditionals.js.map +1 -0
  33. package/dist/diagnostics-oveE-laV.d.ts +79 -0
  34. package/dist/diagnostics.d.ts +2 -0
  35. package/dist/diagnostics.js +62 -0
  36. package/dist/diagnostics.js.map +1 -0
  37. package/dist/emit.d.ts +50 -0
  38. package/dist/emit.js +151 -0
  39. package/dist/emit.js.map +1 -0
  40. package/dist/footer.d.ts +66 -0
  41. package/dist/footer.js +90 -0
  42. package/dist/footer.js.map +1 -0
  43. package/dist/interpolate.d.ts +27 -0
  44. package/dist/interpolate.js +61 -0
  45. package/dist/interpolate.js.map +1 -0
  46. package/dist/jsx-dev-runtime.d.ts +10 -0
  47. package/dist/jsx-dev-runtime.js +7 -0
  48. package/dist/jsx-dev-runtime.js.map +1 -0
  49. package/dist/jsx-runtime-fSxcvlx4.d.ts +57 -0
  50. package/dist/jsx-runtime.d.ts +2 -0
  51. package/dist/jsx-runtime.js +102 -0
  52. package/dist/jsx-runtime.js.map +1 -0
  53. package/dist/lexer.d.ts +60 -0
  54. package/dist/lexer.js +269 -0
  55. package/dist/lexer.js.map +1 -0
  56. package/dist/model.d.ts +39 -0
  57. package/dist/model.js +90 -0
  58. package/dist/model.js.map +1 -0
  59. package/dist/ops.d.ts +208 -0
  60. package/dist/ops.js +546 -0
  61. package/dist/ops.js.map +1 -0
  62. package/dist/parser-yzjUOGhc.d.ts +387 -0
  63. package/dist/parser.d.ts +2 -0
  64. package/dist/parser.js +821 -0
  65. package/dist/parser.js.map +1 -0
  66. package/dist/predicate-DTtVtLK7.d.ts +49 -0
  67. package/dist/predicate.d.ts +2 -0
  68. package/dist/predicate.js +417 -0
  69. package/dist/predicate.js.map +1 -0
  70. package/dist/presets/category-mapping.d.ts +13 -0
  71. package/dist/presets/category-mapping.js +49 -0
  72. package/dist/presets/category-mapping.js.map +1 -0
  73. package/dist/presets/public-gallery.d.ts +490 -0
  74. package/dist/presets/public-gallery.js +563 -0
  75. package/dist/presets/public-gallery.js.map +1 -0
  76. package/dist/presets/public-handoff.d.ts +68 -0
  77. package/dist/presets/public-handoff.js +105 -0
  78. package/dist/presets/public-handoff.js.map +1 -0
  79. package/dist/presets/public-preview-artifacts.d.ts +41 -0
  80. package/dist/presets/public-preview-artifacts.js +109 -0
  81. package/dist/presets/public-preview-artifacts.js.map +1 -0
  82. package/dist/presets/public-preview.d.ts +1168 -0
  83. package/dist/presets/public-preview.js +1158 -0
  84. package/dist/presets/public-preview.js.map +1 -0
  85. package/dist/presets/sources.d.ts +7 -0
  86. package/dist/presets/sources.js +54 -0
  87. package/dist/presets/sources.js.map +1 -0
  88. package/dist/presets.d.ts +350 -0
  89. package/dist/presets.js +1251 -0
  90. package/dist/presets.js.map +1 -0
  91. package/dist/printer.d.ts +6 -0
  92. package/dist/printer.js +122 -0
  93. package/dist/printer.js.map +1 -0
  94. package/dist/registry.d.ts +42 -0
  95. package/dist/registry.js +646 -0
  96. package/dist/registry.js.map +1 -0
  97. package/dist/render-template.d.ts +42 -0
  98. package/dist/render-template.js +74 -0
  99. package/dist/render-template.js.map +1 -0
  100. package/dist/sms-segments-y4FpPilJ.d.ts +13 -0
  101. package/dist/sms-segments.d.ts +2 -0
  102. package/dist/sms-segments.js +46 -0
  103. package/dist/sms-segments.js.map +1 -0
  104. package/dist/theme-6ApjjiBo.d.ts +116 -0
  105. package/dist/theme-css.d.ts +24 -0
  106. package/dist/theme-css.js +509 -0
  107. package/dist/theme-css.js.map +1 -0
  108. package/dist/theme.d.ts +2 -0
  109. package/dist/theme.js +95 -0
  110. package/dist/theme.js.map +1 -0
  111. package/dist/tokens.d.ts +6 -0
  112. package/dist/tokens.js +417 -0
  113. package/dist/tokens.js.map +1 -0
  114. package/dist/variables-3NGLpNTB.d.ts +77 -0
  115. package/dist/variables.d.ts +2 -0
  116. package/dist/variables.js +141 -0
  117. package/dist/variables.js.map +1 -0
  118. package/dist/version-BITr58kk.js +96 -0
  119. package/dist/version-BITr58kk.js.map +1 -0
  120. package/generated/sml-agent-contract.json +917 -0
  121. package/generated/sml-email-agent-contract.json +790 -0
  122. package/generated/sml-email-language-reference.md +160 -0
  123. package/generated/sml-language-reference.md +220 -0
  124. package/package.json +203 -0
package/AGENTS.md ADDED
@@ -0,0 +1,8 @@
1
+ # Samva Markup consumer guide
2
+
3
+ Author TSX with `jsxImportSource: "@samva/markup"`. Import components from
4
+ `@samva/markup/components` and variable types from `@samva/markup/variables`; a template file should
5
+ default-export at most one template. Use documented package subpaths only.
6
+
7
+ Samva templates emit SML for server-side compilation. Do not replace SML with precompiled HTML in
8
+ CLI push workflows. Use `@samva/vite` for discovery, local preview, and `buildTemplates()`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arya Labs, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @samva/markup
2
+
3
+ Type-safe authoring and compilation primitives for Samva email, SMS, and WhatsApp templates. Use it
4
+ directly for TSX templates or together with `@samva/vite` for the visual editor and project build.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install --save-dev @samva/markup @samva/vite vite
10
+ ```
11
+
12
+ Configure TypeScript to use the Samva JSX runtime:
13
+
14
+ ```json
15
+ {
16
+ "compilerOptions": {
17
+ "jsx": "react-jsx",
18
+ "jsxImportSource": "@samva/markup"
19
+ }
20
+ }
21
+ ```
22
+
23
+ Then author a default-exported template:
24
+
25
+ ```tsx
26
+ import { Email, Heading, Text } from "@samva/markup/components";
27
+ import type { Variables } from "@samva/markup/variables";
28
+
29
+ export default function Welcome(v: Variables<{ firstName: string }>) {
30
+ return (
31
+ <Email>
32
+ <Heading level={1}>Welcome, {v.firstName}</Heading>
33
+ <Text>Thanks for joining Samva.</Text>
34
+ </Email>
35
+ );
36
+ }
37
+ ```
38
+
39
+ The public subpaths also expose the SML parser, printer, compiler, diagnostics, variables, theme
40
+ primitives, channel compilers, and gallery preset data. Prefer the highest-level entrypoint that
41
+ matches the task instead of depending on implementation files.
@@ -0,0 +1,120 @@
1
+ import { c as SpanSchema } from "./diagnostics-oveE-laV.js";
2
+ import { Schema } from "effect";
3
+ //#region src/ast.d.ts
4
+ declare const BLOCK_TAGS: readonly ["Email", "Subject", "Preview", "Container", "Section", "Row", "Column", "Heading", "Text", "Button", "Image", "Divider", "Spacer", "List", "ListItem", "If", "Else"];
5
+ declare const INLINE_TAGS: readonly ["strong", "em", "a"];
6
+ /** Email-only helper tags scoped by their parent primitive. */
7
+ declare const AUXILIARY_TAGS: readonly ["Source"];
8
+ /** Structured-channel tags: the sms/whatsapp vocabulary beyond shared If/Else. */
9
+ declare const CHANNEL_TAGS: readonly ["Sms", "WhatsApp", "Header", "Body", "Footer", "Buttons"];
10
+ 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"];
11
+ type BlockTag = (typeof BLOCK_TAGS)[number];
12
+ type InlineTag = (typeof INLINE_TAGS)[number];
13
+ type AuxiliaryTag = (typeof AUXILIARY_TAGS)[number];
14
+ type ChannelTag = (typeof CHANNEL_TAGS)[number];
15
+ type SmlTag = (typeof SML_TAGS)[number];
16
+ /** Channels supported by the public SML document model. */
17
+ type Channel = "email" | "sms" | "whatsapp";
18
+ /** Resolved primary width for email documents that omit `<Email width={…}>`. */
19
+ declare const DEFAULT_EMAIL_WIDTH = 600;
20
+ /** Root tag → channel. The root tag IS the channel declaration. */
21
+ declare const CHANNEL_BY_ROOT_TAG: {
22
+ readonly Email: "email";
23
+ readonly Sms: "sms";
24
+ readonly WhatsApp: "whatsapp";
25
+ };
26
+ type RootTag = keyof typeof CHANNEL_BY_ROOT_TAG;
27
+ declare const ROOT_TAGS: ReadonlyArray<RootTag>;
28
+ declare const isRootTag: (tag: string) => tag is RootTag;
29
+ /** The channel a parsed document targets, read off its root tag. */
30
+ declare const documentChannel: (document: SmlDocument) => Channel;
31
+ declare const AttributeValueSchema: Schema.Union<readonly [Schema.Struct<{
32
+ readonly kind: Schema.Literal<"string">;
33
+ readonly value: Schema.String;
34
+ }>, Schema.Struct<{
35
+ readonly kind: Schema.Literal<"number">;
36
+ readonly value: Schema.Finite;
37
+ }>]>;
38
+ type AttributeValue = typeof AttributeValueSchema.Type;
39
+ declare const AttributeSchema: Schema.Struct<{
40
+ readonly name: Schema.String;
41
+ readonly value: Schema.Union<readonly [Schema.Struct<{
42
+ readonly kind: Schema.Literal<"string">;
43
+ readonly value: Schema.String;
44
+ }>, Schema.Struct<{
45
+ readonly kind: Schema.Literal<"number">;
46
+ readonly value: Schema.Finite;
47
+ }>]>;
48
+ /** Span of the whole `name="value"` pair. */
49
+ readonly span: Schema.Struct<{
50
+ readonly start: Schema.Finite;
51
+ readonly end: Schema.Finite;
52
+ }>;
53
+ /** Span of the value contents (inside quotes/braces) — class diagnostics point here. */
54
+ readonly valueSpan: Schema.Struct<{
55
+ readonly start: Schema.Finite;
56
+ readonly end: Schema.Finite;
57
+ }>;
58
+ }>;
59
+ interface Attribute extends Schema.Schema.Type<typeof AttributeSchema> {}
60
+ declare const TextNodeSchema: Schema.Struct<{
61
+ readonly kind: Schema.Literal<"text">;
62
+ readonly id: Schema.String;
63
+ readonly value: Schema.String;
64
+ readonly span: Schema.Struct<{
65
+ readonly start: Schema.Finite;
66
+ readonly end: Schema.Finite;
67
+ }>;
68
+ }>;
69
+ interface TextNode extends Schema.Schema.Type<typeof TextNodeSchema> {}
70
+ declare const InterpolationNodeSchema: Schema.Struct<{
71
+ readonly kind: Schema.Literal<"interpolation">;
72
+ readonly id: Schema.String;
73
+ readonly name: Schema.String;
74
+ /** {{name}} vs ${name} — both today's syntaxes, preserved through print. */
75
+ readonly syntax: Schema.Literals<readonly ["curly", "dollar"]>;
76
+ readonly span: Schema.Struct<{
77
+ readonly start: Schema.Finite;
78
+ readonly end: Schema.Finite;
79
+ }>;
80
+ }>;
81
+ interface InterpolationNode extends Schema.Schema.Type<typeof InterpolationNodeSchema> {}
82
+ interface ElementNode {
83
+ readonly kind: "element";
84
+ readonly id: string;
85
+ readonly tag: SmlTag;
86
+ readonly attributes: ReadonlyArray<Attribute>;
87
+ readonly children: ReadonlyArray<SmlNode>;
88
+ readonly span: typeof SpanSchema.Type;
89
+ }
90
+ type SmlNode = ElementNode | TextNode | InterpolationNode;
91
+ declare const ElementNodeSchema: Schema.Codec<ElementNode>;
92
+ declare const SmlNodeSchema: Schema.Codec<SmlNode>;
93
+ declare const SmlDocumentSchema: Schema.Struct<{
94
+ readonly kind: Schema.Literal<"document">;
95
+ /** Always a root element: `<Email>`, `<Sms>`, or `<WhatsApp>`. */
96
+ readonly root: Schema.Codec<ElementNode, ElementNode, never, never>;
97
+ }>;
98
+ interface SmlDocument extends Schema.Schema.Type<typeof SmlDocumentSchema> {}
99
+ declare const attributeValue: (node: ElementNode, name: string) => AttributeValue | undefined;
100
+ declare const stringAttribute: (node: ElementNode, name: string) => string | undefined;
101
+ declare const numberAttribute: (node: ElementNode, name: string) => number | undefined;
102
+ /**
103
+ * The editor-only display label (navigator rename) — a universal attribute the
104
+ * compiler never reads, so it never reaches HTML. Absent when unset.
105
+ */
106
+ declare const nodeLabel: (node: ElementNode) => string | undefined;
107
+ /**
108
+ * The editor-only visibility flag. A `hidden="true"` node stays in the source
109
+ * (and the navigator, dimmed) but is dropped from every compile — preview and
110
+ * send alike. Any other value / absence means visible.
111
+ */
112
+ declare const isHiddenNode: (node: ElementNode) => boolean;
113
+ /**
114
+ * All variable references in the document: `{{name}}` / `${name}` in children
115
+ * and attribute values, plus names referenced by `<If test="...">` predicates.
116
+ */
117
+ declare const collectVariableReferences: (document: SmlDocument) => ReadonlySet<string>;
118
+ //#endregion
119
+ export { attributeValue as A, SmlDocument as C, SmlTag as D, SmlNodeSchema as E, nodeLabel as F, numberAttribute as I, stringAttribute as L, documentChannel as M, isHiddenNode as N, TextNode as O, isRootTag as P, SML_TAGS as S, SmlNode as T, InlineTag as _, AttributeValueSchema as a, ROOT_TAGS as b, BlockTag as c, Channel as d, ChannelTag as f, INLINE_TAGS as g, ElementNodeSchema as h, AttributeValue as i, collectVariableReferences as j, TextNodeSchema as k, CHANNEL_BY_ROOT_TAG as l, ElementNode as m, Attribute as n, AuxiliaryTag as o, DEFAULT_EMAIL_WIDTH as p, AttributeSchema as r, BLOCK_TAGS as s, AUXILIARY_TAGS as t, CHANNEL_TAGS as u, InterpolationNode as v, SmlDocumentSchema as w, RootTag as x, InterpolationNodeSchema as y };
120
+ //# sourceMappingURL=ast-DAhnAvQN.d.ts.map
package/dist/ast.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { A as attributeValue, C as SmlDocument, D as SmlTag, E as SmlNodeSchema, F as nodeLabel, I as numberAttribute, L as stringAttribute, M as documentChannel, N as isHiddenNode, O as TextNode, P as isRootTag, S as SML_TAGS, T as SmlNode, _ as InlineTag, a as AttributeValueSchema, b as ROOT_TAGS, c as BlockTag, d as Channel, f as ChannelTag, g as INLINE_TAGS, h as ElementNodeSchema, i as AttributeValue, j as collectVariableReferences, k as TextNodeSchema, l as CHANNEL_BY_ROOT_TAG, m as ElementNode, n as Attribute, o as AuxiliaryTag, p as DEFAULT_EMAIL_WIDTH, r as AttributeSchema, s as BLOCK_TAGS, t as AUXILIARY_TAGS, u as CHANNEL_TAGS, v as InterpolationNode, w as SmlDocumentSchema, x as RootTag, y as InterpolationNodeSchema } from "./ast-DAhnAvQN.js";
2
+ export { AUXILIARY_TAGS, Attribute, AttributeSchema, AttributeValue, AttributeValueSchema, AuxiliaryTag, BLOCK_TAGS, BlockTag, CHANNEL_BY_ROOT_TAG, CHANNEL_TAGS, Channel, ChannelTag, DEFAULT_EMAIL_WIDTH, ElementNode, ElementNodeSchema, INLINE_TAGS, InlineTag, InterpolationNode, InterpolationNodeSchema, ROOT_TAGS, RootTag, SML_TAGS, SmlDocument, SmlDocumentSchema, SmlNode, SmlNodeSchema, SmlTag, TextNode, TextNodeSchema, attributeValue, collectVariableReferences, documentChannel, isHiddenNode, isRootTag, nodeLabel, numberAttribute, stringAttribute };
package/dist/ast.js ADDED
@@ -0,0 +1,159 @@
1
+ import { SpanSchema } from "./diagnostics.js";
2
+ import { collectPredicateVariables, parsePredicate } from "./predicate.js";
3
+ import { Result, Schema } from "effect";
4
+ //#region src/ast.ts
5
+ const BLOCK_TAGS = [
6
+ "Email",
7
+ "Subject",
8
+ "Preview",
9
+ "Container",
10
+ "Section",
11
+ "Row",
12
+ "Column",
13
+ "Heading",
14
+ "Text",
15
+ "Button",
16
+ "Image",
17
+ "Divider",
18
+ "Spacer",
19
+ "List",
20
+ "ListItem",
21
+ "If",
22
+ "Else"
23
+ ];
24
+ const INLINE_TAGS = [
25
+ "strong",
26
+ "em",
27
+ "a"
28
+ ];
29
+ /** Email-only helper tags scoped by their parent primitive. */
30
+ const AUXILIARY_TAGS = ["Source"];
31
+ /** Structured-channel tags: the sms/whatsapp vocabulary beyond shared If/Else. */
32
+ const CHANNEL_TAGS = [
33
+ "Sms",
34
+ "WhatsApp",
35
+ "Header",
36
+ "Body",
37
+ "Footer",
38
+ "Buttons"
39
+ ];
40
+ const SML_TAGS = [
41
+ ...BLOCK_TAGS,
42
+ ...INLINE_TAGS,
43
+ ...AUXILIARY_TAGS,
44
+ ...CHANNEL_TAGS
45
+ ];
46
+ /** Resolved primary width for email documents that omit `<Email width={…}>`. */
47
+ const DEFAULT_EMAIL_WIDTH = 600;
48
+ /** Root tag → channel. The root tag IS the channel declaration. */
49
+ const CHANNEL_BY_ROOT_TAG = {
50
+ Email: "email",
51
+ Sms: "sms",
52
+ WhatsApp: "whatsapp"
53
+ };
54
+ const ROOT_TAGS = Object.keys(CHANNEL_BY_ROOT_TAG);
55
+ const isRootTag = (tag) => tag in CHANNEL_BY_ROOT_TAG;
56
+ /** The channel a parsed document targets, read off its root tag. */
57
+ const documentChannel = (document) => CHANNEL_BY_ROOT_TAG[document.root.tag];
58
+ const AttributeValueSchema = Schema.Union([Schema.Struct({
59
+ kind: Schema.Literal("string"),
60
+ value: Schema.String
61
+ }), Schema.Struct({
62
+ kind: Schema.Literal("number"),
63
+ value: Schema.Finite
64
+ })]);
65
+ const AttributeSchema = Schema.Struct({
66
+ name: Schema.String,
67
+ value: AttributeValueSchema,
68
+ /** Span of the whole `name="value"` pair. */
69
+ span: SpanSchema,
70
+ /** Span of the value contents (inside quotes/braces) — class diagnostics point here. */
71
+ valueSpan: SpanSchema
72
+ });
73
+ const TextNodeSchema = Schema.Struct({
74
+ kind: Schema.Literal("text"),
75
+ id: Schema.String,
76
+ value: Schema.String,
77
+ span: SpanSchema
78
+ });
79
+ const InterpolationNodeSchema = Schema.Struct({
80
+ kind: Schema.Literal("interpolation"),
81
+ id: Schema.String,
82
+ name: Schema.String,
83
+ /** {{name}} vs ${name} — both today's syntaxes, preserved through print. */
84
+ syntax: Schema.Literals(["curly", "dollar"]),
85
+ span: SpanSchema
86
+ });
87
+ const ElementNodeSchema = Schema.Struct({
88
+ kind: Schema.Literal("element"),
89
+ id: Schema.String,
90
+ tag: Schema.Literals(SML_TAGS),
91
+ attributes: Schema.Array(AttributeSchema),
92
+ children: Schema.Array(Schema.suspend(() => SmlNodeSchema)),
93
+ span: SpanSchema
94
+ });
95
+ const SmlNodeSchema = Schema.Union([
96
+ ElementNodeSchema,
97
+ TextNodeSchema,
98
+ InterpolationNodeSchema
99
+ ]);
100
+ const SmlDocumentSchema = Schema.Struct({
101
+ kind: Schema.Literal("document"),
102
+ /** Always a root element: `<Email>`, `<Sms>`, or `<WhatsApp>`. */
103
+ root: ElementNodeSchema
104
+ });
105
+ const attributeValue = (node, name) => node.attributes.find((attribute) => attribute.name === name)?.value;
106
+ const stringAttribute = (node, name) => {
107
+ const value = attributeValue(node, name);
108
+ return value?.kind === "string" ? value.value : void 0;
109
+ };
110
+ const numberAttribute = (node, name) => {
111
+ const value = attributeValue(node, name);
112
+ if (value === void 0) return void 0;
113
+ return value.kind === "number" ? value.value : Number.parseInt(value.value, 10);
114
+ };
115
+ /**
116
+ * The editor-only display label (navigator rename) — a universal attribute the
117
+ * compiler never reads, so it never reaches HTML. Absent when unset.
118
+ */
119
+ const nodeLabel = (node) => stringAttribute(node, "label");
120
+ /**
121
+ * The editor-only visibility flag. A `hidden="true"` node stays in the source
122
+ * (and the navigator, dimmed) but is dropped from every compile — preview and
123
+ * send alike. Any other value / absence means visible.
124
+ */
125
+ const isHiddenNode = (node) => stringAttribute(node, "hidden") === "true";
126
+ /**
127
+ * All variable references in the document: `{{name}}` / `${name}` in children
128
+ * and attribute values, plus names referenced by `<If test="...">` predicates.
129
+ */
130
+ const collectVariableReferences = (document) => {
131
+ const names = /* @__PURE__ */ new Set();
132
+ const attributePattern = /\$\{\s*\/?([\w.]+)\s*\}|\{\{\s*([\w.]+)\s*\}\}/g;
133
+ const visit = (node) => {
134
+ if (node.kind === "interpolation") {
135
+ names.add(node.name);
136
+ return;
137
+ }
138
+ if (node.kind === "text") return;
139
+ for (const attribute of node.attributes) {
140
+ if (attribute.value.kind !== "string") continue;
141
+ if (node.tag === "If" && attribute.name === "test") {
142
+ const parsed = parsePredicate(attribute.value.value);
143
+ if (Result.isSuccess(parsed)) for (const name of collectPredicateVariables(parsed.success)) names.add(name);
144
+ continue;
145
+ }
146
+ for (const match of attribute.value.value.matchAll(attributePattern)) {
147
+ const name = match[1] ?? match[2];
148
+ if (name !== void 0) names.add(name);
149
+ }
150
+ }
151
+ for (const child of node.children) visit(child);
152
+ };
153
+ visit(document.root);
154
+ return names;
155
+ };
156
+ //#endregion
157
+ export { AUXILIARY_TAGS, AttributeSchema, AttributeValueSchema, BLOCK_TAGS, CHANNEL_BY_ROOT_TAG, CHANNEL_TAGS, DEFAULT_EMAIL_WIDTH, ElementNodeSchema, INLINE_TAGS, InterpolationNodeSchema, ROOT_TAGS, SML_TAGS, SmlDocumentSchema, SmlNodeSchema, TextNodeSchema, attributeValue, collectVariableReferences, documentChannel, isHiddenNode, isRootTag, nodeLabel, numberAttribute, stringAttribute };
158
+
159
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","names":[],"sources":["../src/ast.ts"],"sourcesContent":["import { Result, Schema } from \"effect\";\n\nimport { SpanSchema } from \"./diagnostics\";\nimport { collectPredicateVariables, parsePredicate } from \"./predicate\";\n\n// Typed SML AST. The parser is the only producer; the schema exists for\n// boundaries that ship AST JSON (editor ops, agent tools). Shape only — the\n// content model (what may nest where) is the parser's job, so decoding an AST\n// someone assembled by hand still round-trips through print → parse for full\n// validation.\n//\n// Node ids are assigned in document order at parse time (\"n0\", \"n1\", ...):\n// identical source always yields identical ids, which the compiler relies on\n// for deterministic artifacts and the editor for DOM ⇄ AST mapping.\n//\n// `tag` is a closed vocabulary — permanently: composition lives in the TSX\n// authoring layer, so SML stays a flat closed grammar. New tags (e.g. <For>)\n// land by widening the unions — nothing else about the node shape assumes a\n// fixed set.\n//\n// One document = one channel, declared by the root tag: <Email> (blocks +\n// inline + Tailwind subset), <Sms> (verbatim text body), <WhatsApp> (Meta's\n// template anatomy as the closed grammar). Cross-channel compile is rejected\n// by design; what the channels share is the language machinery.\n\nexport const BLOCK_TAGS = [\n \"Email\",\n \"Subject\",\n \"Preview\",\n \"Container\",\n \"Section\",\n \"Row\",\n \"Column\",\n \"Heading\",\n \"Text\",\n \"Button\",\n \"Image\",\n \"Divider\",\n \"Spacer\",\n \"List\",\n \"ListItem\",\n \"If\",\n \"Else\",\n] as const;\n\nexport const INLINE_TAGS = [\"strong\", \"em\", \"a\"] as const;\n\n/** Email-only helper tags scoped by their parent primitive. */\nexport const AUXILIARY_TAGS = [\"Source\"] as const;\n\n/** Structured-channel tags: the sms/whatsapp vocabulary beyond shared If/Else. */\nexport const CHANNEL_TAGS = [\"Sms\", \"WhatsApp\", \"Header\", \"Body\", \"Footer\", \"Buttons\"] as const;\n\nexport const SML_TAGS = [\n ...BLOCK_TAGS,\n ...INLINE_TAGS,\n ...AUXILIARY_TAGS,\n ...CHANNEL_TAGS,\n] as const;\n\nexport type BlockTag = (typeof BLOCK_TAGS)[number];\nexport type InlineTag = (typeof INLINE_TAGS)[number];\nexport type AuxiliaryTag = (typeof AUXILIARY_TAGS)[number];\nexport type ChannelTag = (typeof CHANNEL_TAGS)[number];\nexport type SmlTag = (typeof SML_TAGS)[number];\n\n// ── Channels ─────────────────────────────────────────────────────────────────\n\n/** Channels supported by the public SML document model. */\nexport type Channel = \"email\" | \"sms\" | \"whatsapp\";\n\n/** Resolved primary width for email documents that omit `<Email width={…}>`. */\nexport const DEFAULT_EMAIL_WIDTH = 600;\n\n/** Root tag → channel. The root tag IS the channel declaration. */\nexport const CHANNEL_BY_ROOT_TAG = {\n Email: \"email\",\n Sms: \"sms\",\n WhatsApp: \"whatsapp\",\n} as const satisfies Partial<Record<SmlTag, Channel>>;\n\nexport type RootTag = keyof typeof CHANNEL_BY_ROOT_TAG;\n\nexport const ROOT_TAGS = Object.keys(CHANNEL_BY_ROOT_TAG) as ReadonlyArray<RootTag>;\n\nexport const isRootTag = (tag: string): tag is RootTag => tag in CHANNEL_BY_ROOT_TAG;\n\n/** The channel a parsed document targets, read off its root tag. */\nexport const documentChannel = (document: SmlDocument): Channel =>\n CHANNEL_BY_ROOT_TAG[document.root.tag as RootTag];\n\nexport const AttributeValueSchema = Schema.Union([\n Schema.Struct({\n kind: Schema.Literal(\"string\"),\n value: Schema.String,\n }),\n Schema.Struct({\n kind: Schema.Literal(\"number\"),\n value: Schema.Finite,\n }),\n]);\n\nexport type AttributeValue = typeof AttributeValueSchema.Type;\n\nexport const AttributeSchema = Schema.Struct({\n name: Schema.String,\n value: AttributeValueSchema,\n /** Span of the whole `name=\"value\"` pair. */\n span: SpanSchema,\n /** Span of the value contents (inside quotes/braces) — class diagnostics point here. */\n valueSpan: SpanSchema,\n});\n\nexport interface Attribute extends Schema.Schema.Type<typeof AttributeSchema> {}\n\nexport const TextNodeSchema = Schema.Struct({\n kind: Schema.Literal(\"text\"),\n id: Schema.String,\n value: Schema.String,\n span: SpanSchema,\n});\n\nexport interface TextNode extends Schema.Schema.Type<typeof TextNodeSchema> {}\n\nexport const InterpolationNodeSchema = Schema.Struct({\n kind: Schema.Literal(\"interpolation\"),\n id: Schema.String,\n name: Schema.String,\n /** {{name}} vs ${name} — both today's syntaxes, preserved through print. */\n syntax: Schema.Literals([\"curly\", \"dollar\"]),\n span: SpanSchema,\n});\n\nexport interface InterpolationNode extends Schema.Schema.Type<typeof InterpolationNodeSchema> {}\n\nexport interface ElementNode {\n readonly kind: \"element\";\n readonly id: string;\n readonly tag: SmlTag;\n readonly attributes: ReadonlyArray<Attribute>;\n readonly children: ReadonlyArray<SmlNode>;\n readonly span: typeof SpanSchema.Type;\n}\n\nexport type SmlNode = ElementNode | TextNode | InterpolationNode;\n\nexport const ElementNodeSchema: Schema.Codec<ElementNode> = Schema.Struct({\n kind: Schema.Literal(\"element\"),\n id: Schema.String,\n tag: Schema.Literals(SML_TAGS),\n attributes: Schema.Array(AttributeSchema),\n children: Schema.Array(Schema.suspend((): Schema.Codec<SmlNode> => SmlNodeSchema)),\n span: SpanSchema,\n});\n\nexport const SmlNodeSchema: Schema.Codec<SmlNode> = Schema.Union([\n ElementNodeSchema,\n TextNodeSchema,\n InterpolationNodeSchema,\n]);\n\nexport const SmlDocumentSchema = Schema.Struct({\n kind: Schema.Literal(\"document\"),\n /** Always a root element: `<Email>`, `<Sms>`, or `<WhatsApp>`. */\n root: ElementNodeSchema,\n});\n\nexport interface SmlDocument extends Schema.Schema.Type<typeof SmlDocumentSchema> {}\n\nexport const attributeValue = (node: ElementNode, name: string): AttributeValue | undefined =>\n node.attributes.find((attribute) => attribute.name === name)?.value;\n\nexport const stringAttribute = (node: ElementNode, name: string): string | undefined => {\n const value = attributeValue(node, name);\n return value?.kind === \"string\" ? value.value : undefined;\n};\n\nexport const numberAttribute = (node: ElementNode, name: string): number | undefined => {\n const value = attributeValue(node, name);\n if (value === undefined) return undefined;\n return value.kind === \"number\" ? value.value : Number.parseInt(value.value, 10);\n};\n\n/**\n * The editor-only display label (navigator rename) — a universal attribute the\n * compiler never reads, so it never reaches HTML. Absent when unset.\n */\nexport const nodeLabel = (node: ElementNode): string | undefined => stringAttribute(node, \"label\");\n\n/**\n * The editor-only visibility flag. A `hidden=\"true\"` node stays in the source\n * (and the navigator, dimmed) but is dropped from every compile — preview and\n * send alike. Any other value / absence means visible.\n */\nexport const isHiddenNode = (node: ElementNode): boolean =>\n stringAttribute(node, \"hidden\") === \"true\";\n\n/**\n * All variable references in the document: `{{name}}` / `${name}` in children\n * and attribute values, plus names referenced by `<If test=\"...\">` predicates.\n */\nexport const collectVariableReferences = (document: SmlDocument): ReadonlySet<string> => {\n const names = new Set<string>();\n const attributePattern = /\\$\\{\\s*\\/?([\\w.]+)\\s*\\}|\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g;\n const visit = (node: SmlNode): void => {\n if (node.kind === \"interpolation\") {\n names.add(node.name);\n return;\n }\n if (node.kind === \"text\") return;\n for (const attribute of node.attributes) {\n if (attribute.value.kind !== \"string\") continue;\n if (node.tag === \"If\" && attribute.name === \"test\") {\n // Parser-produced documents always carry a valid predicate; a\n // hand-assembled AST with a broken one contributes nothing here and is\n // caught by the print → parse validation round-trip.\n const parsed = parsePredicate(attribute.value.value);\n if (Result.isSuccess(parsed)) {\n for (const name of collectPredicateVariables(parsed.success)) names.add(name);\n }\n continue;\n }\n for (const match of attribute.value.value.matchAll(attributePattern)) {\n const name = match[1] ?? match[2];\n if (name !== undefined) names.add(name);\n }\n }\n for (const child of node.children) visit(child);\n };\n visit(document.root);\n return names;\n};\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ //#region src/candidate.d.ts
2
+ interface ParsedCandidate {
3
+ readonly raw: string;
4
+ /** Variant names in written order (validity is checked by the resolver). */
5
+ readonly variants: ReadonlyArray<string>;
6
+ /** root[-value] with variants/modifier/important stripped. */
7
+ readonly base: string;
8
+ /** Contents after the last `/` outside brackets, or null. */
9
+ readonly modifier: string | null;
10
+ /** Trailing `!`. */
11
+ readonly important: boolean;
12
+ }
13
+ declare const parseCandidate: (raw: string) => ParsedCandidate;
14
+ /**
15
+ * Splits `base` into (root, value) against a known root set, preferring the
16
+ * longest matching root ("max-w-lg" → root "max-w", not "max").
17
+ * `value` is `null` for a bare root ("border", "rounded"),
18
+ * `{ kind: "arbitrary" }` for `root-[...]`, else `{ kind: "named" }`.
19
+ */
20
+ declare const splitRoot: (base: string, roots: ReadonlySet<string>) => {
21
+ readonly root: string;
22
+ readonly value: {
23
+ kind: "named" | "arbitrary";
24
+ value: string;
25
+ } | null;
26
+ } | null;
27
+ //#endregion
28
+ export { ParsedCandidate, parseCandidate, splitRoot };
29
+ //# sourceMappingURL=candidate.d.ts.map
@@ -0,0 +1,88 @@
1
+ //#region src/candidate.ts
2
+ const splitOutsideBrackets = (value, separator) => {
3
+ const parts = [];
4
+ let depth = 0;
5
+ let start = 0;
6
+ for (let index = 0; index < value.length; index++) {
7
+ const char = value[index];
8
+ if (char === "[") depth++;
9
+ else if (char === "]") depth = Math.max(0, depth - 1);
10
+ else if (char === separator && depth === 0) {
11
+ parts.push(value.slice(start, index));
12
+ start = index + 1;
13
+ }
14
+ }
15
+ parts.push(value.slice(start));
16
+ return parts;
17
+ };
18
+ const parseCandidate = (raw) => {
19
+ let rest = raw;
20
+ const important = rest.endsWith("!");
21
+ if (important) rest = rest.slice(0, -1);
22
+ const segments = splitOutsideBrackets(rest, ":");
23
+ const base = segments[segments.length - 1];
24
+ const variants = segments.slice(0, -1);
25
+ const lastSlash = (() => {
26
+ let depth = 0;
27
+ for (let index = base.length - 1; index >= 0; index--) {
28
+ const char = base[index];
29
+ if (char === "]") depth++;
30
+ else if (char === "[") depth = Math.max(0, depth - 1);
31
+ else if (char === "/" && depth === 0) return index;
32
+ }
33
+ return -1;
34
+ })();
35
+ if (lastSlash === -1) return {
36
+ raw,
37
+ variants,
38
+ base,
39
+ modifier: null,
40
+ important
41
+ };
42
+ return {
43
+ raw,
44
+ variants,
45
+ base: base.slice(0, lastSlash),
46
+ modifier: base.slice(lastSlash + 1),
47
+ important
48
+ };
49
+ };
50
+ /**
51
+ * Splits `base` into (root, value) against a known root set, preferring the
52
+ * longest matching root ("max-w-lg" → root "max-w", not "max").
53
+ * `value` is `null` for a bare root ("border", "rounded"),
54
+ * `{ kind: "arbitrary" }` for `root-[...]`, else `{ kind: "named" }`.
55
+ */
56
+ const splitRoot = (base, roots) => {
57
+ if (roots.has(base)) return {
58
+ root: base,
59
+ value: null
60
+ };
61
+ let dash = base.lastIndexOf("-");
62
+ while (dash > 0) {
63
+ const root = base.slice(0, dash);
64
+ if (roots.has(root)) {
65
+ const rawValue = base.slice(dash + 1);
66
+ if (rawValue.startsWith("[") && rawValue.endsWith("]")) return {
67
+ root,
68
+ value: {
69
+ kind: "arbitrary",
70
+ value: rawValue.slice(1, -1)
71
+ }
72
+ };
73
+ return {
74
+ root,
75
+ value: {
76
+ kind: "named",
77
+ value: rawValue
78
+ }
79
+ };
80
+ }
81
+ dash = base.lastIndexOf("-", dash - 1);
82
+ }
83
+ return null;
84
+ };
85
+ //#endregion
86
+ export { parseCandidate, splitRoot };
87
+
88
+ //# sourceMappingURL=candidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidate.js","names":[],"sources":["../src/candidate.ts"],"sourcesContent":["// Tailwind candidate segmentation: `[sm:|dark:]root[-value | -[arbitrary]][/modifier][!]`.\n// Bracket-aware splitting only — which roots exist and what values mean is the\n// registry's job (registry.ts). Mirrors the shape of tailwindcss/src/candidate.ts\n// at ~10% of the grammar.\n\nexport interface ParsedCandidate {\n readonly raw: string;\n /** Variant names in written order (validity is checked by the resolver). */\n readonly variants: ReadonlyArray<string>;\n /** root[-value] with variants/modifier/important stripped. */\n readonly base: string;\n /** Contents after the last `/` outside brackets, or null. */\n readonly modifier: string | null;\n /** Trailing `!`. */\n readonly important: boolean;\n}\n\nconst splitOutsideBrackets = (value: string, separator: string): Array<string> => {\n const parts: Array<string> = [];\n let depth = 0;\n let start = 0;\n for (let index = 0; index < value.length; index++) {\n const char = value[index]!;\n if (char === \"[\") depth++;\n else if (char === \"]\") depth = Math.max(0, depth - 1);\n else if (char === separator && depth === 0) {\n parts.push(value.slice(start, index));\n start = index + 1;\n }\n }\n parts.push(value.slice(start));\n return parts;\n};\n\nexport const parseCandidate = (raw: string): ParsedCandidate => {\n let rest = raw;\n const important = rest.endsWith(\"!\");\n if (important) rest = rest.slice(0, -1);\n\n const segments = splitOutsideBrackets(rest, \":\");\n const base = segments[segments.length - 1]!;\n const variants = segments.slice(0, -1);\n\n const lastSlash = (() => {\n let depth = 0;\n for (let index = base.length - 1; index >= 0; index--) {\n const char = base[index]!;\n if (char === \"]\") depth++;\n else if (char === \"[\") depth = Math.max(0, depth - 1);\n else if (char === \"/\" && depth === 0) return index;\n }\n return -1;\n })();\n\n if (lastSlash === -1) return { raw, variants, base, modifier: null, important };\n return {\n raw,\n variants,\n base: base.slice(0, lastSlash),\n modifier: base.slice(lastSlash + 1),\n important,\n };\n};\n\n/**\n * Splits `base` into (root, value) against a known root set, preferring the\n * longest matching root (\"max-w-lg\" → root \"max-w\", not \"max\").\n * `value` is `null` for a bare root (\"border\", \"rounded\"),\n * `{ kind: \"arbitrary\" }` for `root-[...]`, else `{ kind: \"named\" }`.\n */\nexport const splitRoot = (\n base: string,\n roots: ReadonlySet<string>,\n): {\n readonly root: string;\n readonly value: { kind: \"named\" | \"arbitrary\"; value: string } | null;\n} | null => {\n if (roots.has(base)) return { root: base, value: null };\n let dash = base.lastIndexOf(\"-\");\n while (dash > 0) {\n const root = base.slice(0, dash);\n if (roots.has(root)) {\n const rawValue = base.slice(dash + 1);\n if (rawValue.startsWith(\"[\") && rawValue.endsWith(\"]\")) {\n return { root, value: { kind: \"arbitrary\", value: rawValue.slice(1, -1) } };\n }\n return { root, value: { kind: \"named\", value: rawValue } };\n }\n dash = base.lastIndexOf(\"-\", dash - 1);\n }\n return null;\n};\n"],"mappings":";AAiBA,MAAM,wBAAwB,OAAe,cAAqC;CAChF,MAAM,QAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;EACjD,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,KAAK;OACb,IAAI,SAAS,KAAK,QAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;OAC/C,IAAI,SAAS,aAAa,UAAU,GAAG;GAC1C,MAAM,KAAK,MAAM,MAAM,OAAO,KAAK,CAAC;GACpC,QAAQ,QAAQ;EAClB;CACF;CACA,MAAM,KAAK,MAAM,MAAM,KAAK,CAAC;CAC7B,OAAO;AACT;AAEA,MAAa,kBAAkB,QAAiC;CAC9D,IAAI,OAAO;CACX,MAAM,YAAY,KAAK,SAAS,GAAG;CACnC,IAAI,WAAW,OAAO,KAAK,MAAM,GAAG,EAAE;CAEtC,MAAM,WAAW,qBAAqB,MAAM,GAAG;CAC/C,MAAM,OAAO,SAAS,SAAS,SAAS;CACxC,MAAM,WAAW,SAAS,MAAM,GAAG,EAAE;CAErC,MAAM,mBAAmB;EACvB,IAAI,QAAQ;EACZ,KAAK,IAAI,QAAQ,KAAK,SAAS,GAAG,SAAS,GAAG,SAAS;GACrD,MAAM,OAAO,KAAK;GAClB,IAAI,SAAS,KAAK;QACb,IAAI,SAAS,KAAK,QAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;QAC/C,IAAI,SAAS,OAAO,UAAU,GAAG,OAAO;EAC/C;EACA,OAAO;CACT,EAAA,CAAG;CAEH,IAAI,cAAc,IAAI,OAAO;EAAE;EAAK;EAAU;EAAM,UAAU;EAAM;CAAU;CAC9E,OAAO;EACL;EACA;EACA,MAAM,KAAK,MAAM,GAAG,SAAS;EAC7B,UAAU,KAAK,MAAM,YAAY,CAAC;EAClC;CACF;AACF;;;;;;;AAQA,MAAa,aACX,MACA,UAIU;CACV,IAAI,MAAM,IAAI,IAAI,GAAG,OAAO;EAAE,MAAM;EAAM,OAAO;CAAK;CACtD,IAAI,OAAO,KAAK,YAAY,GAAG;CAC/B,OAAO,OAAO,GAAG;EACf,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI;EAC/B,IAAI,MAAM,IAAI,IAAI,GAAG;GACnB,MAAM,WAAW,KAAK,MAAM,OAAO,CAAC;GACpC,IAAI,SAAS,WAAW,GAAG,KAAK,SAAS,SAAS,GAAG,GACnD,OAAO;IAAE;IAAM,OAAO;KAAE,MAAM;KAAa,OAAO,SAAS,MAAM,GAAG,EAAE;IAAE;GAAE;GAE5E,OAAO;IAAE;IAAM,OAAO;KAAE,MAAM;KAAS,OAAO;IAAS;GAAE;EAC3D;EACA,OAAO,KAAK,YAAY,KAAK,OAAO,CAAC;CACvC;CACA,OAAO;AACT"}