@caelo-cms/shared 0.10.21 → 0.10.23

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 (241) hide show
  1. package/dist/ai-tools.d.ts +291 -231
  2. package/dist/ai-tools.d.ts.map +1 -1
  3. package/dist/ai-tools.js +349 -281
  4. package/dist/ai-tools.js.map +1 -1
  5. package/dist/auth-forms.d.ts.map +1 -1
  6. package/dist/auth-forms.js +4 -1
  7. package/dist/auth-forms.js.map +1 -1
  8. package/dist/base-css.d.ts +24 -0
  9. package/dist/base-css.d.ts.map +1 -0
  10. package/dist/base-css.js +28 -0
  11. package/dist/base-css.js.map +1 -0
  12. package/dist/build-page.d.ts +330 -0
  13. package/dist/build-page.d.ts.map +1 -0
  14. package/dist/build-page.js +282 -0
  15. package/dist/build-page.js.map +1 -0
  16. package/dist/content.d.ts +322 -9
  17. package/dist/content.d.ts.map +1 -1
  18. package/dist/content.js +354 -11
  19. package/dist/content.js.map +1 -1
  20. package/dist/css-gradient-scan.d.ts +14 -0
  21. package/dist/css-gradient-scan.d.ts.map +1 -0
  22. package/dist/css-gradient-scan.js +81 -0
  23. package/dist/css-gradient-scan.js.map +1 -0
  24. package/dist/css-var-scan.d.ts +56 -0
  25. package/dist/css-var-scan.d.ts.map +1 -0
  26. package/dist/css-var-scan.js +97 -0
  27. package/dist/css-var-scan.js.map +1 -0
  28. package/dist/design-manifest.d.ts +36 -0
  29. package/dist/design-manifest.d.ts.map +1 -0
  30. package/dist/design-manifest.js +90 -0
  31. package/dist/design-manifest.js.map +1 -0
  32. package/dist/fonts.d.ts +89 -0
  33. package/dist/fonts.d.ts.map +1 -0
  34. package/dist/fonts.js +241 -0
  35. package/dist/fonts.js.map +1 -0
  36. package/dist/genesis-inventory.d.ts +32 -0
  37. package/dist/genesis-inventory.d.ts.map +1 -0
  38. package/dist/genesis-inventory.js +186 -0
  39. package/dist/genesis-inventory.js.map +1 -0
  40. package/dist/genesis.d.ts +62 -0
  41. package/dist/genesis.d.ts.map +1 -0
  42. package/dist/genesis.js +78 -0
  43. package/dist/genesis.js.map +1 -0
  44. package/dist/i18n.d.ts +44 -1
  45. package/dist/i18n.d.ts.map +1 -1
  46. package/dist/i18n.js +72 -6
  47. package/dist/i18n.js.map +1 -1
  48. package/dist/index.d.ts +27 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +27 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/interactions.d.ts +23 -0
  53. package/dist/interactions.d.ts.map +1 -0
  54. package/dist/interactions.js +44 -0
  55. package/dist/interactions.js.map +1 -0
  56. package/dist/media.d.ts +101 -16
  57. package/dist/media.d.ts.map +1 -1
  58. package/dist/media.js +126 -15
  59. package/dist/media.js.map +1 -1
  60. package/dist/page-log.d.ts +94 -0
  61. package/dist/page-log.d.ts.map +1 -0
  62. package/dist/page-log.js +111 -0
  63. package/dist/page-log.js.map +1 -0
  64. package/dist/preview-compose.d.ts +79 -0
  65. package/dist/preview-compose.d.ts.map +1 -1
  66. package/dist/preview-compose.js +155 -25
  67. package/dist/preview-compose.js.map +1 -1
  68. package/dist/proposal-status.d.ts +40 -0
  69. package/dist/proposal-status.d.ts.map +1 -0
  70. package/dist/proposal-status.js +34 -0
  71. package/dist/proposal-status.js.map +1 -0
  72. package/dist/responsive-images.d.ts +64 -0
  73. package/dist/responsive-images.d.ts.map +1 -0
  74. package/dist/responsive-images.js +98 -0
  75. package/dist/responsive-images.js.map +1 -0
  76. package/dist/safe-keys.d.ts +9 -0
  77. package/dist/safe-keys.d.ts.map +1 -0
  78. package/dist/safe-keys.js +20 -0
  79. package/dist/safe-keys.js.map +1 -0
  80. package/dist/seo.d.ts +8 -0
  81. package/dist/seo.d.ts.map +1 -1
  82. package/dist/seo.js +3 -1
  83. package/dist/seo.js.map +1 -1
  84. package/dist/skills.d.ts +14 -68
  85. package/dist/skills.d.ts.map +1 -1
  86. package/dist/skills.js +19 -113
  87. package/dist/skills.js.map +1 -1
  88. package/dist/strip-cdata.d.ts +7 -0
  89. package/dist/strip-cdata.d.ts.map +1 -0
  90. package/dist/strip-cdata.js +48 -0
  91. package/dist/strip-cdata.js.map +1 -0
  92. package/dist/structured-sets.d.ts +6 -16
  93. package/dist/structured-sets.d.ts.map +1 -1
  94. package/dist/structured-sets.js +5 -16
  95. package/dist/structured-sets.js.map +1 -1
  96. package/dist/subagents.d.ts +105 -3
  97. package/dist/subagents.d.ts.map +1 -1
  98. package/dist/subagents.js +224 -41
  99. package/dist/subagents.js.map +1 -1
  100. package/dist/template-engine.d.ts +85 -0
  101. package/dist/template-engine.d.ts.map +1 -0
  102. package/dist/template-engine.js +403 -0
  103. package/dist/template-engine.js.map +1 -0
  104. package/dist/theme-importers/auto-detect.d.ts +26 -0
  105. package/dist/theme-importers/auto-detect.d.ts.map +1 -0
  106. package/dist/theme-importers/auto-detect.js +42 -0
  107. package/dist/theme-importers/auto-detect.js.map +1 -0
  108. package/dist/theme-importers/css-comments.d.ts +12 -0
  109. package/dist/theme-importers/css-comments.d.ts.map +1 -0
  110. package/dist/theme-importers/css-comments.js +15 -0
  111. package/dist/theme-importers/css-comments.js.map +1 -0
  112. package/dist/theme-importers/dtcg.d.ts +46 -0
  113. package/dist/theme-importers/dtcg.d.ts.map +1 -0
  114. package/dist/theme-importers/dtcg.js +111 -0
  115. package/dist/theme-importers/dtcg.js.map +1 -0
  116. package/dist/theme-importers/loose.d.ts +3 -0
  117. package/dist/theme-importers/loose.d.ts.map +1 -0
  118. package/dist/theme-importers/loose.js +76 -0
  119. package/dist/theme-importers/loose.js.map +1 -0
  120. package/dist/theme-importers/shadcn.d.ts +24 -0
  121. package/dist/theme-importers/shadcn.d.ts.map +1 -0
  122. package/dist/theme-importers/shadcn.js +135 -0
  123. package/dist/theme-importers/shadcn.js.map +1 -0
  124. package/dist/theme-importers/style-dictionary.d.ts +17 -0
  125. package/dist/theme-importers/style-dictionary.d.ts.map +1 -0
  126. package/dist/theme-importers/style-dictionary.js +125 -0
  127. package/dist/theme-importers/style-dictionary.js.map +1 -0
  128. package/dist/theme-importers/tailwind.d.ts +3 -0
  129. package/dist/theme-importers/tailwind.d.ts.map +1 -0
  130. package/dist/theme-importers/tailwind.js +218 -0
  131. package/dist/theme-importers/tailwind.js.map +1 -0
  132. package/dist/theme-literal-binding.d.ts +37 -0
  133. package/dist/theme-literal-binding.d.ts.map +1 -0
  134. package/dist/theme-literal-binding.js +138 -0
  135. package/dist/theme-literal-binding.js.map +1 -0
  136. package/dist/theme-normalize.d.ts +31 -0
  137. package/dist/theme-normalize.d.ts.map +1 -0
  138. package/dist/theme-normalize.js +587 -0
  139. package/dist/theme-normalize.js.map +1 -0
  140. package/dist/theme-ramp.d.ts +55 -0
  141. package/dist/theme-ramp.d.ts.map +1 -0
  142. package/dist/theme-ramp.js +149 -0
  143. package/dist/theme-ramp.js.map +1 -0
  144. package/dist/theme-render.d.ts +105 -0
  145. package/dist/theme-render.d.ts.map +1 -0
  146. package/dist/theme-render.js +441 -0
  147. package/dist/theme-render.js.map +1 -0
  148. package/dist/themes-errors.d.ts +109 -0
  149. package/dist/themes-errors.d.ts.map +1 -0
  150. package/dist/themes-errors.js +170 -0
  151. package/dist/themes-errors.js.map +1 -0
  152. package/dist/themes.d.ts +343 -0
  153. package/dist/themes.d.ts.map +1 -0
  154. package/dist/themes.js +697 -0
  155. package/dist/themes.js.map +1 -0
  156. package/dist/version.d.ts +7 -4
  157. package/dist/version.d.ts.map +1 -1
  158. package/dist/version.js +6 -3
  159. package/dist/version.js.map +1 -1
  160. package/package.json +10 -2
  161. package/src/__tests__/redos-hardening.test.ts +160 -0
  162. package/src/ai-tools-add-module-modes.test.ts +106 -0
  163. package/src/ai-tools-position.test.ts +134 -0
  164. package/src/ai-tools.test.ts +81 -0
  165. package/src/ai-tools.ts +1179 -0
  166. package/src/auth-forms.ts +36 -0
  167. package/src/base-css.ts +30 -0
  168. package/src/build-page.test.ts +228 -0
  169. package/src/build-page.ts +319 -0
  170. package/src/cap-failures.ts +67 -0
  171. package/src/content.test.ts +170 -0
  172. package/src/content.ts +620 -0
  173. package/src/context.ts +43 -0
  174. package/src/css-gradient-scan.ts +88 -0
  175. package/src/css-var-scan.test.ts +96 -0
  176. package/src/css-var-scan.ts +144 -0
  177. package/src/derive-module-type.test.ts +80 -0
  178. package/src/design-manifest.ts +93 -0
  179. package/src/fonts.test.ts +157 -0
  180. package/src/fonts.ts +296 -0
  181. package/src/genesis-inventory.test.ts +86 -0
  182. package/src/genesis-inventory.ts +215 -0
  183. package/src/genesis-sanitize.test.ts +35 -0
  184. package/src/genesis.ts +87 -0
  185. package/src/i18n.test.ts +274 -0
  186. package/src/i18n.ts +269 -0
  187. package/src/index.test.ts +10 -0
  188. package/src/index.ts +59 -0
  189. package/src/interactions.ts +48 -0
  190. package/src/logger.ts +147 -0
  191. package/src/media.test.ts +160 -0
  192. package/src/media.ts +355 -0
  193. package/src/page-log.test.ts +163 -0
  194. package/src/page-log.ts +124 -0
  195. package/src/preview-compose.test.ts +602 -0
  196. package/src/preview-compose.ts +709 -0
  197. package/src/preview-scanner.test.ts +96 -0
  198. package/src/preview-scanner.ts +214 -0
  199. package/src/proposal-status.test.ts +69 -0
  200. package/src/proposal-status.ts +40 -0
  201. package/src/responsive-images.test.ts +104 -0
  202. package/src/responsive-images.ts +151 -0
  203. package/src/result.ts +29 -0
  204. package/src/safe-keys.ts +21 -0
  205. package/src/seo.test.ts +234 -0
  206. package/src/seo.ts +261 -0
  207. package/src/skills.ts +48 -0
  208. package/src/snapshots.test.ts +80 -0
  209. package/src/snapshots.ts +81 -0
  210. package/src/strip-cdata.test.ts +41 -0
  211. package/src/strip-cdata.ts +50 -0
  212. package/src/structured-sets.ts +180 -0
  213. package/src/subagents.test.ts +262 -0
  214. package/src/subagents.ts +432 -0
  215. package/src/template-engine.test.ts +379 -0
  216. package/src/template-engine.ts +520 -0
  217. package/src/theme-gradient.test.ts +92 -0
  218. package/src/theme-importers/__tests__/proto-pollution.test.ts +54 -0
  219. package/src/theme-importers/auto-detect.ts +84 -0
  220. package/src/theme-importers/css-comments.ts +15 -0
  221. package/src/theme-importers/dtcg.ts +106 -0
  222. package/src/theme-importers/loose.ts +76 -0
  223. package/src/theme-importers/shadcn.ts +133 -0
  224. package/src/theme-importers/style-dictionary.ts +125 -0
  225. package/src/theme-importers/tailwind.ts +217 -0
  226. package/src/theme-literal-binding.test.ts +71 -0
  227. package/src/theme-literal-binding.ts +159 -0
  228. package/src/theme-motion.test.ts +115 -0
  229. package/src/theme-normalize-envelope.test.ts +43 -0
  230. package/src/theme-normalize-gradient.test.ts +135 -0
  231. package/src/theme-normalize.ts +661 -0
  232. package/src/theme-ramp.ts +187 -0
  233. package/src/theme-render-sanitize.test.ts +45 -0
  234. package/src/theme-render.test.ts +119 -0
  235. package/src/theme-render.ts +487 -0
  236. package/src/theme-shadow.test.ts +56 -0
  237. package/src/themes-errors.ts +199 -0
  238. package/src/themes.ts +842 -0
  239. package/src/translation.test.ts +160 -0
  240. package/src/translation.ts +295 -0
  241. package/src/version.ts +66 -0
