@samva/markup 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +25 -5
  2. package/README.md +92 -26
  3. package/dist/{ast-DAhnAvQN.d.ts → ast-63I-VLQx.d.ts} +15 -2
  4. package/dist/ast.d.ts +2 -2
  5. package/dist/ast.js +15 -2
  6. package/dist/{checks-CW0nFj5n.js → checks-DXuERO4B.js} +37 -3
  7. package/dist/checks.d.ts +8 -1
  8. package/dist/checks.js +2 -2
  9. package/dist/compile-sms.d.ts +2 -2
  10. package/dist/compile-whatsapp.d.ts +2 -2
  11. package/dist/compile.d.ts +9 -84
  12. package/dist/compile.js +11 -920
  13. package/dist/compiler.d.ts +301 -0
  14. package/dist/compiler.js +1389 -0
  15. package/dist/components.d.ts +4 -4
  16. package/dist/conditionals.js +1 -1
  17. package/dist/corpus-Dz5irJ9x.d.ts +161 -0
  18. package/dist/diagnostics-Djd9ckLg.d.ts +90 -0
  19. package/dist/diagnostics.js +6 -3
  20. package/dist/elements-DtwKlFVH.d.ts +820 -0
  21. package/dist/email/components.d.ts +119 -0
  22. package/dist/email/components.js +207 -0
  23. package/dist/email/diagnostics.d.ts +2 -0
  24. package/dist/email/diagnostics.js +56 -0
  25. package/dist/email/elements.d.ts +2 -0
  26. package/dist/email/elements.js +299 -0
  27. package/dist/email/html.d.ts +42 -0
  28. package/dist/email/html.js +266 -0
  29. package/dist/email/jsx-dev-runtime.d.ts +26 -0
  30. package/dist/email/jsx-dev-runtime.js +12 -0
  31. package/dist/email/jsx-runtime.d.ts +2 -0
  32. package/dist/email/jsx-runtime.js +60 -0
  33. package/dist/email/render.d.ts +36 -0
  34. package/dist/email/render.js +6 -0
  35. package/dist/email/selection.d.ts +102 -0
  36. package/dist/email/selection.js +143 -0
  37. package/dist/email/source-edit.d.ts +67 -0
  38. package/dist/email/source-edit.js +185 -0
  39. package/dist/email/stylesheets.d.ts +2 -0
  40. package/dist/email/stylesheets.js +43 -0
  41. package/dist/email/text.d.ts +7 -0
  42. package/dist/email/text.js +92 -0
  43. package/dist/emit.d.ts +13 -12
  44. package/dist/emit.js +63 -63
  45. package/dist/input-schema-BIfzI63B.d.ts +23 -0
  46. package/dist/input-schema.d.ts +2 -0
  47. package/dist/input-schema.js +170 -0
  48. package/dist/jsx-dev-runtime.d.ts +4 -3
  49. package/dist/jsx-dev-runtime.js +9 -1
  50. package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-C5k_Zq0A.d.ts} +4 -4
  51. package/dist/jsx-runtime.d.ts +1 -1
  52. package/dist/jsx-runtime.js +20 -1
  53. package/dist/model.d.ts +1 -1
  54. package/dist/ops.d.ts +1 -1
  55. package/dist/ops.js +2 -2
  56. package/dist/{parser-yzjUOGhc.d.ts → parser-CGBS73Gz.d.ts} +12 -3
  57. package/dist/parser.d.ts +2 -2
  58. package/dist/parser.js +48 -9
  59. package/dist/predicate-CvkzT-a2.js +539 -0
  60. package/dist/{predicate-DTtVtLK7.d.ts → predicate-G2SeSvoN.d.ts} +9 -2
  61. package/dist/predicate.d.ts +2 -2
  62. package/dist/predicate.js +2 -417
  63. package/dist/presets/corpus.d.ts +2 -0
  64. package/dist/presets/corpus.js +186 -0
  65. package/dist/presets/public-gallery.d.ts +241 -459
  66. package/dist/presets/public-gallery.js +305 -5
  67. package/dist/presets/public-handoff.d.ts +75 -3
  68. package/dist/presets/public-handoff.js +220 -37
  69. package/dist/presets/public-preview-artifacts.js +20 -26
  70. package/dist/presets/public-preview.d.ts +40 -1152
  71. package/dist/presets/public-preview.js +388 -1152
  72. package/dist/presets/sources.d.ts +17 -4
  73. package/dist/presets/sources.js +1212 -43
  74. package/dist/presets.d.ts +2 -2
  75. package/dist/printer.d.ts +1 -1
  76. package/dist/render-Cddsy_XF.js +585 -0
  77. package/dist/render-template.d.ts +6 -3
  78. package/dist/render-template.js +4 -2
  79. package/dist/source-locations-BCJQnIfU.d.ts +89 -0
  80. package/dist/source-locations.d.ts +2 -0
  81. package/dist/source-locations.js +125 -0
  82. package/dist/stylesheets-oDRAnYA3.d.ts +94 -0
  83. package/dist/template-Dy5yOPyp.d.ts +60 -0
  84. package/dist/template.d.ts +2 -0
  85. package/dist/template.js +37 -0
  86. package/dist/tsx-source-instrumentation.d.ts +23 -0
  87. package/dist/tsx-source-instrumentation.js +97 -0
  88. package/dist/tsx-tracer.d.ts +116 -0
  89. package/dist/tsx-tracer.js +985 -0
  90. package/dist/{variables-3NGLpNTB.d.ts → variables-if1VrV34.d.ts} +2 -2
  91. package/dist/variables.d.ts +1 -1
  92. package/dist/variables.js +1 -1
  93. package/generated/sml-agent-contract.json +574 -782
  94. package/generated/sml-email-agent-contract.json +502 -677
  95. package/generated/sml-email-language-reference.md +94 -160
  96. package/generated/sml-language-reference.md +195 -220
  97. package/package.json +111 -8
