@samva/markup 0.1.0 → 0.1.2

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 (74) hide show
  1. package/dist/ast.js +1 -1
  2. package/dist/{checks-CW0nFj5n.js → checks-D-zFQt5R.js} +2 -2
  3. package/dist/checks.js +1 -1
  4. package/dist/compile-sms.d.ts +1 -1
  5. package/dist/compile-whatsapp.d.ts +1 -1
  6. package/dist/components.d.ts +3 -3
  7. package/dist/conditionals.js +1 -1
  8. package/dist/diagnostics.js +2 -2
  9. package/dist/jsx-dev-runtime.d.ts +1 -1
  10. package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-eNIMY87F.d.ts} +2 -2
  11. package/dist/jsx-runtime.d.ts +1 -1
  12. package/dist/ops.js +2 -2
  13. package/dist/{parser-yzjUOGhc.d.ts → parser-DkQKIjDa.d.ts} +11 -2
  14. package/dist/parser.d.ts +2 -2
  15. package/dist/parser.js +48 -9
  16. package/dist/predicate-CvkzT-a2.js +539 -0
  17. package/dist/{predicate-DTtVtLK7.d.ts → predicate-G2SeSvoN.d.ts} +9 -2
  18. package/dist/predicate.d.ts +2 -2
  19. package/dist/predicate.js +2 -417
  20. package/dist/presets/category-mapping.d.ts +1 -1
  21. package/dist/presets/public-gallery.d.ts +2 -490
  22. package/dist/presets/public-gallery.js +2 -1
  23. package/dist/presets/public-handoff.d.ts +2 -68
  24. package/dist/presets/public-handoff.js +110 -24
  25. package/dist/presets/public-preview-artifacts.d.ts +2 -2
  26. package/dist/presets/public-preview.d.ts +2 -1168
  27. package/dist/presets/public-preview.js +17 -17
  28. package/dist/presets/sources.js +17 -17
  29. package/dist/public-gallery-D0Zv0pZE.d.ts +611 -0
  30. package/dist/public-preview-u1Z9aaGq.d.ts +1168 -0
  31. package/dist/render-template.d.ts +2 -2
  32. package/dist/render-template.js +1 -1
  33. package/dist/{variables-3NGLpNTB.d.ts → variables-if1VrV34.d.ts} +2 -2
  34. package/dist/variables.d.ts +1 -1
  35. package/dist/variables.js +1 -1
  36. package/generated/sml-email-language-reference.md +1 -1
  37. package/generated/sml-language-reference.md +1 -1
  38. package/package.json +6 -3
  39. package/dist/ast.js.map +0 -1
  40. package/dist/candidate.js.map +0 -1
  41. package/dist/checks-CW0nFj5n.js.map +0 -1
  42. package/dist/classes.js.map +0 -1
  43. package/dist/compile-sms.js.map +0 -1
  44. package/dist/compile-whatsapp.js.map +0 -1
  45. package/dist/compile.js.map +0 -1
  46. package/dist/components.js.map +0 -1
  47. package/dist/conditionals.js.map +0 -1
  48. package/dist/diagnostics.js.map +0 -1
  49. package/dist/emit.js.map +0 -1
  50. package/dist/footer.js.map +0 -1
  51. package/dist/interpolate.js.map +0 -1
  52. package/dist/jsx-dev-runtime.js.map +0 -1
  53. package/dist/jsx-runtime.js.map +0 -1
  54. package/dist/lexer.js.map +0 -1
  55. package/dist/model.js.map +0 -1
  56. package/dist/ops.js.map +0 -1
  57. package/dist/parser.js.map +0 -1
  58. package/dist/predicate.js.map +0 -1
  59. package/dist/presets/category-mapping.js.map +0 -1
  60. package/dist/presets/public-gallery.js.map +0 -1
  61. package/dist/presets/public-handoff.js.map +0 -1
  62. package/dist/presets/public-preview-artifacts.js.map +0 -1
  63. package/dist/presets/public-preview.js.map +0 -1
  64. package/dist/presets/sources.js.map +0 -1
  65. package/dist/presets.js.map +0 -1
  66. package/dist/printer.js.map +0 -1
  67. package/dist/registry.js.map +0 -1
  68. package/dist/render-template.js.map +0 -1
  69. package/dist/sms-segments.js.map +0 -1
  70. package/dist/theme-css.js.map +0 -1
  71. package/dist/theme.js.map +0 -1
  72. package/dist/tokens.js.map +0 -1
  73. package/dist/variables.js.map +0 -1
  74. package/dist/version-BITr58kk.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"classes.js","names":[],"sources":["../src/classes.ts"],"sourcesContent":["import { parseCandidate, splitRoot } from \"./candidate\";\nimport type { Diagnostic, DiagnosticRepair, Span } from \"./diagnostics\";\nimport type { Declaration } from \"./registry\";\nimport { forbiddenUtilities, registryRoots, utilityRegistry } from \"./registry\";\nimport type { TokenTable } from \"./theme\";\n\n// Class-attribute resolution: candidate parsing (candidate.ts) × utility\n// registry (registry.ts) × layered token table (theme.ts) → literal\n// declarations, with class-level spans inside the attribute so compile errors\n// point at the exact class, not the whole attribute.\n\nexport type ClassVariant = \"base\" | \"sm\" | \"dark\";\n\nexport interface ResolvedClass {\n readonly raw: string;\n readonly span: Span;\n readonly variant: ClassVariant;\n readonly declarations: ReadonlyArray<Declaration>;\n /**\n * Automatic dark-mode counterparts: present only on a base-variant\n * color-family class whose named token has a `colorsDark` entry. Literal\n * values and tokens without a dark pair never get auto-dark; explicit dark:\n * classes win by being emitted after these (CSS last-wins).\n */\n readonly darkDeclarations?: ReadonlyArray<Declaration> | undefined;\n}\n\nexport interface ClassResolution {\n readonly classes: ReadonlyArray<ResolvedClass>;\n readonly errors: ReadonlyArray<Diagnostic>;\n readonly warnings: ReadonlyArray<Diagnostic>;\n}\n\nconst KNOWN_ROOT_SUMMARY =\n \"supported utilities: p*/m* spacing, w/max-w/h, text-*, leading-*, tracking-*, font-*, bg-*, bg-gradient-to-* with from-*/to-*, border*, rounded*, uppercase, underline/no-underline, block/inline-block/hidden\";\n\nconst VARIANT_ERRORS: Readonly<Record<string, string>> = {\n hover: \"email clients don't apply hover: styles — remove the variant\",\n focus: \"email clients don't apply focus: styles — remove the variant\",\n active: \"email clients don't apply active: styles — remove the variant\",\n md: \"only the sm: breakpoint is supported (progressive enhancement) — use sm:\",\n lg: \"only the sm: breakpoint is supported (progressive enhancement) — use sm:\",\n xl: \"only the sm: breakpoint is supported (progressive enhancement) — use sm:\",\n \"2xl\": \"only the sm: breakpoint is supported (progressive enhancement) — use sm:\",\n};\n\nconst REJECTED_ARBITRARY_FUNCTIONS = [\n [\"var(\", \"CSS variables don't exist in compiled email — use a theme token or a literal value\"],\n [\"calc(\", \"calc() is unsupported in Outlook for Windows — use a literal px value\"],\n [\"oklch(\", \"oklch() is unsupported in email clients — use a literal hex color\"],\n [\n \"rgb(\",\n \"use a literal hex color — rgb()/rgba() alpha renders solid black in Outlook for Windows\",\n ],\n [\"hsl(\", \"use a literal hex color — hsl() is unreliable across email clients\"],\n] as const;\n\nconst removeClassRepair = (raw: string, explanation: string): DiagnosticRepair => ({\n kind: \"remove-class\",\n raw,\n suggestions: [],\n explanation,\n});\n\nconst replaceClassRepair = (\n raw: string,\n suggestions: ReadonlyArray<string>,\n explanation: string,\n): DiagnosticRepair => ({\n kind: \"replace-class\",\n raw,\n suggestions,\n explanation,\n});\n\nconst primitiveRepair = (\n raw: string,\n suggestions: ReadonlyArray<string>,\n explanation: string,\n): DiagnosticRepair => ({\n kind: \"use-primitive\",\n raw,\n suggestions,\n explanation,\n});\n\nconst variantRepair = (raw: string, variantName: string): DiagnosticRepair =>\n variantName === \"md\" || variantName === \"lg\" || variantName === \"xl\" || variantName === \"2xl\"\n ? replaceClassRepair(\n raw,\n [`sm:${raw.slice(raw.indexOf(\":\") + 1)}`, raw.slice(raw.indexOf(\":\") + 1)],\n \"Only sm: exists for responsive email enhancement. Use sm: or remove the variant.\",\n )\n : replaceClassRepair(\n raw,\n [raw.slice(raw.indexOf(\":\") + 1)],\n \"Email clients do not support this interaction/responsive variant. Remove the variant.\",\n );\n\nconst forbiddenRepair = (raw: string): DiagnosticRepair => {\n if (/^(inline-)?(flex|grid)$/.test(raw) || raw.startsWith(\"gap-\")) {\n return primitiveRepair(\n raw,\n [\"<Row><Column>...</Column></Row>\", \"<Spacer height={16} />\"],\n \"Email layout must use table-backed primitives instead of flex/grid utilities.\",\n );\n }\n if (raw.startsWith(\"items-\") || raw.startsWith(\"justify-\")) {\n return primitiveRepair(\n raw,\n ['<Row><Column valign=\"middle\">...</Column></Row>', \"text-center\"],\n \"Use Row/Column alignment or text alignment instead of flexbox alignment utilities.\",\n );\n }\n if (raw.startsWith(\"overflow-\") || raw === \"truncate\" || raw.startsWith(\"line-clamp-\")) {\n return primitiveRepair(\n raw,\n [\"shorten the copy\", \"split content into rows/sections\"],\n \"Email clients cannot reliably clip or line-clamp content.\",\n );\n }\n return removeClassRepair(raw, \"This CSS feature is not reliable in email clients.\");\n};\n\nconst invalidValueRepair = (\n raw: string,\n root: string,\n value: string,\n): DiagnosticRepair | undefined => {\n if (root === \"text\" && /^\\d+(\\.\\d+)?px$/.test(value)) {\n return replaceClassRepair(\n raw,\n [\"text-[15px]\", \"text-base\", \"text-lg\", \"text-xl\", \"text-2xl\"],\n \"Arbitrary text sizes must be px values between 8px and 72px, or use a named text scale.\",\n );\n }\n if (root.startsWith(\"rounded\")) {\n return replaceClassRepair(\n raw,\n [`${root}-md`, `${root}-lg`, `${root}-xl`, `${root}-[12px]`],\n \"Border radius values must use the supported radius scale, none/full, or a px arbitrary value.\",\n );\n }\n return undefined;\n};\n\n/**\n * Resolves one class attribute value. `offset` is the absolute source offset\n * of the attribute value's first character, so every class gets its own span.\n * Collects all errors instead of failing fast — agents fix a list at a time.\n */\n// A zero-value margin can't shift layout when a client strips margins, so the\n// warn-tier margin advisory — which targets margin used as spacing — doesn't\n// apply to it. Non-zero margins keep warning.\nconst ZERO_LENGTH = /^0(?:px|rem|em|%)?$/;\nconst isZeroValued = (declarations: ReadonlyArray<{ readonly value: string }>): boolean =>\n declarations.every((declaration) => ZERO_LENGTH.test(declaration.value));\n\nexport const resolveClasses = (\n classAttribute: string,\n theme: TokenTable,\n offset: number,\n): ClassResolution => {\n const classes: Array<ResolvedClass> = [];\n const errors: Array<Diagnostic> = [];\n const warnings: Array<Diagnostic> = [];\n\n const pattern = /\\S+/g;\n for (const match of classAttribute.matchAll(pattern)) {\n const raw = match[0];\n const span: Span = { start: offset + match.index, end: offset + match.index + raw.length };\n const fail = (code: string, message: string, repair?: DiagnosticRepair): void => {\n errors.push(repair === undefined ? { code, message, span } : { code, message, span, repair });\n };\n\n const candidate = parseCandidate(raw);\n\n if (candidate.important) {\n fail(\n \"unsupported-important\",\n `remove the trailing ! from \"${raw}\" — the compiler manages !important for sm:/dark: rules`,\n removeClassRepair(raw, \"The compiler manages specificity for sm:/dark: rules.\"),\n );\n continue;\n }\n\n if (candidate.variants.length > 1) {\n fail(\n \"unsupported-variant\",\n `at most one variant per class — split \"${raw}\" into separate sm:/dark: classes`,\n replaceClassRepair(\n raw,\n [candidate.base],\n \"Use at most one variant per class; duplicate the base utility if both sm: and dark: variants are needed.\",\n ),\n );\n continue;\n }\n const variantName = candidate.variants[0];\n if (variantName !== undefined && variantName !== \"sm\" && variantName !== \"dark\") {\n const message =\n VARIANT_ERRORS[variantName] ?? \"unknown variant — only sm: and dark: are supported\";\n fail(\n \"unsupported-variant\",\n `${variantName}: on \"${raw}\" — ${message}`,\n variantRepair(raw, variantName),\n );\n continue;\n }\n const variant: ClassVariant = variantName ?? \"base\";\n\n const forbidden = forbiddenUtilities.find((entry) =>\n entry.match.endsWith(\"-\")\n ? candidate.base.startsWith(entry.match)\n : candidate.base === entry.match,\n );\n if (forbidden !== undefined) {\n fail(\"forbidden-class\", `class \"${raw}\" — ${forbidden.message}`, forbiddenRepair(raw));\n continue;\n }\n\n if (candidate.modifier !== null) {\n fail(\n \"unsupported-modifier\",\n `the /${candidate.modifier} modifier on \"${raw}\" compiles to alpha colors, which render solid black in Outlook for Windows (Word engine) — use a solid color token`,\n replaceClassRepair(\n raw,\n [candidate.base],\n \"Alpha modifiers are unsafe in Outlook for Windows. Use a solid token or literal hex color.\",\n ),\n );\n continue;\n }\n\n const split = splitRoot(candidate.base, registryRoots);\n if (split === null) {\n fail(\n \"unknown-class\",\n `unknown class \"${raw}\" — ${KNOWN_ROOT_SUMMARY}`,\n removeClassRepair(raw, \"Use only the closed SML email utility subset.\"),\n );\n continue;\n }\n\n if (split.value?.kind === \"arbitrary\") {\n const arbitraryValue = split.value.value;\n const rejection = REJECTED_ARBITRARY_FUNCTIONS.find(([prefix]) =>\n arbitraryValue.includes(prefix),\n );\n if (rejection !== undefined) {\n fail(\n \"unsupported-arbitrary-value\",\n `class \"${raw}\" — ${rejection[1]}`,\n replaceClassRepair(\n raw,\n split.root === \"text\" || split.root === \"bg\" || split.root === \"border\"\n ? [`${split.root}-[#374151]`]\n : [],\n \"Use literal px lengths or literal hex colors only; functions and variables are not email-safe.\",\n ),\n );\n continue;\n }\n }\n\n const families = utilityRegistry.filter((family) => family.root === split.root);\n let resolved: ResolvedClass | null = null;\n\n for (const family of families) {\n let declarations: ReadonlyArray<Declaration> | null = null;\n if (split.value === null) {\n declarations = family.bare ?? null;\n } else if (split.value.kind === \"named\") {\n declarations = family.named?.(split.value.value, theme) ?? null;\n } else {\n declarations = family.arbitrary?.(split.value.value, theme) ?? null;\n }\n if (declarations === null) continue;\n\n if (family.baseOnly === true && variantName !== undefined) {\n fail(\n \"unsupported-variant\",\n `${variantName}: on \"${raw}\" — gradient utilities are combined per node at compile time and can't ride sm:/dark: head rules; remove the variant`,\n replaceClassRepair(\n raw,\n [candidate.base],\n \"Gradient utilities compile per element, not as variant head rules.\",\n ),\n );\n resolved = null;\n break;\n }\n if (variant === \"dark\" && family.colorFamily !== true) {\n fail(\n \"dark-variant-non-color\",\n `dark: applies only to color utilities (bg-*, text-{color}, border-{color}) — remove dark: from \"${raw}\"`,\n replaceClassRepair(\n raw,\n [candidate.base],\n \"Only color utilities can have dark: variants in email.\",\n ),\n );\n resolved = null;\n break;\n }\n if (family.warn !== undefined && !isZeroValued(declarations)) {\n warnings.push({\n code: \"warn-tier-class\",\n message: `class \"${raw}\" — ${family.warn}`,\n span,\n });\n }\n // Auto token-dark: only base-variant color classes with a NAMED token\n // that has a dark pair. sm: color classes would need a combined\n // media query the machinery doesn't have, and dark: classes are already\n // dark. Color families emit single-property declarations whose value IS\n // the resolved color, so the dark counterpart is a value swap.\n // (`variantName === undefined` ⇔ base variant — phrased this way because\n // one tsgo config over-narrows `variant` here and flags the literal\n // comparison as TS2367.)\n const darkHex =\n variantName === undefined && family.colorFamily === true && split.value?.kind === \"named\"\n ? theme.colorsDark[split.value.value]\n : undefined;\n resolved =\n darkHex === undefined\n ? { raw, span, variant, declarations }\n : {\n raw,\n span,\n variant,\n declarations,\n darkDeclarations: declarations.map((declaration) => ({\n property: declaration.property,\n value: darkHex,\n })),\n };\n break;\n }\n\n if (resolved !== null) {\n classes.push(resolved);\n continue;\n }\n if (errors.length > 0 && errors[errors.length - 1]!.span === span) continue;\n\n const valueHint =\n split.value === null\n ? `\"${split.root}\" needs a value`\n : `\"${split.value.value}\" is not a valid value for ${split.root}-*`;\n fail(\n \"unknown-class\",\n `unknown class \"${raw}\" — ${valueHint}`,\n split.value === null ? undefined : invalidValueRepair(raw, split.root, split.value.value),\n );\n }\n\n return { classes, errors, warnings };\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-sms.js","names":[],"sources":["../src/compile-sms.ts"],"sourcesContent":["import type { ElementNode } from \"./ast\";\nimport { stringAttribute } from \"./ast\";\nimport { channelTextVariants, flattenChannelText, primaryChannelText } from \"./channel-text\";\nimport type { Diagnostic } from \"./diagnostics\";\nimport type { SMS_TAG_SPECS } from \"./parser\";\nimport type { SmsEncoding } from \"./sms-segments\";\nimport { hasSmsOptOut, smsSegmentInfo } from \"./sms-segments\";\nimport { COMPILER_VERSION } from \"./version\";\n\n// The sms compile backend: an <Sms> document lowers to a plain text body with\n// `sml:` conditional markers and literal `{{vars}}` — send time runs\n// resolveArtifactConditionals then interpolate, exactly like email artifacts.\n// No classes, no theming, no token table.\n//\n// Segment math convention: a variable counts at VARIABLE_WORST_CASE_CHARS\n// GSM-safe characters (there is no send-time value to measure; @samva/document\n// counted the literal `{{name}}` placeholder, which under-counts short names —\n// 16 is the documented worst-case width). Conditionals fork the body:\n// `estimatedSegments` measures the primary rendering (every <If> true);\n// `maxSegments` is the worst branch combination, present only when the\n// document has conditionals. Encoding is worst-case across combinations — one\n// non-GSM character in any branch prices the whole template as UCS-2.\n\n/** Documented worst-case rendered width of one `{{variable}}`. */\nexport const VARIABLE_WORST_CASE_CHARS = 16;\n\n/** Derived from the grammar's closed category set (mirrors smsCategoryEnum). */\nexport type SmsCategory = (typeof SMS_TAG_SPECS)[\"Sms\"][\"attributes\"][\"category\"][\"values\"][number];\n\nexport interface CompiledSms {\n readonly channel: \"sms\";\n /** The root tag's authoring-intent category; compliance state stays on variant rows. */\n readonly category?: SmsCategory | undefined;\n /** Template body: text verbatim, `{{vars}}` literal, `<If>` as sml: markers. */\n readonly body: string;\n readonly encoding: SmsEncoding;\n /** Segments of the primary rendering (every <If> true, variables at worst case). */\n readonly estimatedSegments: number;\n /** Worst branch combination; present only when the body has conditionals. */\n readonly maxSegments?: number | undefined;\n readonly warnings: ReadonlyArray<Diagnostic>;\n readonly compilerVersion: string;\n}\n\nconst WORST_CASE = { worstCaseChars: VARIABLE_WORST_CASE_CHARS } as const;\n\n/** Root is a parsed `<Sms>` element; the grammar already validated its shape. */\nexport const compileSmsRoot = (root: ElementNode): CompiledSms => {\n const body = flattenChannelText(root.children);\n const category = stringAttribute(root, \"category\") as SmsCategory | undefined;\n\n const hasConditionals = root.children.some(\n (child) => child.kind === \"element\" && child.tag === \"If\",\n );\n const variants = channelTextVariants(root.children, WORST_CASE);\n const variantInfo = variants.map(smsSegmentInfo);\n const encoding: SmsEncoding = variantInfo.some((info) => info.encoding === \"ucs2\")\n ? \"ucs2\"\n : \"gsm7\";\n const estimatedSegments = smsSegmentInfo(\n primaryChannelText(root.children, WORST_CASE),\n ).segmentCount;\n const worstSegments = Math.max(\n estimatedSegments,\n ...variantInfo.map((info) => info.segmentCount),\n );\n\n // Warnings ported from @samva/document collectSmsChecks — same findings,\n // now span-carrying compiler diagnostics.\n const warnings: Array<Diagnostic> = [];\n const warn = (code: string, message: string): void => {\n warnings.push({ code, message, span: root.span });\n };\n if (encoding === \"ucs2\") {\n warn(\n \"sms-ucs2\",\n \"sms uses non-GSM characters (UCS-2); each segment holds 70 characters instead of 160\",\n );\n }\n if (worstSegments > 1) {\n warn(\n \"sms-multi-segment\",\n `sms spans ${worstSegments} segments (variables counted at ${VARIABLE_WORST_CASE_CHARS} characters); each segment is billed separately`,\n );\n }\n if (worstSegments > 10) {\n warn(\"sms-segment-limit\", \"sms exceeds 10 segments and may be rejected by carriers\");\n }\n const literalPrimary = primaryChannelText(root.children, \"literal\");\n if (literalPrimary.trim().length > 0 && !hasSmsOptOut(literalPrimary)) {\n warn(\n \"sms-opt-out\",\n 'sms should include opt-out instructions (e.g. \"Reply STOP to unsubscribe\")',\n );\n }\n\n return {\n channel: \"sms\",\n category,\n body,\n encoding,\n estimatedSegments,\n maxSegments: hasConditionals ? worstSegments : undefined,\n warnings,\n compilerVersion: COMPILER_VERSION,\n };\n};\n"],"mappings":";;;;;AAwBA,MAAa,4BAA4B;AAoBzC,MAAM,aAAa,EAAE,gBAAA,GAA0C;;AAG/D,MAAa,kBAAkB,SAAmC;CAChE,MAAM,OAAO,mBAAmB,KAAK,QAAQ;CAC7C,MAAM,WAAW,gBAAgB,MAAM,UAAU;CAEjD,MAAM,kBAAkB,KAAK,SAAS,MACnC,UAAU,MAAM,SAAS,aAAa,MAAM,QAAQ,IACvD;CAEA,MAAM,cADW,oBAAoB,KAAK,UAAU,UACzB,CAAC,CAAC,IAAI,cAAc;CAC/C,MAAM,WAAwB,YAAY,MAAM,SAAS,KAAK,aAAa,MAAM,IAC7E,SACA;CACJ,MAAM,oBAAoB,eACxB,mBAAmB,KAAK,UAAU,UAAU,CAC9C,CAAC,CAAC;CACF,MAAM,gBAAgB,KAAK,IACzB,mBACA,GAAG,YAAY,KAAK,SAAS,KAAK,YAAY,CAChD;CAIA,MAAM,WAA8B,CAAC;CACrC,MAAM,QAAQ,MAAc,YAA0B;EACpD,SAAS,KAAK;GAAE;GAAM;GAAS,MAAM,KAAK;EAAK,CAAC;CAClD;CACA,IAAI,aAAa,QACf,KACE,YACA,sFACF;CAEF,IAAI,gBAAgB,GAClB,KACE,qBACA,aAAa,cAAc,kFAC7B;CAEF,IAAI,gBAAgB,IAClB,KAAK,qBAAqB,yDAAyD;CAErF,MAAM,iBAAiB,mBAAmB,KAAK,UAAU,SAAS;CAClE,IAAI,eAAe,KAAK,CAAC,CAAC,SAAS,KAAK,CAAC,aAAa,cAAc,GAClE,KACE,eACA,8EACF;CAGF,OAAO;EACL,SAAS;EACT;EACA;EACA;EACA;EACA,aAAa,kBAAkB,gBAAgB,KAAA;EAC/C;EACA,iBAAiB;CACnB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-whatsapp.js","names":[],"sources":["../src/compile-whatsapp.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport type { ElementNode } from \"./ast\";\nimport { stringAttribute } from \"./ast\";\nimport type { InterpolationWidth } from \"./channel-text\";\nimport { channelTextVariants, flattenChannelText } from \"./channel-text\";\nimport type { Diagnostic } from \"./diagnostics\";\nimport { SmlCompileError } from \"./diagnostics\";\nimport { variableReferences } from \"./interpolate\";\nimport type { WHATSAPP_TAG_SPECS } from \"./parser\";\nimport { COMPILER_VERSION } from \"./version\";\n\n// The whatsapp compile backend: a <WhatsApp> document lowers 1:1 onto the\n// whatsapp_template_variants columns (name/language/category, header parts,\n// body/footer content, buttons jsonb, examples jsonb). Text slots carry\n// literal `{{vars}}` and `sml:` conditional markers like every other channel's\n// compiled content; provider registration payloads are the API layer's job.\n//\n// Meta constraints ride along as diagnostics, ported from @samva/document\n// channel-checks and the dashboard editor rather than re-derived from Meta's\n// docs: length limits (header/footer 60, body 1024, button text 25) are\n// warnings (the advisory posture channel-checks had); button count limits and\n// per-type attribute requirements are hard errors (the dashboard editor\n// blocked those outright).\n// @see https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/components/\n\ntype WhatsAppSpecs = typeof WHATSAPP_TAG_SPECS;\n\n/** Mirrors whatsappTemplateCategoryEnum, derived from the grammar's closed set. */\nexport type WhatsAppTemplateCategory =\n WhatsAppSpecs[\"WhatsApp\"][\"attributes\"][\"category\"][\"values\"][number];\n\n/** Header type as authored (`type` attribute; defaults to \"text\"). */\nexport type WhatsAppHeaderType = WhatsAppSpecs[\"Header\"][\"attributes\"][\"type\"][\"values\"][number];\n\nexport type WhatsAppHeaderMediaType = Exclude<WhatsAppHeaderType, \"text\">;\n\ntype WhatsAppButtonSourceType = WhatsAppSpecs[\"Button\"][\"attributes\"][\"type\"][\"values\"][number];\n\n/** Meta-wire button rows, stored as-is in the buttons jsonb column. */\nexport type WhatsAppButtonPayload =\n | { readonly type: \"QUICK_REPLY\"; readonly text: string }\n | { readonly type: \"URL\"; readonly text: string; readonly url: string }\n | { readonly type: \"PHONE_NUMBER\"; readonly text: string; readonly phone_number: string }\n | { readonly type: \"COPY_CODE\"; readonly example: string };\n\n/** Variable names referenced per slot — the raw material for Meta example payloads. */\nexport interface WhatsAppExamples {\n readonly header?: ReadonlyArray<string> | undefined;\n readonly body?: ReadonlyArray<string> | undefined;\n readonly footer?: ReadonlyArray<string> | undefined;\n readonly buttons?: ReadonlyArray<string> | undefined;\n}\n\nexport interface CompiledWhatsApp {\n readonly channel: \"whatsapp\";\n /** Authoring-intent metadata; the API layer defaults name from the template slug. */\n readonly name?: string | undefined;\n /** The API layer defaults language from the variant locale (\"en_US\"). */\n readonly language?: string | undefined;\n readonly category: WhatsAppTemplateCategory;\n readonly headerType?: WhatsAppHeaderType | undefined;\n readonly headerMediaType?: WhatsAppHeaderMediaType | undefined;\n readonly headerContent?: string | undefined;\n readonly bodyContent: string;\n readonly footerContent?: string | undefined;\n readonly buttons: ReadonlyArray<WhatsAppButtonPayload>;\n readonly examples: WhatsAppExamples;\n readonly warnings: ReadonlyArray<Diagnostic>;\n readonly compilerVersion: string;\n}\n\n// Meta message-template limits, ported from @samva/document channel-checks.\nconst WHATSAPP_BUTTON_LIMITS = {\n URL: 2,\n PHONE_NUMBER: 1,\n QUICK_REPLY: 10,\n total: 10,\n} as const;\n\nconst WHATSAPP_HEADER_MAX = 60;\nconst WHATSAPP_FOOTER_MAX = 60;\nconst WHATSAPP_BODY_MAX = 1024;\nconst WHATSAPP_BUTTON_TEXT_MAX = 25;\n\nconst POSITIONAL_PATTERN = /\\{\\{\\s*(\\d+)\\s*\\}\\}/;\nconst NAMED_PATTERN = /\\{\\{\\s*([A-Za-z_][\\w.]*)\\s*\\}\\}/;\n\nconst LITERAL: InterpolationWidth = \"literal\";\n\n/** Longest possible send-time rendering of a slot, with literal placeholders. */\nconst worstCaseLength = (node: ElementNode): number =>\n Math.max(0, ...channelTextVariants(node.children, LITERAL).map((variant) => variant.length));\n\nconst component = (root: ElementNode, tag: string): ElementNode | undefined =>\n root.children.find(\n (child): child is ElementNode => child.kind === \"element\" && child.tag === tag,\n );\n\nconst slotExamples = (texts: ReadonlyArray<string>): ReadonlyArray<string> | undefined => {\n const names = new Set<string>();\n for (const text of texts) {\n for (const name of variableReferences(text)) names.add(name);\n }\n return names.size === 0 ? undefined : [...names].sort();\n};\n\n/** Root is a parsed `<WhatsApp>` element; the grammar already validated its anatomy. */\nexport const compileWhatsAppRoot = (\n root: ElementNode,\n): Result.Result<CompiledWhatsApp, SmlCompileError> => {\n const errors: Array<Diagnostic> = [];\n const warnings: Array<Diagnostic> = [];\n\n const header = component(root, \"Header\");\n const body = component(root, \"Body\")!; // grammar: <Body> is required\n const footer = component(root, \"Footer\");\n const buttonsNode = component(root, \"Buttons\");\n\n const headerType =\n header === undefined\n ? undefined\n : ((stringAttribute(header, \"type\") ?? \"text\") as WhatsAppHeaderType);\n const headerContent = header === undefined ? undefined : flattenChannelText(header.children);\n const bodyContent = flattenChannelText(body.children);\n const footerContent = footer === undefined ? undefined : flattenChannelText(footer.children);\n\n // ── Length limits (ported advisory checks, now with spans) ────────────────\n if (\n header !== undefined &&\n headerType === \"text\" &&\n worstCaseLength(header) > WHATSAPP_HEADER_MAX\n ) {\n warnings.push({\n code: \"whatsapp-header-length\",\n message: `whatsapp header should stay under ${WHATSAPP_HEADER_MAX} characters`,\n span: header.span,\n });\n }\n if (worstCaseLength(body) > WHATSAPP_BODY_MAX) {\n warnings.push({\n code: \"whatsapp-body-length\",\n message: `whatsapp body should stay under ${WHATSAPP_BODY_MAX} characters`,\n span: body.span,\n });\n }\n if (footer !== undefined && worstCaseLength(footer) > WHATSAPP_FOOTER_MAX) {\n warnings.push({\n code: \"whatsapp-footer-length\",\n message: `whatsapp footer should stay under ${WHATSAPP_FOOTER_MAX} characters`,\n span: footer.span,\n });\n }\n\n // ── Buttons ────────────────────────────────────────────────────────────────\n const buttonElements =\n buttonsNode === undefined\n ? []\n : buttonsNode.children.filter(\n (child): child is ElementNode => child.kind === \"element\" && child.tag === \"Button\",\n );\n const buttons: Array<WhatsAppButtonPayload> = [];\n for (const button of buttonElements) {\n const type = stringAttribute(button, \"type\") as WhatsAppButtonSourceType;\n const text = flattenChannelText(button.children);\n const url = stringAttribute(button, \"url\");\n const phone = stringAttribute(button, \"phone\");\n\n if (url !== undefined && type !== \"url\") {\n errors.push({\n code: \"whatsapp-button-url\",\n message: `url is only valid on <Button type=\"url\"> — remove it or change the type`,\n span: button.span,\n });\n continue;\n }\n if (phone !== undefined && type !== \"phone\") {\n errors.push({\n code: \"whatsapp-button-phone\",\n message: `phone is only valid on <Button type=\"phone\"> — remove it or change the type`,\n span: button.span,\n });\n continue;\n }\n if (type !== \"copy-code\" && worstCaseLength(button) > WHATSAPP_BUTTON_TEXT_MAX) {\n warnings.push({\n code: \"whatsapp-button-text-length\",\n message: `whatsapp button text should stay under ${WHATSAPP_BUTTON_TEXT_MAX} characters`,\n span: button.span,\n });\n }\n switch (type) {\n case \"quick-reply\":\n buttons.push({ type: \"QUICK_REPLY\", text });\n break;\n case \"url\": {\n if (url === undefined) {\n errors.push({\n code: \"whatsapp-button-url\",\n message: `<Button type=\"url\"> requires a url attribute — add url=\"https://...\"`,\n span: button.span,\n });\n break;\n }\n buttons.push({ type: \"URL\", text, url });\n break;\n }\n case \"phone\": {\n if (phone === undefined) {\n errors.push({\n code: \"whatsapp-button-phone\",\n message: `<Button type=\"phone\"> requires a phone attribute — add phone=\"+15551234567\"`,\n span: button.span,\n });\n break;\n }\n buttons.push({ type: \"PHONE_NUMBER\", text, phone_number: phone });\n break;\n }\n case \"copy-code\":\n // The label is fixed by WhatsApp; the content is the code to copy.\n buttons.push({ type: \"COPY_CODE\", example: text });\n break;\n }\n }\n\n // Count limits (ported; the dashboard editor blocks these, so they are errors).\n const buttonsSpan = buttonsNode?.span ?? root.span;\n const countOf = (type: WhatsAppButtonPayload[\"type\"]): number =>\n buttons.filter((button) => button.type === type).length;\n if (countOf(\"URL\") > WHATSAPP_BUTTON_LIMITS.URL) {\n errors.push({\n code: \"whatsapp-button-limit\",\n message: `whatsapp allows at most ${WHATSAPP_BUTTON_LIMITS.URL} URL buttons`,\n span: buttonsSpan,\n });\n }\n if (countOf(\"PHONE_NUMBER\") > WHATSAPP_BUTTON_LIMITS.PHONE_NUMBER) {\n errors.push({\n code: \"whatsapp-button-limit\",\n message: `whatsapp allows at most ${WHATSAPP_BUTTON_LIMITS.PHONE_NUMBER} phone-number button`,\n span: buttonsSpan,\n });\n }\n if (countOf(\"QUICK_REPLY\") > WHATSAPP_BUTTON_LIMITS.QUICK_REPLY) {\n errors.push({\n code: \"whatsapp-button-limit\",\n message: `whatsapp allows at most ${WHATSAPP_BUTTON_LIMITS.QUICK_REPLY} quick-reply buttons`,\n span: buttonsSpan,\n });\n }\n if (buttonElements.length > WHATSAPP_BUTTON_LIMITS.total) {\n errors.push({\n code: \"whatsapp-button-limit\",\n message: `whatsapp allows at most ${WHATSAPP_BUTTON_LIMITS.total} buttons total`,\n span: buttonsSpan,\n });\n }\n\n // ── Placeholder style mixing (ported) ─────────────────────────────────────\n // Meta requires one placeholder style per template: positional {{1}} or\n // named {{var}}. Scan component text and dynamic button urls.\n const placeholderSources = [\n headerContent ?? \"\",\n bodyContent,\n footerContent ?? \"\",\n ...buttons.map((button) => (button.type === \"URL\" ? button.url : \"\")),\n ];\n const hasPositional = placeholderSources.some((source) => POSITIONAL_PATTERN.test(source));\n const hasNamed = placeholderSources.some((source) => NAMED_PATTERN.test(source));\n if (hasPositional && hasNamed) {\n warnings.push({\n code: \"whatsapp-mixed-placeholders\",\n message:\n \"whatsapp mixes positional {{1}} and named {{var}} placeholders; Meta requires one style\",\n span: root.span,\n });\n }\n\n if (errors.length > 0) {\n return Result.fail(new SmlCompileError({ diagnostics: errors }));\n }\n\n return Result.succeed({\n channel: \"whatsapp\",\n name: stringAttribute(root, \"name\"),\n language: stringAttribute(root, \"language\"),\n category: stringAttribute(root, \"category\") as WhatsAppTemplateCategory,\n headerType,\n headerMediaType:\n headerType === undefined || headerType === \"text\"\n ? undefined\n : (headerType satisfies WhatsAppHeaderMediaType),\n headerContent,\n bodyContent,\n footerContent,\n buttons,\n examples: {\n header: slotExamples([headerContent ?? \"\"]),\n body: slotExamples([bodyContent]),\n footer: slotExamples([footerContent ?? \"\"]),\n buttons: slotExamples(\n buttons.map((button) =>\n button.type === \"URL\"\n ? `${button.text} ${button.url}`\n : button.type === \"COPY_CODE\"\n ? button.example\n : button.text,\n ),\n ),\n },\n warnings,\n compilerVersion: COMPILER_VERSION,\n });\n};\n"],"mappings":";;;;;;AAyEA,MAAM,yBAAyB;CAC7B,KAAK;CACL,cAAc;CACd,aAAa;CACb,OAAO;AACT;AAEA,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,2BAA2B;AAEjC,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AAEtB,MAAM,UAA8B;;AAGpC,MAAM,mBAAmB,SACvB,KAAK,IAAI,GAAG,GAAG,oBAAoB,KAAK,UAAU,OAAO,CAAC,CAAC,KAAK,YAAY,QAAQ,MAAM,CAAC;AAE7F,MAAM,aAAa,MAAmB,QACpC,KAAK,SAAS,MACX,UAAgC,MAAM,SAAS,aAAa,MAAM,QAAQ,GAC7E;AAEF,MAAM,gBAAgB,UAAoE;CACxF,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,QAAQ,OACjB,KAAK,MAAM,QAAQ,mBAAmB,IAAI,GAAG,MAAM,IAAI,IAAI;CAE7D,OAAO,MAAM,SAAS,IAAI,KAAA,IAAY,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK;AACxD;;AAGA,MAAa,uBACX,SACqD;CACrD,MAAM,SAA4B,CAAC;CACnC,MAAM,WAA8B,CAAC;CAErC,MAAM,SAAS,UAAU,MAAM,QAAQ;CACvC,MAAM,OAAO,UAAU,MAAM,MAAM;CACnC,MAAM,SAAS,UAAU,MAAM,QAAQ;CACvC,MAAM,cAAc,UAAU,MAAM,SAAS;CAE7C,MAAM,aACJ,WAAW,KAAA,IACP,KAAA,IACE,gBAAgB,QAAQ,MAAM,KAAK;CAC3C,MAAM,gBAAgB,WAAW,KAAA,IAAY,KAAA,IAAY,mBAAmB,OAAO,QAAQ;CAC3F,MAAM,cAAc,mBAAmB,KAAK,QAAQ;CACpD,MAAM,gBAAgB,WAAW,KAAA,IAAY,KAAA,IAAY,mBAAmB,OAAO,QAAQ;CAG3F,IACE,WAAW,KAAA,KACX,eAAe,UACf,gBAAgB,MAAM,IAAI,qBAE1B,SAAS,KAAK;EACZ,MAAM;EACN,SAAS,qCAAqC,oBAAoB;EAClE,MAAM,OAAO;CACf,CAAC;CAEH,IAAI,gBAAgB,IAAI,IAAI,mBAC1B,SAAS,KAAK;EACZ,MAAM;EACN,SAAS,mCAAmC,kBAAkB;EAC9D,MAAM,KAAK;CACb,CAAC;CAEH,IAAI,WAAW,KAAA,KAAa,gBAAgB,MAAM,IAAI,qBACpD,SAAS,KAAK;EACZ,MAAM;EACN,SAAS,qCAAqC,oBAAoB;EAClE,MAAM,OAAO;CACf,CAAC;CAIH,MAAM,iBACJ,gBAAgB,KAAA,IACZ,CAAC,IACD,YAAY,SAAS,QAClB,UAAgC,MAAM,SAAS,aAAa,MAAM,QAAQ,QAC7E;CACN,MAAM,UAAwC,CAAC;CAC/C,KAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,OAAO,gBAAgB,QAAQ,MAAM;EAC3C,MAAM,OAAO,mBAAmB,OAAO,QAAQ;EAC/C,MAAM,MAAM,gBAAgB,QAAQ,KAAK;EACzC,MAAM,QAAQ,gBAAgB,QAAQ,OAAO;EAE7C,IAAI,QAAQ,KAAA,KAAa,SAAS,OAAO;GACvC,OAAO,KAAK;IACV,MAAM;IACN,SAAS;IACT,MAAM,OAAO;GACf,CAAC;GACD;EACF;EACA,IAAI,UAAU,KAAA,KAAa,SAAS,SAAS;GAC3C,OAAO,KAAK;IACV,MAAM;IACN,SAAS;IACT,MAAM,OAAO;GACf,CAAC;GACD;EACF;EACA,IAAI,SAAS,eAAe,gBAAgB,MAAM,IAAI,0BACpD,SAAS,KAAK;GACZ,MAAM;GACN,SAAS,0CAA0C,yBAAyB;GAC5E,MAAM,OAAO;EACf,CAAC;EAEH,QAAQ,MAAR;GACE,KAAK;IACH,QAAQ,KAAK;KAAE,MAAM;KAAe;IAAK,CAAC;IAC1C;GACF,KAAK;IACH,IAAI,QAAQ,KAAA,GAAW;KACrB,OAAO,KAAK;MACV,MAAM;MACN,SAAS;MACT,MAAM,OAAO;KACf,CAAC;KACD;IACF;IACA,QAAQ,KAAK;KAAE,MAAM;KAAO;KAAM;IAAI,CAAC;IACvC;GAEF,KAAK;IACH,IAAI,UAAU,KAAA,GAAW;KACvB,OAAO,KAAK;MACV,MAAM;MACN,SAAS;MACT,MAAM,OAAO;KACf,CAAC;KACD;IACF;IACA,QAAQ,KAAK;KAAE,MAAM;KAAgB;KAAM,cAAc;IAAM,CAAC;IAChE;GAEF,KAAK,aAEH,QAAQ,KAAK;IAAE,MAAM;IAAa,SAAS;GAAK,CAAC;EAErD;CACF;CAGA,MAAM,cAAc,aAAa,QAAQ,KAAK;CAC9C,MAAM,WAAW,SACf,QAAQ,QAAQ,WAAW,OAAO,SAAS,IAAI,CAAC,CAAC;CACnD,IAAI,QAAQ,KAAK,IAAI,uBAAuB,KAC1C,OAAO,KAAK;EACV,MAAM;EACN,SAAS,2BAA2B,uBAAuB,IAAI;EAC/D,MAAM;CACR,CAAC;CAEH,IAAI,QAAQ,cAAc,IAAI,uBAAuB,cACnD,OAAO,KAAK;EACV,MAAM;EACN,SAAS,2BAA2B,uBAAuB,aAAa;EACxE,MAAM;CACR,CAAC;CAEH,IAAI,QAAQ,aAAa,IAAI,uBAAuB,aAClD,OAAO,KAAK;EACV,MAAM;EACN,SAAS,2BAA2B,uBAAuB,YAAY;EACvE,MAAM;CACR,CAAC;CAEH,IAAI,eAAe,SAAS,uBAAuB,OACjD,OAAO,KAAK;EACV,MAAM;EACN,SAAS,2BAA2B,uBAAuB,MAAM;EACjE,MAAM;CACR,CAAC;CAMH,MAAM,qBAAqB;EACzB,iBAAiB;EACjB;EACA,iBAAiB;EACjB,GAAG,QAAQ,KAAK,WAAY,OAAO,SAAS,QAAQ,OAAO,MAAM,EAAG;CACtE;CACA,MAAM,gBAAgB,mBAAmB,MAAM,WAAW,mBAAmB,KAAK,MAAM,CAAC;CACzF,MAAM,WAAW,mBAAmB,MAAM,WAAW,cAAc,KAAK,MAAM,CAAC;CAC/E,IAAI,iBAAiB,UACnB,SAAS,KAAK;EACZ,MAAM;EACN,SACE;EACF,MAAM,KAAK;CACb,CAAC;CAGH,IAAI,OAAO,SAAS,GAClB,OAAO,OAAO,KAAK,IAAI,gBAAgB,EAAE,aAAa,OAAO,CAAC,CAAC;CAGjE,OAAO,OAAO,QAAQ;EACpB,SAAS;EACT,MAAM,gBAAgB,MAAM,MAAM;EAClC,UAAU,gBAAgB,MAAM,UAAU;EAC1C,UAAU,gBAAgB,MAAM,UAAU;EAC1C;EACA,iBACE,eAAe,KAAA,KAAa,eAAe,SACvC,KAAA,IACC;EACP;EACA;EACA;EACA;EACA,UAAU;GACR,QAAQ,aAAa,CAAC,iBAAiB,EAAE,CAAC;GAC1C,MAAM,aAAa,CAAC,WAAW,CAAC;GAChC,QAAQ,aAAa,CAAC,iBAAiB,EAAE,CAAC;GAC1C,SAAS,aACP,QAAQ,KAAK,WACX,OAAO,SAAS,QACZ,GAAG,OAAO,KAAK,GAAG,OAAO,QACzB,OAAO,SAAS,cACd,OAAO,UACP,OAAO,IACf,CACF;EACF;EACA;EACA,iBAAiB;CACnB,CAAC;AACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile.js","names":[],"sources":["../src/compile.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport type { ElementNode, SmlDocument, SmlNode } from \"./ast\";\nimport { DEFAULT_EMAIL_WIDTH, isHiddenNode, numberAttribute, stringAttribute } from \"./ast\";\nimport type { ClassVariant, ResolvedClass } from \"./classes\";\nimport { resolveClasses } from \"./classes\";\nimport type { CompiledSms } from \"./compile-sms\";\nimport { compileSmsRoot } from \"./compile-sms\";\nimport type { CompiledWhatsApp } from \"./compile-whatsapp\";\nimport { compileWhatsAppRoot } from \"./compile-whatsapp\";\nimport type { Diagnostic } from \"./diagnostics\";\nimport { SmlCompileError, SmlParseError } from \"./diagnostics\";\nimport { parseSml } from \"./parser\";\nimport type { Declaration } from \"./registry\";\nimport {\n GRADIENT_DIRECTION_PROPERTY,\n GRADIENT_FROM_PROPERTY,\n GRADIENT_TO_PROPERTY,\n} from \"./registry\";\nimport type { ThemeLayers, TokenTable } from \"./theme\";\nimport { escapeCssString, resolveTokenTable } from \"./theme\";\nimport { baseTokens } from \"./tokens\";\nimport { COMPILER_VERSION } from \"./version\";\n\n/**\n * The SML compiler: pure functions, deterministic bytes for identical\n * (source, theme, compilerVersion, base) — content addressing in R2 depends on\n * it. No Date, no randomness, no platform APIs; runs identically in Worker,\n * browser, and CLI.\n *\n * Pipeline: AST → expand primitives → resolve tokens+classes → inline (base\n * declarations → style=\"\"; sm:/dark: → head <style> with !important) → lower\n * to tables + mso fallbacks → print artifact HTML + plain-text alt.\n *\n * Client-compat knowledge ported from react-email (see the design brief §3):\n * - Body: Yahoo/AOL strip body styles → outer table, inner td carries them.\n * - Container/Section: padding split off the table onto the inner td\n * (Klaviyo/Outlook).\n * - Outlook button: mso-padding-alt:0 + <!--[if mso]> hair-space runs with\n * mso-font-width (≤500%) / mso-text-raise (px→pt = ×3/4).\n * - Preview: hidden div padded to ~200 chars with zero-width characters,\n * marked data-skip-in-text for the plain-text pass.\n * - Img reset, hr as border-top, x-apple-disable-message-reformatting, XHTML\n * transitional doctype, width/align HTML attributes, role=\"presentation\".\n *\n * Two bases: \"outlook-safe\" (default — tables + mso conditionals + XHTML\n * doctype) and \"modern\" (HTML5 doctype, divs for Container/Section, real\n * padding on buttons, no conditional comments). Rows keep tables in both bases\n * — columns are genuinely tabular content.\n */\n\nexport { COMPILER_VERSION } from \"./version\";\n\nexport type CompileBase = \"outlook-safe\" | \"modern\";\n\n/** Email-only knobs; sms/whatsapp compile ignores both (no theming, no bases). */\nexport interface CompileOptions {\n readonly base?: CompileBase | undefined;\n readonly theme?:\n | (Omit<ThemeLayers, \"base\"> & { readonly base?: TokenTable | undefined })\n | undefined;\n /** Omit editor DOM instrumentation from exported/sent HTML artifacts. */\n readonly stripInstrumentation?: boolean | undefined;\n}\n\nexport interface CompiledEmail {\n readonly channel: \"email\";\n readonly html: string;\n /** Plain-text alternative ({{vars}} verbatim, envelope metadata skipped). */\n readonly text: string;\n /** Authored <Subject> text with interpolation tokens preserved; null when absent. */\n readonly subjectText: string | null;\n readonly warnings: ReadonlyArray<Diagnostic>;\n readonly base: CompileBase;\n readonly compilerVersion: string;\n}\n\n/**\n * The compile result, discriminated on `channel` — which the root tag\n * declares. email → HTML + plain-text artifacts; sms → body + segment math;\n * whatsapp → whatsapp_template_variants column shapes.\n */\nexport type CompiledTemplate = CompiledEmail | CompiledSms | CompiledWhatsApp;\n\n// ── Internal HTML tree ────────────────────────────────────────────────────────\n\nexport interface HtmlElement {\n readonly kind: \"element\";\n readonly tag: string;\n /** Ordered; serialized before style. */\n readonly attrs: ReadonlyArray<readonly [string, string]>;\n /** Serialized as style=\"\" in order — CSS last-wins handles overrides. */\n readonly style: ReadonlyArray<Declaration>;\n readonly children: ReadonlyArray<HtmlNode>;\n readonly void?: boolean | undefined;\n}\n\nexport type HtmlTextNode = { readonly kind: \"text\"; readonly value: string };\n\nexport type HtmlRawNode = {\n readonly kind: \"raw\";\n readonly html: string;\n};\n\n/** Children spliced into the parent's block flow — <If> lowers to one. */\nexport interface HtmlFragment {\n readonly kind: \"fragment\";\n readonly children: ReadonlyArray<HtmlNode>;\n}\n\nexport type HtmlNode = HtmlElement | HtmlTextNode | HtmlRawNode | HtmlFragment;\n\nexport interface CompileHeadStyleAsset {\n readonly kind: \"style\";\n readonly css: string;\n /** Serialized style asset as emitted in <head>. */\n readonly html: string;\n /** True for the Outlook-hidden webfont style block. */\n readonly outlookHidden: boolean;\n}\n\nexport interface CompileHeadAssets {\n readonly styles: ReadonlyArray<CompileHeadStyleAsset>;\n readonly links: ReadonlyArray<HtmlElement>;\n}\n\nexport type HtmlNodeIdMap = Readonly<Record<string, ReadonlyArray<HtmlElement>>>;\n\nexport interface CompiledEmailArtifact extends CompiledEmail {\n /** Resolved primary document width in pixels. */\n readonly width: number;\n readonly tree: HtmlElement;\n readonly doctype: string;\n readonly headAssets: CompileHeadAssets;\n readonly nodeIdMap: HtmlNodeIdMap;\n}\n\nexport type CompiledTemplateArtifact = CompiledEmailArtifact | CompiledSms | CompiledWhatsApp;\n\nconst element = (\n tag: string,\n attrs: Array<readonly [string, string]>,\n style: Array<Declaration>,\n children: Array<HtmlNode>,\n options?: { readonly void?: boolean | undefined },\n): HtmlElement => ({\n kind: \"element\",\n tag,\n attrs,\n style,\n children,\n void: options?.void === true ? true : undefined,\n});\n\nconst text = (value: string): HtmlNode => ({ kind: \"text\", value });\nconst raw = (html: string): HtmlNode => ({ kind: \"raw\", html });\n\n// ── Serialization ─────────────────────────────────────────────────────────────\n\nconst INLINE_HTML_TAGS = new Set([\"a\", \"span\", \"strong\", \"em\", \"i\", \"title\"]);\n\nconst escapeHtmlText = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\");\n\nconst escapeHtmlAttribute = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll('\"', \"&quot;\");\n\nconst serializeStyle = (style: ReadonlyArray<Declaration>): string =>\n style.map((declaration) => `${declaration.property}:${declaration.value}`).join(\";\");\n\nconst serializeOpenTag = (node: HtmlElement, selfClose: boolean): string => {\n let result = `<${node.tag}`;\n for (const [name, value] of node.attrs) {\n result += value === \"\" ? ` ${name}=\"\"` : ` ${name}=\"${escapeHtmlAttribute(value)}\"`;\n }\n if (node.style.length > 0)\n result += ` style=\"${escapeHtmlAttribute(serializeStyle(node.style))}\"`;\n return result + (selfClose ? \"/>\" : \">\");\n};\n\nconst isInlineNode = (node: HtmlNode): boolean =>\n node.kind === \"fragment\" ? false : node.kind !== \"element\" || INLINE_HTML_TAGS.has(node.tag);\n\nexport const serializeHtmlNode = (node: HtmlNode): string => {\n if (node.kind === \"text\") return escapeHtmlText(node.value);\n if (node.kind === \"raw\") return node.html;\n if (node.kind === \"fragment\") return node.children.map(serializeHtmlNode).join(\"\\n\");\n if (node.void === true || node.children.length === 0) {\n if (node.void === true) return serializeOpenTag(node, true);\n return `${serializeOpenTag(node, false)}</${node.tag}>`;\n }\n const inlineContent = node.children.every(isInlineNode);\n const children = node.children.map(serializeHtmlNode);\n if (inlineContent) return `${serializeOpenTag(node, false)}${children.join(\"\")}</${node.tag}>`;\n return `${serializeOpenTag(node, false)}\\n${children.join(\"\\n\")}\\n</${node.tag}>`;\n};\n\n// ── Class → style plumbing ────────────────────────────────────────────────────\n\ninterface HeadRule {\n readonly selector: string;\n readonly declarations: ReadonlyArray<Declaration>;\n}\n\ninterface CompileState {\n readonly base: CompileBase;\n readonly emailWidth: number;\n readonly theme: TokenTable;\n readonly stripInstrumentation: boolean;\n readonly errors: Array<Diagnostic>;\n readonly warnings: Array<Diagnostic>;\n readonly smRules: Array<HeadRule>;\n readonly darkRules: Array<HeadRule>;\n}\n\ninterface NodeStyles {\n readonly inline: Array<Declaration>;\n /** Present when the node needs a class attribute for head rules. */\n readonly className: string | null;\n}\n\n// ── Gradient combination ──────────────────────────────────────────────────────\n// The gradient family resolves to placeholder declarations (registry.ts); one\n// node's direction + from + to combine here into real background declarations.\n// Modern base: a CSS linear-gradient plus a solid from-color fallback.\n// Outlook-safe base: the solid from-color only — Outlook for Windows renders\n// no gradient, and a VML fill is not robust enough to be worth it.\n\nconst GRADIENT_PROPERTIES = new Set([\n GRADIENT_DIRECTION_PROPERTY,\n GRADIENT_FROM_PROPERTY,\n GRADIENT_TO_PROPERTY,\n]);\n\nconst isGradientClass = (resolved: ResolvedClass): boolean =>\n resolved.declarations.some((declaration) => GRADIENT_PROPERTIES.has(declaration.property));\n\nconst combineGradient = (\n state: CompileState,\n baseClasses: ReadonlyArray<ResolvedClass>,\n): Array<Declaration> => {\n const gradientClasses = baseClasses.filter(isGradientClass);\n if (gradientClasses.length === 0) {\n return baseClasses.flatMap((resolved) => [...resolved.declarations]);\n }\n\n // Last-wins per slot, like CSS.\n const slots = new Map<string, string>();\n for (const resolved of gradientClasses) {\n for (const declaration of resolved.declarations) {\n slots.set(declaration.property, declaration.value);\n }\n }\n const direction = slots.get(GRADIENT_DIRECTION_PROPERTY);\n const from = slots.get(GRADIENT_FROM_PROPERTY);\n const to = slots.get(GRADIENT_TO_PROPERTY);\n const anchor = gradientClasses[0]!;\n const missing: Array<string> = [];\n if (direction === undefined) missing.push(\"a direction (bg-gradient-to-b/t/l/r)\");\n if (from === undefined) missing.push(\"a start color (from-{color})\");\n if (to === undefined) missing.push(\"an end color (to-{color})\");\n if (missing.length > 0) {\n state.errors.push({\n code: \"incomplete-gradient\",\n message: `incomplete gradient near \"${anchor.raw}\" — add ${missing.join(\" and \")}`,\n span: anchor.span,\n });\n return baseClasses\n .filter((resolved) => !isGradientClass(resolved))\n .flatMap((resolved) => [...resolved.declarations]);\n }\n\n const combined: Array<Declaration> = [{ property: \"background-color\", value: from! }];\n if (state.base === \"modern\") {\n combined.push({\n property: \"background-image\",\n value: `linear-gradient(${direction},${from},${to})`,\n });\n }\n\n // The combined declarations take the FIRST gradient class's position so CSS\n // last-wins ordering against other background utilities stays authored.\n const out: Array<Declaration> = [];\n for (const resolved of baseClasses) {\n if (isGradientClass(resolved)) {\n if (resolved === anchor) out.push(...combined);\n continue;\n }\n out.push(...resolved.declarations);\n }\n return out;\n};\n\nconst resolveNodeStyles = (\n state: CompileState,\n node: ElementNode,\n defaults: {\n readonly inline: ReadonlyArray<Declaration>;\n readonly dark: ReadonlyArray<Declaration>;\n } = { inline: [], dark: [] },\n): NodeStyles => {\n const classAttribute = node.attributes.find((attribute) => attribute.name === \"class\");\n if (classAttribute === undefined || classAttribute.value.kind !== \"string\") {\n if (defaults.dark.length === 0) {\n return { inline: [...defaults.inline], className: null };\n }\n const className = `sml-${node.id}`;\n state.darkRules.push({ selector: `.${className}`, declarations: defaults.dark });\n return { inline: [...defaults.inline], className };\n }\n const resolution = resolveClasses(\n classAttribute.value.value,\n state.theme,\n classAttribute.valueSpan.start,\n );\n state.errors.push(...resolution.errors);\n state.warnings.push(...resolution.warnings);\n\n const byVariant = (variant: ClassVariant): Array<ResolvedClass> =>\n resolution.classes.filter((resolved) => resolved.variant === variant);\n\n const base = byVariant(\"base\");\n const inline = [...defaults.inline, ...combineGradient(state, base)];\n const smDeclarations = byVariant(\"sm\").flatMap((resolved) => [...resolved.declarations]);\n // Auto token-dark rules come BEFORE explicit dark: rules — both land in the\n // same node-classed head rule, so an explicit dark:bg-* wins by last-wins.\n const darkDeclarations = [\n ...defaults.dark,\n ...base.flatMap((resolved) => [...(resolved.darkDeclarations ?? [])]),\n ...byVariant(\"dark\").flatMap((resolved) => [...resolved.declarations]),\n ];\n\n if (smDeclarations.length === 0 && darkDeclarations.length === 0) {\n return { inline, className: null };\n }\n const className = `sml-${node.id}`;\n if (smDeclarations.length > 0) {\n state.smRules.push({\n selector: `.${className}`,\n declarations: smDeclarations,\n });\n }\n if (darkDeclarations.length > 0) {\n state.darkRules.push({\n selector: `.${className}`,\n declarations: darkDeclarations,\n });\n }\n return { inline, className };\n};\n\n/**\n * Inline anchors are deliberately a narrower surface than block primitives:\n * classes resolve through the exact same safe registry, but source IDs stay on\n * their block owner so the canvas does not expose links as independent nodes.\n *\n * A link always has a concrete color and decoration. That prevents each email\n * client from choosing its browser-default blue/underline treatment; authored\n * classes follow these defaults and therefore win by normal CSS last-wins.\n */\nconst resolveInlineLinkStyles = (state: CompileState, node: ElementNode): NodeStyles =>\n resolveNodeStyles(state, node, {\n inline: [\n { property: \"color\", value: state.theme.colors.brand! },\n { property: \"text-decoration\", value: \"underline\" },\n ],\n dark:\n state.theme.colorsDark.brand === undefined\n ? []\n : [{ property: \"color\", value: state.theme.colorsDark.brand }],\n });\n\nconst nodeAttrs = (\n state: CompileState,\n node: ElementNode,\n styles: NodeStyles,\n extra: Array<readonly [string, string]> = [],\n options?: { readonly extraClassName?: string | undefined },\n): Array<readonly [string, string]> => {\n const attrs: Array<readonly [string, string]> = state.stripInstrumentation\n ? [...extra]\n : [[\"data-sml-id\", node.id], ...extra];\n const classNames = [styles.className, options?.extraClassName].filter(\n (className): className is string => className !== null && className !== undefined,\n );\n if (classNames.length > 0) attrs.push([\"class\", classNames.join(\" \")]);\n return attrs;\n};\n\nconst isPaddingProperty = (property: string): boolean =>\n property === \"padding\" || property.startsWith(\"padding-\");\n\nconst splitPadding = (\n declarations: ReadonlyArray<Declaration>,\n): { padding: Array<Declaration>; rest: Array<Declaration> } => {\n const padding: Array<Declaration> = [];\n const rest: Array<Declaration> = [];\n for (const declaration of declarations) {\n (isPaddingProperty(declaration.property) ? padding : rest).push(declaration);\n }\n return { padding, rest };\n};\n\n// ── Background image (Section/Container) ─────────────────────────────────────\n// A background URL rides an attribute, not a class. The compiler emits\n// email-safe CSS — an escaped `url(...)`, position/size (defaults center/cover),\n// no-repeat — plus, on the outlook-safe base, the legacy `background` cell\n// attribute (honored by more clients than CSS alone). The URL is\n// attacker-adjacent input, so escapeCssString neutralizes a `'`/`\\`/`</style>`\n// breakout inside the url() string before the HTML serializer escapes the style\n// attribute around it. No VML `<v:rect>` fallback: a fill needs pixel width AND\n// height, but a Section's height is content-driven and unknown at compile time,\n// so Outlook Windows degrades to its background-color and checks warns via\n// css-background-image.\nconst BACKGROUND_POSITION_DEFAULT = \"center\";\nconst BACKGROUND_SIZE_DEFAULT = \"cover\";\n\nconst backgroundImageUrl = (node: ElementNode): string | undefined => {\n const url = stringAttribute(node, \"backgroundImage\");\n return url === undefined || url === \"\" ? undefined : url;\n};\n\nconst backgroundImageStyle = (node: ElementNode): Array<Declaration> => {\n const url = backgroundImageUrl(node);\n if (url === undefined) return [];\n return [\n { property: \"background-image\", value: `url('${escapeCssString(url)}')` },\n {\n property: \"background-position\",\n value: stringAttribute(node, \"backgroundPosition\") ?? BACKGROUND_POSITION_DEFAULT,\n },\n {\n property: \"background-size\",\n value: stringAttribute(node, \"backgroundSize\") ?? BACKGROUND_SIZE_DEFAULT,\n },\n { property: \"background-repeat\", value: \"no-repeat\" },\n ];\n};\n\n// ── Outlook button hack (ported from react-email button/utils) ───────────────\n\nconst MAX_FONT_WIDTH = 5;\n\nconst computeFontWidthAndSpaceCount = (expectedWidth: number): readonly [number, number] => {\n if (expectedWidth === 0) return [0, 0];\n let spaceCount = 0;\n const requiredFontWidth = (): number =>\n spaceCount > 0 ? expectedWidth / spaceCount / 2 : Number.POSITIVE_INFINITY;\n while (requiredFontWidth() > MAX_FONT_WIDTH) spaceCount++;\n return [requiredFontWidth(), spaceCount];\n};\n\nconst pxToPt = (px: number): number => (px * 3) / 4;\n\nconst paddingPx = (declarations: ReadonlyArray<Declaration>) => {\n const padding = { top: 0, right: 0, bottom: 0, left: 0 };\n const parse = (value: string): number => {\n const match = /^([\\d.]+)px$/.exec(value);\n return match === null ? 0 : Number.parseFloat(match[1]!);\n };\n for (const declaration of declarations) {\n switch (declaration.property) {\n case \"padding\": {\n const all = parse(declaration.value);\n padding.top = padding.right = padding.bottom = padding.left = all;\n break;\n }\n case \"padding-top\":\n padding.top = parse(declaration.value);\n break;\n case \"padding-right\":\n padding.right = parse(declaration.value);\n break;\n case \"padding-bottom\":\n padding.bottom = parse(declaration.value);\n break;\n case \"padding-left\":\n padding.left = parse(declaration.value);\n break;\n default:\n break;\n }\n }\n return padding;\n};\n\n// ── Preview (ported from react-email preview) ────────────────────────────────\n\nconst PREVIEW_MAX_LENGTH = 200;\nconst PREVIEW_WHITESPACE = \"\\xa0\\u200C\\u200B\\u200D\\u200E\\u200F\\uFEFF\";\n\n// ── Primitive expansion ───────────────────────────────────────────────────────\n\nconst TABLE_RESET: Array<readonly [string, string]> = [\n [\"border\", \"0\"],\n [\"cellpadding\", \"0\"],\n [\"cellspacing\", \"0\"],\n [\"role\", \"presentation\"],\n];\n\nconst expandInline = (state: CompileState, nodes: ReadonlyArray<SmlNode>): Array<HtmlNode> =>\n nodes.map((node) => {\n if (node.kind === \"text\") return text(node.value);\n if (node.kind === \"interpolation\") {\n return text(node.syntax === \"curly\" ? `{{${node.name}}}` : `\\${${node.name}}`);\n }\n if (node.tag === \"a\") {\n const styles = resolveInlineLinkStyles(state, node);\n const attrs: Array<readonly [string, string]> = [\n [\"href\", stringAttribute(node, \"href\") ?? \"\"],\n [\"target\", \"_blank\"],\n ];\n if (styles.className !== null) attrs.push([\"class\", styles.className]);\n return element(\"a\", attrs, styles.inline, expandInline(state, node.children));\n }\n return element(node.tag, [], [], expandInline(state, node.children));\n });\n\nconst expandBlock = (state: CompileState, node: ElementNode): HtmlNode => {\n const styles = resolveNodeStyles(state, node);\n switch (node.tag) {\n case \"Container\":\n case \"Section\": {\n const { padding, rest } = splitPadding(styles.inline);\n const bgStyle = backgroundImageStyle(node);\n const bgUrl = backgroundImageUrl(node);\n const children = node.children.flatMap((child) =>\n child.kind === \"element\" ? [expandBlock(state, child)] : [],\n );\n if (state.base === \"modern\") {\n const style: Array<Declaration> =\n node.tag === \"Container\"\n ? [\n { property: \"max-width\", value: `${state.emailWidth}px` },\n { property: \"margin-left\", value: \"auto\" },\n { property: \"margin-right\", value: \"auto\" },\n ]\n : [];\n // Background image sits after `rest` so a class-authored bg-color reads\n // as its load-failure fallback on the same element (CSS last-wins keeps\n // padding trailing).\n return element(\n \"div\",\n nodeAttrs(state, node, styles),\n [...style, ...rest, ...bgStyle, ...padding],\n children,\n );\n }\n const tableStyle: Array<Declaration> =\n node.tag === \"Container\"\n ? [{ property: \"max-width\", value: `${state.emailWidth}px` }, ...rest]\n : rest;\n // The background rides the inner td (where content sits): the CSS declarations\n // for modern clients and the legacy `background` attribute for the rest.\n const tdAttrs: Array<readonly [string, string]> =\n bgUrl === undefined ? [] : [[\"background\", bgUrl]];\n return element(\n \"table\",\n nodeAttrs(state, node, styles, [[\"align\", \"center\"], [\"width\", \"100%\"], ...TABLE_RESET]),\n tableStyle,\n [\n element(\n \"tbody\",\n [],\n [],\n [\n element(\n \"tr\",\n [],\n [{ property: \"width\", value: \"100%\" }],\n [element(\"td\", tdAttrs, [...bgStyle, ...padding], children)],\n ),\n ],\n ),\n ],\n );\n }\n case \"Row\": {\n const columns = node.children.flatMap((child) =>\n child.kind === \"element\" ? [expandBlock(state, child)] : [],\n );\n const { padding, rest } = splitPadding(styles.inline);\n const rowTable = (tableStyle: Array<Declaration>) =>\n element(\n \"table\",\n nodeAttrs(state, node, styles, [[\"align\", \"center\"], [\"width\", \"100%\"], ...TABLE_RESET]),\n tableStyle,\n [\n element(\n \"tbody\",\n [],\n [{ property: \"width\", value: \"100%\" }],\n [element(\"tr\", [], [{ property: \"width\", value: \"100%\" }], columns)],\n ),\n ],\n );\n // Modern clients honor padding on the table; Outlook drops it, so the\n // outlook-safe base carries it on a wrapping td like Container/Section.\n // Non-padding styles stay on the INNER table with nodeAttrs — sm:/dark:\n // head rules target the className there, and an override only wins when\n // it lives on the same element as the inline style it must beat.\n if (state.base === \"modern\" || padding.length === 0) {\n return rowTable(styles.inline);\n }\n return element(\n \"table\",\n [[\"align\", \"center\"], [\"width\", \"100%\"], ...TABLE_RESET],\n [],\n [\n element(\n \"tbody\",\n [],\n [],\n [\n element(\n \"tr\",\n [],\n [{ property: \"width\", value: \"100%\" }],\n [element(\"td\", [], padding, [rowTable(rest)])],\n ),\n ],\n ),\n ],\n );\n }\n case \"Column\": {\n const width = stringAttribute(node, \"width\");\n const valign = stringAttribute(node, \"valign\");\n const children = node.children.flatMap((child) =>\n child.kind === \"element\" ? [expandBlock(state, child)] : [],\n );\n // valign rides both the HTML attribute (Outlook honors the attribute,\n // not the CSS) and vertical-align (modern clients), on both bases.\n return element(\n \"td\",\n nodeAttrs(state, node, styles, [\n ...(width === undefined ? [] : [[\"width\", width] as const]),\n ...(valign === undefined ? [] : [[\"valign\", valign] as const]),\n ]),\n [\n ...(valign === undefined ? [] : [{ property: \"vertical-align\", value: valign }]),\n ...styles.inline,\n ],\n children,\n );\n }\n case \"Heading\": {\n const level = numberAttribute(node, \"level\") ?? 1;\n return element(\n `h${level}`,\n nodeAttrs(state, node, styles),\n styles.inline,\n expandInline(state, node.children),\n );\n }\n case \"Text\": {\n const defaults: Array<Declaration> = [\n { property: \"font-size\", value: \"14px\" },\n { property: \"line-height\", value: \"24px\" },\n { property: \"margin-top\", value: \"16px\" },\n { property: \"margin-bottom\", value: \"16px\" },\n ];\n return element(\n \"p\",\n nodeAttrs(state, node, styles),\n [...defaults, ...styles.inline],\n expandInline(state, node.children),\n );\n }\n case \"Button\":\n return expandButton(state, node, styles);\n case \"List\": {\n // Semantic <ul>/<ol> — supported everywhere, and lighter than table-based\n // lists. Spacing matches <Text> (16px block margins); the explicit\n // padding-left carries the bullet indent in clients that honor it, while\n // Outlook for Windows substitutes its own default indent (degrades to a\n // slightly wider gutter, never breaks).\n const tag = stringAttribute(node, \"type\") === \"ordered\" ? \"ol\" : \"ul\";\n const defaults: Array<Declaration> = [\n { property: \"margin-top\", value: \"16px\" },\n { property: \"margin-bottom\", value: \"16px\" },\n { property: \"padding-left\", value: \"24px\" },\n ];\n const items = node.children.flatMap((child) =>\n child.kind === \"element\" ? [expandBlock(state, child)] : [],\n );\n return element(tag, nodeAttrs(state, node, styles), [...defaults, ...styles.inline], items);\n }\n case \"ListItem\": {\n // Type/leading match <Text>; the small bottom margin separates items\n // without <Spacer> ceremony (margins on <li> hold up in Outlook).\n const defaults: Array<Declaration> = [\n { property: \"font-size\", value: \"14px\" },\n { property: \"line-height\", value: \"24px\" },\n { property: \"margin-top\", value: \"0px\" },\n { property: \"margin-bottom\", value: \"8px\" },\n ];\n return element(\n \"li\",\n nodeAttrs(state, node, styles),\n [...defaults, ...styles.inline],\n expandInline(state, node.children),\n );\n }\n case \"If\": {\n // Both branches survive into the artifact behind comment markers keyed\n // by the stable node id (unique per document, so nesting resolves by\n // exact-id matching). If renders no element of its own — the markers ARE\n // the node; resolveArtifactConditionals selects a branch at send time.\n const test = stringAttribute(node, \"test\") ?? \"\";\n const elements = node.children.filter(\n (child): child is ElementNode => child.kind === \"element\",\n );\n const elseNode = elements.find((child) => child.tag === \"Else\");\n const children: Array<HtmlNode> = [raw(`<!--sml:if:${node.id} ${test}-->`)];\n for (const child of elements) {\n if (child.tag !== \"Else\") children.push(expandBlock(state, child));\n }\n if (elseNode !== undefined) {\n children.push(raw(`<!--sml:else:${node.id}-->`));\n for (const child of elseNode.children) {\n if (child.kind === \"element\") children.push(expandBlock(state, child));\n }\n }\n children.push(raw(`<!--sml:endif:${node.id}-->`));\n return { kind: \"fragment\", children };\n }\n case \"Image\": {\n const width = numberAttribute(node, \"width\");\n const height = numberAttribute(node, \"height\");\n const src = stringAttribute(node, \"src\") ?? \"\";\n const alt = stringAttribute(node, \"alt\") ?? \"\";\n const darkSource = node.children.find(\n (child): child is ElementNode =>\n child.kind === \"element\" &&\n child.tag === \"Source\" &&\n stringAttribute(child, \"scheme\") === \"dark\",\n );\n const darkImageSource =\n darkSource === undefined ? undefined : stringAttribute(darkSource, \"src\");\n const attrs = nodeAttrs(state, node, styles, [\n [\"alt\", alt],\n [\"src\", src],\n ...(width === undefined ? [] : [[\"width\", String(width)] as const]),\n ...(height === undefined ? [] : [[\"height\", String(height)] as const]),\n ]);\n const reset: Array<Declaration> = [\n { property: \"display\", value: \"block\" },\n { property: \"outline\", value: \"none\" },\n { property: \"border\", value: \"none\" },\n { property: \"text-decoration\", value: \"none\" },\n ];\n if (darkImageSource !== undefined) {\n const lightClassName = `sml-${node.id}-theme-light`;\n const darkClassName = `sml-${node.id}-theme-dark`;\n state.darkRules.push(\n {\n selector: state.stripInstrumentation\n ? `.${lightClassName}`\n : `[data-sml-id=\"${node.id}\"][data-sml-theme=\"light\"]`,\n declarations: [{ property: \"display\", value: \"none\" }],\n },\n {\n selector: state.stripInstrumentation\n ? `.${darkClassName}`\n : `[data-sml-id=\"${node.id}\"][data-sml-theme=\"dark\"]`,\n declarations: [{ property: \"display\", value: \"block\" }],\n },\n );\n const themedAttrs = (theme: \"light\" | \"dark\", imageSrc: string) => {\n const themeClassName = theme === \"light\" ? lightClassName : darkClassName;\n return nodeAttrs(\n state,\n node,\n styles,\n [\n ...(state.stripInstrumentation ? [] : [[\"data-sml-theme\", theme] as const]),\n [\"alt\", alt],\n [\"src\", imageSrc],\n ...(width === undefined ? [] : [[\"width\", String(width)] as const]),\n ...(height === undefined ? [] : [[\"height\", String(height)] as const]),\n ],\n state.stripInstrumentation ? { extraClassName: themeClassName } : undefined,\n );\n };\n return {\n kind: \"fragment\",\n children: [\n element(\"img\", themedAttrs(\"light\", src), [...reset, ...styles.inline], [], {\n void: true,\n }),\n element(\n \"img\",\n themedAttrs(\"dark\", darkImageSource),\n [{ property: \"display\", value: \"none\" }, ...reset.slice(1), ...styles.inline],\n [],\n { void: true },\n ),\n ],\n };\n }\n return element(\"img\", attrs, [...reset, ...styles.inline], [], { void: true });\n }\n case \"Divider\": {\n const defaults: Array<Declaration> = [\n { property: \"width\", value: \"100%\" },\n { property: \"border\", value: \"none\" },\n { property: \"border-top\", value: \"1px solid #eaeaea\" },\n ];\n return element(\"hr\", nodeAttrs(state, node, styles), [...defaults, ...styles.inline], [], {\n void: true,\n });\n }\n case \"Spacer\": {\n const height = numberAttribute(node, \"height\") ?? 0;\n return element(\n \"div\",\n nodeAttrs(state, node, styles),\n [\n { property: \"height\", value: `${height}px` },\n { property: \"line-height\", value: `${height}px` },\n { property: \"font-size\", value: \"1px\" },\n ],\n [raw(\"&#8202;\")],\n );\n }\n default:\n // Email/envelope metadata handled by the document expander; inline tags cannot be\n // block children (parser content model). Fail loudly if it ever happens.\n state.errors.push({\n code: \"internal-unexpected-node\",\n message: `unexpected <${node.tag}> during expansion`,\n span: node.span,\n });\n return raw(\"\");\n }\n};\n\nconst expandButton = (state: CompileState, node: ElementNode, styles: NodeStyles): HtmlNode => {\n const href = stringAttribute(node, \"href\") ?? \"\";\n const label = expandInline(state, node.children);\n const baseStyle: Array<Declaration> = [\n { property: \"line-height\", value: \"100%\" },\n { property: \"text-decoration\", value: \"none\" },\n { property: \"display\", value: \"inline-block\" },\n { property: \"max-width\", value: \"100%\" },\n { property: \"mso-padding-alt\", value: \"0px\" },\n ];\n const attrs = nodeAttrs(state, node, styles, [\n [\"href\", href],\n [\"target\", \"_blank\"],\n ]);\n\n if (state.base === \"modern\") {\n return element(\"a\", attrs, [...baseStyle, ...styles.inline], label);\n }\n\n const padding = paddingPx(styles.inline);\n const verticalPadding = padding.top + padding.bottom;\n const textRaise = pxToPt(verticalPadding);\n const [leftFontWidth, leftSpaceCount] = computeFontWidthAndSpaceCount(padding.left);\n const [rightFontWidth, rightSpaceCount] = computeFontWidthAndSpaceCount(padding.right);\n\n const innerSpanStyle: Array<Declaration> = [\n { property: \"max-width\", value: \"100%\" },\n { property: \"display\", value: \"inline-block\" },\n { property: \"line-height\", value: \"120%\" },\n { property: \"mso-padding-alt\", value: \"0px\" },\n ...(padding.bottom > 0\n ? // Unitless like the mso conditional — Word's engine reads unitless as pt.\n [{ property: \"mso-text-raise\", value: `${pxToPt(padding.bottom)}` }]\n : []),\n ];\n\n return element(\n \"a\",\n attrs,\n [...baseStyle, ...styles.inline],\n [\n element(\n \"span\",\n [],\n [],\n [\n raw(\n `<!--[if mso]><i style=\"mso-font-width:${leftFontWidth * 100}%;mso-text-raise:${textRaise}\" hidden>${\"&#8202;\".repeat(leftSpaceCount)}</i><![endif]-->`,\n ),\n ],\n ),\n element(\"span\", [], innerSpanStyle, label),\n element(\n \"span\",\n [],\n [],\n [\n raw(\n `<!--[if mso]><i style=\"mso-font-width:${rightFontWidth * 100}%\" hidden>${\"&#8202;\".repeat(rightSpaceCount)}&#8203;</i><![endif]-->`,\n ),\n ],\n ),\n ],\n );\n};\n\nconst expandPreview = (state: CompileState, node: ElementNode): HtmlNode => {\n let previewText = scalarElementText(node);\n let literalLength = 0;\n for (const child of node.children) {\n if (child.kind === \"text\") {\n literalLength += child.value.length;\n }\n }\n previewText = previewText.substring(0, PREVIEW_MAX_LENGTH);\n // Pad against the shortest possible rendered length: interpolations may\n // resolve to \"\" at send, so only literal text is guaranteed to occupy the\n // preview pane. Overshooting the pad is harmless — the run is hidden.\n const guaranteedLength = Math.min(literalLength, PREVIEW_MAX_LENGTH);\n const children: Array<HtmlNode> = [text(previewText)];\n if (guaranteedLength < PREVIEW_MAX_LENGTH) {\n children.push(\n element(\n \"div\",\n [],\n [],\n [text(PREVIEW_WHITESPACE.repeat(PREVIEW_MAX_LENGTH - guaranteedLength))],\n ),\n );\n }\n return element(\n \"div\",\n [\n ...(state.stripInstrumentation ? [] : [[\"data-sml-id\", node.id] as const]),\n [\"data-skip-in-text\", \"true\"],\n ],\n [\n { property: \"display\", value: \"none\" },\n { property: \"overflow\", value: \"hidden\" },\n { property: \"line-height\", value: \"1px\" },\n { property: \"opacity\", value: \"0\" },\n { property: \"max-height\", value: \"0\" },\n { property: \"max-width\", value: \"0\" },\n ],\n children,\n );\n};\n\n// ── Head <style> ──────────────────────────────────────────────────────────────\n\nconst serializeRules = (\n rules: ReadonlyArray<HeadRule>,\n indent: string,\n important: boolean,\n): string =>\n rules\n .map(\n (rule) =>\n `${indent}${rule.selector}{${rule.declarations\n .map((d) => `${d.property}:${d.value}${important ? \" !important\" : \"\"}`)\n .join(\";\")}}`,\n )\n .join(\"\\n\");\n\nconst isBackgroundProperty = (property: string): boolean => property.startsWith(\"background\");\n\nconst buildHeadStyle = (state: CompileState): string => {\n // Dark-mode hygiene: always declare color-scheme, even with zero dark:\n // classes, so honoring clients treat the email as scheme-aware.\n const parts: Array<string> = [\n \":root{color-scheme:light dark;supported-color-schemes:light dark}\",\n ];\n if (state.smRules.length > 0) {\n // sm: is progressive enhancement only — the inline baseline must already be\n // complete. !important beats the inline styles it adjusts.\n parts.push(`@media (min-width:640px){\\n${serializeRules(state.smRules, \" \", true)}\\n}`);\n }\n if (state.darkRules.length > 0) {\n parts.push(\n `@media (prefers-color-scheme:dark){\\n${serializeRules(state.darkRules, \" \", true)}\\n}`,\n );\n // Outlook.com ignores prefers-color-scheme and instead sets data-ogsc\n // (color) / data-ogsb (background) attributes — duplicate the rules.\n const ogscRules = state.darkRules.flatMap((rule) => {\n const declarations = rule.declarations.filter((d) => !isBackgroundProperty(d.property));\n return declarations.length > 0\n ? [{ selector: `[data-ogsc] ${rule.selector}`, declarations }]\n : [];\n });\n const ogsbRules = state.darkRules.flatMap((rule) => {\n const declarations = rule.declarations.filter((d) => isBackgroundProperty(d.property));\n return declarations.length > 0\n ? [{ selector: `[data-ogsb] ${rule.selector}`, declarations }]\n : [];\n });\n if (ogscRules.length > 0) parts.push(serializeRules(ogscRules, \"\", true));\n if (ogsbRules.length > 0) parts.push(serializeRules(ogsbRules, \"\", true));\n }\n return parts.join(\"\\n\");\n};\n\n/**\n * The @font-face block for self-hosted webfont tokens, emitted into a SEPARATE\n * <style> that is hidden from Outlook for Windows.\n *\n * Outlook-Windows must not SEE these rules: it would register an @font-face whose\n * family matches a used font, fail to load the distant src, and render the text in\n * Times New Roman — actively ignoring the inline font-family stack. Hiding the\n * block behind a downlevel-hidden conditional comment makes Outlook treat the font\n * as merely missing, so it falls through the inline stack to its generic instead.\n * (The head's OTHER <style> stays visible — it carries the Outlook.com [data-ogsc]\n * dark rules — so only this sibling is wrapped.) Empty when no token self-hosts.\n */\n// A `unicode-range` value is a token list, NOT a quoted string, so escaping is\n// meaningless for it. It is emitted unquoted into the rule (and thus into the\n// surrounding <style>), so a value carrying `}` or `</style>` would break out of\n// the rule or the style element and inject HTML into the delivered email. It\n// flows verbatim from the Google css2 API through storage with only\n// `Schema.optional(Schema.String)` validation, so validate the grammar at the\n// emit boundary and fail closed: emit the descriptor only when the value is a\n// comma-separated list of `U+`-prefixed codepoint tokens, else omit it entirely\n// (the face still loads, just without range-narrowing).\nconst UNICODE_RANGE = /^U\\+[0-9A-Fa-f?]{1,6}(-[0-9A-Fa-f]{1,6})?$/;\nconst isUnicodeRange = (value: string): boolean =>\n value.split(\",\").every((token) => UNICODE_RANGE.test(token.trim()) && token.trim() !== \"\");\n\nconst buildFontFaceCss = (state: CompileState): string => {\n const rules = Object.entries(state.theme.fontFaces)\n .flatMap(([name, set]) => {\n // Only emit faces still referenced by the resolved inline stack. Layering\n // merges fontFamilies and fontFaces independently, so overriding only\n // fontFamilies.<name> (a different family, no matching face override)\n // would otherwise leave the prior layer's face set orphaned — a dead\n // @font-face for a family the stack no longer leads with. Cross-checking\n // the leading family keeps the two namespaces structurally in sync.\n // The resolved inline stack leads with the escaped, single-quoted family\n // (flattenFontTokens), so compare against the escaped family too — a legit\n // quoted family still matches its own stack.\n const family = escapeCssString(set.family);\n const stack = state.theme.fontFamilies[name];\n if (stack === undefined || !stack.startsWith(`'${family}',`)) return [];\n return set.faces.map((face) => {\n const range =\n face.unicodeRange && isUnicodeRange(face.unicodeRange)\n ? `unicode-range:${face.unicodeRange};`\n : \"\";\n return `@font-face{font-family:'${family}';font-style:${face.style};font-weight:${face.weight};font-display:swap;src:url('${escapeCssString(face.src)}') format('woff2');${range}}`;\n });\n })\n .join(\"\");\n return rules;\n};\n\nconst buildFontFaceStyle = (css: string): string =>\n css === \"\" ? \"\" : `<!--[if !mso]><!--><style>${css}</style><!--<![endif]-->`;\n\n// ── Plain-text alternative ────────────────────────────────────────────────────\n\nconst inlinePlainText = (nodes: ReadonlyArray<SmlNode>): string => {\n let result = \"\";\n for (const node of nodes) {\n if (node.kind === \"text\") result += node.value;\n else if (node.kind === \"interpolation\") {\n result += node.syntax === \"curly\" ? `{{${node.name}}}` : `\\${${node.name}}`;\n } else if (node.kind === \"element\" && node.tag === \"a\") {\n const label = inlinePlainText(node.children);\n const href = stringAttribute(node, \"href\") ?? \"\";\n result += href === \"\" ? label : `${label} (${href})`;\n } else if (node.kind === \"element\") {\n result += inlinePlainText(node.children);\n }\n }\n return result;\n};\n\nconst blockPlainText = (node: ElementNode, blocks: Array<string>): void => {\n switch (node.tag) {\n case \"Subject\":\n case \"Preview\":\n return; // data-skip-in-text\n case \"Heading\":\n case \"Text\": {\n const value = inlinePlainText(node.children).trim();\n if (value.length > 0) blocks.push(value);\n return;\n }\n case \"Button\": {\n const label = inlinePlainText(node.children).trim();\n const href = stringAttribute(node, \"href\") ?? \"\";\n blocks.push(href === \"\" ? label : `${label} (${href})`);\n return;\n }\n case \"Image\": {\n const alt = stringAttribute(node, \"alt\");\n if (alt !== undefined && alt.length > 0) blocks.push(alt);\n return;\n }\n case \"Divider\":\n case \"Spacer\":\n return;\n case \"List\": {\n // One block, one line per item: \"- item\" (unordered) / \"1. item\" (ordered).\n // Ordinals are POSITIONAL (every ListItem counts, empty or not) so they\n // match the HTML artifact, which emits every <li> — an item emptied by a\n // send-time conditional must not renumber the items after it.\n const ordered = stringAttribute(node, \"type\") === \"ordered\";\n const lines: Array<string> = [];\n let position = 0;\n for (const child of node.children) {\n if (child.kind !== \"element\" || child.tag !== \"ListItem\") continue;\n position += 1;\n const value = inlinePlainText(child.children).trim();\n if (value.length === 0) continue;\n lines.push(ordered ? `${position}. ${value}` : `- ${value}`);\n }\n if (lines.length > 0) blocks.push(lines.join(\"\\n\"));\n return;\n }\n case \"If\": {\n // Same markers as the HTML artifact, as standalone blocks, so\n // resolveArtifactConditionals works identically on the plain-text alt.\n const test = stringAttribute(node, \"test\") ?? \"\";\n const elements = node.children.filter(\n (child): child is ElementNode => child.kind === \"element\",\n );\n const elseNode = elements.find((child) => child.tag === \"Else\");\n blocks.push(`<!--sml:if:${node.id} ${test}-->`);\n for (const child of elements) {\n if (child.tag !== \"Else\") blockPlainText(child, blocks);\n }\n if (elseNode !== undefined) {\n blocks.push(`<!--sml:else:${node.id}-->`);\n for (const child of elseNode.children) {\n if (child.kind === \"element\") blockPlainText(child, blocks);\n }\n }\n blocks.push(`<!--sml:endif:${node.id}-->`);\n return;\n }\n default: {\n for (const child of node.children) {\n if (child.kind === \"element\") blockPlainText(child, blocks);\n }\n }\n }\n};\n\n// ── Document assembly ─────────────────────────────────────────────────────────\n\nconst XHTML_DOCTYPE =\n '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">';\n\n/** Compile a parsed document; the root tag routes to its channel backend. */\nexport const compileSml = (\n document: SmlDocument,\n options?: CompileOptions,\n): Result.Result<CompiledTemplate, SmlCompileError> => {\n switch (document.root.tag) {\n case \"Sms\":\n return Result.succeed(compileSmsRoot(document.root));\n case \"WhatsApp\":\n return compileWhatsAppRoot(document.root);\n default:\n return compileEmailSml(document, options);\n }\n};\n\nexport const compileSmlArtifact = (\n document: SmlDocument,\n options?: CompileOptions,\n): Result.Result<CompiledTemplateArtifact, SmlCompileError> => {\n switch (document.root.tag) {\n case \"Sms\":\n return Result.succeed(compileSmsRoot(document.root));\n case \"WhatsApp\":\n return compileWhatsAppRoot(document.root);\n default:\n return compileEmailSmlArtifact(document, options);\n }\n};\n\n/**\n * Drop `hidden=\"true\"` elements (and their subtrees) before expansion, so a\n * hidden node leaves the artifact exactly as if it were never authored — no\n * stray whitespace, and the surviving nodes keep their positional ids (hiding\n * one node never renumbers the rest). The root itself is never filtered.\n */\nconst stripHiddenNodes = (node: ElementNode): ElementNode => ({\n ...node,\n children: node.children.flatMap((child): SmlNode[] => {\n if (child.kind !== \"element\") return [child];\n return isHiddenNode(child) ? [] : [stripHiddenNodes(child)];\n }),\n});\n\nconst addNodeId = (\n map: Record<string, Array<HtmlElement>>,\n id: string,\n elementNode: HtmlElement,\n): void => {\n const current = map[id];\n if (current === undefined) map[id] = [elementNode];\n else current.push(elementNode);\n};\n\nconst collectNodeIdMap = (node: HtmlNode, map: Record<string, Array<HtmlElement>>): void => {\n if (node.kind === \"fragment\") {\n for (const child of node.children) collectNodeIdMap(child, map);\n return;\n }\n if (node.kind !== \"element\") return;\n const id = node.attrs.find(([name]) => name === \"data-sml-id\")?.[1];\n if (id !== undefined) addNodeId(map, id, node);\n for (const child of node.children) collectNodeIdMap(child, map);\n};\n\nconst createNodeIdMap = (tree: HtmlNode): HtmlNodeIdMap => {\n const map: Record<string, Array<HtmlElement>> = {};\n collectNodeIdMap(tree, map);\n return map;\n};\n\nconst compiledEmailFromArtifact = (artifact: CompiledEmailArtifact): CompiledEmail => ({\n channel: artifact.channel,\n html: artifact.html,\n text: artifact.text,\n subjectText: artifact.subjectText,\n warnings: artifact.warnings,\n base: artifact.base,\n compilerVersion: artifact.compilerVersion,\n});\n\nconst scalarElementText = (node: ElementNode): string => {\n let result = \"\";\n for (const child of node.children) {\n if (child.kind === \"text\") result += child.value;\n else if (child.kind === \"interpolation\") {\n result += child.syntax === \"curly\" ? `{{${child.name}}}` : `\\${${child.name}}`;\n }\n }\n return result;\n};\n\nconst compileEmailSml = (\n inputDocument: SmlDocument,\n options?: CompileOptions,\n): Result.Result<CompiledEmail, SmlCompileError> => {\n const artifact = compileEmailSmlArtifact(inputDocument, options);\n if (Result.isFailure(artifact)) return Result.fail(artifact.failure);\n return Result.succeed(compiledEmailFromArtifact(artifact.success));\n};\n\nconst compileEmailSmlArtifact = (\n inputDocument: SmlDocument,\n options?: CompileOptions,\n): Result.Result<CompiledEmailArtifact, SmlCompileError> => {\n const document: SmlDocument = {\n ...inputDocument,\n root: stripHiddenNodes(inputDocument.root),\n };\n const base = options?.base ?? \"outlook-safe\";\n const emailWidth = numberAttribute(document.root, \"width\") ?? DEFAULT_EMAIL_WIDTH;\n const theme = resolveTokenTable({\n base: options?.theme?.base ?? baseTokens,\n overrides: options?.theme?.overrides,\n });\n const state: CompileState = {\n base,\n emailWidth,\n theme,\n stripInstrumentation: options?.stripInstrumentation ?? false,\n errors: [],\n warnings: [],\n smRules: [],\n darkRules: [],\n };\n\n const root = document.root;\n const rootStyles = resolveNodeStyles(state, root);\n const lang = stringAttribute(root, \"lang\") ?? \"en\";\n const dir = stringAttribute(root, \"dir\") ?? \"ltr\";\n\n const preview = root.children.find(\n (child): child is ElementNode => child.kind === \"element\" && child.tag === \"Preview\",\n );\n const subject = root.children.find(\n (child): child is ElementNode => child.kind === \"element\" && child.tag === \"Subject\",\n );\n const blocks = root.children.flatMap((child) =>\n child.kind === \"element\" && child.tag !== \"Preview\" && child.tag !== \"Subject\"\n ? [expandBlock(state, child)]\n : [],\n );\n\n if (state.errors.length > 0) {\n return Result.fail(new SmlCompileError({ diagnostics: state.errors }));\n }\n\n // Yahoo/AOL strip <body> styles: body keeps only background + zeroed margin,\n // the inner td carries the real styles.\n const bodyBackground = rootStyles.inline.filter((d) => isBackgroundProperty(d.property));\n const bodyChildren: Array<HtmlNode> = [];\n if (preview !== undefined) bodyChildren.push(expandPreview(state, preview));\n bodyChildren.push(\n element(\n \"table\",\n [[\"align\", \"center\"], [\"width\", \"100%\"], ...TABLE_RESET],\n [],\n [\n element(\n \"tbody\",\n [],\n [],\n [\n element(\n \"tr\",\n [],\n [],\n [\n element(\n \"td\",\n [\n [\"dir\", dir],\n [\"lang\", lang],\n // The td carries the Email root's inline styles, so it must\n // also carry the className sm:/dark: head rules target —\n // an override only beats the inline style on its own element.\n ...(rootStyles.className !== null\n ? ([[\"class\", rootStyles.className]] as const)\n : []),\n ],\n [...rootStyles.inline],\n blocks,\n ),\n ],\n ),\n ],\n ),\n ],\n ),\n );\n\n const bodyAttrs: Array<readonly [string, string]> = [\n ...(state.stripInstrumentation ? [] : [[\"data-sml-id\", root.id] as const]),\n [\"dir\", dir],\n [\"lang\", lang],\n ];\n if (rootStyles.className !== null) bodyAttrs.push([\"class\", rootStyles.className]);\n const body = element(\n \"body\",\n bodyAttrs,\n [...bodyBackground, { property: \"margin\", value: \"0\" }],\n bodyChildren,\n );\n\n const headStyle = buildHeadStyle(state);\n const fontFaceCss = buildFontFaceCss(state);\n const fontFaceStyle = buildFontFaceStyle(fontFaceCss);\n const headAssets: CompileHeadAssets = {\n styles: [\n {\n kind: \"style\",\n css: headStyle,\n html: `<style>${headStyle}</style>`,\n outlookHidden: false,\n },\n ...(fontFaceCss === \"\"\n ? []\n : [\n {\n kind: \"style\" as const,\n css: fontFaceCss,\n html: fontFaceStyle,\n outlookHidden: true,\n },\n ]),\n ],\n links: [],\n };\n const head = element(\n \"head\",\n [],\n [],\n [\n element(\n \"meta\",\n [\n [\"content\", \"text/html; charset=UTF-8\"],\n [\"http-equiv\", \"Content-Type\"],\n ],\n [],\n [],\n { void: true },\n ),\n // Without this, WebKit mail clients lay out at a ~980px virtual viewport\n // and the sm: min-width rules fire unconditionally on phones.\n element(\n \"meta\",\n [\n [\"name\", \"viewport\"],\n [\"content\", \"width=device-width\"],\n ],\n [],\n [],\n { void: true },\n ),\n element(\"meta\", [[\"name\", \"x-apple-disable-message-reformatting\"]], [], [], { void: true }),\n element(\n \"meta\",\n [\n [\"name\", \"color-scheme\"],\n [\"content\", \"light dark\"],\n ],\n [],\n [],\n { void: true },\n ),\n element(\n \"meta\",\n [\n [\"name\", \"supported-color-schemes\"],\n [\"content\", \"light dark\"],\n ],\n [],\n [],\n {\n void: true,\n },\n ),\n element(\"style\", [], [], [raw(headStyle)]),\n // Self-hosted webfont @font-face, in its own Outlook-hidden <style> sibling.\n // Omitted entirely (not an empty node) when no token self-hosts, so the\n // system-font baseline stays byte-identical.\n ...(fontFaceStyle !== \"\" ? [raw(fontFaceStyle)] : []),\n ],\n );\n\n const htmlAttrs: Array<readonly [string, string]> =\n base === \"outlook-safe\"\n ? [\n [\"dir\", dir],\n [\"lang\", lang],\n [\"xmlns\", \"http://www.w3.org/1999/xhtml\"],\n [\"xmlns:o\", \"urn:schemas-microsoft-com:office:office\"],\n [\"xmlns:v\", \"urn:schemas-microsoft-com:vml\"],\n ]\n : [\n [\"dir\", dir],\n [\"lang\", lang],\n ];\n\n const doctype = base === \"outlook-safe\" ? XHTML_DOCTYPE : \"<!DOCTYPE html>\";\n const tree = element(\"html\", htmlAttrs, [], [head, body]);\n const html = `${doctype}\\n${serializeHtmlNode(tree)}\\n`;\n\n const textBlocks: Array<string> = [];\n for (const child of root.children) {\n if (child.kind === \"element\") blockPlainText(child, textBlocks);\n }\n\n return Result.succeed({\n channel: \"email\" as const,\n width: emailWidth,\n html,\n text: textBlocks.join(\"\\n\\n\"),\n subjectText: subject === undefined ? null : scalarElementText(subject),\n warnings: state.warnings,\n base,\n compilerVersion: COMPILER_VERSION,\n tree,\n doctype,\n headAssets,\n nodeIdMap: createNodeIdMap(tree),\n });\n};\n\nexport const compileSource = (\n source: string,\n options?: CompileOptions,\n): Result.Result<CompiledTemplate, SmlParseError | SmlCompileError> => {\n const parsed = parseSml(source);\n if (Result.isFailure(parsed)) return Result.fail(parsed.failure);\n return compileSml(parsed.success, options);\n};\n\nexport const compileSourceArtifact = (\n source: string,\n options?: CompileOptions,\n): Result.Result<CompiledTemplateArtifact, SmlParseError | SmlCompileError> => {\n const parsed = parseSml(source);\n if (Result.isFailure(parsed)) return Result.fail(parsed.failure);\n return compileSmlArtifact(parsed.success, options);\n};\n"],"mappings":";;;;;;;;;;;;AA2IA,MAAM,WACJ,KACA,OACA,OACA,UACA,aACiB;CACjB,MAAM;CACN;CACA;CACA;CACA;CACA,MAAM,SAAS,SAAS,OAAO,OAAO,KAAA;AACxC;AAEA,MAAM,QAAQ,WAA6B;CAAE,MAAM;CAAQ;AAAM;AACjE,MAAM,OAAO,UAA4B;CAAE,MAAM;CAAO;AAAK;AAI7D,MAAM,mCAAmB,IAAI,IAAI;CAAC;CAAK;CAAQ;CAAU;CAAM;CAAK;AAAO,CAAC;AAE5E,MAAM,kBAAkB,UACtB,MAAM,WAAW,KAAK,OAAO,CAAC,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,WAAW,KAAK,MAAM;AAE/E,MAAM,uBAAuB,UAC3B,MAAM,WAAW,KAAK,OAAO,CAAC,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,WAAW,MAAK,QAAQ;AAEjF,MAAM,kBAAkB,UACtB,MAAM,KAAK,gBAAgB,GAAG,YAAY,SAAS,GAAG,YAAY,OAAO,CAAC,CAAC,KAAK,GAAG;AAErF,MAAM,oBAAoB,MAAmB,cAA+B;CAC1E,IAAI,SAAS,IAAI,KAAK;CACtB,KAAK,MAAM,CAAC,MAAM,UAAU,KAAK,OAC/B,UAAU,UAAU,KAAK,IAAI,KAAK,OAAO,IAAI,KAAK,IAAI,oBAAoB,KAAK,EAAE;CAEnF,IAAI,KAAK,MAAM,SAAS,GACtB,UAAU,WAAW,oBAAoB,eAAe,KAAK,KAAK,CAAC,EAAE;CACvE,OAAO,UAAU,YAAY,OAAO;AACtC;AAEA,MAAM,gBAAgB,SACpB,KAAK,SAAS,aAAa,QAAQ,KAAK,SAAS,aAAa,iBAAiB,IAAI,KAAK,GAAG;AAE7F,MAAa,qBAAqB,SAA2B;CAC3D,IAAI,KAAK,SAAS,QAAQ,OAAO,eAAe,KAAK,KAAK;CAC1D,IAAI,KAAK,SAAS,OAAO,OAAO,KAAK;CACrC,IAAI,KAAK,SAAS,YAAY,OAAO,KAAK,SAAS,IAAI,iBAAiB,CAAC,CAAC,KAAK,IAAI;CACnF,IAAI,KAAK,SAAS,QAAQ,KAAK,SAAS,WAAW,GAAG;EACpD,IAAI,KAAK,SAAS,MAAM,OAAO,iBAAiB,MAAM,IAAI;EAC1D,OAAO,GAAG,iBAAiB,MAAM,KAAK,EAAE,IAAI,KAAK,IAAI;CACvD;CACA,MAAM,gBAAgB,KAAK,SAAS,MAAM,YAAY;CACtD,MAAM,WAAW,KAAK,SAAS,IAAI,iBAAiB;CACpD,IAAI,eAAe,OAAO,GAAG,iBAAiB,MAAM,KAAK,IAAI,SAAS,KAAK,EAAE,EAAE,IAAI,KAAK,IAAI;CAC5F,OAAO,GAAG,iBAAiB,MAAM,KAAK,EAAE,IAAI,SAAS,KAAK,IAAI,EAAE,MAAM,KAAK,IAAI;AACjF;AAiCA,MAAM,sCAAsB,IAAI,IAAI;CAClC;CACA;CACA;AACF,CAAC;AAED,MAAM,mBAAmB,aACvB,SAAS,aAAa,MAAM,gBAAgB,oBAAoB,IAAI,YAAY,QAAQ,CAAC;AAE3F,MAAM,mBACJ,OACA,gBACuB;CACvB,MAAM,kBAAkB,YAAY,OAAO,eAAe;CAC1D,IAAI,gBAAgB,WAAW,GAC7B,OAAO,YAAY,SAAS,aAAa,CAAC,GAAG,SAAS,YAAY,CAAC;CAIrE,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,YAAY,iBACrB,KAAK,MAAM,eAAe,SAAS,cACjC,MAAM,IAAI,YAAY,UAAU,YAAY,KAAK;CAGrD,MAAM,YAAY,MAAM,IAAI,2BAA2B;CACvD,MAAM,OAAO,MAAM,IAAI,sBAAsB;CAC7C,MAAM,KAAK,MAAM,IAAI,oBAAoB;CACzC,MAAM,SAAS,gBAAgB;CAC/B,MAAM,UAAyB,CAAC;CAChC,IAAI,cAAc,KAAA,GAAW,QAAQ,KAAK,sCAAsC;CAChF,IAAI,SAAS,KAAA,GAAW,QAAQ,KAAK,8BAA8B;CACnE,IAAI,OAAO,KAAA,GAAW,QAAQ,KAAK,2BAA2B;CAC9D,IAAI,QAAQ,SAAS,GAAG;EACtB,MAAM,OAAO,KAAK;GAChB,MAAM;GACN,SAAS,6BAA6B,OAAO,IAAI,UAAU,QAAQ,KAAK,OAAO;GAC/E,MAAM,OAAO;EACf,CAAC;EACD,OAAO,YACJ,QAAQ,aAAa,CAAC,gBAAgB,QAAQ,CAAC,CAAC,CAChD,SAAS,aAAa,CAAC,GAAG,SAAS,YAAY,CAAC;CACrD;CAEA,MAAM,WAA+B,CAAC;EAAE,UAAU;EAAoB,OAAO;CAAM,CAAC;CACpF,IAAI,MAAM,SAAS,UACjB,SAAS,KAAK;EACZ,UAAU;EACV,OAAO,mBAAmB,UAAU,GAAG,KAAK,GAAG,GAAG;CACpD,CAAC;CAKH,MAAM,MAA0B,CAAC;CACjC,KAAK,MAAM,YAAY,aAAa;EAClC,IAAI,gBAAgB,QAAQ,GAAG;GAC7B,IAAI,aAAa,QAAQ,IAAI,KAAK,GAAG,QAAQ;GAC7C;EACF;EACA,IAAI,KAAK,GAAG,SAAS,YAAY;CACnC;CACA,OAAO;AACT;AAEA,MAAM,qBACJ,OACA,MACA,WAGI;CAAE,QAAQ,CAAC;CAAG,MAAM,CAAC;AAAE,MACZ;CACf,MAAM,iBAAiB,KAAK,WAAW,MAAM,cAAc,UAAU,SAAS,OAAO;CACrF,IAAI,mBAAmB,KAAA,KAAa,eAAe,MAAM,SAAS,UAAU;EAC1E,IAAI,SAAS,KAAK,WAAW,GAC3B,OAAO;GAAE,QAAQ,CAAC,GAAG,SAAS,MAAM;GAAG,WAAW;EAAK;EAEzD,MAAM,YAAY,OAAO,KAAK;EAC9B,MAAM,UAAU,KAAK;GAAE,UAAU,IAAI;GAAa,cAAc,SAAS;EAAK,CAAC;EAC/E,OAAO;GAAE,QAAQ,CAAC,GAAG,SAAS,MAAM;GAAG;EAAU;CACnD;CACA,MAAM,aAAa,eACjB,eAAe,MAAM,OACrB,MAAM,OACN,eAAe,UAAU,KAC3B;CACA,MAAM,OAAO,KAAK,GAAG,WAAW,MAAM;CACtC,MAAM,SAAS,KAAK,GAAG,WAAW,QAAQ;CAE1C,MAAM,aAAa,YACjB,WAAW,QAAQ,QAAQ,aAAa,SAAS,YAAY,OAAO;CAEtE,MAAM,OAAO,UAAU,MAAM;CAC7B,MAAM,SAAS,CAAC,GAAG,SAAS,QAAQ,GAAG,gBAAgB,OAAO,IAAI,CAAC;CACnE,MAAM,iBAAiB,UAAU,IAAI,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,SAAS,YAAY,CAAC;CAGvF,MAAM,mBAAmB;EACvB,GAAG,SAAS;EACZ,GAAG,KAAK,SAAS,aAAa,CAAC,GAAI,SAAS,oBAAoB,CAAC,CAAE,CAAC;EACpE,GAAG,UAAU,MAAM,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,SAAS,YAAY,CAAC;CACvE;CAEA,IAAI,eAAe,WAAW,KAAK,iBAAiB,WAAW,GAC7D,OAAO;EAAE;EAAQ,WAAW;CAAK;CAEnC,MAAM,YAAY,OAAO,KAAK;CAC9B,IAAI,eAAe,SAAS,GAC1B,MAAM,QAAQ,KAAK;EACjB,UAAU,IAAI;EACd,cAAc;CAChB,CAAC;CAEH,IAAI,iBAAiB,SAAS,GAC5B,MAAM,UAAU,KAAK;EACnB,UAAU,IAAI;EACd,cAAc;CAChB,CAAC;CAEH,OAAO;EAAE;EAAQ;CAAU;AAC7B;;;;;;;;;;AAWA,MAAM,2BAA2B,OAAqB,SACpD,kBAAkB,OAAO,MAAM;CAC7B,QAAQ,CACN;EAAE,UAAU;EAAS,OAAO,MAAM,MAAM,OAAO;CAAO,GACtD;EAAE,UAAU;EAAmB,OAAO;CAAY,CACpD;CACA,MACE,MAAM,MAAM,WAAW,UAAU,KAAA,IAC7B,CAAC,IACD,CAAC;EAAE,UAAU;EAAS,OAAO,MAAM,MAAM,WAAW;CAAM,CAAC;AACnE,CAAC;AAEH,MAAM,aACJ,OACA,MACA,QACA,QAA0C,CAAC,GAC3C,YACqC;CACrC,MAAM,QAA0C,MAAM,uBAClD,CAAC,GAAG,KAAK,IACT,CAAC,CAAC,eAAe,KAAK,EAAE,GAAG,GAAG,KAAK;CACvC,MAAM,aAAa,CAAC,OAAO,WAAW,SAAS,cAAc,CAAC,CAAC,QAC5D,cAAmC,cAAc,QAAQ,cAAc,KAAA,CAC1E;CACA,IAAI,WAAW,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,WAAW,KAAK,GAAG,CAAC,CAAC;CACrE,OAAO;AACT;AAEA,MAAM,qBAAqB,aACzB,aAAa,aAAa,SAAS,WAAW,UAAU;AAE1D,MAAM,gBACJ,iBAC8D;CAC9D,MAAM,UAA8B,CAAC;CACrC,MAAM,OAA2B,CAAC;CAClC,KAAK,MAAM,eAAe,cACxB,CAAC,kBAAkB,YAAY,QAAQ,IAAI,UAAU,KAAA,CAAM,KAAK,WAAW;CAE7E,OAAO;EAAE;EAAS;CAAK;AACzB;AAaA,MAAM,8BAA8B;AACpC,MAAM,0BAA0B;AAEhC,MAAM,sBAAsB,SAA0C;CACpE,MAAM,MAAM,gBAAgB,MAAM,iBAAiB;CACnD,OAAO,QAAQ,KAAA,KAAa,QAAQ,KAAK,KAAA,IAAY;AACvD;AAEA,MAAM,wBAAwB,SAA0C;CACtE,MAAM,MAAM,mBAAmB,IAAI;CACnC,IAAI,QAAQ,KAAA,GAAW,OAAO,CAAC;CAC/B,OAAO;EACL;GAAE,UAAU;GAAoB,OAAO,QAAQ,gBAAgB,GAAG,EAAE;EAAI;EACxE;GACE,UAAU;GACV,OAAO,gBAAgB,MAAM,oBAAoB,KAAK;EACxD;EACA;GACE,UAAU;GACV,OAAO,gBAAgB,MAAM,gBAAgB,KAAK;EACpD;EACA;GAAE,UAAU;GAAqB,OAAO;EAAY;CACtD;AACF;AAIA,MAAM,iBAAiB;AAEvB,MAAM,iCAAiC,kBAAqD;CAC1F,IAAI,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC;CACrC,IAAI,aAAa;CACjB,MAAM,0BACJ,aAAa,IAAI,gBAAgB,aAAa,IAAI,OAAO;CAC3D,OAAO,kBAAkB,IAAI,gBAAgB;CAC7C,OAAO,CAAC,kBAAkB,GAAG,UAAU;AACzC;AAEA,MAAM,UAAU,OAAwB,KAAK,IAAK;AAElD,MAAM,aAAa,iBAA6C;CAC9D,MAAM,UAAU;EAAE,KAAK;EAAG,OAAO;EAAG,QAAQ;EAAG,MAAM;CAAE;CACvD,MAAM,SAAS,UAA0B;EACvC,MAAM,QAAQ,eAAe,KAAK,KAAK;EACvC,OAAO,UAAU,OAAO,IAAI,OAAO,WAAW,MAAM,EAAG;CACzD;CACA,KAAK,MAAM,eAAe,cACxB,QAAQ,YAAY,UAApB;EACE,KAAK;GAEH,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,OAD3C,MAAM,YAAY,KACkC;GAChE;EAEF,KAAK;GACH,QAAQ,MAAM,MAAM,YAAY,KAAK;GACrC;EACF,KAAK;GACH,QAAQ,QAAQ,MAAM,YAAY,KAAK;GACvC;EACF,KAAK;GACH,QAAQ,SAAS,MAAM,YAAY,KAAK;GACxC;EACF,KAAK,gBACH,QAAQ,OAAO,MAAM,YAAY,KAAK;CAI1C;CAEF,OAAO;AACT;AAIA,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAI3B,MAAM,cAAgD;CACpD,CAAC,UAAU,GAAG;CACd,CAAC,eAAe,GAAG;CACnB,CAAC,eAAe,GAAG;CACnB,CAAC,QAAQ,cAAc;AACzB;AAEA,MAAM,gBAAgB,OAAqB,UACzC,MAAM,KAAK,SAAS;CAClB,IAAI,KAAK,SAAS,QAAQ,OAAO,KAAK,KAAK,KAAK;CAChD,IAAI,KAAK,SAAS,iBAChB,OAAO,KAAK,KAAK,WAAW,UAAU,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK,EAAE;CAE/E,IAAI,KAAK,QAAQ,KAAK;EACpB,MAAM,SAAS,wBAAwB,OAAO,IAAI;EAClD,MAAM,QAA0C,CAC9C,CAAC,QAAQ,gBAAgB,MAAM,MAAM,KAAK,EAAE,GAC5C,CAAC,UAAU,QAAQ,CACrB;EACA,IAAI,OAAO,cAAc,MAAM,MAAM,KAAK,CAAC,SAAS,OAAO,SAAS,CAAC;EACrE,OAAO,QAAQ,KAAK,OAAO,OAAO,QAAQ,aAAa,OAAO,KAAK,QAAQ,CAAC;CAC9E;CACA,OAAO,QAAQ,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,aAAa,OAAO,KAAK,QAAQ,CAAC;AACrE,CAAC;AAEH,MAAM,eAAe,OAAqB,SAAgC;CACxE,MAAM,SAAS,kBAAkB,OAAO,IAAI;CAC5C,QAAQ,KAAK,KAAb;EACE,KAAK;EACL,KAAK,WAAW;GACd,MAAM,EAAE,SAAS,SAAS,aAAa,OAAO,MAAM;GACpD,MAAM,UAAU,qBAAqB,IAAI;GACzC,MAAM,QAAQ,mBAAmB,IAAI;GACrC,MAAM,WAAW,KAAK,SAAS,SAAS,UACtC,MAAM,SAAS,YAAY,CAAC,YAAY,OAAO,KAAK,CAAC,IAAI,CAAC,CAC5D;GACA,IAAI,MAAM,SAAS,UAAU;IAC3B,MAAM,QACJ,KAAK,QAAQ,cACT;KACE;MAAE,UAAU;MAAa,OAAO,GAAG,MAAM,WAAW;KAAI;KACxD;MAAE,UAAU;MAAe,OAAO;KAAO;KACzC;MAAE,UAAU;MAAgB,OAAO;KAAO;IAC5C,IACA,CAAC;IAIP,OAAO,QACL,OACA,UAAU,OAAO,MAAM,MAAM,GAC7B;KAAC,GAAG;KAAO,GAAG;KAAM,GAAG;KAAS,GAAG;IAAO,GAC1C,QACF;GACF;GACA,MAAM,aACJ,KAAK,QAAQ,cACT,CAAC;IAAE,UAAU;IAAa,OAAO,GAAG,MAAM,WAAW;GAAI,GAAG,GAAG,IAAI,IACnE;GAGN,MAAM,UACJ,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,cAAc,KAAK,CAAC;GACnD,OAAO,QACL,SACA,UAAU,OAAO,MAAM,QAAQ;IAAC,CAAC,SAAS,QAAQ;IAAG,CAAC,SAAS,MAAM;IAAG,GAAG;GAAW,CAAC,GACvF,YACA,CACE,QACE,SACA,CAAC,GACD,CAAC,GACD,CACE,QACE,MACA,CAAC,GACD,CAAC;IAAE,UAAU;IAAS,OAAO;GAAO,CAAC,GACrC,CAAC,QAAQ,MAAM,SAAS,CAAC,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,CAC7D,CACF,CACF,CACF,CACF;EACF;EACA,KAAK,OAAO;GACV,MAAM,UAAU,KAAK,SAAS,SAAS,UACrC,MAAM,SAAS,YAAY,CAAC,YAAY,OAAO,KAAK,CAAC,IAAI,CAAC,CAC5D;GACA,MAAM,EAAE,SAAS,SAAS,aAAa,OAAO,MAAM;GACpD,MAAM,YAAY,eAChB,QACE,SACA,UAAU,OAAO,MAAM,QAAQ;IAAC,CAAC,SAAS,QAAQ;IAAG,CAAC,SAAS,MAAM;IAAG,GAAG;GAAW,CAAC,GACvF,YACA,CACE,QACE,SACA,CAAC,GACD,CAAC;IAAE,UAAU;IAAS,OAAO;GAAO,CAAC,GACrC,CAAC,QAAQ,MAAM,CAAC,GAAG,CAAC;IAAE,UAAU;IAAS,OAAO;GAAO,CAAC,GAAG,OAAO,CAAC,CACrE,CACF,CACF;GAMF,IAAI,MAAM,SAAS,YAAY,QAAQ,WAAW,GAChD,OAAO,SAAS,OAAO,MAAM;GAE/B,OAAO,QACL,SACA;IAAC,CAAC,SAAS,QAAQ;IAAG,CAAC,SAAS,MAAM;IAAG,GAAG;GAAW,GACvD,CAAC,GACD,CACE,QACE,SACA,CAAC,GACD,CAAC,GACD,CACE,QACE,MACA,CAAC,GACD,CAAC;IAAE,UAAU;IAAS,OAAO;GAAO,CAAC,GACrC,CAAC,QAAQ,MAAM,CAAC,GAAG,SAAS,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAC/C,CACF,CACF,CACF,CACF;EACF;EACA,KAAK,UAAU;GACb,MAAM,QAAQ,gBAAgB,MAAM,OAAO;GAC3C,MAAM,SAAS,gBAAgB,MAAM,QAAQ;GAC7C,MAAM,WAAW,KAAK,SAAS,SAAS,UACtC,MAAM,SAAS,YAAY,CAAC,YAAY,OAAO,KAAK,CAAC,IAAI,CAAC,CAC5D;GAGA,OAAO,QACL,MACA,UAAU,OAAO,MAAM,QAAQ,CAC7B,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,CAAU,GACzD,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,UAAU,MAAM,CAAU,CAC9D,CAAC,GACD,CACE,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,CAAC;IAAE,UAAU;IAAkB,OAAO;GAAO,CAAC,GAC9E,GAAG,OAAO,MACZ,GACA,QACF;EACF;EACA,KAAK,WAAW;GACd,MAAM,QAAQ,gBAAgB,MAAM,OAAO,KAAK;GAChD,OAAO,QACL,IAAI,SACJ,UAAU,OAAO,MAAM,MAAM,GAC7B,OAAO,QACP,aAAa,OAAO,KAAK,QAAQ,CACnC;EACF;EACA,KAAK,QAOH,OAAO,QACL,KACA,UAAU,OAAO,MAAM,MAAM,GAC7B,CAAC,GAAG;GARJ;IAAE,UAAU;IAAa,OAAO;GAAO;GACvC;IAAE,UAAU;IAAe,OAAO;GAAO;GACzC;IAAE,UAAU;IAAc,OAAO;GAAO;GACxC;IAAE,UAAU;IAAiB,OAAO;GAAO;EAKhC,GAAG,GAAG,OAAO,MAAM,GAC9B,aAAa,OAAO,KAAK,QAAQ,CACnC;EAEF,KAAK,UACH,OAAO,aAAa,OAAO,MAAM,MAAM;EACzC,KAAK,QAAQ;GAMX,MAAM,MAAM,gBAAgB,MAAM,MAAM,MAAM,YAAY,OAAO;GACjE,MAAM,WAA+B;IACnC;KAAE,UAAU;KAAc,OAAO;IAAO;IACxC;KAAE,UAAU;KAAiB,OAAO;IAAO;IAC3C;KAAE,UAAU;KAAgB,OAAO;IAAO;GAC5C;GACA,MAAM,QAAQ,KAAK,SAAS,SAAS,UACnC,MAAM,SAAS,YAAY,CAAC,YAAY,OAAO,KAAK,CAAC,IAAI,CAAC,CAC5D;GACA,OAAO,QAAQ,KAAK,UAAU,OAAO,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,GAAG,OAAO,MAAM,GAAG,KAAK;EAC5F;EACA,KAAK,YASH,OAAO,QACL,MACA,UAAU,OAAO,MAAM,MAAM,GAC7B,CAAC,GAAG;GARJ;IAAE,UAAU;IAAa,OAAO;GAAO;GACvC;IAAE,UAAU;IAAe,OAAO;GAAO;GACzC;IAAE,UAAU;IAAc,OAAO;GAAM;GACvC;IAAE,UAAU;IAAiB,OAAO;GAAM;EAK/B,GAAG,GAAG,OAAO,MAAM,GAC9B,aAAa,OAAO,KAAK,QAAQ,CACnC;EAEF,KAAK,MAAM;GAKT,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;GAC9C,MAAM,WAAW,KAAK,SAAS,QAC5B,UAAgC,MAAM,SAAS,SAClD;GACA,MAAM,WAAW,SAAS,MAAM,UAAU,MAAM,QAAQ,MAAM;GAC9D,MAAM,WAA4B,CAAC,IAAI,cAAc,KAAK,GAAG,GAAG,KAAK,IAAI,CAAC;GAC1E,KAAK,MAAM,SAAS,UAClB,IAAI,MAAM,QAAQ,QAAQ,SAAS,KAAK,YAAY,OAAO,KAAK,CAAC;GAEnE,IAAI,aAAa,KAAA,GAAW;IAC1B,SAAS,KAAK,IAAI,gBAAgB,KAAK,GAAG,IAAI,CAAC;IAC/C,KAAK,MAAM,SAAS,SAAS,UAC3B,IAAI,MAAM,SAAS,WAAW,SAAS,KAAK,YAAY,OAAO,KAAK,CAAC;GAEzE;GACA,SAAS,KAAK,IAAI,iBAAiB,KAAK,GAAG,IAAI,CAAC;GAChD,OAAO;IAAE,MAAM;IAAY;GAAS;EACtC;EACA,KAAK,SAAS;GACZ,MAAM,QAAQ,gBAAgB,MAAM,OAAO;GAC3C,MAAM,SAAS,gBAAgB,MAAM,QAAQ;GAC7C,MAAM,MAAM,gBAAgB,MAAM,KAAK,KAAK;GAC5C,MAAM,MAAM,gBAAgB,MAAM,KAAK,KAAK;GAC5C,MAAM,aAAa,KAAK,SAAS,MAC9B,UACC,MAAM,SAAS,aACf,MAAM,QAAQ,YACd,gBAAgB,OAAO,QAAQ,MAAM,MACzC;GACA,MAAM,kBACJ,eAAe,KAAA,IAAY,KAAA,IAAY,gBAAgB,YAAY,KAAK;GAC1E,MAAM,QAAQ,UAAU,OAAO,MAAM,QAAQ;IAC3C,CAAC,OAAO,GAAG;IACX,CAAC,OAAO,GAAG;IACX,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,KAAK,CAAC,CAAU;IACjE,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,UAAU,OAAO,MAAM,CAAC,CAAU;GACtE,CAAC;GACD,MAAM,QAA4B;IAChC;KAAE,UAAU;KAAW,OAAO;IAAQ;IACtC;KAAE,UAAU;KAAW,OAAO;IAAO;IACrC;KAAE,UAAU;KAAU,OAAO;IAAO;IACpC;KAAE,UAAU;KAAmB,OAAO;IAAO;GAC/C;GACA,IAAI,oBAAoB,KAAA,GAAW;IACjC,MAAM,iBAAiB,OAAO,KAAK,GAAG;IACtC,MAAM,gBAAgB,OAAO,KAAK,GAAG;IACrC,MAAM,UAAU,KACd;KACE,UAAU,MAAM,uBACZ,IAAI,mBACJ,iBAAiB,KAAK,GAAG;KAC7B,cAAc,CAAC;MAAE,UAAU;MAAW,OAAO;KAAO,CAAC;IACvD,GACA;KACE,UAAU,MAAM,uBACZ,IAAI,kBACJ,iBAAiB,KAAK,GAAG;KAC7B,cAAc,CAAC;MAAE,UAAU;MAAW,OAAO;KAAQ,CAAC;IACxD,CACF;IACA,MAAM,eAAe,OAAyB,aAAqB;KACjE,MAAM,iBAAiB,UAAU,UAAU,iBAAiB;KAC5D,OAAO,UACL,OACA,MACA,QACA;MACE,GAAI,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC,kBAAkB,KAAK,CAAU;MACzE,CAAC,OAAO,GAAG;MACX,CAAC,OAAO,QAAQ;MAChB,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,KAAK,CAAC,CAAU;MACjE,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,UAAU,OAAO,MAAM,CAAC,CAAU;KACtE,GACA,MAAM,uBAAuB,EAAE,gBAAgB,eAAe,IAAI,KAAA,CACpE;IACF;IACA,OAAO;KACL,MAAM;KACN,UAAU,CACR,QAAQ,OAAO,YAAY,SAAS,GAAG,GAAG,CAAC,GAAG,OAAO,GAAG,OAAO,MAAM,GAAG,CAAC,GAAG,EAC1E,MAAM,KACR,CAAC,GACD,QACE,OACA,YAAY,QAAQ,eAAe,GACnC;MAAC;OAAE,UAAU;OAAW,OAAO;MAAO;MAAG,GAAG,MAAM,MAAM,CAAC;MAAG,GAAG,OAAO;KAAM,GAC5E,CAAC,GACD,EAAE,MAAM,KAAK,CACf,CACF;IACF;GACF;GACA,OAAO,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,GAAG,OAAO,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;EAC/E;EACA,KAAK,WAMH,OAAO,QAAQ,MAAM,UAAU,OAAO,MAAM,MAAM,GAAG,CAAC,GAAG;GAJvD;IAAE,UAAU;IAAS,OAAO;GAAO;GACnC;IAAE,UAAU;IAAU,OAAO;GAAO;GACpC;IAAE,UAAU;IAAc,OAAO;GAAoB;EAES,GAAG,GAAG,OAAO,MAAM,GAAG,CAAC,GAAG,EACxF,MAAM,KACR,CAAC;EAEH,KAAK,UAAU;GACb,MAAM,SAAS,gBAAgB,MAAM,QAAQ,KAAK;GAClD,OAAO,QACL,OACA,UAAU,OAAO,MAAM,MAAM,GAC7B;IACE;KAAE,UAAU;KAAU,OAAO,GAAG,OAAO;IAAI;IAC3C;KAAE,UAAU;KAAe,OAAO,GAAG,OAAO;IAAI;IAChD;KAAE,UAAU;KAAa,OAAO;IAAM;GACxC,GACA,CAAC,IAAI,SAAS,CAAC,CACjB;EACF;EACA;GAGE,MAAM,OAAO,KAAK;IAChB,MAAM;IACN,SAAS,eAAe,KAAK,IAAI;IACjC,MAAM,KAAK;GACb,CAAC;GACD,OAAO,IAAI,EAAE;CACjB;AACF;AAEA,MAAM,gBAAgB,OAAqB,MAAmB,WAAiC;CAC7F,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;CAC9C,MAAM,QAAQ,aAAa,OAAO,KAAK,QAAQ;CAC/C,MAAM,YAAgC;EACpC;GAAE,UAAU;GAAe,OAAO;EAAO;EACzC;GAAE,UAAU;GAAmB,OAAO;EAAO;EAC7C;GAAE,UAAU;GAAW,OAAO;EAAe;EAC7C;GAAE,UAAU;GAAa,OAAO;EAAO;EACvC;GAAE,UAAU;GAAmB,OAAO;EAAM;CAC9C;CACA,MAAM,QAAQ,UAAU,OAAO,MAAM,QAAQ,CAC3C,CAAC,QAAQ,IAAI,GACb,CAAC,UAAU,QAAQ,CACrB,CAAC;CAED,IAAI,MAAM,SAAS,UACjB,OAAO,QAAQ,KAAK,OAAO,CAAC,GAAG,WAAW,GAAG,OAAO,MAAM,GAAG,KAAK;CAGpE,MAAM,UAAU,UAAU,OAAO,MAAM;CACvC,MAAM,kBAAkB,QAAQ,MAAM,QAAQ;CAC9C,MAAM,YAAY,OAAO,eAAe;CACxC,MAAM,CAAC,eAAe,kBAAkB,8BAA8B,QAAQ,IAAI;CAClF,MAAM,CAAC,gBAAgB,mBAAmB,8BAA8B,QAAQ,KAAK;CAErF,MAAM,iBAAqC;EACzC;GAAE,UAAU;GAAa,OAAO;EAAO;EACvC;GAAE,UAAU;GAAW,OAAO;EAAe;EAC7C;GAAE,UAAU;GAAe,OAAO;EAAO;EACzC;GAAE,UAAU;GAAmB,OAAO;EAAM;EAC5C,GAAI,QAAQ,SAAS,IAEjB,CAAC;GAAE,UAAU;GAAkB,OAAO,GAAG,OAAO,QAAQ,MAAM;EAAI,CAAC,IACnE,CAAC;CACP;CAEA,OAAO,QACL,KACA,OACA,CAAC,GAAG,WAAW,GAAG,OAAO,MAAM,GAC/B;EACE,QACE,QACA,CAAC,GACD,CAAC,GACD,CACE,IACE,yCAAyC,gBAAgB,IAAI,mBAAmB,UAAU,WAAW,UAAU,OAAO,cAAc,EAAE,iBACxI,CACF,CACF;EACA,QAAQ,QAAQ,CAAC,GAAG,gBAAgB,KAAK;EACzC,QACE,QACA,CAAC,GACD,CAAC,GACD,CACE,IACE,yCAAyC,iBAAiB,IAAI,YAAY,UAAU,OAAO,eAAe,EAAE,wBAC9G,CACF,CACF;CACF,CACF;AACF;AAEA,MAAM,iBAAiB,OAAqB,SAAgC;CAC1E,IAAI,cAAc,kBAAkB,IAAI;CACxC,IAAI,gBAAgB;CACpB,KAAK,MAAM,SAAS,KAAK,UACvB,IAAI,MAAM,SAAS,QACjB,iBAAiB,MAAM,MAAM;CAGjC,cAAc,YAAY,UAAU,GAAG,kBAAkB;CAIzD,MAAM,mBAAmB,KAAK,IAAI,eAAe,kBAAkB;CACnE,MAAM,WAA4B,CAAC,KAAK,WAAW,CAAC;CACpD,IAAI,mBAAmB,oBACrB,SAAS,KACP,QACE,OACA,CAAC,GACD,CAAC,GACD,CAAC,KAAK,mBAAmB,OAAO,qBAAqB,gBAAgB,CAAC,CAAC,CACzE,CACF;CAEF,OAAO,QACL,OACA,CACE,GAAI,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC,eAAe,KAAK,EAAE,CAAU,GACxE,CAAC,qBAAqB,MAAM,CAC9B,GACA;EACE;GAAE,UAAU;GAAW,OAAO;EAAO;EACrC;GAAE,UAAU;GAAY,OAAO;EAAS;EACxC;GAAE,UAAU;GAAe,OAAO;EAAM;EACxC;GAAE,UAAU;GAAW,OAAO;EAAI;EAClC;GAAE,UAAU;GAAc,OAAO;EAAI;EACrC;GAAE,UAAU;GAAa,OAAO;EAAI;CACtC,GACA,QACF;AACF;AAIA,MAAM,kBACJ,OACA,QACA,cAEA,MACG,KACE,SACC,GAAG,SAAS,KAAK,SAAS,GAAG,KAAK,aAC/B,KAAK,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,YAAY,gBAAgB,IAAI,CAAC,CACvE,KAAK,GAAG,EAAE,EACjB,CAAC,CACA,KAAK,IAAI;AAEd,MAAM,wBAAwB,aAA8B,SAAS,WAAW,YAAY;AAE5F,MAAM,kBAAkB,UAAgC;CAGtD,MAAM,QAAuB,CAC3B,mEACF;CACA,IAAI,MAAM,QAAQ,SAAS,GAGzB,MAAM,KAAK,8BAA8B,eAAe,MAAM,SAAS,MAAM,IAAI,EAAE,IAAI;CAEzF,IAAI,MAAM,UAAU,SAAS,GAAG;EAC9B,MAAM,KACJ,wCAAwC,eAAe,MAAM,WAAW,MAAM,IAAI,EAAE,IACtF;EAGA,MAAM,YAAY,MAAM,UAAU,SAAS,SAAS;GAClD,MAAM,eAAe,KAAK,aAAa,QAAQ,MAAM,CAAC,qBAAqB,EAAE,QAAQ,CAAC;GACtF,OAAO,aAAa,SAAS,IACzB,CAAC;IAAE,UAAU,eAAe,KAAK;IAAY;GAAa,CAAC,IAC3D,CAAC;EACP,CAAC;EACD,MAAM,YAAY,MAAM,UAAU,SAAS,SAAS;GAClD,MAAM,eAAe,KAAK,aAAa,QAAQ,MAAM,qBAAqB,EAAE,QAAQ,CAAC;GACrF,OAAO,aAAa,SAAS,IACzB,CAAC;IAAE,UAAU,eAAe,KAAK;IAAY;GAAa,CAAC,IAC3D,CAAC;EACP,CAAC;EACD,IAAI,UAAU,SAAS,GAAG,MAAM,KAAK,eAAe,WAAW,IAAI,IAAI,CAAC;EACxE,IAAI,UAAU,SAAS,GAAG,MAAM,KAAK,eAAe,WAAW,IAAI,IAAI,CAAC;CAC1E;CACA,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;;;;;;;;AAuBA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB,UACtB,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,UAAU,cAAc,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM,KAAK,MAAM,EAAE;AAE3F,MAAM,oBAAoB,UAAgC;CAwBxD,OAvBc,OAAO,QAAQ,MAAM,MAAM,SAAS,CAAC,CAChD,SAAS,CAAC,MAAM,SAAS;EAUxB,MAAM,SAAS,gBAAgB,IAAI,MAAM;EACzC,MAAM,QAAQ,MAAM,MAAM,aAAa;EACvC,IAAI,UAAU,KAAA,KAAa,CAAC,MAAM,WAAW,IAAI,OAAO,GAAG,GAAG,OAAO,CAAC;EACtE,OAAO,IAAI,MAAM,KAAK,SAAS;GAC7B,MAAM,QACJ,KAAK,gBAAgB,eAAe,KAAK,YAAY,IACjD,iBAAiB,KAAK,aAAa,KACnC;GACN,OAAO,2BAA2B,OAAO,eAAe,KAAK,MAAM,eAAe,KAAK,OAAO,8BAA8B,gBAAgB,KAAK,GAAG,EAAE,qBAAqB,MAAM;EACnL,CAAC;CACH,CAAC,CAAC,CACD,KAAK,EACG;AACb;AAEA,MAAM,sBAAsB,QAC1B,QAAQ,KAAK,KAAK,6BAA6B,IAAI;AAIrD,MAAM,mBAAmB,UAA0C;CACjE,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,QAAQ,UAAU,KAAK;MACpC,IAAI,KAAK,SAAS,iBACrB,UAAU,KAAK,WAAW,UAAU,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK;MACpE,IAAI,KAAK,SAAS,aAAa,KAAK,QAAQ,KAAK;EACtD,MAAM,QAAQ,gBAAgB,KAAK,QAAQ;EAC3C,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;EAC9C,UAAU,SAAS,KAAK,QAAQ,GAAG,MAAM,IAAI,KAAK;CACpD,OAAO,IAAI,KAAK,SAAS,WACvB,UAAU,gBAAgB,KAAK,QAAQ;CAG3C,OAAO;AACT;AAEA,MAAM,kBAAkB,MAAmB,WAAgC;CACzE,QAAQ,KAAK,KAAb;EACE,KAAK;EACL,KAAK,WACH;EACF,KAAK;EACL,KAAK,QAAQ;GACX,MAAM,QAAQ,gBAAgB,KAAK,QAAQ,CAAC,CAAC,KAAK;GAClD,IAAI,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK;GACvC;EACF;EACA,KAAK,UAAU;GACb,MAAM,QAAQ,gBAAgB,KAAK,QAAQ,CAAC,CAAC,KAAK;GAClD,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;GAC9C,OAAO,KAAK,SAAS,KAAK,QAAQ,GAAG,MAAM,IAAI,KAAK,EAAE;GACtD;EACF;EACA,KAAK,SAAS;GACZ,MAAM,MAAM,gBAAgB,MAAM,KAAK;GACvC,IAAI,QAAQ,KAAA,KAAa,IAAI,SAAS,GAAG,OAAO,KAAK,GAAG;GACxD;EACF;EACA,KAAK;EACL,KAAK,UACH;EACF,KAAK,QAAQ;GAKX,MAAM,UAAU,gBAAgB,MAAM,MAAM,MAAM;GAClD,MAAM,QAAuB,CAAC;GAC9B,IAAI,WAAW;GACf,KAAK,MAAM,SAAS,KAAK,UAAU;IACjC,IAAI,MAAM,SAAS,aAAa,MAAM,QAAQ,YAAY;IAC1D,YAAY;IACZ,MAAM,QAAQ,gBAAgB,MAAM,QAAQ,CAAC,CAAC,KAAK;IACnD,IAAI,MAAM,WAAW,GAAG;IACxB,MAAM,KAAK,UAAU,GAAG,SAAS,IAAI,UAAU,KAAK,OAAO;GAC7D;GACA,IAAI,MAAM,SAAS,GAAG,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC;GAClD;EACF;EACA,KAAK,MAAM;GAGT,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;GAC9C,MAAM,WAAW,KAAK,SAAS,QAC5B,UAAgC,MAAM,SAAS,SAClD;GACA,MAAM,WAAW,SAAS,MAAM,UAAU,MAAM,QAAQ,MAAM;GAC9D,OAAO,KAAK,cAAc,KAAK,GAAG,GAAG,KAAK,IAAI;GAC9C,KAAK,MAAM,SAAS,UAClB,IAAI,MAAM,QAAQ,QAAQ,eAAe,OAAO,MAAM;GAExD,IAAI,aAAa,KAAA,GAAW;IAC1B,OAAO,KAAK,gBAAgB,KAAK,GAAG,IAAI;IACxC,KAAK,MAAM,SAAS,SAAS,UAC3B,IAAI,MAAM,SAAS,WAAW,eAAe,OAAO,MAAM;GAE9D;GACA,OAAO,KAAK,iBAAiB,KAAK,GAAG,IAAI;GACzC;EACF;EACA,SACE,KAAK,MAAM,SAAS,KAAK,UACvB,IAAI,MAAM,SAAS,WAAW,eAAe,OAAO,MAAM;CAGhE;AACF;AAIA,MAAM,gBACJ;;AAGF,MAAa,cACX,UACA,YACqD;CACrD,QAAQ,SAAS,KAAK,KAAtB;EACE,KAAK,OACH,OAAO,OAAO,QAAQ,eAAe,SAAS,IAAI,CAAC;EACrD,KAAK,YACH,OAAO,oBAAoB,SAAS,IAAI;EAC1C,SACE,OAAO,gBAAgB,UAAU,OAAO;CAC5C;AACF;AAEA,MAAa,sBACX,UACA,YAC6D;CAC7D,QAAQ,SAAS,KAAK,KAAtB;EACE,KAAK,OACH,OAAO,OAAO,QAAQ,eAAe,SAAS,IAAI,CAAC;EACrD,KAAK,YACH,OAAO,oBAAoB,SAAS,IAAI;EAC1C,SACE,OAAO,wBAAwB,UAAU,OAAO;CACpD;AACF;;;;;;;AAQA,MAAM,oBAAoB,UAAoC;CAC5D,GAAG;CACH,UAAU,KAAK,SAAS,SAAS,UAAqB;EACpD,IAAI,MAAM,SAAS,WAAW,OAAO,CAAC,KAAK;EAC3C,OAAO,aAAa,KAAK,IAAI,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC;CAC5D,CAAC;AACH;AAEA,MAAM,aACJ,KACA,IACA,gBACS;CACT,MAAM,UAAU,IAAI;CACpB,IAAI,YAAY,KAAA,GAAW,IAAI,MAAM,CAAC,WAAW;MAC5C,QAAQ,KAAK,WAAW;AAC/B;AAEA,MAAM,oBAAoB,MAAgB,QAAkD;CAC1F,IAAI,KAAK,SAAS,YAAY;EAC5B,KAAK,MAAM,SAAS,KAAK,UAAU,iBAAiB,OAAO,GAAG;EAC9D;CACF;CACA,IAAI,KAAK,SAAS,WAAW;CAC7B,MAAM,KAAK,KAAK,MAAM,MAAM,CAAC,UAAU,SAAS,aAAa,CAAC,GAAG;CACjE,IAAI,OAAO,KAAA,GAAW,UAAU,KAAK,IAAI,IAAI;CAC7C,KAAK,MAAM,SAAS,KAAK,UAAU,iBAAiB,OAAO,GAAG;AAChE;AAEA,MAAM,mBAAmB,SAAkC;CACzD,MAAM,MAA0C,CAAC;CACjD,iBAAiB,MAAM,GAAG;CAC1B,OAAO;AACT;AAEA,MAAM,6BAA6B,cAAoD;CACrF,SAAS,SAAS;CAClB,MAAM,SAAS;CACf,MAAM,SAAS;CACf,aAAa,SAAS;CACtB,UAAU,SAAS;CACnB,MAAM,SAAS;CACf,iBAAiB,SAAS;AAC5B;AAEA,MAAM,qBAAqB,SAA8B;CACvD,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,KAAK,UACvB,IAAI,MAAM,SAAS,QAAQ,UAAU,MAAM;MACtC,IAAI,MAAM,SAAS,iBACtB,UAAU,MAAM,WAAW,UAAU,KAAK,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK;CAGhF,OAAO;AACT;AAEA,MAAM,mBACJ,eACA,YACkD;CAClD,MAAM,WAAW,wBAAwB,eAAe,OAAO;CAC/D,IAAI,OAAO,UAAU,QAAQ,GAAG,OAAO,OAAO,KAAK,SAAS,OAAO;CACnE,OAAO,OAAO,QAAQ,0BAA0B,SAAS,OAAO,CAAC;AACnE;AAEA,MAAM,2BACJ,eACA,YAC0D;CAC1D,MAAM,WAAwB;EAC5B,GAAG;EACH,MAAM,iBAAiB,cAAc,IAAI;CAC3C;CACA,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,aAAa,gBAAgB,SAAS,MAAM,OAAO,KAAA;CAKzD,MAAM,QAAsB;EAC1B;EACA;EACA,OAPY,kBAAkB;GAC9B,MAAM,SAAS,OAAO,QAAQ;GAC9B,WAAW,SAAS,OAAO;EAC7B,CAIM;EACJ,sBAAsB,SAAS,wBAAwB;EACvD,QAAQ,CAAC;EACT,UAAU,CAAC;EACX,SAAS,CAAC;EACV,WAAW,CAAC;CACd;CAEA,MAAM,OAAO,SAAS;CACtB,MAAM,aAAa,kBAAkB,OAAO,IAAI;CAChD,MAAM,OAAO,gBAAgB,MAAM,MAAM,KAAK;CAC9C,MAAM,MAAM,gBAAgB,MAAM,KAAK,KAAK;CAE5C,MAAM,UAAU,KAAK,SAAS,MAC3B,UAAgC,MAAM,SAAS,aAAa,MAAM,QAAQ,SAC7E;CACA,MAAM,UAAU,KAAK,SAAS,MAC3B,UAAgC,MAAM,SAAS,aAAa,MAAM,QAAQ,SAC7E;CACA,MAAM,SAAS,KAAK,SAAS,SAAS,UACpC,MAAM,SAAS,aAAa,MAAM,QAAQ,aAAa,MAAM,QAAQ,YACjE,CAAC,YAAY,OAAO,KAAK,CAAC,IAC1B,CAAC,CACP;CAEA,IAAI,MAAM,OAAO,SAAS,GACxB,OAAO,OAAO,KAAK,IAAI,gBAAgB,EAAE,aAAa,MAAM,OAAO,CAAC,CAAC;CAKvE,MAAM,iBAAiB,WAAW,OAAO,QAAQ,MAAM,qBAAqB,EAAE,QAAQ,CAAC;CACvF,MAAM,eAAgC,CAAC;CACvC,IAAI,YAAY,KAAA,GAAW,aAAa,KAAK,cAAc,OAAO,OAAO,CAAC;CAC1E,aAAa,KACX,QACE,SACA;EAAC,CAAC,SAAS,QAAQ;EAAG,CAAC,SAAS,MAAM;EAAG,GAAG;CAAW,GACvD,CAAC,GACD,CACE,QACE,SACA,CAAC,GACD,CAAC,GACD,CACE,QACE,MACA,CAAC,GACD,CAAC,GACD,CACE,QACE,MACA;EACE,CAAC,OAAO,GAAG;EACX,CAAC,QAAQ,IAAI;EAIb,GAAI,WAAW,cAAc,OACxB,CAAC,CAAC,SAAS,WAAW,SAAS,CAAC,IACjC,CAAC;CACP,GACA,CAAC,GAAG,WAAW,MAAM,GACrB,MACF,CACF,CACF,CACF,CACF,CACF,CACF,CACF;CAEA,MAAM,YAA8C;EAClD,GAAI,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC,eAAe,KAAK,EAAE,CAAU;EACxE,CAAC,OAAO,GAAG;EACX,CAAC,QAAQ,IAAI;CACf;CACA,IAAI,WAAW,cAAc,MAAM,UAAU,KAAK,CAAC,SAAS,WAAW,SAAS,CAAC;CACjF,MAAM,OAAO,QACX,QACA,WACA,CAAC,GAAG,gBAAgB;EAAE,UAAU;EAAU,OAAO;CAAI,CAAC,GACtD,YACF;CAEA,MAAM,YAAY,eAAe,KAAK;CACtC,MAAM,cAAc,iBAAiB,KAAK;CAC1C,MAAM,gBAAgB,mBAAmB,WAAW;CACpD,MAAM,aAAgC;EACpC,QAAQ,CACN;GACE,MAAM;GACN,KAAK;GACL,MAAM,UAAU,UAAU;GAC1B,eAAe;EACjB,GACA,GAAI,gBAAgB,KAChB,CAAC,IACD,CACE;GACE,MAAM;GACN,KAAK;GACL,MAAM;GACN,eAAe;EACjB,CACF,CACN;EACA,OAAO,CAAC;CACV;CACA,MAAM,OAAO,QACX,QACA,CAAC,GACD,CAAC,GACD;EACE,QACE,QACA,CACE,CAAC,WAAW,0BAA0B,GACtC,CAAC,cAAc,cAAc,CAC/B,GACA,CAAC,GACD,CAAC,GACD,EAAE,MAAM,KAAK,CACf;EAGA,QACE,QACA,CACE,CAAC,QAAQ,UAAU,GACnB,CAAC,WAAW,oBAAoB,CAClC,GACA,CAAC,GACD,CAAC,GACD,EAAE,MAAM,KAAK,CACf;EACA,QAAQ,QAAQ,CAAC,CAAC,QAAQ,sCAAsC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;EAC1F,QACE,QACA,CACE,CAAC,QAAQ,cAAc,GACvB,CAAC,WAAW,YAAY,CAC1B,GACA,CAAC,GACD,CAAC,GACD,EAAE,MAAM,KAAK,CACf;EACA,QACE,QACA,CACE,CAAC,QAAQ,yBAAyB,GAClC,CAAC,WAAW,YAAY,CAC1B,GACA,CAAC,GACD,CAAC,GACD,EACE,MAAM,KACR,CACF;EACA,QAAQ,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC;EAIzC,GAAI,kBAAkB,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC;CACrD,CACF;CAEA,MAAM,YACJ,SAAS,iBACL;EACE,CAAC,OAAO,GAAG;EACX,CAAC,QAAQ,IAAI;EACb,CAAC,SAAS,8BAA8B;EACxC,CAAC,WAAW,yCAAyC;EACrD,CAAC,WAAW,+BAA+B;CAC7C,IACA,CACE,CAAC,OAAO,GAAG,GACX,CAAC,QAAQ,IAAI,CACf;CAEN,MAAM,UAAU,SAAS,iBAAiB,gBAAgB;CAC1D,MAAM,OAAO,QAAQ,QAAQ,WAAW,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC;CACxD,MAAM,OAAO,GAAG,QAAQ,IAAI,kBAAkB,IAAI,EAAE;CAEpD,MAAM,aAA4B,CAAC;CACnC,KAAK,MAAM,SAAS,KAAK,UACvB,IAAI,MAAM,SAAS,WAAW,eAAe,OAAO,UAAU;CAGhE,OAAO,OAAO,QAAQ;EACpB,SAAS;EACT,OAAO;EACP;EACA,MAAM,WAAW,KAAK,MAAM;EAC5B,aAAa,YAAY,KAAA,IAAY,OAAO,kBAAkB,OAAO;EACrE,UAAU,MAAM;EAChB;EACA,iBAAiB;EACjB;EACA;EACA;EACA,WAAW,gBAAgB,IAAI;CACjC,CAAC;AACH;AAEA,MAAa,iBACX,QACA,YACqE;CACrE,MAAM,SAAS,SAAS,MAAM;CAC9B,IAAI,OAAO,UAAU,MAAM,GAAG,OAAO,OAAO,KAAK,OAAO,OAAO;CAC/D,OAAO,WAAW,OAAO,SAAS,OAAO;AAC3C;AAEA,MAAa,yBACX,QACA,YAC6E;CAC7E,MAAM,SAAS,SAAS,MAAM;CAC9B,IAAI,OAAO,UAAU,MAAM,GAAG,OAAO,OAAO,KAAK,OAAO,OAAO;CAC/D,OAAO,mBAAmB,OAAO,SAAS,OAAO;AACnD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"components.js","names":[],"sources":["../src/components.ts"],"sourcesContent":["import type { Attribute, AttributeValue, ElementNode, SmlTag } from \"./ast\";\nimport { elementDraft, normalizeChildren, type SmlChildren } from \"./jsx-runtime\";\nimport type { AttributeSpec, TagSpec } from \"./parser\";\nimport { EMAIL_TAG_SPECS, SMS_TAG_SPECS, WHATSAPP_TAG_SPECS } from \"./parser\";\nimport {\n isPredicateRef,\n isVariableRef,\n printPredicateRef,\n VARIABLE_REF,\n type PredicateRef,\n type VariableRef,\n} from \"./variables\";\n\n// Typed TSX components for the full SML vocabulary — email blocks + inline\n// tags, plus the sms/whatsapp channel tags (design brief §7b/§7f). Prop types\n// are DERIVED from the parser's channel spec tables at the type level — there\n// is no hand-written mirror of the grammar here; widening a table widens these\n// components' accepted props automatically. Each component maps its typed\n// props onto ordered AST attributes and normalized children, then hands off to\n// the jsx-runtime's element builder. The output is validated by renderTemplate's\n// print → parse pass, so components restrict *authoring* while the parser stays\n// the single source of grammatical truth.\n\n// ── Type-level prop derivation from the spec tables ─────────────────────────────\n\n/**\n * The scalar type an attribute spec accepts. `level` narrows to the Heading\n * union; a spec with `values` narrows to that literal union (no VariableRef —\n * enum attributes are structural, not send-time holes); other string\n * attributes also take a VariableRef (href={v.ctaUrl}), which embeds as its\n * send-time interpolation.\n */\ntype AttributeType<Name extends string, Spec extends AttributeSpec> = Name extends \"level\"\n ? HeadingLevel\n : Spec[\"type\"] extends \"number\"\n ? number\n : Spec[\"values\"] extends ReadonlyArray<string>\n ? Spec[\"values\"][number]\n : string | VariableRef<string>;\n\n/** Required attributes become required props; the rest are optional. */\ntype AttributeProps<Attributes extends Readonly<Record<string, AttributeSpec>>> = {\n [K in keyof Attributes as Attributes[K][\"required\"] extends true ? K : never]: AttributeType<\n K & string,\n Attributes[K]\n >;\n} & {\n [K in keyof Attributes as Attributes[K][\"required\"] extends true ? never : K]?: AttributeType<\n K & string,\n Attributes[K]\n >;\n};\n\ntype ChildrenProp<Content extends TagSpec[\"content\"]> = Content extends \"void\"\n ? Record<never, never>\n : { readonly children?: SmlChildren | undefined };\n\n/** Full prop type for one spec: its attributes (typed/required) plus children where allowed. */\ntype PropsOf<Spec extends TagSpec> = AttributeProps<Spec[\"attributes\"]> &\n ChildrenProp<Spec[\"content\"]>;\n\n/** Prop type for an email-table tag (the shared If/Else live here too). */\ntype PropsFor<Tag extends keyof typeof EMAIL_TAG_SPECS> = PropsOf<(typeof EMAIL_TAG_SPECS)[Tag]>;\n\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\n// ── Attribute assembly ──────────────────────────────────────────────────────────\n\nconst PLACEHOLDER_SPAN = { start: 0, end: 0 } as const;\n\nconst attribute = (name: string, value: AttributeValue): Attribute => ({\n name,\n value,\n span: PLACEHOLDER_SPAN,\n valueSpan: PLACEHOLDER_SPAN,\n});\n\n/**\n * Maps a props object onto ordered attributes in AUTHORED order — object keys\n * preserve JSX insertion order, and the printer preserves attribute order, so\n * `<Column width=\"50%\" class=\"pr-2\">` round-trips as written rather than being\n * reordered to the spec's declaration order. `children` is skipped; the spec's\n * declared type per attribute picks the AttributeValue kind.\n */\nconst attributesFor = (spec: TagSpec, props: Record<string, unknown>): Array<Attribute> => {\n const out: Array<Attribute> = [];\n for (const [name, value] of Object.entries(props)) {\n if (name === \"children\" || value === undefined) continue;\n const attributeSpec = spec.attributes[name];\n // A VariableRef in attribute position (href={v.ctaUrl}) embeds as its\n // send-time interpolation — String(ref) would trip the coercion guard.\n if (isVariableRef(value)) {\n out.push(attribute(name, { kind: \"string\", value: `{{${value[VARIABLE_REF].path}}}` }));\n continue;\n }\n // Unknown attributes are a type error at authoring; if one slips through a\n // loosely-typed caller, emit it as a string and let the parser reject it.\n if (attributeSpec?.type === \"number\") {\n out.push(attribute(name, { kind: \"number\", value: value as number }));\n } else {\n out.push(attribute(name, { kind: \"string\", value: String(value) }));\n }\n }\n return out;\n};\n\n/** Builds a component for a tag whose attributes are all plain scalars. */\nconst scalarComponent =\n <Tag extends SmlTag, Spec extends TagSpec>(tag: Tag, spec: Spec) =>\n (props: PropsOf<Spec>): ElementNode => {\n const record = props as Record<string, unknown>;\n return elementDraft(\n tag,\n attributesFor(spec, record),\n spec.content === \"void\" ? [] : normalizeChildren(record.children),\n );\n };\n\n// ── Email block components ──────────────────────────────────────────────────────\n\nexport const Email = scalarComponent(\"Email\", EMAIL_TAG_SPECS.Email);\nexport const Subject = scalarComponent(\"Subject\", EMAIL_TAG_SPECS.Subject);\nexport const Preview = scalarComponent(\"Preview\", EMAIL_TAG_SPECS.Preview);\nexport const Container = scalarComponent(\"Container\", EMAIL_TAG_SPECS.Container);\nexport const Section = scalarComponent(\"Section\", EMAIL_TAG_SPECS.Section);\nexport const Row = scalarComponent(\"Row\", EMAIL_TAG_SPECS.Row);\nexport const Column = scalarComponent(\"Column\", EMAIL_TAG_SPECS.Column);\nexport const Text = scalarComponent(\"Text\", EMAIL_TAG_SPECS.Text);\nexport const Image = scalarComponent(\"Image\", EMAIL_TAG_SPECS.Image);\nexport const Source = scalarComponent(\"Source\", EMAIL_TAG_SPECS.Source);\nexport const Divider = scalarComponent(\"Divider\", EMAIL_TAG_SPECS.Divider);\nexport const Spacer = scalarComponent(\"Spacer\", EMAIL_TAG_SPECS.Spacer);\nexport const List = scalarComponent(\"List\", EMAIL_TAG_SPECS.List);\nexport const ListItem = scalarComponent(\"ListItem\", EMAIL_TAG_SPECS.ListItem);\n\n/** Heading — `level` is the literal 1–6 union the grammar bounds it to. */\nexport const Heading = (props: PropsFor<\"Heading\">): ElementNode =>\n scalarComponent(\"Heading\", EMAIL_TAG_SPECS.Heading)(props);\n\n/**\n * `<Button>` is two tags in one name: the email link-button (`href` required)\n * and the WhatsApp template button (`type` required, `url`/`phone` per type).\n * The union keeps both authorable; the parser's per-channel table rejects the\n * wrong one at render time.\n */\nexport const Button = (\n props: PropsFor<\"Button\"> | PropsOf<(typeof WHATSAPP_TAG_SPECS)[\"Button\"]>,\n): ElementNode => {\n const record = props as Record<string, unknown>;\n return elementDraft(\n \"Button\",\n attributesFor(EMAIL_TAG_SPECS.Button, record),\n normalizeChildren(record.children),\n );\n};\n\n// ── Inline helpers ──────────────────────────────────────────────────────────────\n\nexport const Strong = scalarComponent(\"strong\", EMAIL_TAG_SPECS.strong);\nexport const Em = scalarComponent(\"em\", EMAIL_TAG_SPECS.em);\nexport const A = scalarComponent(\"a\", EMAIL_TAG_SPECS.a);\n\n// ── Channel roots + WhatsApp anatomy ────────────────────────────────────────────\n//\n// Text content inside <Sms>, <Header>/<Body>/<Footer>, and <Button> is\n// VERBATIM — the JSX transform trims JSX text per the JSX spec, so multi-line\n// bodies are authored as template-literal children: {`line one\\nline two`}.\n\nexport const Sms = scalarComponent(\"Sms\", SMS_TAG_SPECS.Sms);\nexport const WhatsApp = scalarComponent(\"WhatsApp\", WHATSAPP_TAG_SPECS.WhatsApp);\nexport const Header = scalarComponent(\"Header\", WHATSAPP_TAG_SPECS.Header);\nexport const Body = scalarComponent(\"Body\", WHATSAPP_TAG_SPECS.Body);\nexport const Footer = scalarComponent(\"Footer\", WHATSAPP_TAG_SPECS.Footer);\nexport const Buttons = scalarComponent(\"Buttons\", WHATSAPP_TAG_SPECS.Buttons);\n\n// ── Conditionals ────────────────────────────────────────────────────────────────\n\n/**\n * `<If test={v.plan.eq(\"pro\").and(v.seats.gt(5))}>…<Else>…</Else></If>`. The\n * predicate ref is printed to the closed predicate source the parser validates;\n * a plain VariableRef (someone wrote `test={v.plan}`) is a common slip, so name\n * the fix instead of coercing.\n */\nexport const If = (props: {\n readonly test: PredicateRef;\n readonly children?: SmlChildren | undefined;\n}): ElementNode => {\n if (!isPredicateRef(props.test)) {\n const hint = isVariableRef(props.test)\n ? `— a bare variable is not a condition; write test={${\"var\"}.exists()} or a comparison like .eq(...)`\n : \"— build it with the variable predicate builders (eq/ne/gt/…, and/or/not)\";\n // oxlint-disable-next-line samva/no-try-catch-or-throw, samva/no-error-constructor -- User-build authoring boundary: a non-predicate <If test> is a programmer error that must fail the user's TSX build loudly, not become a typed Effect failure.\n throw new TypeError(`<If test> must be a predicate ${hint}`);\n }\n return elementDraft(\n \"If\",\n [attribute(\"test\", { kind: \"string\", value: printPredicateRef(props.test) })],\n normalizeChildren(props.children),\n );\n};\n\n/** `<Else>` — the trailing branch of an `<If>`; children only. */\nexport const Else = (props: { readonly children?: SmlChildren | undefined }): ElementNode =>\n elementDraft(\"Else\", [], normalizeChildren(props.children));\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"conditionals.js","names":[],"sources":["../src/conditionals.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport { evaluatePredicate, parsePredicate } from \"./predicate\";\n\n// Send-time / preview branch selection over compiled artifacts. The compiler\n// leaves both branches of every <If> in the artifact behind comment markers:\n//\n// <!--sml:if:n4 plan == 'pro'--> A <!--sml:else:n4--> B <!--sml:endif:n4-->\n//\n// (else segment omitted when the <If> has no <Else>). Node ids are unique per\n// document, so nested regions resolve by exact-id matching — no depth\n// counting. The same markers appear in the plain-text alt, so this runs\n// identically on both artifacts. It must run BEFORE {{}} interpolation: sent\n// mail never contains markers.\n\nconst IF_MARKER = \"<!--sml:if:\";\nconst MARKER_CLOSE = \"-->\";\n\nconst resolve = (content: string, variables: Record<string, unknown>): string => {\n const start = content.indexOf(IF_MARKER);\n if (start === -1) return content;\n const headEnd = content.indexOf(MARKER_CLOSE, start);\n if (headEnd === -1) {\n // Unterminated marker — impossible for compiler-produced artifacts. Leave\n // the region visible rather than guessing at a repair.\n return content;\n }\n const header = content.slice(start + IF_MARKER.length, headEnd);\n const spaceIndex = header.indexOf(\" \");\n const id = spaceIndex === -1 ? header : header.slice(0, spaceIndex);\n const predicateSource = spaceIndex === -1 ? \"\" : header.slice(spaceIndex + 1);\n\n const elseMarker = `<!--sml:else:${id}-->`;\n const endMarker = `<!--sml:endif:${id}-->`;\n const bodyStart = headEnd + MARKER_CLOSE.length;\n const endIndex = content.indexOf(endMarker, bodyStart);\n if (endIndex === -1) {\n // Missing endif — same corrupt-input posture as above.\n return content;\n }\n const elseCandidate = content.indexOf(elseMarker, bodyStart);\n const elseIndex = elseCandidate !== -1 && elseCandidate < endIndex ? elseCandidate : -1;\n\n // An unparsable predicate cannot occur in compiler output (the parser\n // validates and canonicalizes it); resolve it like a missing variable: false.\n const parsed = parsePredicate(predicateSource);\n const condition = Result.isSuccess(parsed) && evaluatePredicate(parsed.success, variables);\n\n const branchA = content.slice(bodyStart, elseIndex === -1 ? endIndex : elseIndex);\n const branchB = elseIndex === -1 ? \"\" : content.slice(elseIndex + elseMarker.length, endIndex);\n // Trim after nested resolution: dropped inner regions leave edge whitespace\n // of their own. Branch boundaries are block-level, so trimming is safe.\n const selected = resolve(condition ? branchA : branchB, variables).trim();\n const rest = resolve(content.slice(endIndex + endMarker.length), variables);\n return content.slice(0, start) + selected + rest;\n};\n\n/**\n * Selects <If> branches in a compiled artifact (html or plain-text alt) for\n * one recipient's variables and strips all sml: markers. Marker-free content\n * passes through unchanged.\n */\nexport const resolveArtifactConditionals = (\n content: string,\n variables: Record<string, unknown>,\n): string => {\n if (!content.includes(IF_MARKER)) return content;\n // Dropped branches leave blank runs behind; both artifacts use at most one\n // blank line legitimately, so deeper runs are always resolution residue.\n return resolve(content, variables).replace(/\\n{3,}/g, \"\\n\\n\");\n};\n"],"mappings":";;;AAeA,MAAM,YAAY;AAClB,MAAM,eAAe;AAErB,MAAM,WAAW,SAAiB,cAA+C;CAC/E,MAAM,QAAQ,QAAQ,QAAQ,SAAS;CACvC,IAAI,UAAU,IAAI,OAAO;CACzB,MAAM,UAAU,QAAQ,QAAQ,cAAc,KAAK;CACnD,IAAI,YAAY,IAGd,OAAO;CAET,MAAM,SAAS,QAAQ,MAAM,QAAQ,IAAkB,OAAO;CAC9D,MAAM,aAAa,OAAO,QAAQ,GAAG;CACrC,MAAM,KAAK,eAAe,KAAK,SAAS,OAAO,MAAM,GAAG,UAAU;CAClE,MAAM,kBAAkB,eAAe,KAAK,KAAK,OAAO,MAAM,aAAa,CAAC;CAE5E,MAAM,aAAa,gBAAgB,GAAG;CACtC,MAAM,YAAY,iBAAiB,GAAG;CACtC,MAAM,YAAY,UAAU;CAC5B,MAAM,WAAW,QAAQ,QAAQ,WAAW,SAAS;CACrD,IAAI,aAAa,IAEf,OAAO;CAET,MAAM,gBAAgB,QAAQ,QAAQ,YAAY,SAAS;CAC3D,MAAM,YAAY,kBAAkB,MAAM,gBAAgB,WAAW,gBAAgB;CAIrF,MAAM,SAAS,eAAe,eAAe;CAC7C,MAAM,YAAY,OAAO,UAAU,MAAM,KAAK,kBAAkB,OAAO,SAAS,SAAS;CAEzF,MAAM,UAAU,QAAQ,MAAM,WAAW,cAAc,KAAK,WAAW,SAAS;CAChF,MAAM,UAAU,cAAc,KAAK,KAAK,QAAQ,MAAM,YAAY,WAAW,QAAQ,QAAQ;CAG7F,MAAM,WAAW,QAAQ,YAAY,UAAU,SAAS,SAAS,CAAC,CAAC,KAAK;CACxE,MAAM,OAAO,QAAQ,QAAQ,MAAM,WAAW,UAAU,MAAM,GAAG,SAAS;CAC1E,OAAO,QAAQ,MAAM,GAAG,KAAK,IAAI,WAAW;AAC9C;;;;;;AAOA,MAAa,+BACX,SACA,cACW;CACX,IAAI,CAAC,QAAQ,SAAS,SAAS,GAAG,OAAO;CAGzC,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,QAAQ,WAAW,MAAM;AAC9D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnostics.js","names":[],"sources":["../src/diagnostics.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// Every SML diagnostic carries a source span and a message that names the fix.\n// Spans are absolute character offsets into the source; line/column rendering\n// is derived on demand via a newline-offset line map + binary search (the same\n// model as the TypeScript scanner), so tokens and nodes stay (start, end) pairs.\n\nexport const SpanSchema = Schema.Struct({\n start: Schema.Finite,\n end: Schema.Finite,\n});\n\nexport interface Span extends Schema.Schema.Type<typeof SpanSchema> {}\n\nexport const DiagnosticRepairSchema = Schema.Struct({\n kind: Schema.Literals([\"replace-class\", \"remove-class\", \"rewrite-node\", \"use-primitive\"]),\n raw: Schema.optional(Schema.String),\n suggestions: Schema.Array(Schema.String),\n explanation: Schema.String,\n});\n\nexport interface DiagnosticRepair extends Schema.Schema.Type<typeof DiagnosticRepairSchema> {}\n\nexport const DiagnosticSchema = Schema.Struct({\n code: Schema.String,\n message: Schema.String,\n span: SpanSchema,\n repair: Schema.optional(DiagnosticRepairSchema),\n});\n\nexport interface Diagnostic extends Schema.Schema.Type<typeof DiagnosticSchema> {}\n\nexport class SmlParseError extends Schema.TaggedErrorClass<SmlParseError>()(\"SmlParseError\", {\n diagnostics: Schema.Array(DiagnosticSchema),\n}) {\n override get message() {\n return this.diagnostics.map((diagnostic) => diagnostic.message).join(\"\\n\");\n }\n}\n\nexport class SmlCompileError extends Schema.TaggedErrorClass<SmlCompileError>()(\"SmlCompileError\", {\n diagnostics: Schema.Array(DiagnosticSchema),\n}) {\n override get message() {\n return this.diagnostics.map((diagnostic) => diagnostic.message).join(\"\\n\");\n }\n}\n\n/** Newline-offset line map. Build once per source, query with `lineColumn`. */\nexport const buildLineMap = (source: string): ReadonlyArray<number> => {\n const lineStarts = [0];\n for (let index = 0; index < source.length; index++) {\n if (source.charCodeAt(index) === 10 /* \\n */) lineStarts.push(index + 1);\n }\n return lineStarts;\n};\n\n/** 1-based line/column for an absolute offset, via binary search. */\nexport const lineColumn = (\n lineMap: ReadonlyArray<number>,\n offset: number,\n): { readonly line: number; readonly column: number } => {\n let low = 0;\n let high = lineMap.length - 1;\n while (low < high) {\n const middle = (low + high + 1) >> 1;\n if (lineMap[middle]! <= offset) low = middle;\n else high = middle - 1;\n }\n return { line: low + 1, column: offset - lineMap[low]! + 1 };\n};\n\n/** \"line 12:8 unknown class ...\" — the shape agents iterate on. */\nexport const formatDiagnostic = (source: string, diagnostic: Diagnostic): string => {\n const { line, column } = lineColumn(buildLineMap(source), diagnostic.span.start);\n return `line ${line}:${column} ${diagnostic.message}`;\n};\n"],"mappings":";;AAOA,MAAa,aAAa,OAAO,OAAO;CACtC,OAAO,OAAO;CACd,KAAK,OAAO;AACd,CAAC;AAID,MAAa,yBAAyB,OAAO,OAAO;CAClD,MAAM,OAAO,SAAS;EAAC;EAAiB;EAAgB;EAAgB;CAAe,CAAC;CACxF,KAAK,OAAO,SAAS,OAAO,MAAM;CAClC,aAAa,OAAO,MAAM,OAAO,MAAM;CACvC,aAAa,OAAO;AACtB,CAAC;AAID,MAAa,mBAAmB,OAAO,OAAO;CAC5C,MAAM,OAAO;CACb,SAAS,OAAO;CAChB,MAAM;CACN,QAAQ,OAAO,SAAS,sBAAsB;AAChD,CAAC;AAID,IAAa,gBAAb,cAAmC,OAAO,iBAAgC,CAAC,CAAC,iBAAiB,EAC3F,aAAa,OAAO,MAAM,gBAAgB,EAC5C,CAAC,CAAC,CAAC;CACD,IAAa,UAAU;EACrB,OAAO,KAAK,YAAY,KAAK,eAAe,WAAW,OAAO,CAAC,CAAC,KAAK,IAAI;CAC3E;AACF;AAEA,IAAa,kBAAb,cAAqC,OAAO,iBAAkC,CAAC,CAAC,mBAAmB,EACjG,aAAa,OAAO,MAAM,gBAAgB,EAC5C,CAAC,CAAC,CAAC;CACD,IAAa,UAAU;EACrB,OAAO,KAAK,YAAY,KAAK,eAAe,WAAW,OAAO,CAAC,CAAC,KAAK,IAAI;CAC3E;AACF;;AAGA,MAAa,gBAAgB,WAA0C;CACrE,MAAM,aAAa,CAAC,CAAC;CACrB,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SACzC,IAAI,OAAO,WAAW,KAAK,MAAM,IAAa,WAAW,KAAK,QAAQ,CAAC;CAEzE,OAAO;AACT;;AAGA,MAAa,cACX,SACA,WACuD;CACvD,IAAI,MAAM;CACV,IAAI,OAAO,QAAQ,SAAS;CAC5B,OAAO,MAAM,MAAM;EACjB,MAAM,SAAU,MAAM,OAAO,KAAM;EACnC,IAAI,QAAQ,WAAY,QAAQ,MAAM;OACjC,OAAO,SAAS;CACvB;CACA,OAAO;EAAE,MAAM,MAAM;EAAG,QAAQ,SAAS,QAAQ,OAAQ;CAAE;AAC7D;;AAGA,MAAa,oBAAoB,QAAgB,eAAmC;CAClF,MAAM,EAAE,MAAM,WAAW,WAAW,aAAa,MAAM,GAAG,WAAW,KAAK,KAAK;CAC/E,OAAO,QAAQ,KAAK,GAAG,OAAO,GAAG,WAAW;AAC9C"}
package/dist/emit.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"emit.js","names":[],"sources":["../src/emit.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport { isRootTag } from \"./ast\";\nimport type { Diagnostic } from \"./diagnostics\";\nimport type { JsxResult } from \"./jsx-runtime\";\nimport { renderTemplate, type Template, type TemplateVariable } from \"./render-template\";\nimport { createVariablesProxy } from \"./variables\";\n\n/**\n * emitModule — the module-level emit boundary for the TSX authoring layer\n * (design brief §7b). The vite plugin (M12) does the importing: TSX runs at the\n * USER's build time and this receives the already-evaluated module object (a\n * record of its exports). emitModule stays bundler-agnostic — it never touches\n * the filesystem, esbuild, or vite. It discovers the named template functions,\n * runs each through renderTemplate (print → parse validation), and additionally\n * renders each one TWICE to guard emission determinism (a build-time clock or\n * RNG produces a different second emission and is reported as unstable).\n *\n * Non-function and helper exports are ignored silently; a module with zero\n * templates is a distinct diagnostic (the common \"forgot to export the function\"\n * / \"imported the wrong file\" mistake).\n *\n * Template discovery\n *\n * Discovery is by PROBING, not by an explicit `isTemplate` marker. A module\n * export is a template iff it is a function that, when invoked with a fresh\n * recording proxy, returns a channel root element (JsxResult with\n * `kind: \"element\"` and an <Email>/<Sms>/<WhatsApp> tag). Rationale for\n * probing over a marker:\n *\n * • The canonical template shape (brief §7b) is a plain named exported\n * function of `Variables<T>` — requiring a `Foo.isTemplate = true` tag or a\n * `defineTemplate()` wrapper would add ceremony the brief explicitly avoids\n * (\"named exported function is the canonical template shape\").\n * • The proxy makes the probe cheap and side-effect-free for well-formed\n * templates: property accesses record variables and return refs; no network,\n * no I/O. Composition helpers that assemble sub-trees also just build AST.\n *\n * Soundness boundary (documented tradeoff): probing invokes the function, so a\n * helper export with arbitrary arity or side effects is executed once during\n * discovery. We contain the two failure shapes:\n *\n * • A helper that THROWS when called with a proxy (wrong argument shape, a\n * coercion-guard trip, an invalid-child assembly) is classified as a\n * non-template and skipped — the throw is swallowed only for the probe. This\n * is the one place we catch, and it is confined to classification.\n * • A helper that RETURNS a non-<Email> node (a bare <Container>, a Fragment,\n * a string) is likewise skipped.\n *\n * The corollary: a genuine template whose body throws (a real authoring bug —\n * coercing a variable, a malformed child) is misclassified as a helper and\n * silently skipped rather than surfacing its error. That is acceptable because\n * the same bug breaks the template's real emit regardless, and the zero-template\n * diagnostic fires when it was the module's only template. The probe re-runs the\n * function; renderTemplate runs it again on the confirmed templates, so a\n * template function must be a pure builder of its AST (no external side effects)\n * — which the recording-proxy model already requires.\n */\n\n/** Probe one export: is it a function that yields a channel root document? */\nconst isTemplateFunction = (value: unknown): value is Template<Record<string, unknown>> => {\n if (typeof value !== \"function\") return false;\n const { proxy } = createVariablesProxy<Record<string, unknown>>();\n let result: JsxResult;\n // oxlint-disable-next-line samva/no-try-catch-or-throw -- Discovery probe: a helper export that throws when invoked with a recording proxy is not a template. This catch is confined to classification (brief §7b probing decision); confirmed templates are rendered by renderTemplate, which lets authoring errors propagate.\n try {\n result = (value as Template<Record<string, unknown>>)(proxy);\n } catch {\n return false;\n }\n return (\n // oxlint-disable-next-line samva/no-hand-rolled-object-guard -- This narrows the already-typed JsxResult union; Predicate.isObject erases its discriminant and breaks the kind/tag narrowing below.\n result !== null &&\n typeof result === \"object\" &&\n result.kind === \"element\" &&\n isRootTag(result.tag)\n );\n};\n\n// ── Result shapes ────────────────────────────────────────────────────────────────\n\n/**\n * One template's emission outcome. `ok` is true only when the template rendered\n * cleanly with zero diagnostics (valid grammar AND stable emission). `source` is\n * the first render's canonical SML whenever the render itself succeeded — it is\n * present even for an unstable template (so the build can show what it produced),\n * and `undefined` only when the print → parse validation failed outright.\n */\nexport type EmittedTemplate =\n | {\n readonly name: string;\n readonly ok: true;\n readonly source: string;\n readonly variables: ReadonlyArray<TemplateVariable>;\n readonly diagnostics: readonly [];\n }\n | {\n readonly name: string;\n readonly ok: false;\n readonly source: string | undefined;\n readonly variables: ReadonlyArray<TemplateVariable>;\n readonly diagnostics: ReadonlyArray<Diagnostic>;\n };\n\n/** The aggregate emit result for a module. */\nexport interface EmittedModule {\n /** True when at least one template was found and every found template emitted cleanly. */\n readonly ok: boolean;\n readonly templates: ReadonlyArray<EmittedTemplate>;\n /**\n * Module-level diagnostics not tied to a single template — currently only the\n * zero-templates-found case. Per-template failures live on each entry.\n */\n readonly diagnostics: ReadonlyArray<Diagnostic>;\n}\n\nconst ZERO_TEMPLATES: Diagnostic = {\n code: \"no-templates\",\n message:\n \"no template exports found — a template is a named exported function returning a channel root element (e.g. `export function WelcomeEmail(v: Variables<{…}>) { return <Email>…</Email>; }`; <Sms> and <WhatsApp> roots work the same way)\",\n span: { start: 0, end: 0 },\n};\n\nconst unstableDiagnostic = (name: string): Diagnostic => ({\n code: \"unstable-emission\",\n message: `template \"${name}\" emits different source on each render — its output depends on non-deterministic build-time data (a clock or random source); templates must be pure functions of their variables`,\n span: { start: 0, end: 0 },\n});\n\n// ── The module emitter ────────────────────────────────────────────────────────────\n\n/**\n * Discovers template functions in an already-evaluated module object, renders\n * each to canonical SML with a determinism guard, and returns per-template\n * results plus an aggregate ok/fail. `moduleExports` is a plain record of the\n * module's exports (the vite plugin imports the TSX; this stays bundler-agnostic\n * and pure).\n *\n * Discovery is exhaustive and order-preserving over `Object.entries`: every\n * function export is probed, non-templates (helpers, constants, types-erased\n * exports) are ignored silently, and a module with zero templates yields a\n * single `no-templates` diagnostic with `ok: false`.\n */\nexport const emitModule = (moduleExports: Record<string, unknown>): EmittedModule => {\n const templates: Array<EmittedTemplate> = [];\n\n for (const [name, value] of Object.entries(moduleExports)) {\n if (!isTemplateFunction(value)) continue;\n templates.push(emitOne(name, value));\n }\n\n if (templates.length === 0) {\n return { ok: false, templates: [], diagnostics: [ZERO_TEMPLATES] };\n }\n return {\n ok: templates.every((template) => template.ok),\n templates,\n diagnostics: [],\n };\n};\n\n/**\n * Renders one confirmed template with the determinism guard. The first render is\n * the authoritative emission; the second exists only to detect instability\n * (a build-time clock or random source). Both renders run at BUILD time only\n * (the plugin) —\n * runtime send-path rendering never double-renders. For a deterministic template\n * the second render is pure redundant work at build time; that is the accepted\n * cost of the guard, paid once per template per build.\n */\nconst emitOne = (name: string, template: Template<Record<string, unknown>>): EmittedTemplate => {\n const first = renderTemplate(template);\n if (Result.isFailure(first)) {\n return {\n name,\n ok: false,\n source: undefined,\n variables: [],\n diagnostics: first.failure.diagnostics,\n };\n }\n const rendered = first.success;\n\n // Determinism guard: a second render must produce byte-identical source. A\n // grammar failure on the second pass would itself be an instability, so treat\n // any non-matching second emission (failure or differing source) as unstable.\n const second = renderTemplate(template);\n const stable = Result.isSuccess(second) && second.success.source === rendered.source;\n if (stable) {\n return {\n name,\n ok: true,\n source: rendered.source,\n variables: rendered.variables,\n diagnostics: [],\n };\n }\n\n // Unstable: keep the first render's source (for the build to display) but mark\n // the template not-ok so the module aggregate fails.\n return {\n name,\n ok: false,\n source: rendered.source,\n variables: rendered.variables,\n diagnostics: [unstableDiagnostic(name)],\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAM,sBAAsB,UAA+D;CACzF,IAAI,OAAO,UAAU,YAAY,OAAO;CACxC,MAAM,EAAE,UAAU,qBAA8C;CAChE,IAAI;CAEJ,IAAI;EACF,SAAU,MAA4C,KAAK;CAC7D,QAAQ;EACN,OAAO;CACT;CACA,OAEE,WAAW,QACX,OAAO,WAAW,YAClB,OAAO,SAAS,aAChB,UAAU,OAAO,GAAG;AAExB;AAuCA,MAAM,iBAA6B;CACjC,MAAM;CACN,SACE;CACF,MAAM;EAAE,OAAO;EAAG,KAAK;CAAE;AAC3B;AAEA,MAAM,sBAAsB,UAA8B;CACxD,MAAM;CACN,SAAS,aAAa,KAAK;CAC3B,MAAM;EAAE,OAAO;EAAG,KAAK;CAAE;AAC3B;;;;;;;;;;;;;AAgBA,MAAa,cAAc,kBAA0D;CACnF,MAAM,YAAoC,CAAC;CAE3C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,aAAa,GAAG;EACzD,IAAI,CAAC,mBAAmB,KAAK,GAAG;EAChC,UAAU,KAAK,QAAQ,MAAM,KAAK,CAAC;CACrC;CAEA,IAAI,UAAU,WAAW,GACvB,OAAO;EAAE,IAAI;EAAO,WAAW,CAAC;EAAG,aAAa,CAAC,cAAc;CAAE;CAEnE,OAAO;EACL,IAAI,UAAU,OAAO,aAAa,SAAS,EAAE;EAC7C;EACA,aAAa,CAAC;CAChB;AACF;;;;;;;;;;AAWA,MAAM,WAAW,MAAc,aAAiE;CAC9F,MAAM,QAAQ,eAAe,QAAQ;CACrC,IAAI,OAAO,UAAU,KAAK,GACxB,OAAO;EACL;EACA,IAAI;EACJ,QAAQ,KAAA;EACR,WAAW,CAAC;EACZ,aAAa,MAAM,QAAQ;CAC7B;CAEF,MAAM,WAAW,MAAM;CAKvB,MAAM,SAAS,eAAe,QAAQ;CAEtC,IADe,OAAO,UAAU,MAAM,KAAK,OAAO,QAAQ,WAAW,SAAS,QAE5E,OAAO;EACL;EACA,IAAI;EACJ,QAAQ,SAAS;EACjB,WAAW,SAAS;EACpB,aAAa,CAAC;CAChB;CAKF,OAAO;EACL;EACA,IAAI;EACJ,QAAQ,SAAS;EACjB,WAAW,SAAS;EACpB,aAAa,CAAC,mBAAmB,IAAI,CAAC;CACxC;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"footer.js","names":[],"sources":["../src/footer.ts"],"sourcesContent":["import type { TokenTable } from \"./theme.js\";\n\n// The injected email footer (settings cascade M5). A send whose resolved\n// `email.footer.enabled` is true appends this block beneath the body: the\n// sender's company identity (name, CAN-SPAM physical address, social links)\n// plus the per-recipient unsubscribe link. It is composed from the send's\n// effective `design.footer` content and the resolved theme token table, so the\n// footer inherits the org/domain/template brand (font + brand color) without a\n// separate styling system. Callers map their effective footer settings onto\n// `FooterContent`.\n\n/** The per-recipient unsubscribe URL is not known at accept time (the body is\n * shared across a send's deliveries), so the footer is composed with this\n * placeholder in the unsubscribe href and the plain-text tail. The email queue\n * processor substitutes each delivery's minted token URL at dispatch. The link\n * carries `ses:no-track` so click tracking never rewrites (and thereby breaks)\n * the placeholder. */\nexport const FOOTER_UNSUBSCRIBE_PLACEHOLDER = \"%samva:unsubscribe-url%\";\n\n/** One labelled social link. Structurally mirrors `DesignSocialLink`. */\nexport interface FooterSocialLink {\n readonly label: string;\n readonly url: string;\n}\n\n/** A structured postal address. Structurally mirrors `DesignAddress`. */\nexport interface FooterAddress {\n readonly street1: string;\n readonly street2?: string | undefined;\n readonly city: string;\n readonly region: string;\n readonly postalCode: string;\n readonly country: string;\n}\n\n/** The effective footer content. Structurally mirrors `DesignFooter`. */\nexport interface FooterContent {\n readonly companyName?: string | undefined;\n readonly address?: FooterAddress | undefined;\n readonly socialLinks?: ReadonlyArray<FooterSocialLink> | undefined;\n readonly customHtml?: string | undefined;\n}\n\n/** Which compliance-relevant footer fields the effective content is missing,\n * surfaced on the design read path for M6's dashboard nudge. */\nexport interface FooterCompleteness {\n /** No physical mailing address is set anywhere in the cascade (CAN-SPAM). */\n readonly missingAddress: boolean;\n}\n\nexport interface RenderedFooter {\n /** Table-based footer block, ready to inject before the body's `</body>`. */\n readonly html: string;\n /** Plain-text footer tail, appended to the text alternative. */\n readonly text: string;\n readonly completeness: FooterCompleteness;\n}\n\nconst escapeText = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll(\">\", \"&gt;\");\n\nconst escapeAttribute = (value: string): string =>\n value.replaceAll(\"&\", \"&amp;\").replaceAll(\"<\", \"&lt;\").replaceAll('\"', \"&quot;\");\n\n// Only http(s) and mailto links are emitted as anchors; anything else (e.g. a\n// `javascript:` scheme in org-authored social data) renders as inert text so a\n// footer can never smuggle an active link into delivered mail.\nconst isSafeUrl = (url: string): boolean => /^(https?:|mailto:)/i.test(url.trim());\n\nconst addressLines = (address: FooterAddress): ReadonlyArray<string> => {\n const region = [address.city, address.region, address.postalCode].filter(Boolean).join(\", \");\n return [address.street1, address.street2, region, address.country].filter(\n (line): line is string => typeof line === \"string\" && line.trim().length > 0,\n );\n};\n\n/**\n * Which compliance-relevant fields the footer content lacks — the signal M6's\n * dashboard nudge reads. Derivable from `design.footer` alone (no token table),\n * so the design read path can report it without composing the block. A\n * `customHtml` footer is treated as complete (the org owns its own content).\n */\nexport const footerCompleteness = (footer: FooterContent | undefined): FooterCompleteness => ({\n missingAddress:\n footer?.customHtml === undefined &&\n (footer?.address === undefined || addressLines(footer.address).length === 0),\n});\n\n/**\n * Compose the injected footer for one send. `unsubscribeUrl` is the placeholder\n * (`FOOTER_UNSUBSCRIBE_PLACEHOLDER`) when a group resolved and dispatch will\n * mint the per-recipient link, or `null` to omit the unsubscribe row entirely\n * (no group resolved). Never throws and never blocks: whatever content exists\n * renders, and the missing pieces are reported via `completeness`.\n */\nexport const renderFooterBlock = (input: {\n readonly footer: FooterContent;\n readonly tokens: TokenTable;\n readonly unsubscribeUrl: string | null;\n readonly unsubscribeLabel?: string | undefined;\n}): RenderedFooter => {\n const { footer, tokens, unsubscribeUrl } = input;\n const unsubscribeLabel = input.unsubscribeLabel ?? \"Unsubscribe\";\n\n // Muted-on-surface footer type, inheriting the brand body font and brand link\n // color from the resolved token table so an org theme carries into the footer.\n const fontFamily = tokens.fontFamilies[\"body\"] ?? tokens.fontFamilies[\"sans\"] ?? \"sans-serif\";\n const mutedColor = tokens.colors[\"gray-500\"] ?? tokens.colors[\"neutral-500\"] ?? \"#6a7282\";\n const linkColor = tokens.colors[\"brand\"] ?? mutedColor;\n const containerPx = tokens.containerPx[\"xl\"] ?? 600;\n const fontSizePx = tokens.text[\"xs\"]?.sizePx ?? 12;\n const lineHeightPx = tokens.text[\"xs\"]?.lineHeightPx ?? 18;\n\n const rows: string[] = [];\n const textLines: string[] = [];\n\n if (typeof footer.customHtml === \"string\" && footer.customHtml.trim().length > 0) {\n // Escape hatch: org-authored footer HTML replaces the structured block. The\n // compliance unsubscribe row below is still appended — its link is the\n // per-recipient token the org cannot author into static HTML.\n rows.push(`<tr><td style=\"padding:0 0 12px 0;\">${footer.customHtml}</td></tr>`);\n } else {\n if (typeof footer.companyName === \"string\" && footer.companyName.trim().length > 0) {\n rows.push(\n `<tr><td style=\"padding:0 0 4px 0;font-weight:600;\">${escapeText(footer.companyName)}</td></tr>`,\n );\n textLines.push(footer.companyName);\n }\n if (footer.address !== undefined) {\n const lines = addressLines(footer.address);\n if (lines.length > 0) {\n rows.push(\n `<tr><td style=\"padding:0 0 8px 0;\">${lines.map(escapeText).join(\"<br />\")}</td></tr>`,\n );\n textLines.push(lines.join(\", \"));\n }\n }\n if (footer.socialLinks !== undefined && footer.socialLinks.length > 0) {\n const links = footer.socialLinks.map((link) =>\n isSafeUrl(link.url)\n ? `<a href=\"${escapeAttribute(link.url.trim())}\" style=\"color:${linkColor};text-decoration:underline;\" ses:no-track>${escapeText(link.label)}</a>`\n : escapeText(link.label),\n );\n rows.push(\n `<tr><td style=\"padding:0 0 8px 0;\">${links.join('<span style=\"padding:0 6px;\">·</span>')}</td></tr>`,\n );\n textLines.push(footer.socialLinks.map((link) => `${link.label}: ${link.url}`).join(\" \"));\n }\n }\n\n if (unsubscribeUrl !== null) {\n rows.push(\n `<tr><td style=\"padding:0;\"><a href=\"${escapeAttribute(unsubscribeUrl)}\" style=\"color:${mutedColor};text-decoration:underline;\" ses:no-track>${escapeText(unsubscribeLabel)}</a></td></tr>`,\n );\n textLines.push(`${unsubscribeLabel}: ${unsubscribeUrl}`);\n }\n\n const html =\n rows.length === 0\n ? \"\"\n : [\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"width:100%;border-collapse:collapse;\">`,\n `<tr><td align=\"center\" style=\"padding:32px 16px;\">`,\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"${containerPx}\" style=\"width:100%;max-width:${containerPx}px;border-collapse:collapse;font-family:${fontFamily};font-size:${fontSizePx}px;line-height:${lineHeightPx}px;color:${mutedColor};text-align:center;\">`,\n ...rows,\n `</table>`,\n `</td></tr>`,\n `</table>`,\n ].join(\"\");\n\n return {\n html,\n text: textLines.length === 0 ? \"\" : `\\n\\n---\\n${textLines.join(\"\\n\")}`,\n completeness: footerCompleteness(footer),\n };\n};\n"],"mappings":";;;;;;;AAiBA,MAAa,iCAAiC;AAyC9C,MAAM,cAAc,UAClB,MAAM,WAAW,KAAK,OAAO,CAAC,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,WAAW,KAAK,MAAM;AAE/E,MAAM,mBAAmB,UACvB,MAAM,WAAW,KAAK,OAAO,CAAC,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,WAAW,MAAK,QAAQ;AAKjF,MAAM,aAAa,QAAyB,sBAAsB,KAAK,IAAI,KAAK,CAAC;AAEjF,MAAM,gBAAgB,YAAkD;CACtE,MAAM,SAAS;EAAC,QAAQ;EAAM,QAAQ;EAAQ,QAAQ;CAAU,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;CAC3F,OAAO;EAAC,QAAQ;EAAS,QAAQ;EAAS;EAAQ,QAAQ;CAAO,CAAC,CAAC,QAChE,SAAyB,OAAO,SAAS,YAAY,KAAK,KAAK,CAAC,CAAC,SAAS,CAC7E;AACF;;;;;;;AAQA,MAAa,sBAAsB,YAA2D,EAC5F,gBACE,QAAQ,eAAe,KAAA,MACtB,QAAQ,YAAY,KAAA,KAAa,aAAa,OAAO,OAAO,CAAC,CAAC,WAAW,GAC9E;;;;;;;;AASA,MAAa,qBAAqB,UAKZ;CACpB,MAAM,EAAE,QAAQ,QAAQ,mBAAmB;CAC3C,MAAM,mBAAmB,MAAM,oBAAoB;CAInD,MAAM,aAAa,OAAO,aAAa,WAAW,OAAO,aAAa,WAAW;CACjF,MAAM,aAAa,OAAO,OAAO,eAAe,OAAO,OAAO,kBAAkB;CAChF,MAAM,YAAY,OAAO,OAAO,YAAY;CAC5C,MAAM,cAAc,OAAO,YAAY,SAAS;CAChD,MAAM,aAAa,OAAO,KAAK,KAAK,EAAE,UAAU;CAChD,MAAM,eAAe,OAAO,KAAK,KAAK,EAAE,gBAAgB;CAExD,MAAM,OAAiB,CAAC;CACxB,MAAM,YAAsB,CAAC;CAE7B,IAAI,OAAO,OAAO,eAAe,YAAY,OAAO,WAAW,KAAK,CAAC,CAAC,SAAS,GAI7E,KAAK,KAAK,uCAAuC,OAAO,WAAW,WAAW;MACzE;EACL,IAAI,OAAO,OAAO,gBAAgB,YAAY,OAAO,YAAY,KAAK,CAAC,CAAC,SAAS,GAAG;GAClF,KAAK,KACH,sDAAsD,WAAW,OAAO,WAAW,EAAE,WACvF;GACA,UAAU,KAAK,OAAO,WAAW;EACnC;EACA,IAAI,OAAO,YAAY,KAAA,GAAW;GAChC,MAAM,QAAQ,aAAa,OAAO,OAAO;GACzC,IAAI,MAAM,SAAS,GAAG;IACpB,KAAK,KACH,sCAAsC,MAAM,IAAI,UAAU,CAAC,CAAC,KAAK,QAAQ,EAAE,WAC7E;IACA,UAAU,KAAK,MAAM,KAAK,IAAI,CAAC;GACjC;EACF;EACA,IAAI,OAAO,gBAAgB,KAAA,KAAa,OAAO,YAAY,SAAS,GAAG;GACrE,MAAM,QAAQ,OAAO,YAAY,KAAK,SACpC,UAAU,KAAK,GAAG,IACd,YAAY,gBAAgB,KAAK,IAAI,KAAK,CAAC,EAAE,iBAAiB,UAAU,4CAA4C,WAAW,KAAK,KAAK,EAAE,QAC3I,WAAW,KAAK,KAAK,CAC3B;GACA,KAAK,KACH,sCAAsC,MAAM,KAAK,yCAAuC,EAAE,WAC5F;GACA,UAAU,KAAK,OAAO,YAAY,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC;EAC1F;CACF;CAEA,IAAI,mBAAmB,MAAM;EAC3B,KAAK,KACH,uCAAuC,gBAAgB,cAAc,EAAE,iBAAiB,WAAW,4CAA4C,WAAW,gBAAgB,EAAE,eAC9K;EACA,UAAU,KAAK,GAAG,iBAAiB,IAAI,gBAAgB;CACzD;CAeA,OAAO;EACL,MAbA,KAAK,WAAW,IACZ,KACA;GACE;GACA;GACA,gFAAgF,YAAY,gCAAgC,YAAY,0CAA0C,WAAW,aAAa,WAAW,iBAAiB,aAAa,WAAW,WAAW;GACzQ,GAAG;GACH;GACA;GACA;EACF,CAAC,CAAC,KAAK,EAAE;EAIb,MAAM,UAAU,WAAW,IAAI,KAAK,YAAY,UAAU,KAAK,IAAI;EACnE,cAAc,mBAAmB,MAAM;CACzC;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"interpolate.js","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// Send-time `{{name}}` / `${name}` interpolation. Originally ported from\n// @samva/document (variables.ts); now a single left-to-right pass over both\n// syntaxes (see `variableReferencePattern`) so a substituted value is never\n// re-scanned — a variable whose value itself contains `{{x}}`/`${x}` is emitted\n// literally instead of being recursively interpolated. Channel-agnostic: it\n// runs over compiled email artifacts, sms bodies, and whatsapp component text\n// alike, always AFTER resolveArtifactConditionals has selected branches and\n// stripped markers.\n\nconst limitedString = (maxLength: number) => Schema.String.check(Schema.isMaxLength(maxLength));\n\nexport const DocumentVariableTypeSchema = Schema.Literals([\n \"string\",\n \"number\",\n \"boolean\",\n \"date\",\n \"array\",\n \"object\",\n]);\n\nexport type DocumentVariableType = typeof DocumentVariableTypeSchema.Type;\n\n/** One entry of a template's declared variable schema. */\nexport const DocumentVariableSchema = Schema.Struct({\n name: limitedString(128),\n type: DocumentVariableTypeSchema,\n required: Schema.Boolean,\n default: Schema.optional(Schema.Unknown),\n description: Schema.optional(limitedString(512)),\n});\n\nexport interface DocumentVariable extends Schema.Schema.Type<typeof DocumentVariableSchema> {}\n\nexport const variableReferencePattern = /\\$\\{\\s*\\/?([\\w.]+)\\s*\\}|\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g;\n\nexport const getVariableValue = (\n variables: Record<string, unknown>,\n variableSchema: ReadonlyArray<DocumentVariable>,\n name: string,\n): unknown => {\n const schema = variableSchema.find((item) => item.name === name);\n const value = variables[name];\n if (value !== undefined && value !== null) return value;\n return schema?.default;\n};\n\nexport const stringifyVariable = (value: unknown): string => {\n if (value === undefined || value === null) return \"\";\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n};\n\nexport interface InterpolationResult {\n readonly rendered: string;\n /** Required variables that resolved to nothing (no value, no default). */\n readonly missing: ReadonlySet<string>;\n}\n\nexport const interpolate = (\n text: string,\n variables: Record<string, unknown>,\n variableSchema: ReadonlyArray<DocumentVariable>,\n): InterpolationResult => {\n const missing = new Set<string>();\n const substitute = (name: string): string => {\n const value = getVariableValue(variables, variableSchema, name);\n const schema = variableSchema.find((item) => item.name === name);\n if ((value === undefined || value === null) && schema?.required) missing.add(name);\n return stringifyVariable(value);\n };\n // One pass over the combined pattern: group 1 is the `${…}` name, group 2 the\n // `{{…}}` name; exactly one is set per match. `replace` resets the shared\n // pattern's lastIndex, so sharing it with `variableReferences` is safe.\n const rendered = text.replace(\n variableReferencePattern,\n (_match, dollarName: string | undefined, braceName: string | undefined) =>\n substitute(dollarName ?? braceName ?? \"\"),\n );\n return { rendered, missing };\n};\n\nexport const variableReferences = (text: string): Set<string> => {\n const names = new Set<string>();\n for (const match of text.matchAll(variableReferencePattern)) {\n const name = match[1] ?? match[2];\n if (name !== undefined && name !== \"\") names.add(name);\n }\n return names;\n};\n\nexport const addVariableReferences = (target: Set<string>, text: string): void => {\n variableReferences(text).forEach((name) => target.add(name));\n};\n"],"mappings":";;AAWA,MAAM,iBAAiB,cAAsB,OAAO,OAAO,MAAM,OAAO,YAAY,SAAS,CAAC;AAE9F,MAAa,6BAA6B,OAAO,SAAS;CACxD;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAKD,MAAa,yBAAyB,OAAO,OAAO;CAClD,MAAM,cAAc,GAAG;CACvB,MAAM;CACN,UAAU,OAAO;CACjB,SAAS,OAAO,SAAS,OAAO,OAAO;CACvC,aAAa,OAAO,SAAS,cAAc,GAAG,CAAC;AACjD,CAAC;AAID,MAAa,2BAA2B;AAExC,MAAa,oBACX,WACA,gBACA,SACY;CACZ,MAAM,SAAS,eAAe,MAAM,SAAS,KAAK,SAAS,IAAI;CAC/D,MAAM,QAAQ,UAAU;CACxB,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,OAAO,QAAQ;AACjB;AAEA,MAAa,qBAAqB,UAA2B;CAC3D,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,iBAAiB,MAAM,OAAO,MAAM,YAAY;CACpD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,IAAI;CAChD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAC1D,OAAO,OAAO,KAAK;AACrB;AAQA,MAAa,eACX,MACA,WACA,mBACwB;CACxB,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,cAAc,SAAyB;EAC3C,MAAM,QAAQ,iBAAiB,WAAW,gBAAgB,IAAI;EAC9D,MAAM,SAAS,eAAe,MAAM,SAAS,KAAK,SAAS,IAAI;EAC/D,KAAK,UAAU,KAAA,KAAa,UAAU,SAAS,QAAQ,UAAU,QAAQ,IAAI,IAAI;EACjF,OAAO,kBAAkB,KAAK;CAChC;CASA,OAAO;EAAE,UALQ,KAAK,QACpB,2BACC,QAAQ,YAAgC,cACvC,WAAW,cAAc,aAAa,EAAE,CAE5B;EAAG;CAAQ;AAC7B;AAEA,MAAa,sBAAsB,SAA8B;CAC/D,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,SAAS,KAAK,SAAS,wBAAwB,GAAG;EAC3D,MAAM,OAAO,MAAM,MAAM,MAAM;EAC/B,IAAI,SAAS,KAAA,KAAa,SAAS,IAAI,MAAM,IAAI,IAAI;CACvD;CACA,OAAO;AACT;AAEA,MAAa,yBAAyB,QAAqB,SAAuB;CAChF,mBAAmB,IAAI,CAAC,CAAC,SAAS,SAAS,OAAO,IAAI,IAAI,CAAC;AAC7D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"jsx-dev-runtime.js","names":[],"sources":["../src/jsx-dev-runtime.ts"],"sourcesContent":["import type { Component } from \"./jsx-runtime\";\nimport { Fragment, jsx, type JsxResult } from \"./jsx-runtime\";\n\n// Development JSX runtime. The dev transform emits `jsxDEV(...)` with extra\n// debug arguments (key, isStaticChildren, source, self) that we don't need —\n// SML has no runtime reconciliation, only build-time construction. We forward to\n// the production `jsx`. Fragment + the JSX namespace re-export so tooling that\n// imports the dev runtime sees the same types.\n\nexport { Fragment } from \"./jsx-runtime\";\nexport type { JSX } from \"./jsx-runtime\";\n\ninterface JsxDevProps {\n readonly children?: unknown;\n readonly [key: string]: unknown;\n}\n\nexport const jsxDEV = (\n type: Component<JsxDevProps> | typeof Fragment,\n props: JsxDevProps,\n): JsxResult => jsx(type, props);\n"],"mappings":";;AAiBA,MAAa,UACX,MACA,UACc,IAAI,MAAM,KAAK"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/jsx-runtime.ts"],"sourcesContent":["import { Predicate } from \"effect\";\n\nimport type { Attribute, ElementNode, InterpolationNode, SmlNode, TextNode } from \"./ast\";\nimport { isVariableRef, VARIABLE_REF, type VariableRef } from \"./variables\";\n\n// Automatic-runtime JSX factory for the TSX authoring layer (design brief §7b).\n//\n// This is EVALUATION, not parsing: `template.tsx` runs at the user's build time,\n// its JSX transforms to jsx()/jsxs() calls, and those construct M1 AST drafts.\n// We never statically analyse TSX — any TypeScript that runs is accepted and the\n// OUTPUT is validated against the closed grammar (print → parse in renderTemplate).\n//\n// Drafts are real `SmlNode` shapes with placeholder id/span: the canonical path\n// is print → parse, so parse assigns the authoritative ids and spans and runs the\n// full content-model validation. Nothing here re-implements the grammar; the\n// typed components in ./components restrict what callers can construct, and the\n// parser is the backstop for hand-assembled or dynamically-built trees.\n\nconst PLACEHOLDER_SPAN = { start: 0, end: 0 } as const;\n\n/** A component is a plain function of its props that returns a draft node. */\nexport type Component<P> = (props: P) => SmlNode;\n\n/**\n * What may appear in a component's `children`. Booleans/null/undefined are\n * skipped (so `{cond && <X/>}` and `{cond ? <A/> : null}` are BUILD-TIME\n * conditionals that evaluate away); strings/numbers become text; VariableRefs\n * become `{{interpolation}}` nodes; arrays flatten; nested nodes nest. Anything\n * else is a typed authoring error.\n */\nexport type SmlChild =\n | SmlNode\n | FragmentDraft\n | VariableRef<unknown>\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlyArray<SmlChild>;\n\nexport type SmlChildren = SmlChild | ReadonlyArray<SmlChild>;\n\n/** Fragment children pass through un-wrapped — a Fragment is not an element. */\nexport interface FragmentDraft {\n readonly kind: \"fragment\";\n readonly children: ReadonlyArray<SmlNode>;\n}\n\nexport type JsxResult = SmlNode | FragmentDraft;\n\nconst isFragmentDraft = (value: unknown): value is FragmentDraft =>\n Predicate.hasProperty(value, \"kind\") && value.kind === \"fragment\";\n\nconst isSmlNode = (value: unknown): value is SmlNode => {\n if (!Predicate.hasProperty(value, \"kind\")) return false;\n const kind = value.kind;\n return kind === \"element\" || kind === \"text\" || kind === \"interpolation\";\n};\n\nconst textNode = (value: string): TextNode => ({\n kind: \"text\",\n id: \"\",\n value,\n span: PLACEHOLDER_SPAN,\n});\n\nconst interpolationNode = (ref: VariableRef<unknown>): InterpolationNode => ({\n kind: \"interpolation\",\n id: \"\",\n name: ref[VARIABLE_REF].path,\n // TSX authoring uses curly syntax exclusively; `${name}` is a source-only\n // alternate the parser still accepts, never emitted from code.\n syntax: \"curly\",\n span: PLACEHOLDER_SPAN,\n});\n\n/**\n * Flattens and normalizes a component's raw `children` prop into AST child\n * nodes. Fragments splice their children in; booleans/null/undefined drop out.\n */\nexport const normalizeChildren = (children: unknown): Array<SmlNode> => {\n const out: Array<SmlNode> = [];\n const push = (child: unknown): void => {\n if (child === null || child === undefined || typeof child === \"boolean\") return;\n if (typeof child === \"string\") {\n out.push(textNode(child));\n return;\n }\n if (typeof child === \"number\") {\n out.push(textNode(String(child)));\n return;\n }\n if (Array.isArray(child)) {\n for (const item of child) push(item);\n return;\n }\n if (isVariableRef(child)) {\n out.push(interpolationNode(child));\n return;\n }\n if (isFragmentDraft(child)) {\n for (const node of child.children) out.push(node);\n return;\n }\n if (isSmlNode(child)) {\n out.push(child);\n return;\n }\n // oxlint-disable-next-line samva/no-try-catch-or-throw, samva/no-error-constructor -- User-build authoring boundary: this runs in the user's TSX build (not Effect domain); an invalid child is a programmer error that must fail their build loudly, not become a typed Effect failure.\n throw new TypeError(\n `invalid SML child: ${describe(child)} — children may be text, numbers, variables, elements, arrays of these, or false/null/undefined (which are skipped)`,\n );\n };\n push(children);\n return out;\n};\n\nconst describe = (value: unknown): string => {\n if (typeof value === \"function\")\n return \"a function (did you pass a component instead of an element?)\";\n if (typeof value === \"object\") return `an object (${JSON.stringify(value)})`;\n return `${typeof value} (${JSON.stringify(value)})`;\n};\n\n// ── Element draft builder (used by ./components) ────────────────────────────────\n\n/**\n * Builds an element draft from a tag, attribute list (already in authored\n * order), and normalized children. Kept here so the runtime owns the AST shape;\n * ./components maps typed props onto attributes and calls this.\n */\nexport const elementDraft = (\n tag: ElementNode[\"tag\"],\n attributes: ReadonlyArray<Attribute>,\n children: ReadonlyArray<SmlNode>,\n): ElementNode => ({\n kind: \"element\",\n id: \"\",\n tag,\n attributes,\n children,\n span: PLACEHOLDER_SPAN,\n});\n\n// ── jsx / jsxs / Fragment (automatic runtime contract) ──────────────────────────\n\ninterface JsxProps {\n readonly children?: unknown;\n readonly [key: string]: unknown;\n}\n\n/**\n * The automatic-runtime entry point. `type` is always one of our typed\n * component functions (or `Fragment`); we invoke it with its props. The JSX\n * transform routes single-child elements here and multi-child elements to\n * `jsxs` — behaviour is identical for us (components normalize their own\n * children), so `jsxs` re-exports this.\n */\nexport const jsx = (type: Component<JsxProps> | typeof Fragment, props: JsxProps): JsxResult => {\n if (type === Fragment) {\n return { kind: \"fragment\", children: normalizeChildren(props.children) };\n }\n return (type as Component<JsxProps>)(props);\n};\n\nexport const jsxs = jsx;\n\n/** Groups children without an element wrapper. Its children splice into the parent. */\nexport const Fragment = Symbol.for(\"@samva/markup/Fragment\");\n\n// ── JSX namespace (automatic runtime contract) ──────────────────────────────────\n//\n// With `jsxImportSource: \"@samva/markup\"` the TSX transform resolves types from\n// this module's `JSX` namespace. Our vocabulary is all capitalized components\n// (functions from ./components), so there are no lowercase intrinsics — a `<div>`\n// simply has no component and fails to resolve, which is the closed grammar\n// enforced at the type level. Every element evaluates to an `SmlNode`.\n\nexport namespace JSX {\n export type Element = JsxResult;\n export type ElementType = Component<never> | typeof Fragment;\n export interface ElementChildrenAttribute {\n readonly children: object;\n }\n // No lowercase host elements: the grammar is closed and component-only, so\n // `<div>` has no matching intrinsic and fails to type-resolve.\n export type IntrinsicElements = Record<string, never>;\n}\n"],"mappings":";;;AAkBA,MAAM,mBAAmB;CAAE,OAAO;CAAG,KAAK;AAAE;AAiC5C,MAAM,mBAAmB,UACvB,UAAU,YAAY,OAAO,MAAM,KAAK,MAAM,SAAS;AAEzD,MAAM,aAAa,UAAqC;CACtD,IAAI,CAAC,UAAU,YAAY,OAAO,MAAM,GAAG,OAAO;CAClD,MAAM,OAAO,MAAM;CACnB,OAAO,SAAS,aAAa,SAAS,UAAU,SAAS;AAC3D;AAEA,MAAM,YAAY,WAA6B;CAC7C,MAAM;CACN,IAAI;CACJ;CACA,MAAM;AACR;AAEA,MAAM,qBAAqB,SAAkD;CAC3E,MAAM;CACN,IAAI;CACJ,MAAM,IAAI,aAAa,CAAC;CAGxB,QAAQ;CACR,MAAM;AACR;;;;;AAMA,MAAa,qBAAqB,aAAsC;CACtE,MAAM,MAAsB,CAAC;CAC7B,MAAM,QAAQ,UAAyB;EACrC,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,UAAU,WAAW;EACzE,IAAI,OAAO,UAAU,UAAU;GAC7B,IAAI,KAAK,SAAS,KAAK,CAAC;GACxB;EACF;EACA,IAAI,OAAO,UAAU,UAAU;GAC7B,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,CAAC;GAChC;EACF;EACA,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI;GACnC;EACF;EACA,IAAI,cAAc,KAAK,GAAG;GACxB,IAAI,KAAK,kBAAkB,KAAK,CAAC;GACjC;EACF;EACA,IAAI,gBAAgB,KAAK,GAAG;GAC1B,KAAK,MAAM,QAAQ,MAAM,UAAU,IAAI,KAAK,IAAI;GAChD;EACF;EACA,IAAI,UAAU,KAAK,GAAG;GACpB,IAAI,KAAK,KAAK;GACd;EACF;EAEA,MAAM,IAAI,UACR,sBAAsB,SAAS,KAAK,EAAE,oHACxC;CACF;CACA,KAAK,QAAQ;CACb,OAAO;AACT;AAEA,MAAM,YAAY,UAA2B;CAC3C,IAAI,OAAO,UAAU,YACnB,OAAO;CACT,IAAI,OAAO,UAAU,UAAU,OAAO,cAAc,KAAK,UAAU,KAAK,EAAE;CAC1E,OAAO,GAAG,OAAO,MAAM,IAAI,KAAK,UAAU,KAAK,EAAE;AACnD;;;;;;AASA,MAAa,gBACX,KACA,YACA,cACiB;CACjB,MAAM;CACN,IAAI;CACJ;CACA;CACA;CACA,MAAM;AACR;;;;;;;;AAgBA,MAAa,OAAO,MAA6C,UAA+B;CAC9F,IAAI,SAAS,UACX,OAAO;EAAE,MAAM;EAAY,UAAU,kBAAkB,MAAM,QAAQ;CAAE;CAEzE,OAAQ,KAA6B,KAAK;AAC5C;AAEA,MAAa,OAAO;;AAGpB,MAAa,WAAW,OAAO,IAAI,wBAAwB"}
package/dist/lexer.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"lexer.js","names":[],"sources":["../src/lexer.ts"],"sourcesContent":["// Hand-rolled SML lexer, modeled on the TypeScript scanner's JSX mode and\n// sucrase's strict-input discipline: charCode switch dispatch, (kind, start,\n// end) tokens with lazy substring materialization, and two parser-driven scan\n// modes (text between tags, tokens inside tags). Runs per-keystroke in the\n// editor — no regexes, no throw, no allocation beyond the token objects.\n\nconst CHAR_TAB = 9;\nconst CHAR_LINE_FEED = 10;\nconst CHAR_CARRIAGE_RETURN = 13;\nconst CHAR_SPACE = 32;\nconst CHAR_BANG = 33;\nconst CHAR_DOUBLE_QUOTE = 34;\nconst CHAR_HASH = 35;\nconst CHAR_DOLLAR = 36;\nconst CHAR_AMPERSAND = 38;\nconst CHAR_SINGLE_QUOTE = 39;\nconst CHAR_SLASH = 47;\nconst CHAR_SEMICOLON = 59;\nconst CHAR_LESS_THAN = 60;\nconst CHAR_EQUALS = 61;\nconst CHAR_GREATER_THAN = 62;\nconst CHAR_OPEN_BRACE = 123;\nconst CHAR_CLOSE_BRACE = 125;\nconst CHAR_DOT = 46;\nconst CHAR_UNDERSCORE = 95;\nconst CHAR_0 = 48;\nconst CHAR_9 = 57;\nconst CHAR_UPPER_A = 65;\nconst CHAR_UPPER_Z = 90;\nconst CHAR_LOWER_A = 97;\nconst CHAR_LOWER_X = 120;\nconst CHAR_LOWER_Z = 122;\n\nexport const TokenKind = {\n EndOfFile: 0,\n /** `<` opening a start tag (text mode). */\n LessThan: 1,\n /** `</` opening a close tag (text mode). */\n LessThanSlash: 2,\n GreaterThan: 3,\n SlashGreaterThan: 4,\n Identifier: 5,\n Equals: 6,\n /** Quoted attribute value; getStringValue decodes entities. */\n String: 7,\n OpenBrace: 8,\n CloseBrace: 9,\n /** Unsigned integer inside a brace attribute value. */\n Number: 10,\n /** Raw text run; getTextValue decodes entities. */\n Text: 11,\n /** `{{name}}` or `${name}`; name/syntax carried on the token. */\n Interpolation: 12,\n /** Lexically invalid input; error carried on the token. */\n Unknown: 13,\n} as const;\n\nexport type TokenKind = (typeof TokenKind)[keyof typeof TokenKind];\n\nexport interface TokenError {\n readonly code: string;\n readonly message: string;\n}\n\nexport interface Token {\n readonly kind: TokenKind;\n readonly start: number;\n readonly end: number;\n /** Interpolation only. */\n readonly name?: string | undefined;\n /** Interpolation only. */\n readonly syntax?: \"curly\" | \"dollar\" | undefined;\n /** Unknown only. */\n readonly error?: TokenError | undefined;\n}\n\nconst isWhitespace = (code: number): boolean =>\n code === CHAR_SPACE ||\n code === CHAR_TAB ||\n code === CHAR_LINE_FEED ||\n code === CHAR_CARRIAGE_RETURN;\n\nconst isDigit = (code: number): boolean => code >= CHAR_0 && code <= CHAR_9;\n\nconst isLetter = (code: number): boolean =>\n (code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z) || (code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z);\n\nconst isNameChar = (code: number): boolean =>\n isLetter(code) || isDigit(code) || code === CHAR_UNDERSCORE || code === CHAR_DOT;\n\nconst NAMED_ENTITIES: Readonly<Record<string, string>> = {\n amp: \"&\",\n lt: \"<\",\n gt: \">\",\n quot: '\"',\n apos: \"'\",\n nbsp: \" \",\n};\n\n/**\n * Validates the entity starting at `offset` (which points at `&`).\n * Returns the end offset (past `;`) or -1 when malformed.\n */\nconst scanEntity = (source: string, offset: number): number => {\n let index = offset + 1;\n if (source.charCodeAt(index) === CHAR_HASH) {\n index++;\n const hex = source.charCodeAt(index) === CHAR_LOWER_X;\n if (hex) index++;\n const digitsStart = index;\n while (index < source.length) {\n const code = source.charCodeAt(index);\n const isHexDigit =\n isDigit(code) || (hex && ((code >= 97 && code <= 102) || (code >= 65 && code <= 70)));\n if (!isHexDigit) break;\n index++;\n }\n if (index === digitsStart) return -1;\n return source.charCodeAt(index) === CHAR_SEMICOLON ? index + 1 : -1;\n }\n const nameStart = index;\n while (index < source.length && isLetter(source.charCodeAt(index))) index++;\n const name = source.slice(nameStart, index);\n if (!(name in NAMED_ENTITIES)) return -1;\n return source.charCodeAt(index) === CHAR_SEMICOLON ? index + 1 : -1;\n};\n\n/** Decodes entities in text that already passed `scanEntity` validation. */\nexport const decodeEntities = (raw: string): string => {\n if (!raw.includes(\"&\")) return raw;\n let result = \"\";\n let index = 0;\n while (index < raw.length) {\n const ampersand = raw.indexOf(\"&\", index);\n if (ampersand === -1) {\n result += raw.slice(index);\n break;\n }\n result += raw.slice(index, ampersand);\n const semicolon = raw.indexOf(\";\", ampersand);\n if (semicolon === -1) {\n // No terminating ';' — the scanner rejects unterminated entities before\n // this runs, but never rely on that here: emit the rest literally\n // instead of resetting the cursor into an infinite loop.\n result += raw.slice(ampersand);\n break;\n }\n const body = raw.slice(ampersand + 1, semicolon);\n if (body.startsWith(\"#x\") || body.startsWith(\"#X\")) {\n result += String.fromCodePoint(Number.parseInt(body.slice(2), 16));\n } else if (body.startsWith(\"#\")) {\n result += String.fromCodePoint(Number.parseInt(body.slice(1), 10));\n } else {\n result += NAMED_ENTITIES[body]!;\n }\n index = semicolon + 1;\n }\n return result;\n};\n\nexport interface Lexer {\n /** Current scan position (start of the next token). */\n readonly position: () => number;\n /** Scan one token in text mode (between tags). */\n readonly scanTextToken: () => Token;\n /** Scan one token in tag mode (inside `<...>`), skipping whitespace. */\n readonly scanTagToken: () => Token;\n /** Lazy raw source slice for a token. */\n readonly getText: (token: Token) => string;\n /** Entity-decoded value of a Text token. */\n readonly getTextValue: (token: Token) => string;\n /** Entity-decoded contents of a String token (without quotes). */\n readonly getStringValue: (token: Token) => string;\n}\n\nexport const createLexer = (source: string): Lexer => {\n let position = 0;\n const length = source.length;\n\n const token = (kind: TokenKind, start: number, extra?: Partial<Token>): Token => {\n const created: Token = { kind, start, end: position, ...extra };\n return created;\n };\n\n const unknown = (start: number, code: string, message: string): Token => {\n if (position === start) position = Math.min(start + 1, length);\n return token(TokenKind.Unknown, start, { error: { code, message } });\n };\n\n const scanInterpolation = (start: number, syntax: \"curly\" | \"dollar\"): Token => {\n // Preserves today's interpolation grammar exactly (packages/document\n // variables.ts): {{ name }} and ${ /name } with optional inner whitespace,\n // names of [\\w.]+, and a tolerated leading slash in the dollar form.\n position = start + 2;\n while (position < length && isWhitespace(source.charCodeAt(position))) position++;\n if (syntax === \"dollar\" && source.charCodeAt(position) === CHAR_SLASH) position++;\n const nameStart = position;\n while (position < length && isNameChar(source.charCodeAt(position))) position++;\n const name = source.slice(nameStart, position);\n while (position < length && isWhitespace(source.charCodeAt(position))) position++;\n const closed =\n syntax === \"curly\"\n ? source.charCodeAt(position) === CHAR_CLOSE_BRACE &&\n source.charCodeAt(position + 1) === CHAR_CLOSE_BRACE\n : source.charCodeAt(position) === CHAR_CLOSE_BRACE;\n if (name.length === 0 || !closed) {\n position = start;\n const example = syntax === \"curly\" ? \"{{variableName}}\" : \"${variableName}\";\n return unknown(\n start,\n \"invalid-interpolation\",\n `malformed interpolation — write ${example} using letters, digits, underscore, or dot`,\n );\n }\n position += syntax === \"curly\" ? 2 : 1;\n return token(TokenKind.Interpolation, start, { name, syntax });\n };\n\n const scanTextToken = (): Token => {\n const start = position;\n if (position >= length) return token(TokenKind.EndOfFile, start);\n\n const first = source.charCodeAt(position);\n if (first === CHAR_LESS_THAN) {\n const next = source.charCodeAt(position + 1);\n if (next === CHAR_SLASH) {\n position += 2;\n return token(TokenKind.LessThanSlash, start);\n }\n if (isLetter(next)) {\n position += 1;\n return token(TokenKind.LessThan, start);\n }\n if (next === CHAR_BANG) {\n return unknown(\n start,\n \"comment-not-supported\",\n source.startsWith(\"<!--\", position)\n ? \"comments are not part of SML — remove the <!-- ... --> block\"\n : \"unexpected <! — SML has no doctype or declaration syntax; remove it\",\n );\n }\n return unknown(start, \"stray-angle-bracket\", \"stray < in text — escape it as &lt;\");\n }\n if (first === CHAR_OPEN_BRACE && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) {\n return scanInterpolation(start, \"curly\");\n }\n if (first === CHAR_DOLLAR && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) {\n return scanInterpolation(start, \"dollar\");\n }\n\n // Plain text run: stop at `<`, `{{`, `${`, or EOF. Validate entities inline\n // so malformed `&` gets a precise span.\n while (position < length) {\n const code = source.charCodeAt(position);\n if (code === CHAR_LESS_THAN) break;\n if (code === CHAR_OPEN_BRACE && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) break;\n if (code === CHAR_DOLLAR && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) break;\n if (code === CHAR_AMPERSAND) {\n const entityEnd = scanEntity(source, position);\n if (entityEnd === -1) {\n if (position > start) return token(TokenKind.Text, start);\n return unknown(\n start,\n \"bare-ampersand\",\n \"bare & in text — escape it as &amp; (or use a supported entity like &lt;)\",\n );\n }\n position = entityEnd;\n continue;\n }\n position++;\n }\n return token(TokenKind.Text, start);\n };\n\n const scanTagToken = (): Token => {\n while (position < length && isWhitespace(source.charCodeAt(position))) position++;\n const start = position;\n if (position >= length) return token(TokenKind.EndOfFile, start);\n\n const code = source.charCodeAt(position);\n switch (code) {\n case CHAR_GREATER_THAN:\n position++;\n return token(TokenKind.GreaterThan, start);\n case CHAR_SLASH:\n if (source.charCodeAt(position + 1) === CHAR_GREATER_THAN) {\n position += 2;\n return token(TokenKind.SlashGreaterThan, start);\n }\n return unknown(\n start,\n \"unexpected-character\",\n \"unexpected / inside tag — write /> to self-close\",\n );\n case CHAR_EQUALS:\n position++;\n return token(TokenKind.Equals, start);\n case CHAR_OPEN_BRACE:\n position++;\n return token(TokenKind.OpenBrace, start);\n case CHAR_CLOSE_BRACE:\n position++;\n return token(TokenKind.CloseBrace, start);\n case CHAR_DOUBLE_QUOTE:\n case CHAR_SINGLE_QUOTE: {\n position++;\n while (position < length) {\n const current = source.charCodeAt(position);\n if (current === code) break;\n if (current === CHAR_AMPERSAND) {\n const entityEnd = scanEntity(source, position);\n if (entityEnd === -1) {\n return unknown(\n position,\n \"bare-ampersand\",\n \"bare & in attribute value — escape it as &amp;\",\n );\n }\n position = entityEnd;\n continue;\n }\n if (current === CHAR_LESS_THAN || current === CHAR_LINE_FEED) break;\n position++;\n }\n if (source.charCodeAt(position) !== code) {\n const quote = String.fromCharCode(code);\n return {\n kind: TokenKind.Unknown,\n start,\n end: position,\n error: {\n code: \"unterminated-string\",\n message: `unterminated attribute value — add a closing ${quote}`,\n },\n };\n }\n position++;\n return token(TokenKind.String, start);\n }\n default: {\n if (isLetter(code)) {\n position++;\n while (position < length) {\n const current = source.charCodeAt(position);\n if (!isLetter(current) && !isDigit(current)) break;\n position++;\n }\n return token(TokenKind.Identifier, start);\n }\n if (isDigit(code)) {\n position++;\n while (position < length && isDigit(source.charCodeAt(position))) position++;\n return token(TokenKind.Number, start);\n }\n return unknown(\n start,\n \"unexpected-character\",\n `unexpected character ${JSON.stringify(String.fromCharCode(code))} inside tag`,\n );\n }\n }\n };\n\n return {\n position: () => position,\n scanTextToken,\n scanTagToken,\n getText: (t) => source.slice(t.start, t.end),\n getTextValue: (t) => decodeEntities(source.slice(t.start, t.end)),\n getStringValue: (t) => decodeEntities(source.slice(t.start + 1, t.end - 1)),\n };\n};\n"],"mappings":"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"}
package/dist/model.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../src/model.ts"],"sourcesContent":["import { Result, Schema } from \"effect\";\n\nimport type { AttributeValue, ElementNode, SmlDocument, SmlNode, SmlTag } from \"./ast\";\nimport { parseSml } from \"./parser\";\n\nexport const SmlNodeKeyAttrsSchema = Schema.Record(\n Schema.String,\n Schema.Union([Schema.String, Schema.Finite]),\n);\n\nexport const SmlNodeIndexEntrySchema = Schema.Struct({\n id: Schema.String,\n tag: Schema.String,\n order: Schema.Finite,\n parentId: Schema.NullOr(Schema.String),\n childIndex: Schema.Finite,\n depth: Schema.Finite,\n path: Schema.String,\n textPreview: Schema.String,\n attrs: SmlNodeKeyAttrsSchema,\n});\n\nexport type SmlNodeIndexEntry = typeof SmlNodeIndexEntrySchema.Type & {\n readonly tag: SmlTag;\n};\n\nexport const SmlNodeIndexSchema = Schema.Struct({\n nodes: Schema.Array(SmlNodeIndexEntrySchema),\n});\n\nexport type SmlNodeIndex = typeof SmlNodeIndexSchema.Type & {\n readonly nodes: readonly SmlNodeIndexEntry[];\n};\n\nconst KEY_ATTRS = new Set([\n \"alt\",\n \"class\",\n \"dir\",\n \"height\",\n \"hidden\",\n \"href\",\n \"label\",\n \"lang\",\n \"level\",\n \"name\",\n \"scheme\",\n \"src\",\n \"test\",\n \"valign\",\n \"width\",\n]);\n\nconst attrValue = (value: AttributeValue): string | number => value.value;\n\nconst truncate = (value: string, length: number): string =>\n value.length <= length ? value : `${value.slice(0, length - 3)}...`;\n\nconst normalizePreview = (value: string): string => truncate(value.replace(/\\s+/g, \" \").trim(), 96);\n\nconst textPreview = (node: ElementNode): string => {\n const parts: string[] = [];\n const visit = (child: SmlNode): void => {\n if (child.kind === \"text\") {\n parts.push(child.value);\n return;\n }\n if (child.kind === \"interpolation\") {\n parts.push(`{{${child.name}}}`);\n return;\n }\n for (const nested of child.children) visit(nested);\n };\n for (const child of node.children) visit(child);\n return normalizePreview(parts.join(\" \"));\n};\n\nconst keyAttrs = (node: ElementNode): Readonly<Record<string, string | number>> =>\n Object.fromEntries(\n node.attributes\n .filter((attribute) => KEY_ATTRS.has(attribute.name))\n .map((attribute) => [attribute.name, attrValue(attribute.value)]),\n );\n\nexport const deriveNodeIndexFromDocument = (document: SmlDocument): SmlNodeIndex => {\n const nodes: SmlNodeIndexEntry[] = [];\n let order = 0;\n\n const visit = (\n node: SmlNode,\n parentId: string | null,\n childIndex: number,\n depth: number,\n parentPath: string,\n ): void => {\n if (node.kind !== \"element\") return;\n if (node.tag === \"Source\") return;\n\n const pathSegment = `${node.tag}[${childIndex}]`;\n const path = parentPath ? `${parentPath}/${pathSegment}` : pathSegment;\n nodes.push({\n id: node.id,\n tag: node.tag,\n order: order++,\n parentId,\n childIndex,\n depth,\n path,\n textPreview: textPreview(node),\n attrs: keyAttrs(node),\n });\n\n let elementChildIndex = 0;\n for (const child of node.children) {\n if (child.kind === \"element\") {\n visit(child, node.id, elementChildIndex, depth + 1, path);\n elementChildIndex++;\n }\n }\n };\n\n visit(document.root, null, 0, 0, \"\");\n return { nodes };\n};\n\nexport const deriveNodeIndex = (source: string): Result.Result<SmlNodeIndex, string> => {\n const parsed = parseSml(source);\n if (Result.isFailure(parsed)) {\n return Result.fail(\n parsed.failure.diagnostics.map((diagnostic) => diagnostic.message).join(\"\\n\"),\n );\n }\n return Result.succeed(deriveNodeIndexFromDocument(parsed.success));\n};\n"],"mappings":";;;AAKA,MAAa,wBAAwB,OAAO,OAC1C,OAAO,QACP,OAAO,MAAM,CAAC,OAAO,QAAQ,OAAO,MAAM,CAAC,CAC7C;AAEA,MAAa,0BAA0B,OAAO,OAAO;CACnD,IAAI,OAAO;CACX,KAAK,OAAO;CACZ,OAAO,OAAO;CACd,UAAU,OAAO,OAAO,OAAO,MAAM;CACrC,YAAY,OAAO;CACnB,OAAO,OAAO;CACd,MAAM,OAAO;CACb,aAAa,OAAO;CACpB,OAAO;AACT,CAAC;AAMD,MAAa,qBAAqB,OAAO,OAAO,EAC9C,OAAO,OAAO,MAAM,uBAAuB,EAC7C,CAAC;AAMD,MAAM,4BAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,aAAa,UAA2C,MAAM;AAEpE,MAAM,YAAY,OAAe,WAC/B,MAAM,UAAU,SAAS,QAAQ,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE;AAEjE,MAAM,oBAAoB,UAA0B,SAAS,MAAM,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE;AAElG,MAAM,eAAe,SAA8B;CACjD,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAS,UAAyB;EACtC,IAAI,MAAM,SAAS,QAAQ;GACzB,MAAM,KAAK,MAAM,KAAK;GACtB;EACF;EACA,IAAI,MAAM,SAAS,iBAAiB;GAClC,MAAM,KAAK,KAAK,MAAM,KAAK,GAAG;GAC9B;EACF;EACA,KAAK,MAAM,UAAU,MAAM,UAAU,MAAM,MAAM;CACnD;CACA,KAAK,MAAM,SAAS,KAAK,UAAU,MAAM,KAAK;CAC9C,OAAO,iBAAiB,MAAM,KAAK,GAAG,CAAC;AACzC;AAEA,MAAM,YAAY,SAChB,OAAO,YACL,KAAK,WACF,QAAQ,cAAc,UAAU,IAAI,UAAU,IAAI,CAAC,CAAC,CACpD,KAAK,cAAc,CAAC,UAAU,MAAM,UAAU,UAAU,KAAK,CAAC,CAAC,CACpE;AAEF,MAAa,+BAA+B,aAAwC;CAClF,MAAM,QAA6B,CAAC;CACpC,IAAI,QAAQ;CAEZ,MAAM,SACJ,MACA,UACA,YACA,OACA,eACS;EACT,IAAI,KAAK,SAAS,WAAW;EAC7B,IAAI,KAAK,QAAQ,UAAU;EAE3B,MAAM,cAAc,GAAG,KAAK,IAAI,GAAG,WAAW;EAC9C,MAAM,OAAO,aAAa,GAAG,WAAW,GAAG,gBAAgB;EAC3D,MAAM,KAAK;GACT,IAAI,KAAK;GACT,KAAK,KAAK;GACV,OAAO;GACP;GACA;GACA;GACA;GACA,aAAa,YAAY,IAAI;GAC7B,OAAO,SAAS,IAAI;EACtB,CAAC;EAED,IAAI,oBAAoB;EACxB,KAAK,MAAM,SAAS,KAAK,UACvB,IAAI,MAAM,SAAS,WAAW;GAC5B,MAAM,OAAO,KAAK,IAAI,mBAAmB,QAAQ,GAAG,IAAI;GACxD;EACF;CAEJ;CAEA,MAAM,SAAS,MAAM,MAAM,GAAG,GAAG,EAAE;CACnC,OAAO,EAAE,MAAM;AACjB;AAEA,MAAa,mBAAmB,WAAwD;CACtF,MAAM,SAAS,SAAS,MAAM;CAC9B,IAAI,OAAO,UAAU,MAAM,GACzB,OAAO,OAAO,KACZ,OAAO,QAAQ,YAAY,KAAK,eAAe,WAAW,OAAO,CAAC,CAAC,KAAK,IAAI,CAC9E;CAEF,OAAO,OAAO,QAAQ,4BAA4B,OAAO,OAAO,CAAC;AACnE"}