@@ -0,0 +1,403 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * Shared template engine for AI-authored module HTML. Consolidates the
4
+ * no-DB `applyFieldSubstitution` in `preview-compose.ts` and the
5
+ * DB-aware `substituteWithRecursion` in `preview-render.ts` into a
6
+ * single engine built on `mustache.js` (Plan B per issue #71).
7
+ *
8
+ * Grammar (a Mustache subset — see CMS_REQUIREMENTS §3.1 / §5):
9
+ * {{name}} — primitive substitution
10
+ * {{#name}}…inner…{{/name}} — section: ITERATES over a list field
11
+ * (text-list / link-list / module-list);
12
+ * for a SCALAR (text/image/…) or single
13
+ * `module` field it is a CONDITIONAL —
14
+ * the block renders once when the field
15
+ * has a value, and is dropped when empty
16
+ * (never a kind-mismatch — that used to
17
+ * silently drop real scalar content)
18
+ * {{>name}} — single nested module reference
19
+ * (module field kind)
20
+ *
21
+ * Substitution priority for {{name}}:
22
+ * 1. contentValues[name] — per-placement value
23
+ * 2. fields[name].default — module-level default
24
+ * 3. raw `{{name}}` left in place (CLAUDE.md §2 — no fallbacks pre-1.0,
25
+ * so broken templates stay visible)
26
+ *
27
+ * Loud-raw invariant: an unknown `{{name}}` / `{{#name}}` / `{{>name}}`
28
+ * (no matching declared field) is left as raw text in the output, not
29
+ * silenced. Mustache's "unknown → empty string" default is overridden
30
+ * by a sentinel pre-scan + post-substitute pass that re-injects the
31
+ * original Mustache source after rendering. See plan §2 risk #2.
32
+ *
33
+ * Failure markers preserved verbatim from the legacy preview-render
34
+ * for `missingSlots`: `field-not-declared:<name>`,
35
+ * `kind-mismatch:<name> expected=<…> actual=<kind>`,
36
+ * `text-list-malformed:<name>[<i>]`, `link-list-malformed:<name>[<i>]`,
37
+ * `module-list-malformed:<name>[<i>]`, `module-ref-malformed:<name>`.
38
+ * v0.11.1 (issue #76): `theme-asset-unbound:<slot>` for the four
39
+ * `{{theme_<slot>_url}}` placeholders when the active theme's asset
40
+ * slot isn't bound.
41
+ * The chat-runner diag pass + editor missing-content surface read
42
+ * these literal strings; any rename is a silent regression for them.
43
+ *
44
+ * No HTML escaping. Module HTML substitutes raw — modules are the
45
+ * place raw HTML lives (CMS_REQUIREMENTS §3.1). Auto-escape would
46
+ * silently break every <a href="{{url}}"> in the catalog. The
47
+ * `Mustache.escape` override at module load is the public Mustache
48
+ * configuration knob; the engine module is the sole workspace
49
+ * importer of mustache so the singleton mutation is scoped in
50
+ * effect.
51
+ *
52
+ * Partials are caller-supplied (sync `Record<string, string>`). The
53
+ * compose path passes an empty map (no DB) — module / module-list
54
+ * refs become loud HTML comments so static-gen output is visible-
55
+ * broken instead of silent-empty. The preview-render path
56
+ * pre-resolves each nested ref via its existing RenderResolver walk
57
+ * (depth-limit + cycle-detection live there, untouched) and supplies
58
+ * the rendered HTML as a partial.
59
+ */
60
+ import Mustache from "mustache";
61
+ import { MODULE_FIELD_SECTION_KINDS } from "./content.js";
62
+ import { stripCdataGuards } from "./strip-cdata.js";
63
+ // Override Mustache's default HTML escape — module HTML substitutes
64
+ // raw. The engine module is the only workspace importer of mustache,
65
+ // so the singleton mutation is scoped in effect. Plan §2 risk #1: the
66
+ // {{href}} test pins this; any leak of the default escape fails.
67
+ Mustache.escape = (s) => (s === null || s === undefined ? "" : String(s));
68
+ // Field names per the v0.4.0 + v0.12.0 grammar: lowercase ASCII +
69
+ // digits + underscores. Case-insensitive primitive matching (the AI
70
+ // sometimes camelCases the placeholder when extracting from existing
71
+ // HTML) is handled below via lowercased view lookup. Whitespace
72
+ // between `{{` and the sigil (#, >, /) is permitted to match the
73
+ // Mustache spec — an extractor that pretty-prints AI-authored HTML
74
+ // shouldn't silently break section / partial dispatch.
75
+ // The section body uses a "tempered dot" — `(?:(?!CLOSE)[\s\S])*` — instead
76
+ // of a lazy `[\s\S]*?`. The lazy form makes the engine try, then backtrack,
77
+ // the close-tag match at every interior position, which is O(n²) on
78
+ // unclosed/large input (CodeQL js/polynomial-redos). The tempered form
79
+ // consumes one character only when the close tag does not start there, so
80
+ // there is a single unambiguous path. The capture stops at the first close
81
+ // tag for the captured name, exactly as the lazy form did — same output.
82
+ const SECTION_RE = /\{\{\s*#\s*([a-z][a-z0-9_]*)\s*\}\}((?:(?!\{\{\s*\/\s*\1\s*\}\})[\s\S])*)\{\{\s*\/\s*\1\s*\}\}/g;
83
+ const PARTIAL_RE = /\{\{\s*>\s*([a-z][a-z0-9_]*)\s*\}\}/g;
84
+ const PRIMITIVE_RE = /\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*\}\}/g;
85
+ // Section-dispatch kinds (the engine's `{{#name}}` operand). Imported
86
+ // from content.ts so new list-shaped kinds wire through automatically
87
+ // when the canonical declaration is extended.
88
+ const SECTION_KINDS = new Set(MODULE_FIELD_SECTION_KINDS);
89
+ // v0.11.1 (issue #76) — the four theme-asset substitutions the engine
90
+ // resolves from `RenderTemplateInput.themeAssets`. Keys are the
91
+ // `{{name}}` placeholders module authors write; the table maps them
92
+ // back to the asset slot on the ComposeTheme.assets aggregate.
93
+ const THEME_ASSET_KEY_TO_SLOT = {
94
+ theme_logo_url: "logo",
95
+ theme_logo_dark_url: "logoDark",
96
+ theme_favicon_url: "favicon",
97
+ theme_social_share_url: "socialShare",
98
+ };
99
+ const THEME_ASSET_KEYS = Object.keys(THEME_ASSET_KEY_TO_SLOT);
100
+ function isNestedRef(v) {
101
+ return (typeof v === "object" &&
102
+ v !== null &&
103
+ typeof v.moduleId === "string" &&
104
+ typeof v.contentInstanceId === "string");
105
+ }
106
+ /**
107
+ * Render a `caelo:missing` HTML comment carrying a failure reason.
108
+ * The shape (`<!-- caelo:missing reason=<…> -->`) is part of the
109
+ * public failure-marker contract — the chat-runner diag pass reads
110
+ * the comment text out of rendered HTML, and the editor's missing-
111
+ * content surface highlights it for the operator. Exported so the
112
+ * DB-aware preview-render path (`packages/admin-core/src/ops/content/
113
+ * preview-render.ts`) emits the exact same shape without redeclaring.
114
+ */
115
+ export function caeloMissingComment(reason) {
116
+ return `<!-- caelo:missing reason=${reason} -->`;
117
+ }
118
+ const comment = caeloMissingComment;
119
+ /**
120
+ * Render `html` against `contentValues` + `fields` + `partials`,
121
+ * returning the substituted HTML plus a structured `missingSlots`
122
+ * channel. Pure / sync / no IO.
123
+ *
124
+ * @example
125
+ * // AC #1 fixture (issue #71). Iterates a link-list field per element.
126
+ * renderTemplate({
127
+ * html: '<nav>{{#nav_items}}<a href="{{href}}">{{label}}</a>{{/nav_items}}</nav>',
128
+ * fields: [{ name: 'nav_items', kind: 'link-list' }],
129
+ * contentValues: {
130
+ * nav_items: [
131
+ * { label: 'Docs', href: '/docs' },
132
+ * { label: 'Blog', href: '/blog' },
133
+ * ],
134
+ * },
135
+ * });
136
+ * // → {
137
+ * // html: '<nav><a href="/docs">Docs</a><a href="/blog">Blog</a></nav>',
138
+ * // missingSlots: [],
139
+ * // }
140
+ *
141
+ * @see {@link TemplateField} for the field shape (name + kind + optional default).
142
+ * @see {@link ./content.js#MODULE_FIELD_KINDS} for the canonical kind union.
143
+ * @see {@link ./content.js#MODULE_FIELD_SECTION_KINDS} for the kinds the engine
144
+ * iterates via `{{#name}}` (text-list / link-list / module-list).
145
+ */
146
+ export function renderTemplate(input) {
147
+ const missing = [];
148
+ const fieldByName = new Map();
149
+ for (const f of input.fields)
150
+ fieldByName.set(f.name, f);
151
+ const cvs = input.contentValues ?? {};
152
+ const partials = input.partials ?? {};
153
+ // Sentinels survive Mustache.render untouched (they contain no
154
+ // `{{` `}}`), then get restored to the original Mustache source
155
+ // after render — the loud-raw invariant. The prefix carries a
156
+ // per-call UUID so the sentinel string is unguessable from the
157
+ // outside: a module author who pastes the literal `__CAELO_TPL_…__`
158
+ // pattern into their HTML can't break loud-raw restore by
159
+ // intercepting the sentinel for a known-key sentinel — they would
160
+ // have to guess the UUID minted at render time.
161
+ const sentinels = new Map();
162
+ const sentinelPrefix = `__CAELO_TPL_${globalThis.crypto.randomUUID()}_`;
163
+ const mkSentinel = (original) => {
164
+ const key = `${sentinelPrefix}${sentinels.size}__`;
165
+ sentinels.set(key, original);
166
+ return key;
167
+ };
168
+ // 0. Defensively unwrap XHTML-style CDATA guards the model sometimes
169
+ // emits around inline <style>/<script> — they otherwise survive the
170
+ // byte-preserving compose path and leak a stray `]]>` into the page.
171
+ // Store-time normalization (modules.create/update) cleans new
172
+ // modules; this covers any already-stored HTML + the deploy render.
173
+ const sourceHtml = stripCdataGuards(input.html);
174
+ // 1. {{#name}}…{{/name}} sections.
175
+ let html = sourceHtml.replace(SECTION_RE, (match, name, inner) => renderSection(match, name, inner, fieldByName, cvs, partials, missing, mkSentinel));
176
+ // 2. {{>name}} single partials.
177
+ html = html.replace(PARTIAL_RE, (match, name) => renderPartialRef(match, name, fieldByName, cvs, partials, missing, mkSentinel));
178
+ // 3. {{name}} primitives. Pre-rewrite to canonical lowercase form
179
+ // so the (lowercased) view picks them up regardless of source
180
+ // casing. Unknowns become sentinels for loud-raw.
181
+ const declaredFieldNames = new Set();
182
+ for (const f of input.fields) {
183
+ declaredFieldNames.add(f.name);
184
+ declaredFieldNames.add(f.name.toLowerCase());
185
+ }
186
+ const view = {};
187
+ for (const [k, v] of Object.entries(cvs)) {
188
+ view[k.toLowerCase()] = v === null || v === undefined ? "" : v;
189
+ }
190
+ for (const f of input.fields) {
191
+ const lower = f.name.toLowerCase();
192
+ if (lower in view)
193
+ continue;
194
+ if (SECTION_KINDS.has(f.kind) || f.kind === "module")
195
+ continue;
196
+ if (f.default !== undefined && f.default !== null)
197
+ view[lower] = f.default;
198
+ }
199
+ // v0.11.1 (issue #76) — pre-populate four theme-asset substitutions
200
+ // when the caller supplied `themeAssets`. `contentValues` already
201
+ // populated `view` above so any per-placement override wins (per
202
+ // §S19's "contentValues take precedence" invariant). Unbound slots
203
+ // (null) DON'T land in the view — they fall through to the loud-raw
204
+ // path below, but mark themselves with `theme-asset-unbound:<slot>`
205
+ // so the failure marker disambiguates from `field-not-declared`.
206
+ const themeAssetKeys = THEME_ASSET_KEYS;
207
+ const themeAssetSlotByKey = THEME_ASSET_KEY_TO_SLOT;
208
+ const themeAssets = input.themeAssets;
209
+ if (themeAssets) {
210
+ for (const key of themeAssetKeys) {
211
+ const slot = themeAssetSlotByKey[key];
212
+ const url = themeAssets[slot];
213
+ if (typeof url === "string" && url.length > 0 && !(key in view)) {
214
+ view[key] = url;
215
+ }
216
+ }
217
+ }
218
+ html = html.replace(PRIMITIVE_RE, (match, name) => {
219
+ const lower = name.toLowerCase();
220
+ if (lower in view)
221
+ return `{{${lower}}}`;
222
+ // v0.11.1 (issue #76) — theme-asset placeholder with no bound URL.
223
+ // Use a dedicated marker so the failure surface disambiguates from
224
+ // the generic field-not-declared marker.
225
+ if (lower in themeAssetSlotByKey) {
226
+ const slot = themeAssetSlotByKey[lower];
227
+ missing.push(`theme-asset-unbound:${slot}`);
228
+ return mkSentinel(match);
229
+ }
230
+ // No value + no default: leave raw (CLAUDE.md §2). Track in
231
+ // missingSlots only when the field isn't declared at all —
232
+ // declared-but-empty is the operator's responsibility (still
233
+ // authoring), not a system-side gap callers should warn about.
234
+ if (!declaredFieldNames.has(name) && !declaredFieldNames.has(lower)) {
235
+ missing.push(`field-not-declared:${name}`);
236
+ }
237
+ return mkSentinel(match);
238
+ });
239
+ // 4. Render. The view holds only lowercase keys; sentinels survive
240
+ // untouched; sections + partials are already pre-substituted.
241
+ const rendered = Mustache.render(html, view);
242
+ // 5. Restore loud-raw sentinels.
243
+ let final = rendered;
244
+ for (const [sentinel, original] of sentinels) {
245
+ final = final.split(sentinel).join(original);
246
+ }
247
+ return { html: final, missingSlots: missing };
248
+ }
249
+ function renderSection(match, name, inner, fields, cvs, partials, missing, mkSentinel) {
250
+ const field = fields.get(name);
251
+ if (!field) {
252
+ missing.push(`field-not-declared:${name}`);
253
+ return mkSentinel(match);
254
+ }
255
+ if (field.kind === "module-list") {
256
+ return renderModuleList(name, field, cvs, partials, missing);
257
+ }
258
+ if (field.kind === "text-list") {
259
+ return renderTextList(name, inner, field, cvs, missing);
260
+ }
261
+ if (field.kind === "link-list") {
262
+ return renderLinkList(name, inner, field, cvs, missing);
263
+ }
264
+ // Any NON-list field used as a `{{#name}}…{{/name}}` section is a
265
+ // CONDITIONAL (Mustache semantics): render the inner block once when the
266
+ // field has a present, non-empty value; drop it when empty. This is what
267
+ // makes `{{#subtitle}}<p>{{subtitle}}</p>{{/subtitle}}` wrap a scalar
268
+ // text/image field — previously a scalar in a section fell through to
269
+ // `kind-mismatch` and SILENTLY DROPPED real content (headings, body copy,
270
+ // an image, a single nested module). `{{name}}` inside the block resolves
271
+ // in the later primitive pass (a `module` field's `{{>name}}` in the
272
+ // partial pass); `{{.}}`/`{{item}}` are substituted here for parity with
273
+ // the *-list renderers. An empty conditional is intentional, not
274
+ // "missing" — nothing is pushed to `missing`.
275
+ return renderConditionalSection(name, inner, field, cvs);
276
+ }
277
+ /**
278
+ * Render a `{{#name}}…{{/name}}` section over a scalar/module field as a
279
+ * Mustache conditional: the block once when the value is present, else "".
280
+ */
281
+ function renderConditionalSection(name, inner, field, cvs) {
282
+ const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
283
+ if (!isPresentSectionValue(raw))
284
+ return "";
285
+ const value = scalarSectionString(raw);
286
+ return value.length > 0 ? inner.replace(/\{\{\s*(?:\.|item)\s*\}\}/g, () => value) : inner;
287
+ }
288
+ /**
289
+ * Mustache-style truthiness for a conditional section value. Empty string,
290
+ * null/undefined, `false`, and an empty array/object are absent; everything
291
+ * else (incl. a numeric 0 — a real content value, not a control flag) is
292
+ * present.
293
+ */
294
+ function isPresentSectionValue(raw) {
295
+ if (raw === undefined || raw === null || raw === false)
296
+ return false;
297
+ if (typeof raw === "string")
298
+ return raw.trim().length > 0;
299
+ if (typeof raw === "number")
300
+ return !Number.isNaN(raw);
301
+ if (Array.isArray(raw))
302
+ return raw.length > 0;
303
+ if (typeof raw === "object")
304
+ return Object.keys(raw).length > 0;
305
+ return true;
306
+ }
307
+ /** String form for `{{.}}`/`{{item}}` inside a scalar conditional. Objects
308
+ * (module refs / image objects) render through `{{name}}`/`{{>name}}`, not
309
+ * `{{.}}`, so they stringify to "". */
310
+ function scalarSectionString(raw) {
311
+ if (typeof raw === "string")
312
+ return raw;
313
+ if (typeof raw === "number" || typeof raw === "boolean")
314
+ return String(raw);
315
+ return "";
316
+ }
317
+ function renderTextList(name, inner, field, cvs, missing) {
318
+ const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
319
+ if (!Array.isArray(raw))
320
+ return "";
321
+ const parts = [];
322
+ for (let i = 0; i < raw.length; i += 1) {
323
+ const el = raw[i];
324
+ if (typeof el !== "string" && typeof el !== "number" && typeof el !== "boolean") {
325
+ missing.push(`text-list-malformed:${name}[${i}]`);
326
+ parts.push(comment(`text-list-malformed ${name}[${i}]`));
327
+ continue;
328
+ }
329
+ const value = String(el);
330
+ parts.push(inner.replace(/\{\{\s*(?:\.|item)\s*\}\}/g, () => value));
331
+ }
332
+ return parts.join("");
333
+ }
334
+ function renderLinkList(name, inner, field, cvs, missing) {
335
+ const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
336
+ if (!Array.isArray(raw))
337
+ return "";
338
+ const parts = [];
339
+ for (let i = 0; i < raw.length; i += 1) {
340
+ const el = raw[i];
341
+ if (typeof el !== "object" ||
342
+ el === null ||
343
+ typeof el.label !== "string" ||
344
+ typeof el.href !== "string") {
345
+ missing.push(`link-list-malformed:${name}[${i}]`);
346
+ parts.push(comment(`link-list-malformed ${name}[${i}]`));
347
+ continue;
348
+ }
349
+ const { label, href } = el;
350
+ parts.push(inner.replace(/\{\{\s*label\s*\}\}/g, () => label).replace(/\{\{\s*href\s*\}\}/g, () => href));
351
+ }
352
+ return parts.join("");
353
+ }
354
+ function renderModuleList(name, field, cvs, partials, missing) {
355
+ const raw = Object.hasOwn(cvs, name) ? cvs[name] : field.default;
356
+ if (!Array.isArray(raw))
357
+ return "";
358
+ const parts = [];
359
+ for (let i = 0; i < raw.length; i += 1) {
360
+ const el = raw[i];
361
+ if (!isNestedRef(el)) {
362
+ missing.push(`module-list-malformed:${name}[${i}]`);
363
+ parts.push(comment(`module-list-malformed ${name}[${i}]`));
364
+ continue;
365
+ }
366
+ const partialKey = `${name}__${i}`;
367
+ const partialHtml = partials[partialKey];
368
+ if (partialHtml === undefined) {
369
+ // Compose path: no DB → no partials → loud comment so
370
+ // operators see the gap. The preview-render path always
371
+ // supplies a partial (or routes a structured failure marker
372
+ // through it from renderInner), so this branch is the
373
+ // static-gen escape hatch until #70 lands.
374
+ parts.push(`<!-- caelo:module-list ${name} needs recursive renderer (compose path) -->`);
375
+ continue;
376
+ }
377
+ parts.push(partialHtml);
378
+ }
379
+ return parts.join("");
380
+ }
381
+ function renderPartialRef(match, name, fields, cvs, partials, missing, mkSentinel) {
382
+ const field = fields.get(name);
383
+ if (!field) {
384
+ missing.push(`field-not-declared:${name}`);
385
+ return mkSentinel(match);
386
+ }
387
+ if (field.kind !== "module") {
388
+ const reason = `kind-mismatch:${name} expected=module actual=${field.kind}`;
389
+ missing.push(reason);
390
+ return comment(reason);
391
+ }
392
+ const ref = cvs[name];
393
+ if (!isNestedRef(ref)) {
394
+ missing.push(`module-ref-malformed:${name}`);
395
+ return comment(`module-ref-malformed ${name}`);
396
+ }
397
+ const partialHtml = partials[name];
398
+ if (partialHtml === undefined) {
399
+ return `<!-- caelo:module ${name} needs recursive renderer (compose path) -->`;
400
+ }
401
+ return partialHtml;
402
+ }
403
+ //# sourceMappingURL=template-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.js","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * v0.11.1 (issue #76) — theme import format auto-detection chain.
3
+ *
4
+ * Tries each importer in priority order:
5
+ *
6
+ * 1. DTCG (most specific: `$value` leaves)
7
+ * 2. Style Dictionary (`value` leaves)
8
+ * 3. Tailwind 4 (`@theme { … }` block)
9
+ * 4. shadcn (`:root { … }` CSS variables)
10
+ * 5. Loose key-value (`{looseName: value}` JSON object)
11
+ *
12
+ * The first importer that doesn't throw a "shape" rejection wins.
13
+ * "Hard" parse errors (e.g. TailwindImportError for calc()) surface
14
+ * verbatim — they signal the input WAS that format but unparseable.
15
+ *
16
+ * Returns `{format, tokens}` so the caller's tool result can echo the
17
+ * detected format back to the AI for next-turn awareness.
18
+ */
19
+ import type { ThemeDocument } from "../themes.js";
20
+ export type DetectedFormat = "dtcg" | "style-dictionary" | "tailwind" | "shadcn" | "loose";
21
+ export interface AutoDetectResult {
22
+ readonly format: DetectedFormat;
23
+ readonly tokens: ThemeDocument;
24
+ }
25
+ export declare function autoDetectAndImport(body: string): AutoDetectResult;
26
+ //# sourceMappingURL=auto-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-detect.d.ts","sourceRoot":"","sources":["../../src/theme-importers/auto-detect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAelD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAqBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAqBlE"}
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ import { NoImporterMatched, NotDtcgShape, NotLooseShape, NotShadcnShape, NotStyleDictionaryShape, NotTailwindShape, } from "../themes-errors.js";
3
+ import { importDtcg } from "./dtcg.js";
4
+ import { importLoose } from "./loose.js";
5
+ import { importShadcn } from "./shadcn.js";
6
+ import { importStyleDictionary } from "./style-dictionary.js";
7
+ import { importTailwind } from "./tailwind.js";
8
+ const CHAIN = [
9
+ { format: "dtcg", importer: importDtcg, fallThroughOn: [NotDtcgShape] },
10
+ {
11
+ format: "style-dictionary",
12
+ importer: importStyleDictionary,
13
+ fallThroughOn: [NotStyleDictionaryShape],
14
+ },
15
+ { format: "tailwind", importer: importTailwind, fallThroughOn: [NotTailwindShape] },
16
+ { format: "shadcn", importer: importShadcn, fallThroughOn: [NotShadcnShape] },
17
+ { format: "loose", importer: importLoose, fallThroughOn: [NotLooseShape] },
18
+ ];
19
+ export function autoDetectAndImport(body) {
20
+ const attempts = [];
21
+ for (const def of CHAIN) {
22
+ try {
23
+ const tokens = def.importer(body);
24
+ return { format: def.format, tokens };
25
+ }
26
+ catch (e) {
27
+ const isFallThrough = def.fallThroughOn.some((cls) => e instanceof cls);
28
+ if (!isFallThrough) {
29
+ // Hard error: the input WAS in this format but the importer
30
+ // can't handle it. Surface verbatim so the operator/AI gets a
31
+ // concrete fix-it.
32
+ throw e;
33
+ }
34
+ attempts.push({
35
+ format: def.format,
36
+ reason: e instanceof Error ? (e.message.split("\n")[0] ?? "rejected") : "rejected",
37
+ });
38
+ }
39
+ }
40
+ throw new NoImporterMatched(attempts);
41
+ }
42
+ //# sourceMappingURL=auto-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-detect.js","sourceRoot":"","sources":["../../src/theme-importers/auto-detect.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAsBnC,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAgB/C,MAAM,KAAK,GAA2B;IACpC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,YAAY,CAAC,EAAE;IACvE;QACE,MAAM,EAAE,kBAAkB;QAC1B,QAAQ,EAAE,qBAAqB;QAC/B,aAAa,EAAE,CAAC,uBAAuB,CAAC;KACzC;IACD,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,gBAAgB,CAAC,EAAE;IACnF,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,cAAc,CAAC,EAAE;IAC7E,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,aAAa,CAAC,EAAE;CAC3E,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAA8C,EAAE,CAAC;IAC/D,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;YACxE,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,4DAA4D;gBAC5D,8DAA8D;gBAC9D,mBAAmB;gBACnB,MAAM,CAAC,CAAC;YACV,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Strip `/* … *\/` block comments from a CSS string.
3
+ *
4
+ * The body uses a "tempered dot" (`(?:(?!\*\/)[\s\S])*`) rather than a lazy
5
+ * `[\s\S]*?`: the lazy form backtracks O(n²) on an unterminated comment in
6
+ * untrusted theme CSS (CodeQL js/polynomial-redos). The tempered form has a
7
+ * single unambiguous match path and stops at the first `*\/`, so the output
8
+ * is identical to the lazy form on valid CSS. Single-sourced here so the two
9
+ * theme importers (tailwind, shadcn) cannot drift to a vulnerable variant.
10
+ */
11
+ export declare function stripCssComments(css: string): string;
12
+ //# sourceMappingURL=css-comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-comments.d.ts","sourceRoot":"","sources":["../../src/theme-importers/css-comments.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,15 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * Strip `/* … *\/` block comments from a CSS string.
4
+ *
5
+ * The body uses a "tempered dot" (`(?:(?!\*\/)[\s\S])*`) rather than a lazy
6
+ * `[\s\S]*?`: the lazy form backtracks O(n²) on an unterminated comment in
7
+ * untrusted theme CSS (CodeQL js/polynomial-redos). The tempered form has a
8
+ * single unambiguous match path and stops at the first `*\/`, so the output
9
+ * is identical to the lazy form on valid CSS. Single-sourced here so the two
10
+ * theme importers (tailwind, shadcn) cannot drift to a vulnerable variant.
11
+ */
12
+ export function stripCssComments(css) {
13
+ return css.replace(/\/\*(?:(?!\*\/)[\s\S])*\*\//g, "");
14
+ }
15
+ //# sourceMappingURL=css-comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-comments.js","sourceRoot":"","sources":["../../src/theme-importers/css-comments.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,GAAG,CAAC,OAAO,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * v0.11.0 — DTCG (W3C Design Tokens Format) import / export for the
3
+ * `themes` primitive (#45 Phase 7 slice 1).
4
+ *
5
+ * The full format-auto-detect (Style Dictionary / Tailwind 4 @theme /
6
+ * shadcn `:root` / loose key-value) is v0.11.2 — see the follow-up
7
+ * comment §4 on #45. This slice ships the DTCG side only so design
8
+ * tooling (Figma, Tokens Studio, Style Dictionary) round-trips cleanly
9
+ * from day one.
10
+ *
11
+ * The shape we accept IS the same shape `themes.tokens` jsonb carries,
12
+ * because `themes.ts`'s Zod schema mirrors DTCG. So `importDtcg` is
13
+ * essentially `JSON.parse + validateThemeTokens`; `exportDtcg` returns
14
+ * the stored tokens jsonb after stable-stringification. The wrappers
15
+ * exist so we have explicit entry points to grow Style-Dictionary /
16
+ * Tailwind / shadcn / loose parsers next to in v0.11.2 without
17
+ * widening `themes.ts`.
18
+ */
19
+ import { type Theme, type ThemeDocument } from "../themes.js";
20
+ /**
21
+ * Parse a DTCG JSON document into a validated tokens tree. Three
22
+ * failure surfaces:
23
+ *
24
+ * 1. Malformed JSON → `DTCG parse failed: <SyntaxError msg>`
25
+ * 2. Valid JSON, no `$value` anywhere → `NotDtcgShape` (round-2
26
+ * opt §2: pre-Zod sniff for the common case where the operator
27
+ * pastes a Tailwind / Style Dictionary / Figma export and the
28
+ * AI sees a deeply-nested Zod path mismatch instead of a clear
29
+ * "this isn't DTCG" hint).
30
+ * 3. Valid JSON, has $value leaves, but Zod rejects something → the
31
+ * ZodError surfaces verbatim (per-token path mismatch, missing
32
+ * composite sub-field, etc.).
33
+ */
34
+ export declare function importDtcg(body: string): ThemeDocument;
35
+ /**
36
+ * Emit a stable DTCG JSON document for the given theme. Keys are
37
+ * sorted alphabetically at every nesting level so the output is
38
+ * byte-for-byte deterministic — Style Dictionary / Tokens Studio diff
39
+ * cleanly under git.
40
+ *
41
+ * Only `tokens` is exported; asset URLs and the `is_active` flag are
42
+ * Caelo-specific and have no DTCG representation. Operators bind
43
+ * assets through `set_theme_asset`, not through an exported JSON file.
44
+ */
45
+ export declare function exportDtcg(theme: Pick<Theme, "tokens">): string;
46
+ //# sourceMappingURL=dtcg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtcg.d.ts","sourceRoot":"","sources":["../../src/theme-importers/dtcg.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAuB,MAAM,cAAc,CAAC;AAGnF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAYtD;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,CAE/D"}