package/dist/predicate.js CHANGED
@@ -1,417 +1,2 @@
1
- import { Result } from "effect";
2
- //#region src/predicate.ts
3
- const CHAR_TAB = 9;
4
- const CHAR_LINE_FEED = 10;
5
- const CHAR_CARRIAGE_RETURN = 13;
6
- const CHAR_SPACE = 32;
7
- const CHAR_BANG = 33;
8
- const CHAR_SINGLE_QUOTE = 39;
9
- const CHAR_OPEN_PAREN = 40;
10
- const CHAR_CLOSE_PAREN = 41;
11
- const CHAR_MINUS = 45;
12
- const CHAR_DOT = 46;
13
- const CHAR_LESS_THAN = 60;
14
- const CHAR_EQUALS = 61;
15
- const CHAR_GREATER_THAN = 62;
16
- const CHAR_UNDERSCORE = 95;
17
- const CHAR_0 = 48;
18
- const CHAR_9 = 57;
19
- const CHAR_UPPER_A = 65;
20
- const CHAR_UPPER_Z = 90;
21
- const CHAR_LOWER_A = 97;
22
- const CHAR_LOWER_Z = 122;
23
- const isWhitespace = (code) => code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LINE_FEED || code === CHAR_CARRIAGE_RETURN;
24
- const isDigit = (code) => code >= CHAR_0 && code <= CHAR_9;
25
- const isLetter = (code) => code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z || code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z;
26
- const isNameStart = (code) => isLetter(code) || code === CHAR_UNDERSCORE;
27
- const isNameChar = (code) => isNameStart(code) || isDigit(code);
28
- const KEYWORDS = [
29
- "and",
30
- "or",
31
- "not"
32
- ];
33
- const isKeyword = (text) => KEYWORDS.includes(text);
34
- const tokenize = (source, baseOffset) => {
35
- const tokens = [];
36
- let index = 0;
37
- const length = source.length;
38
- const failAt = (code, message, start, end) => Result.fail({ diagnostic: {
39
- code,
40
- message,
41
- span: {
42
- start: baseOffset + start,
43
- end: baseOffset + end
44
- }
45
- } });
46
- while (index < length) {
47
- const code = source.charCodeAt(index);
48
- if (isWhitespace(code)) {
49
- index++;
50
- continue;
51
- }
52
- const start = index;
53
- if (code === CHAR_OPEN_PAREN) {
54
- index++;
55
- tokens.push({
56
- kind: "lparen",
57
- text: "(",
58
- span: {
59
- start: baseOffset + start,
60
- end: baseOffset + index
61
- }
62
- });
63
- continue;
64
- }
65
- if (code === CHAR_CLOSE_PAREN) {
66
- index++;
67
- tokens.push({
68
- kind: "rparen",
69
- text: ")",
70
- span: {
71
- start: baseOffset + start,
72
- end: baseOffset + index
73
- }
74
- });
75
- continue;
76
- }
77
- if (code === CHAR_EQUALS || code === CHAR_BANG) {
78
- if (source.charCodeAt(index + 1) === CHAR_EQUALS) {
79
- index += 2;
80
- tokens.push({
81
- kind: "op",
82
- text: code === CHAR_EQUALS ? "==" : "!=",
83
- span: {
84
- start: baseOffset + start,
85
- end: baseOffset + index
86
- }
87
- });
88
- continue;
89
- }
90
- return failAt("predicate-invalid-operator", `unexpected ${String.fromCharCode(code)} — ${code === CHAR_EQUALS ? "write == to compare" : "write != to compare"}`, start, start + 1);
91
- }
92
- if (code === CHAR_GREATER_THAN || code === CHAR_LESS_THAN) {
93
- index++;
94
- if (source.charCodeAt(index) === CHAR_EQUALS) index++;
95
- tokens.push({
96
- kind: "op",
97
- text: source.slice(start, index),
98
- span: {
99
- start: baseOffset + start,
100
- end: baseOffset + index
101
- }
102
- });
103
- continue;
104
- }
105
- if (code === CHAR_SINGLE_QUOTE) {
106
- index++;
107
- const contentStart = index;
108
- while (index < length && source.charCodeAt(index) !== CHAR_SINGLE_QUOTE) index++;
109
- if (index >= length) return failAt("predicate-unterminated-string", "unterminated string — add a closing '", start, length);
110
- const value = source.slice(contentStart, index);
111
- index++;
112
- if (value.includes("--")) return failAt("predicate-string-double-dash", "string literals cannot contain -- (predicates are embedded in HTML comment markers)", start, index);
113
- tokens.push({
114
- kind: "string",
115
- text: source.slice(start, index),
116
- string: value,
117
- span: {
118
- start: baseOffset + start,
119
- end: baseOffset + index
120
- }
121
- });
122
- continue;
123
- }
124
- if (isDigit(code) || code === CHAR_MINUS && isDigit(source.charCodeAt(index + 1))) {
125
- index++;
126
- while (index < length && isDigit(source.charCodeAt(index))) index++;
127
- if (source.charCodeAt(index) === CHAR_DOT && isDigit(source.charCodeAt(index + 1))) {
128
- index++;
129
- while (index < length && isDigit(source.charCodeAt(index))) index++;
130
- }
131
- tokens.push({
132
- kind: "number",
133
- text: source.slice(start, index),
134
- number: Number.parseFloat(source.slice(start, index)),
135
- span: {
136
- start: baseOffset + start,
137
- end: baseOffset + index
138
- }
139
- });
140
- continue;
141
- }
142
- if (isNameStart(code)) {
143
- index++;
144
- while (index < length && isNameChar(source.charCodeAt(index))) index++;
145
- if (source.charCodeAt(index) === CHAR_DOT) return failAt("predicate-path-not-supported", `unexpected . after "${source.slice(start, index)}" — predicates use flat variable names, not paths`, start, index + 1);
146
- tokens.push({
147
- kind: "name",
148
- text: source.slice(start, index),
149
- span: {
150
- start: baseOffset + start,
151
- end: baseOffset + index
152
- }
153
- });
154
- continue;
155
- }
156
- if (code === CHAR_DOT) return failAt("predicate-path-not-supported", "unexpected . — predicates use flat variable names, not paths", start, start + 1);
157
- return failAt("predicate-unexpected-character", `unexpected character ${JSON.stringify(String.fromCharCode(code))} in predicate`, start, start + 1);
158
- }
159
- tokens.push({
160
- kind: "eof",
161
- text: "",
162
- span: {
163
- start: baseOffset + length,
164
- end: baseOffset + length
165
- }
166
- });
167
- return Result.succeed(tokens);
168
- };
169
- const COMPARISON_OPS = [
170
- "==",
171
- "!=",
172
- ">",
173
- ">=",
174
- "<",
175
- "<="
176
- ];
177
- /**
178
- * Parses a predicate. `baseOffset` is the absolute source offset of the
179
- * predicate's first character (an <If> test attribute passes valueSpan.start),
180
- * so diagnostic and node spans point into the containing document.
181
- */
182
- const parsePredicate = (source, baseOffset = 0) => {
183
- const lexed = tokenize(source, baseOffset);
184
- if (Result.isFailure(lexed)) return Result.fail([lexed.failure.diagnostic]);
185
- const tokens = lexed.success;
186
- let index = 0;
187
- let diagnostic;
188
- const fail = (code, message, span) => {
189
- diagnostic ??= {
190
- code,
191
- message,
192
- span
193
- };
194
- return null;
195
- };
196
- const peek = () => tokens[index];
197
- const advance = () => tokens[index++];
198
- const isKeywordToken = (token, keyword) => token.kind === "name" && token.text === keyword;
199
- const parsePrimary = () => {
200
- const token = advance();
201
- if (token.kind === "lparen") {
202
- const inner = parseOr();
203
- if (inner === null) return null;
204
- const close = advance();
205
- if (close.kind !== "rparen") return fail("predicate-missing-paren", "missing ) — close the group", close.span);
206
- return inner;
207
- }
208
- if (token.kind === "name") {
209
- if (isKeyword(token.text)) return fail("predicate-unexpected-token", `unexpected "${token.text}" — expected a variable name, comparison, or ( group )`, token.span);
210
- const opToken = peek();
211
- if (opToken.kind !== "op") return {
212
- kind: "name",
213
- name: token.text,
214
- span: token.span
215
- };
216
- advance();
217
- const op = COMPARISON_OPS.find((candidate) => candidate === opToken.text);
218
- if (op === void 0) return fail("predicate-invalid-operator", `unknown operator ${opToken.text} — use ==, !=, >, >=, <, or <=`, opToken.span);
219
- const literalToken = advance();
220
- if (literalToken.kind === "string") return {
221
- kind: "comparison",
222
- name: token.text,
223
- op,
224
- literal: {
225
- kind: "string",
226
- value: literalToken.string
227
- },
228
- span: {
229
- start: token.span.start,
230
- end: literalToken.span.end
231
- }
232
- };
233
- if (literalToken.kind === "number") return {
234
- kind: "comparison",
235
- name: token.text,
236
- op,
237
- literal: {
238
- kind: "number",
239
- value: literalToken.number
240
- },
241
- span: {
242
- start: token.span.start,
243
- end: literalToken.span.end
244
- }
245
- };
246
- return fail("predicate-expected-literal", `expected a literal after ${op} — write ${token.text} ${op} 'value' or ${token.text} ${op} 5`, literalToken.span);
247
- }
248
- if (token.kind === "eof") return fail("predicate-unexpected-end", "unexpected end of predicate — expected a variable name, comparison, or ( group )", token.span);
249
- return fail("predicate-unexpected-token", `unexpected ${token.text} — comparisons put the variable first: name == 'value'`, token.span);
250
- };
251
- const parseUnary = () => {
252
- const token = peek();
253
- if (isKeywordToken(token, "not")) {
254
- advance();
255
- const operand = parsePrimary();
256
- if (operand === null) return null;
257
- return {
258
- kind: "not",
259
- operand,
260
- span: {
261
- start: token.span.start,
262
- end: operand.span.end
263
- }
264
- };
265
- }
266
- return parsePrimary();
267
- };
268
- const parseAnd = () => {
269
- let left = parseUnary();
270
- while (left !== null && isKeywordToken(peek(), "and")) {
271
- advance();
272
- const right = parseUnary();
273
- if (right === null) return null;
274
- left = {
275
- kind: "and",
276
- left,
277
- right,
278
- span: {
279
- start: left.span.start,
280
- end: right.span.end
281
- }
282
- };
283
- }
284
- return left;
285
- };
286
- const parseOr = () => {
287
- let left = parseAnd();
288
- while (left !== null && isKeywordToken(peek(), "or")) {
289
- advance();
290
- const right = parseAnd();
291
- if (right === null) return null;
292
- left = {
293
- kind: "or",
294
- left,
295
- right,
296
- span: {
297
- start: left.span.start,
298
- end: right.span.end
299
- }
300
- };
301
- }
302
- return left;
303
- };
304
- if (source.trim().length === 0) return Result.fail([{
305
- code: "predicate-empty",
306
- message: "empty predicate — write e.g. plan == 'pro'",
307
- span: {
308
- start: baseOffset,
309
- end: baseOffset + source.length
310
- }
311
- }]);
312
- const ast = parseOr();
313
- if (ast !== null && diagnostic === void 0) {
314
- const trailing = peek();
315
- if (trailing.kind !== "eof") fail("predicate-trailing-input", `unexpected ${JSON.stringify(trailing.text)} after the predicate — join clauses with "and" or "or"`, trailing.span);
316
- }
317
- if (ast === null || diagnostic !== void 0) return Result.fail([diagnostic ?? {
318
- code: "predicate-parse-failed",
319
- message: "predicate parse failed",
320
- span: {
321
- start: baseOffset,
322
- end: baseOffset + source.length
323
- }
324
- }]);
325
- return Result.succeed(ast);
326
- };
327
- const toNumber = (value) => {
328
- if (typeof value === "number") return value;
329
- if (typeof value === "string" && value.trim() !== "") return Number(value);
330
- return NaN;
331
- };
332
- const compareNumbers = (op, left, right) => {
333
- switch (op) {
334
- case "==": return left === right;
335
- case "!=": return left !== right;
336
- case ">": return left > right;
337
- case ">=": return left >= right;
338
- case "<": return left < right;
339
- case "<=": return left <= right;
340
- }
341
- };
342
- const evaluateComparison = (node, variables) => {
343
- const value = variables[node.name];
344
- if (value === void 0 || value === null) return false;
345
- if (node.literal.kind === "number") {
346
- const left = toNumber(value);
347
- if (Number.isNaN(left)) return false;
348
- return compareNumbers(node.op, left, node.literal.value);
349
- }
350
- if (node.op === "==" || node.op === "!=") {
351
- if (typeof value !== "string") return false;
352
- return node.op === "==" ? value === node.literal.value : value !== node.literal.value;
353
- }
354
- const left = toNumber(value);
355
- const right = toNumber(node.literal.value);
356
- if (Number.isNaN(left) || Number.isNaN(right)) return false;
357
- return compareNumbers(node.op, left, right);
358
- };
359
- const evaluatePredicate = (ast, variables) => {
360
- switch (ast.kind) {
361
- case "or": return evaluatePredicate(ast.left, variables) || evaluatePredicate(ast.right, variables);
362
- case "and": return evaluatePredicate(ast.left, variables) && evaluatePredicate(ast.right, variables);
363
- case "not": return !evaluatePredicate(ast.operand, variables);
364
- case "comparison": return evaluateComparison(ast, variables);
365
- case "name": {
366
- const value = variables[ast.name];
367
- return value !== void 0 && value !== null && value !== "";
368
- }
369
- }
370
- };
371
- const precedence = (node) => {
372
- switch (node.kind) {
373
- case "or": return 1;
374
- case "and": return 2;
375
- case "not": return 3;
376
- case "comparison":
377
- case "name": return 4;
378
- }
379
- };
380
- const printLiteral = (literal) => literal.kind === "string" ? `'${literal.value}'` : String(literal.value);
381
- const printNode = (node, minPrecedence) => {
382
- const wrap = (text) => precedence(node) < minPrecedence ? `(${text})` : text;
383
- switch (node.kind) {
384
- case "or": return wrap(`${printNode(node.left, 1)} or ${printNode(node.right, 1)}`);
385
- case "and": return wrap(`${printNode(node.left, 2)} and ${printNode(node.right, 2)}`);
386
- case "not": return wrap(`not ${printNode(node.operand, 4)}`);
387
- case "comparison": return wrap(`${node.name} ${node.op} ${printLiteral(node.literal)}`);
388
- case "name": return wrap(node.name);
389
- }
390
- };
391
- /** Canonical form: single space around operators/keywords, minimal parens. */
392
- const printPredicate = (ast) => printNode(ast, 0);
393
- const collectPredicateVariables = (ast) => {
394
- const names = /* @__PURE__ */ new Set();
395
- const visit = (node) => {
396
- switch (node.kind) {
397
- case "or":
398
- case "and":
399
- visit(node.left);
400
- visit(node.right);
401
- return;
402
- case "not":
403
- visit(node.operand);
404
- return;
405
- case "comparison":
406
- case "name":
407
- names.add(node.name);
408
- return;
409
- }
410
- };
411
- visit(ast);
412
- return names;
413
- };
414
- //#endregion
415
- export { collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
416
-
417
- //# sourceMappingURL=predicate.js.map
1
+ import { a as printPredicate, i as parsePredicate, n as collectPredicateVariables, r as evaluatePredicate, t as PREDICATE_PARSER_LIMITS } from "./predicate-CvkzT-a2.js";
2
+ export { PREDICATE_PARSER_LIMITS, collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
@@ -0,0 +1,2 @@
1
+ import { a as TemplateCorpusKind, c as TemplateFacetSet, d as templateFacetOptions, i as PublicCorpusRegistrationIssue, l as matchesTemplateFacets, n as OwnedEditablePreset, o as TemplateCorpusRecord, r as PublicCorpusRegistration, s as TemplateFacetQuery, t as InspirationReference, u as registerPublicCorpusEntry } from "../corpus-Dz5irJ9x.js";
2
+ export { InspirationReference, OwnedEditablePreset, PublicCorpusRegistration, PublicCorpusRegistrationIssue, TemplateCorpusKind, TemplateCorpusRecord, TemplateFacetQuery, TemplateFacetSet, matchesTemplateFacets, registerPublicCorpusEntry, templateFacetOptions };
@@ -0,0 +1,186 @@
1
+ import { presetCatalog } from "../presets.js";
2
+ import { Result, Schema } from "effect";
3
+ //#region presets/corpus.ts
4
+ const TemplateCorpusKind = Schema.Literals(["owned-editable-sml", "inspiration-reference"]);
5
+ const templateFacetOptions = {
6
+ jobType: [
7
+ "account-access",
8
+ "commerce",
9
+ "product-communication",
10
+ "lifecycle",
11
+ "publishing-campaigns"
12
+ ],
13
+ lifecycle: [
14
+ "acquisition",
15
+ "activation",
16
+ "engagement",
17
+ "transaction",
18
+ "retention",
19
+ "recovery"
20
+ ],
21
+ industry: [
22
+ "saas",
23
+ "ecommerce",
24
+ "developer-tools",
25
+ "newsletters-media"
26
+ ],
27
+ season: ["evergreen", "seasonal"],
28
+ audience: [
29
+ "product-users",
30
+ "customers",
31
+ "members",
32
+ "readers",
33
+ "shoppers",
34
+ "teams"
35
+ ],
36
+ design: [
37
+ "minimal",
38
+ "branded",
39
+ "editorial",
40
+ "data-heavy",
41
+ "single-cta"
42
+ ],
43
+ brandProvenance: ["samva-original", "third-party-reference"]
44
+ };
45
+ const JobTypeFacet = Schema.Literals(templateFacetOptions.jobType);
46
+ const LifecycleFacet = Schema.Literals(templateFacetOptions.lifecycle);
47
+ const IndustryFacet = Schema.Literals(templateFacetOptions.industry);
48
+ const SeasonFacet = Schema.Literals(templateFacetOptions.season);
49
+ const AudienceFacet = Schema.Literals(templateFacetOptions.audience);
50
+ const DesignFacet = Schema.Literals(templateFacetOptions.design);
51
+ const BrandProvenanceFacet = Schema.Literals(templateFacetOptions.brandProvenance);
52
+ const TemplateFacetSet = Schema.Struct({
53
+ jobType: Schema.Array(JobTypeFacet),
54
+ lifecycle: Schema.Array(LifecycleFacet),
55
+ industry: Schema.Array(IndustryFacet),
56
+ season: Schema.Array(SeasonFacet),
57
+ audience: Schema.Array(AudienceFacet),
58
+ design: Schema.Array(DesignFacet),
59
+ brandProvenance: Schema.Array(BrandProvenanceFacet)
60
+ });
61
+ const CorpusReview = Schema.Struct({
62
+ state: Schema.Literals([
63
+ "draft",
64
+ "approved",
65
+ "rejected"
66
+ ]),
67
+ reviewedBy: Schema.String,
68
+ reviewedAt: Schema.String,
69
+ notes: Schema.String
70
+ });
71
+ const CorpusDisplay = Schema.Struct({
72
+ state: Schema.Literals(["hidden", "public"]),
73
+ attributionLabel: Schema.String
74
+ });
75
+ const CorpusRetention = Schema.Struct({
76
+ policy: Schema.Literals(["retain-until-withdrawn", "retain-while-linked"]),
77
+ reviewedAt: Schema.String
78
+ });
79
+ const CorpusTakedown = Schema.Struct({
80
+ state: Schema.Literals(["active", "removed"]),
81
+ contact: Schema.String,
82
+ procedureUrl: Schema.String
83
+ });
84
+ const OwnedEditablePreset = Schema.TaggedStruct("OwnedEditablePreset", {
85
+ corpusKind: Schema.Literal("owned-editable-sml"),
86
+ provenance: Schema.Struct({
87
+ owner: Schema.Literal("Samva"),
88
+ sourcePath: Schema.String,
89
+ recordedAt: Schema.String
90
+ }),
91
+ review: CorpusReview,
92
+ display: CorpusDisplay,
93
+ retention: CorpusRetention,
94
+ takedown: CorpusTakedown
95
+ });
96
+ const InspirationReference = Schema.TaggedStruct("InspirationReference", {
97
+ corpusKind: Schema.Literal("inspiration-reference"),
98
+ provenance: Schema.Struct({
99
+ publisher: Schema.String,
100
+ sourceUrl: Schema.String,
101
+ capturedAt: Schema.String,
102
+ rightsBasis: Schema.Literals(["display-link-and-commentary", "permission"])
103
+ }),
104
+ review: CorpusReview,
105
+ display: CorpusDisplay,
106
+ retention: CorpusRetention,
107
+ takedown: CorpusTakedown
108
+ });
109
+ const TemplateCorpusRecord = Schema.Union([OwnedEditablePreset, InspirationReference]);
110
+ const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
111
+ const nonEmpty = (value) => value.trim().length > 0;
112
+ const isHttpsUrl = (value) => {
113
+ const url = URL.parse(value);
114
+ return url?.protocol === "https:" && url.hostname.length > 0 && url.username.length === 0 && url.password.length === 0;
115
+ };
116
+ const requiredFacetDimensions = [
117
+ "jobType",
118
+ "lifecycle",
119
+ "industry",
120
+ "season",
121
+ "audience",
122
+ "design",
123
+ "brandProvenance"
124
+ ];
125
+ const canonicalOwnedPresetSources = new Map(presetCatalog.filter((preset) => preset.channel === "email").map((preset) => [preset.id, `packages/markup/presets/${preset.id}.tsx`]));
126
+ /** Decode and gate a record before it can enter any public projection. */
127
+ const registerPublicCorpusEntry = (input) => {
128
+ const decoded = Schema.decodeUnknownResult(Schema.Struct({
129
+ id: Schema.String,
130
+ corpus: TemplateCorpusRecord,
131
+ facets: TemplateFacetSet
132
+ }), { onExcessProperty: "error" })(input);
133
+ if (Result.isFailure(decoded)) return Result.fail({
134
+ id: "unknown",
135
+ message: "record does not match a corpus schema"
136
+ });
137
+ const { id, corpus, facets } = decoded.success;
138
+ const timestamps = [
139
+ corpus.review.reviewedAt,
140
+ corpus.retention.reviewedAt,
141
+ corpus._tag === "InspirationReference" ? corpus.provenance.capturedAt : corpus.provenance.recordedAt
142
+ ];
143
+ if (!nonEmpty(id) || corpus.review.state !== "approved" || !nonEmpty(corpus.review.reviewedBy) || !nonEmpty(corpus.review.notes) || corpus.display.state !== "public" || !nonEmpty(corpus.display.attributionLabel) || corpus.takedown.state !== "active" || !nonEmpty(corpus.takedown.contact) || !isHttpsUrl(corpus.takedown.procedureUrl) || timestamps.some((timestamp) => !ISO_TIMESTAMP.test(timestamp))) return Result.fail({
144
+ id,
145
+ message: "public corpus records require approved review, display, retention, and takedown metadata"
146
+ });
147
+ if (corpus._tag === "OwnedEditablePreset" && (canonicalOwnedPresetSources.get(id) !== corpus.provenance.sourcePath || corpus.retention.policy !== "retain-until-withdrawn" || facets.brandProvenance.some((value) => value !== "samva-original"))) return Result.fail({
148
+ id,
149
+ message: "owned presets require a canonical editable TSX source"
150
+ });
151
+ if (corpus._tag === "InspirationReference" && (!isHttpsUrl(corpus.provenance.sourceUrl) || corpus.retention.policy !== "retain-while-linked" || facets.brandProvenance.some((value) => value !== "third-party-reference"))) return Result.fail({
152
+ id,
153
+ message: "inspiration references require an HTTPS source and third-party provenance facet"
154
+ });
155
+ for (const dimension of requiredFacetDimensions) {
156
+ const values = facets[dimension];
157
+ if (values.length === 0 || new Set(values).size !== values.length) return Result.fail({
158
+ id,
159
+ message: `facet ${dimension} must be populated without duplicates`
160
+ });
161
+ }
162
+ return Result.succeed({
163
+ id,
164
+ corpus,
165
+ facets
166
+ });
167
+ };
168
+ const TemplateFacetQuery = Schema.Struct({
169
+ jobType: Schema.optionalKey(Schema.Array(JobTypeFacet)),
170
+ lifecycle: Schema.optionalKey(Schema.Array(LifecycleFacet)),
171
+ industry: Schema.optionalKey(Schema.Array(IndustryFacet)),
172
+ season: Schema.optionalKey(Schema.Array(SeasonFacet)),
173
+ audience: Schema.optionalKey(Schema.Array(AudienceFacet)),
174
+ design: Schema.optionalKey(Schema.Array(DesignFacet)),
175
+ brandProvenance: Schema.optionalKey(Schema.Array(BrandProvenanceFacet))
176
+ });
177
+ /** Strict intersection: every selected value in every selected dimension must match. */
178
+ const matchesTemplateFacets = (facets, query) => requiredFacetDimensions.every((dimension) => {
179
+ const selected = query[dimension];
180
+ const available = new Set(facets[dimension]);
181
+ return selected === void 0 || selected.every((value) => available.has(value));
182
+ });
183
+ //#endregion
184
+ export { InspirationReference, OwnedEditablePreset, TemplateCorpusKind, TemplateCorpusRecord, TemplateFacetQuery, TemplateFacetSet, matchesTemplateFacets, registerPublicCorpusEntry, templateFacetOptions };
185
+
186
+ //# sourceMappingURL=corpus.js.map