@caelo-cms/provisioning 0.10.72 → 0.11.1

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.
package/dist/agent-db.js CHANGED
@@ -6335,6 +6335,350 @@ var ZodIssueCode = {
6335
6335
  };
6336
6336
  var ZodFirstPartyTypeKind;
6337
6337
  (function(ZodFirstPartyTypeKind) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
6338
+ // ../shared/src/escaped-line-breaks.ts
6339
+ function escapedLineBreakLength(value, i) {
6340
+ if (value[i] !== "\\")
6341
+ return 0;
6342
+ if (value[i + 1] === "n")
6343
+ return 2;
6344
+ if (value[i + 1] === "r" && value[i + 2] === "\\" && value[i + 3] === "n")
6345
+ return 4;
6346
+ return 0;
6347
+ }
6348
+ var REGEX_PRECEDERS = new Set([..."(,=:[!&|?{};+-*%<>~^"]);
6349
+ function skipQuoted(value, start) {
6350
+ const quote = value[start];
6351
+ let j = start + 1;
6352
+ while (j < value.length) {
6353
+ const ch = value[j];
6354
+ if (ch === "\\") {
6355
+ if (value[j + 1] === "n" || value[j + 1] === "r")
6356
+ return -1;
6357
+ j += 2;
6358
+ continue;
6359
+ }
6360
+ if (ch === quote)
6361
+ return j + 1;
6362
+ j += 1;
6363
+ }
6364
+ return j;
6365
+ }
6366
+ function jsLiteralHoldsEscape(value) {
6367
+ let prev = "";
6368
+ let i = 0;
6369
+ while (i < value.length) {
6370
+ const ch = value[i];
6371
+ const lineBreak = escapedLineBreakLength(value, i);
6372
+ if (lineBreak > 0) {
6373
+ prev = "";
6374
+ i += lineBreak;
6375
+ continue;
6376
+ }
6377
+ if (ch === "\\") {
6378
+ i += 2;
6379
+ continue;
6380
+ }
6381
+ if (ch === '"' || ch === "'" || ch === "`") {
6382
+ const end = skipQuoted(value, i);
6383
+ if (end < 0)
6384
+ return true;
6385
+ prev = ch;
6386
+ i = end;
6387
+ continue;
6388
+ }
6389
+ if (ch === "/" && value[i + 1] === "/") {
6390
+ i += 2;
6391
+ while (i < value.length && escapedLineBreakLength(value, i) === 0)
6392
+ i += 1;
6393
+ continue;
6394
+ }
6395
+ if (ch === "/" && value[i + 1] === "*") {
6396
+ const close = value.indexOf("*/", i + 2);
6397
+ i = close < 0 ? value.length : close + 2;
6398
+ continue;
6399
+ }
6400
+ if (ch === "/" && (prev === "" || REGEX_PRECEDERS.has(prev))) {
6401
+ let j = i + 1;
6402
+ let inClass = false;
6403
+ while (j < value.length) {
6404
+ const r = value[j];
6405
+ if (r === "\\") {
6406
+ if (value[j + 1] === "n" || value[j + 1] === "r")
6407
+ return true;
6408
+ j += 2;
6409
+ continue;
6410
+ }
6411
+ if (r === "[")
6412
+ inClass = true;
6413
+ else if (r === "]")
6414
+ inClass = false;
6415
+ else if (r === "/" && !inClass)
6416
+ break;
6417
+ j += 1;
6418
+ }
6419
+ prev = "/";
6420
+ i = j + 1;
6421
+ continue;
6422
+ }
6423
+ if (ch !== " " && ch !== "\t")
6424
+ prev = ch;
6425
+ i += 1;
6426
+ }
6427
+ return false;
6428
+ }
6429
+ function cssLiteralHoldsEscape(value) {
6430
+ let i = 0;
6431
+ while (i < value.length) {
6432
+ const ch = value[i];
6433
+ if (ch === "/" && value[i + 1] === "*") {
6434
+ const close = value.indexOf("*/", i + 2);
6435
+ i = close < 0 ? value.length : close + 2;
6436
+ continue;
6437
+ }
6438
+ if (ch === '"' || ch === "'") {
6439
+ const end = skipQuoted(value, i);
6440
+ if (end < 0)
6441
+ return true;
6442
+ i = end;
6443
+ continue;
6444
+ }
6445
+ i += ch === "\\" ? 2 : 1;
6446
+ }
6447
+ return false;
6448
+ }
6449
+ var RAW_TEXT_ELEMENTS = new Set(["script", "style", "pre", "textarea"]);
6450
+ function htmlLiteralHoldsEscape(value) {
6451
+ const lower = value.toLowerCase();
6452
+ let i = 0;
6453
+ while (i < value.length) {
6454
+ if (value[i] !== "<") {
6455
+ i += 1;
6456
+ continue;
6457
+ }
6458
+ if (value.startsWith("<!--", i)) {
6459
+ const close = value.indexOf("-->", i + 4);
6460
+ const end = close < 0 ? value.length : close + 3;
6461
+ if (/(?<!\\)\\[nr]/.test(value.slice(i, end)))
6462
+ return true;
6463
+ i = end;
6464
+ continue;
6465
+ }
6466
+ const name = /^<([a-zA-Z][a-zA-Z0-9-]*)/.exec(value.slice(i, i + 64))?.[1]?.toLowerCase();
6467
+ let j = i + 1;
6468
+ while (j < value.length && value[j] !== ">") {
6469
+ if (value[j] === '"' || value[j] === "'") {
6470
+ const end = skipQuoted(value, j);
6471
+ if (end < 0)
6472
+ return true;
6473
+ j = end;
6474
+ continue;
6475
+ }
6476
+ j += 1;
6477
+ }
6478
+ i = j + 1;
6479
+ if (name && RAW_TEXT_ELEMENTS.has(name)) {
6480
+ const close = lower.indexOf(`</${name}`, i);
6481
+ const end = close < 0 ? value.length : close;
6482
+ if (/(?<!\\)\\[nr]/.test(value.slice(i, end)))
6483
+ return true;
6484
+ i = end;
6485
+ }
6486
+ }
6487
+ return false;
6488
+ }
6489
+ var LITERAL_HOLDS_ESCAPE = {
6490
+ html: htmlLiteralHoldsEscape,
6491
+ css: cssLiteralHoldsEscape,
6492
+ js: jsLiteralHoldsEscape
6493
+ };
6494
+ var ESCAPED_LINE_BREAK = /(?<!\\)(?:\\r)?\\n/g;
6495
+ function hasEscapedLineBreaks(value, language) {
6496
+ if (/[\r\n]/.test(value))
6497
+ return false;
6498
+ const hits = value.match(ESCAPED_LINE_BREAK)?.length ?? 0;
6499
+ if (hits < 2)
6500
+ return false;
6501
+ if (!/(?<!\\)\\n(?:[ \t]|\\t)/.test(value))
6502
+ return false;
6503
+ return !LITERAL_HOLDS_ESCAPE[language](value);
6504
+ }
6505
+ function decodeEscapedLineBreaks(value, language) {
6506
+ if (typeof value !== "string" || !hasEscapedLineBreaks(value, language))
6507
+ return value;
6508
+ return value.replace(/(?<!\\)(?:\\r)?\\n((?:\\t)*)/g, (_m, tabs) => `
6509
+ ${"\t".repeat(tabs.length / 2)}`);
6510
+ }
6511
+
6512
+ // ../shared/src/lists/fields.ts
6513
+ var LIST_RESERVED_NAMES = new Set([
6514
+ "id",
6515
+ "node",
6516
+ "index",
6517
+ "depth",
6518
+ "first",
6519
+ "last",
6520
+ "items",
6521
+ "page",
6522
+ "site_text",
6523
+ "recurse"
6524
+ ]);
6525
+ var listNameSchema = string2().regex(/^[a-z][a-z0-9_]{0,63}$/, "must be snake_case (lowercase letters, digits, underscores)");
6526
+ var fieldLabel = string2().min(1).max(128);
6527
+ var fieldExtSchema = record(string2().regex(/^[a-z0-9-]+$/), unknown());
6528
+ var base = {
6529
+ name: listNameSchema,
6530
+ label: fieldLabel,
6531
+ required: boolean2().optional(),
6532
+ default: unknown().optional(),
6533
+ ext: fieldExtSchema.optional()
6534
+ };
6535
+ var textField = object({
6536
+ ...base,
6537
+ type: literal("text"),
6538
+ maxLength: number2().int().min(1).max(2000).optional()
6539
+ }).strict();
6540
+ var richtextField = object({
6541
+ ...base,
6542
+ type: literal("richtext"),
6543
+ maxLength: number2().int().min(1).max(20000).optional()
6544
+ }).strict();
6545
+ var linkField = object({ ...base, type: literal("link") }).strict();
6546
+ var mediaField = object({ ...base, type: literal("media") }).strict();
6547
+ var selectField = object({
6548
+ ...base,
6549
+ type: literal("select"),
6550
+ options: array(listNameSchema).min(1).max(64)
6551
+ }).strict();
6552
+ var booleanField = object({ ...base, type: literal("boolean") }).strict();
6553
+ var numberField = object({
6554
+ ...base,
6555
+ type: literal("number"),
6556
+ min: number2().optional(),
6557
+ max: number2().optional()
6558
+ }).strict();
6559
+ var dateField = object({ ...base, type: literal("date") }).strict();
6560
+ var iconField = object({ ...base, type: literal("icon") }).strict();
6561
+ var moduleField = object({
6562
+ ...base,
6563
+ type: literal("module"),
6564
+ accepts: array(string2().regex(/^[a-z0-9-]+$/)).max(32).optional()
6565
+ }).strict();
6566
+ var termField = object({
6567
+ ...base,
6568
+ type: literal("term"),
6569
+ list: string2().regex(/^[a-z0-9-]+$/),
6570
+ multiple: boolean2().optional()
6571
+ }).strict();
6572
+ var fieldDefSchema = discriminatedUnion("type", [
6573
+ textField,
6574
+ richtextField,
6575
+ linkField,
6576
+ mediaField,
6577
+ selectField,
6578
+ booleanField,
6579
+ numberField,
6580
+ dateField,
6581
+ iconField,
6582
+ moduleField,
6583
+ termField
6584
+ ]);
6585
+ var linkValueSchema = object({
6586
+ href: string2().min(1).max(2000),
6587
+ target: _enum(["_self", "_blank"]).optional()
6588
+ }).strict();
6589
+ var embeddedModuleValueSchema = object({ moduleId: string2().uuid(), contentInstanceId: string2().uuid() }).strict();
6590
+
6591
+ // ../shared/src/lists/list-type.ts
6592
+ var LIST_MAX_DEPTH = 8;
6593
+ var LIST_MAX_ITEMS = 500;
6594
+ var LIST_QUERY_MAX_LIMIT = 50;
6595
+ var QUERY_KNOBS = [
6596
+ "page_type",
6597
+ "terms",
6598
+ "terms_mode",
6599
+ "parent",
6600
+ "exclude_current",
6601
+ "order",
6602
+ "limit"
6603
+ ];
6604
+ var SYSTEM_SOURCE_NAMES = [
6605
+ "breadcrumbs",
6606
+ "children",
6607
+ "siblings",
6608
+ "page_terms",
6609
+ "term_pages"
6610
+ ];
6611
+ var queryOrderSchema = union([
6612
+ _enum(["published_desc", "published_asc", "title_asc"]),
6613
+ string2().regex(/^prop_(?:desc|asc):[a-z][a-z0-9_]{0,63}$/)
6614
+ ]);
6615
+ var pageQuerySchema = object({
6616
+ page_type: string2().regex(/^[a-z0-9-]+$/).optional(),
6617
+ terms: array(string2().regex(/^caelo:term\/[0-9a-f-]{36}\/[0-9a-f-]{36}$/)).max(20).optional(),
6618
+ terms_mode: _enum(["any", "all"]).optional(),
6619
+ parent: string2().regex(/^caelo:page\/[0-9a-f-]{36}$/).optional(),
6620
+ exclude_current: boolean2().optional(),
6621
+ order: queryOrderSchema,
6622
+ limit: number2().int().min(1).max(LIST_QUERY_MAX_LIMIT)
6623
+ }).strict();
6624
+ var pageQueryPartialSchema = pageQuerySchema.partial();
6625
+ var sourceDefSchema = discriminatedUnion("kind", [
6626
+ object({
6627
+ kind: literal("query"),
6628
+ editable: array(_enum(QUERY_KNOBS)).max(QUERY_KNOBS.length),
6629
+ defaults: pageQueryPartialSchema
6630
+ }).strict(),
6631
+ object({
6632
+ kind: literal("system"),
6633
+ name: _enum(SYSTEM_SOURCE_NAMES, {
6634
+ error: () => `system sources are ${SYSTEM_SOURCE_NAMES.join(", ")} \u2014 a plugin's list source (consent_categories, language_links, \u2026) is {kind:"plugin", name:"<source>"}, and a module that only iterates it needs no field at all: {{#<source>}}\u2026{{/<source>}}.`
6635
+ })
6636
+ }).strict(),
6637
+ object({
6638
+ kind: literal("plugin"),
6639
+ name: string2().regex(/^[a-z][a-z0-9_]{0,63}$/)
6640
+ }).strict()
6641
+ ]);
6642
+ var childrenSchema = object({
6643
+ name: listNameSchema,
6644
+ allow: array(listNameSchema).min(1).max(32),
6645
+ max: number2().int().min(1).max(LIST_MAX_ITEMS).optional()
6646
+ }).strict();
6647
+ var nodeDefSchema = object({
6648
+ label: string2().min(1).max(128),
6649
+ fields: array(fieldDefSchema).max(32),
6650
+ children: childrenSchema.optional(),
6651
+ current_from: listNameSchema.optional(),
6652
+ source: sourceDefSchema.optional(),
6653
+ ext: fieldExtSchema.optional()
6654
+ }).strict();
6655
+ var LIST_TYPE_ROLES = ["navigation", "vocabulary", "content"];
6656
+ var listTypeDefSchema = object({
6657
+ slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
6658
+ name: string2().min(1).max(200),
6659
+ purpose: string2().min(1).max(1000),
6660
+ role: _enum(LIST_TYPE_ROLES),
6661
+ root: object({
6662
+ allow: array(listNameSchema).min(1).max(32),
6663
+ max: number2().int().min(1).max(LIST_MAX_ITEMS).optional()
6664
+ }).strict(),
6665
+ nodes: record(listNameSchema, nodeDefSchema),
6666
+ max_depth: number2().int().min(1).max(LIST_MAX_DEPTH).optional()
6667
+ }).strict();
6668
+
6669
+ // ../shared/src/lists/items.ts
6670
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
6671
+ var listItemInputSchema = lazy(() => object({
6672
+ id: string2().regex(UUID_RE, "id must be a lowercase uuid").optional(),
6673
+ node: string2().min(1).max(64),
6674
+ values: record(string2(), unknown()).default({}),
6675
+ children: array(listItemInputSchema).optional(),
6676
+ query: record(string2(), unknown()).optional()
6677
+ }).strict());
6678
+
6679
+ // ../shared/src/lists/refs.ts
6680
+ var LIST_REF_RE = /^caelo:list\/([0-9a-f-]{36})$/;
6681
+
6338
6682
  // ../shared/src/content.ts
6339
6683
  var slugSchema = string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, "slug must be lowercase letters, digits, or hyphens (1\u201364 chars, no leading/trailing hyphen)");
6340
6684
  var displayNameSchema = string2().min(1).max(128);
@@ -6343,12 +6687,49 @@ var MODULE_CSS_MAX = 128 * 1024;
6343
6687
  var MODULE_JS_MAX = 256 * 1024;
6344
6688
  var TEMPLATE_HTML_MAX = 512 * 1024;
6345
6689
  var TEMPLATE_CSS_MAX = 128 * 1024;
6346
- var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`);
6690
+ function jsonEncodedCodeMessage(field, value) {
6691
+ const t = value.trim();
6692
+ if (t.length < 2 || !t.startsWith('"') || !t.endsWith('"'))
6693
+ return null;
6694
+ try {
6695
+ if (typeof JSON.parse(t) !== "string")
6696
+ return null;
6697
+ } catch {
6698
+ return null;
6699
+ }
6700
+ return `${field} is JSON-encoded (wrapped in quotes with \\" escapes) \u2014 send the raw ${field} itself, not a JSON string of it.`;
6701
+ }
6702
+ function htmlEscapedMarkupMessage(field, value) {
6703
+ if (value.includes("<") || !/&lt;\/?[a-zA-Z]/.test(value))
6704
+ return null;
6705
+ return `${field} is HTML-escaped (&lt;\u2026&gt; instead of <\u2026>) \u2014 send the markup itself, unescaped.`;
6706
+ }
6707
+ function escapedLineBreaksMessage(field, value) {
6708
+ if (!hasEscapedLineBreaks(value, field))
6709
+ return null;
6710
+ return `${field} has its line breaks escaped (literal \\n sequences, no real line break) \u2014 send the ${field} with real line breaks.`;
6711
+ }
6712
+ var noCorruptMarkup = (field) => (value, ctx) => {
6713
+ const message = jsonEncodedCodeMessage(field, value) ?? htmlEscapedMarkupMessage(field, value) ?? escapedLineBreaksMessage(field, value);
6714
+ if (message)
6715
+ ctx.addIssue({ code: "custom", message });
6716
+ };
6717
+ var noJsonEncodedCode = (field) => (value, ctx) => {
6718
+ const message = jsonEncodedCodeMessage(field, value);
6719
+ if (message)
6720
+ ctx.addIssue({ code: "custom", message });
6721
+ };
6722
+ var noCorruptCss = (value, ctx) => {
6723
+ const message = jsonEncodedCodeMessage("css", value) ?? escapedLineBreaksMessage("css", value);
6724
+ if (message)
6725
+ ctx.addIssue({ code: "custom", message });
6726
+ };
6727
+ var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html"));
6347
6728
  var CDATA_OPEN = "<![CDATA[";
6348
6729
  var noCdata = (value) => !value.includes(CDATA_OPEN);
6349
6730
  var cdataMessage = (field) => `${field} contains an XML CDATA marker (${CDATA_OPEN}) \u2014 module ${field} is stored and served ` + `verbatim, so CDATA wrappers reach the browser unparsed and break rendering. ` + `Remove the wrapper and resend plain ${field === "css" ? "CSS" : "JavaScript"}.`;
6350
- var moduleCss = string2().max(MODULE_CSS_MAX, `css exceeds ${MODULE_CSS_MAX} bytes`).refine(noCdata, cdataMessage("css"));
6351
- var moduleJs = string2().max(MODULE_JS_MAX, `js exceeds ${MODULE_JS_MAX} bytes`).refine(noCdata, cdataMessage("js"));
6731
+ var moduleCss = string2().max(MODULE_CSS_MAX, `css exceeds ${MODULE_CSS_MAX} bytes`).refine(noCdata, cdataMessage("css")).superRefine(noCorruptCss);
6732
+ var moduleJs = string2().max(MODULE_JS_MAX, `js exceeds ${MODULE_JS_MAX} bytes`).refine(noCdata, cdataMessage("js")).superRefine(noJsonEncodedCode("js"));
6352
6733
  var MODULE_FIELD_PRIMITIVE_KINDS = [
6353
6734
  "text",
6354
6735
  "richtext",
@@ -6358,7 +6739,7 @@ var MODULE_FIELD_PRIMITIVE_KINDS = [
6358
6739
  "boolean",
6359
6740
  "link"
6360
6741
  ];
6361
- var MODULE_FIELD_LIST_KINDS = ["text-list", "link-list"];
6742
+ var MODULE_FIELD_LIST_KINDS = ["list"];
6362
6743
  var MODULE_FIELD_NESTED_KINDS = ["module", "module-list"];
6363
6744
  var MODULE_FIELD_KINDS = [
6364
6745
  ...MODULE_FIELD_PRIMITIVE_KINDS,
@@ -6388,26 +6769,55 @@ var moduleFieldModuleListSchema = object({
6388
6769
  min: number2().int().nonnegative().optional(),
6389
6770
  max: number2().int().positive().max(256).optional()
6390
6771
  }).strict();
6391
- var moduleFieldTextListSchema = object({
6392
- name: moduleFieldName,
6393
- kind: literal("text-list"),
6394
- label: moduleFieldLabel,
6395
- min: number2().int().nonnegative().optional(),
6396
- max: number2().int().positive().max(256).optional(),
6397
- default: array(string2()).optional()
6398
- }).strict();
6399
- var moduleFieldLinkListSchema = object({
6772
+ var moduleFieldListSchema = object({
6400
6773
  name: moduleFieldName,
6401
- kind: literal("link-list"),
6774
+ kind: literal("list"),
6402
6775
  label: moduleFieldLabel,
6776
+ listType: slugSchema,
6777
+ bind: _enum(["local", "shared", "source"]).optional(),
6778
+ list: string2().regex(LIST_REF_RE, "list must be caelo:list/<id>").optional(),
6779
+ source: sourceDefSchema.optional(),
6403
6780
  min: number2().int().nonnegative().optional(),
6404
- max: number2().int().positive().max(256).optional(),
6405
- default: array(object({ label: string2(), href: string2() }).strict()).optional()
6781
+ max: number2().int().positive().max(LIST_MAX_ITEMS).optional(),
6782
+ default: union([
6783
+ array(listItemInputSchema),
6784
+ string2().regex(LIST_REF_RE, "a shared default must be caelo:list/<id>")
6785
+ ]).optional()
6406
6786
  }).strict();
6787
+ function listFieldIssues(f) {
6788
+ const bind = f.bind ?? "local";
6789
+ const issues = [];
6790
+ if (bind === "source" && f.source === undefined) {
6791
+ issues.push(`list field "${f.name}": bind "source" needs a source (query, system or plugin).`);
6792
+ }
6793
+ if (bind !== "source" && f.source !== undefined) {
6794
+ issues.push(`list field "${f.name}": source is only allowed with bind "source".`);
6795
+ }
6796
+ if (bind === "local" && f.list !== undefined) {
6797
+ issues.push(`list field "${f.name}": list is only allowed with bind "shared" \u2014 a local list stores its entries in the placement.`);
6798
+ }
6799
+ if (bind === "source" && f.list !== undefined && f.source?.kind !== "system") {
6800
+ issues.push(`list field "${f.name}": list on a source field names the vocabulary of a page_terms source only.`);
6801
+ }
6802
+ if (f.default !== undefined) {
6803
+ if (bind === "local" && typeof f.default === "string") {
6804
+ issues.push(`list field "${f.name}": a local default is an array of entries, not a list reference.`);
6805
+ }
6806
+ if (bind === "shared" && Array.isArray(f.default)) {
6807
+ issues.push(`list field "${f.name}": a shared default is caelo:list/<id> \u2014 create the list with upsert_lists_many first.`);
6808
+ }
6809
+ if (bind === "source") {
6810
+ issues.push(`list field "${f.name}": a source field has no default; its entries are generated.`);
6811
+ }
6812
+ }
6813
+ if (f.min !== undefined && f.max !== undefined && f.min > f.max) {
6814
+ issues.push(`list field "${f.name}": min is greater than max.`);
6815
+ }
6816
+ return issues;
6817
+ }
6407
6818
  var moduleFieldSchema = discriminatedUnion("kind", [
6408
6819
  moduleFieldPrimitiveSchema,
6409
- moduleFieldTextListSchema,
6410
- moduleFieldLinkListSchema,
6820
+ moduleFieldListSchema,
6411
6821
  moduleFieldModuleSchema,
6412
6822
  moduleFieldModuleListSchema
6413
6823
  ]);
@@ -6448,11 +6858,16 @@ var moduleFieldsArray = array(moduleFieldSchema).max(64, "modules may declare at
6448
6858
  return;
6449
6859
  }
6450
6860
  seen.add(f.name);
6861
+ if (f.kind === "list") {
6862
+ for (const message of listFieldIssues(f)) {
6863
+ ctx.addIssue({ code: ZodIssueCode.custom, message });
6864
+ }
6865
+ }
6451
6866
  }
6452
6867
  for (const run of findNumberedScalarFanoutRuns(arr)) {
6453
6868
  ctx.addIssue({
6454
6869
  code: ZodIssueCode.custom,
6455
- message: `numbered-scalar fanout: ${run.map((n) => `"${n}"`).join(", ")} repeat one role as ` + "numbered scalar fields \u2014 repeating content is ONE list field, never numbered scalars " + '(CLAUDE.md \xA71A). Replace the run with a single field: kind "text-list" (list of ' + 'strings, inner template iterates {{.}}), "link-list" (list of {label, href}), or ' + '"module-list" (rich per-item structure: mint a card sub-module and reference it per ' + "item \u2014 in build_page, author the card detached with `ref` and pass " + '[{"$ref": "<ref>"}, \u2026] as the field value). A two-field split like col1/col2 is fine; ' + `${NUMBERED_SCALAR_FANOUT_THRESHOLD}+ numbered siblings is a list.`
6870
+ message: `numbered-scalar fanout: ${run.map((n) => `"${n}"`).join(", ")} repeat one role as ` + "numbered scalar fields \u2014 repeating content is ONE list field, never numbered scalars " + '(CLAUDE.md \xA71A). Replace the run with a single field: kind "list" with a list type ' + '(listType "texts" for plain strings \u2014 the template iterates {{#f}}{{text}}{{/f}}; ' + '"links" for label + link \u2014 {{#f}}<a href="{{link}}">{{label}}</a>{{/f}}), or ' + '"module-list" (rich per-item structure: mint a card sub-module and reference it per ' + "item \u2014 in build_page, author the card detached with `ref` and pass " + '[{"$ref": "<ref>"}, \u2026] as the field value). A two-field split like col1/col2 is fine; ' + `${NUMBERED_SCALAR_FANOUT_THRESHOLD}+ numbered siblings is a list.`
6456
6871
  });
6457
6872
  }
6458
6873
  });
@@ -6481,21 +6896,12 @@ var moduleUpdateSchema = object({
6481
6896
  js: moduleJs.optional(),
6482
6897
  fields: moduleFieldsArray.optional()
6483
6898
  }).strict();
6484
- var TEMPLATE_KINDS = [
6485
- "home",
6486
- "landing",
6487
- "product",
6488
- "blog",
6489
- "doc",
6490
- "content",
6491
- "utility"
6492
- ];
6493
- var templateKindSchema = _enum(TEMPLATE_KINDS);
6899
+ var pageTypeSlugSchema = string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, "page type slug: lowercase letters, digits, hyphens");
6494
6900
  var templateCreateSchema = object({
6495
6901
  slug: slugSchema,
6496
6902
  displayName: displayNameSchema,
6497
- kind: templateKindSchema.default("content"),
6498
- html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`),
6903
+ pageType: pageTypeSlugSchema.optional(),
6904
+ html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html")),
6499
6905
  css: string2().max(TEMPLATE_CSS_MAX, `css exceeds ${TEMPLATE_CSS_MAX} bytes`).default(""),
6500
6906
  layoutId: string2().uuid().optional(),
6501
6907
  blocks: array(object({
@@ -6512,8 +6918,8 @@ var templateBlockSchema = object({
6512
6918
  var templateUpdateSchema = object({
6513
6919
  templateId: string2().uuid(),
6514
6920
  displayName: displayNameSchema.optional(),
6515
- kind: templateKindSchema.optional(),
6516
- html: string2().max(TEMPLATE_HTML_MAX).optional(),
6921
+ pageType: pageTypeSlugSchema.optional(),
6922
+ html: string2().max(TEMPLATE_HTML_MAX).superRefine(noCorruptMarkup("html")).optional(),
6517
6923
  css: string2().max(TEMPLATE_CSS_MAX).optional(),
6518
6924
  layoutId: string2().uuid().optional(),
6519
6925
  blocks: array(templateBlockSchema).optional()
@@ -6523,13 +6929,17 @@ var templateBlocksSetSchema = object({
6523
6929
  blocks: array(templateBlockSchema)
6524
6930
  }).strict();
6525
6931
  var pageStatusSchema = _enum(["draft", "published"]);
6932
+ var pageShortTitleSchema = string2().trim().min(1, "the short title is the page's breadcrumb label and cannot be blank").max(256);
6526
6933
  var pageCreateSchema = object({
6527
6934
  slug: slugSchema,
6528
- name: string2().min(1).max(256).optional(),
6529
6935
  title: string2().min(1).max(256),
6936
+ shortTitle: pageShortTitleSchema.optional(),
6530
6937
  templateId: string2().uuid().optional(),
6531
6938
  status: pageStatusSchema.optional(),
6532
- parentPageId: string2().uuid().nullable().optional()
6939
+ parentPageId: string2().uuid().nullable().optional(),
6940
+ pageType: pageTypeSlugSchema.optional(),
6941
+ properties: record(string2(), unknown()).optional(),
6942
+ terms: array(string2().max(120)).max(100).optional()
6533
6943
  }).strict();
6534
6944
  var pageMoveSchema = object({
6535
6945
  pageId: string2().uuid(),
@@ -6543,12 +6953,15 @@ var pagesMoveManySchema = object({
6543
6953
  var pageUpdateSchema = object({
6544
6954
  pageId: string2().uuid(),
6545
6955
  expectedVersion: number2().int().nonnegative().optional(),
6546
- name: string2().min(1).max(256).optional(),
6547
6956
  title: string2().min(1).max(256).optional(),
6957
+ shortTitle: pageShortTitleSchema.optional(),
6548
6958
  slug: slugSchema.optional(),
6549
6959
  templateId: string2().uuid().optional(),
6550
6960
  status: pageStatusSchema.optional(),
6551
- redirectFromOld: _enum(["auto", "skip"]).optional()
6961
+ redirectFromOld: _enum(["auto", "skip"]).optional(),
6962
+ pageType: pageTypeSlugSchema.optional(),
6963
+ properties: record(string2(), unknown()).optional(),
6964
+ terms: array(string2().max(120)).max(100).optional()
6552
6965
  }).strict();
6553
6966
  var pageSetModulesSchema = object({
6554
6967
  pageId: string2().uuid(),
@@ -6592,6 +7005,11 @@ var forkPlacementContentSchema = object({
6592
7005
  position: number2().int().nonnegative()
6593
7006
  }).strict();
6594
7007
 
7008
+ // ../shared/src/lists/item-attr.ts
7009
+ var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
7010
+ var NAME = "[a-z][a-z0-9_]{0,63}";
7011
+ var LIST_ITEM_ATTR_RE = new RegExp(`^(?:${UUID}/${UUID}|instance/${UUID}/${NAME}/${UUID}|local/${NAME}/${UUID})$`);
7012
+
6595
7013
  // ../shared/src/plugin-preview.ts
6596
7014
  var identifier = string2().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/);
6597
7015
  var pluginPreviewTargetSchema = object({
@@ -6683,6 +7101,42 @@ function unwrapCdataEnvelope(value) {
6683
7101
  return m[1];
6684
7102
  }
6685
7103
 
7104
+ // ../shared/src/source-text-transport.ts
7105
+ function decodeJsonEncodedSource(value) {
7106
+ if (typeof value !== "string")
7107
+ return value;
7108
+ const t = value.trim();
7109
+ if (t.length < 2 || !t.startsWith('"') || !t.endsWith('"'))
7110
+ return value;
7111
+ if (!/\\["ntr\\/u]/.test(t))
7112
+ return value;
7113
+ try {
7114
+ const decoded = JSON.parse(t);
7115
+ return typeof decoded === "string" ? decoded : value;
7116
+ } catch {
7117
+ return value;
7118
+ }
7119
+ }
7120
+ var ENTITIES = {
7121
+ "&lt;": "<",
7122
+ "&gt;": ">",
7123
+ "&quot;": '"',
7124
+ "&#34;": '"',
7125
+ "&#39;": "'",
7126
+ "&#x27;": "'",
7127
+ "&apos;": "'"
7128
+ };
7129
+ function decodeHtmlEscapedSource(value) {
7130
+ if (typeof value !== "string")
7131
+ return value;
7132
+ if (value.includes("<") || !/&lt;\/?[a-zA-Z]/.test(value))
7133
+ return value;
7134
+ return value.replace(/&lt;|&gt;|&quot;|&#34;|&#39;|&#x27;|&apos;/g, (e) => ENTITIES[e] ?? e).replace(/&amp;/g, "&");
7135
+ }
7136
+ function decodeSourceTextTransport(value, language) {
7137
+ return decodeEscapedLineBreaks(decodeHtmlEscapedSource(decodeJsonEncodedSource(unwrapCdataEnvelope(value))), language);
7138
+ }
7139
+
6686
7140
  // ../shared/src/ai-tools.ts
6687
7141
  function stripSurroundingQuotes(v) {
6688
7142
  if (typeof v !== "string")
@@ -6690,31 +7144,13 @@ function stripSurroundingQuotes(v) {
6690
7144
  const m = v.match(/^(["'])([\s\S]+)\1$/);
6691
7145
  return m ? m[2] : v;
6692
7146
  }
6693
- function sourceTextArg(schema) {
6694
- return preprocess(unwrapCdataEnvelope, schema);
7147
+ function sourceTextArg(schema, language) {
7148
+ return preprocess((value) => decodeSourceTextTransport(value, language), schema);
6695
7149
  }
6696
7150
  var positionInputSchema = preprocess(stripSurroundingQuotes, union([
6697
7151
  _enum(["top", "bottom"]),
6698
7152
  preprocess((v) => typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v, number2().int().min(0).max(1000))
6699
7153
  ]));
6700
- var editModuleToolInput = object({
6701
- moduleId: string2().uuid(),
6702
- displayName: string2().min(1).max(128).optional(),
6703
- description: string2().max(1000).optional(),
6704
- kind: _enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
6705
- type: slugSchema.optional(),
6706
- html: sourceTextArg(string2().max(MODULE_HTML_MAX)).optional(),
6707
- css: sourceTextArg(string2().max(MODULE_CSS_MAX)).optional(),
6708
- js: sourceTextArg(string2().max(MODULE_JS_MAX)).optional(),
6709
- fields: array(moduleFieldSchema).max(64).optional(),
6710
- bindThemeLiterals: boolean2().optional()
6711
- }).strict();
6712
- var setPageModuleContentToolInput = object({
6713
- pageId: string2().uuid(),
6714
- blockName: string2().min(1).max(64),
6715
- position: number2().int().nonnegative(),
6716
- contentValues: record(string2(), unknown())
6717
- }).strict();
6718
7154
  var siteMemoryProposeToolInput = object({
6719
7155
  slot: _enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
6720
7156
  body: string2().min(1).max(4000),
@@ -6734,9 +7170,9 @@ var addModuleToolInput = object({
6734
7170
  description: string2().max(1000).optional(),
6735
7171
  kind: _enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
6736
7172
  type: slugSchema.optional(),
6737
- html: sourceTextArg(string2().min(1).max(50000)).optional(),
6738
- css: sourceTextArg(string2().max(50000)).optional(),
6739
- js: sourceTextArg(string2().max(50000)).optional(),
7173
+ html: sourceTextArg(string2().min(1).max(50000), "html").optional(),
7174
+ css: sourceTextArg(string2().max(50000), "css").optional(),
7175
+ js: sourceTextArg(string2().max(50000), "js").optional(),
6740
7176
  fields: array(moduleFieldSchema).max(64).optional(),
6741
7177
  bindThemeLiterals: boolean2().optional(),
6742
7178
  values: record(string2(), unknown()).optional()
@@ -6745,7 +7181,7 @@ var addModuleToolInput = object({
6745
7181
  if (input.moduleId !== undefined) {
6746
7182
  ctx.addIssue({
6747
7183
  code: ZodIssueCode.custom,
6748
- message: "Reusing a module on a layout renders the module's STORED field defaults \u2014 `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via edit_module."
7184
+ message: "Reusing a module on a layout renders the module's STORED field defaults \u2014 `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via update_modules_many."
6749
7185
  });
6750
7186
  } else if (input.fields === undefined || input.fields.length === 0) {
6751
7187
  ctx.addIssue({
@@ -6771,10 +7207,11 @@ var addModuleToolInput = object({
6771
7207
  message: "Pass either `moduleId` (place an existing module from list_modules) " + "or `displayName` + `html` (mint a new module)."
6772
7208
  });
6773
7209
  }
6774
- if (input.fields !== undefined && input.fields.length > 0 && input.values === undefined && !input.fields.some((f) => ("default" in f) && f.default !== undefined)) {
7210
+ const carriesContent = (f) => ("default" in f) && f.default !== undefined || f.kind === "list" && (f.bind === "source" || f.bind === "shared" && f.list !== undefined);
7211
+ if (input.fields !== undefined && input.fields.length > 0 && input.values === undefined && !input.fields.some(carriesContent)) {
6775
7212
  ctx.addIssue({
6776
7213
  code: ZodIssueCode.custom,
6777
- message: "A minted module needs its initial content IN THIS CALL: pass `values` " + "({fieldName: value, \u2026}) for this placement, or give the fields `default`s. " + "Without either, the module renders empty placeholders until a second call."
7214
+ message: "A minted module needs its initial content IN THIS CALL: pass `values` " + "({fieldName: value, \u2026}) for this placement, or give the fields `default`s. " + 'A list field bound to a site list carries its content through `list` ({kind:"list", bind:"shared", list:"caelo:list/<id>"} \u2014 create the list first with upsert_lists_many). ' + "Without either, the module renders empty placeholders until a second call."
6778
7215
  });
6779
7216
  }
6780
7217
  });
@@ -6821,7 +7258,9 @@ var chatChipSchema = object({
6821
7258
  blockName: string2().min(1).max(80).optional(),
6822
7259
  position: number2().int().nonnegative().optional(),
6823
7260
  field: string2().min(1).max(200).optional(),
6824
- index: number2().int().nonnegative().optional()
7261
+ item: string2().uuid().optional(),
7262
+ key: string2().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
7263
+ listItem: string2().max(200).regex(LIST_ITEM_ATTR_RE).optional()
6825
7264
  }).strict();
6826
7265
  var CHAT_MAX_CHIPS = 20;
6827
7266
  var chatSendMessageInput = object({
@@ -6853,7 +7292,9 @@ var chatPublishInput = object({
6853
7292
  "pageLayout",
6854
7293
  "pageModuleContent",
6855
7294
  "layout",
6856
- "structuredSet",
7295
+ "listType",
7296
+ "list",
7297
+ "pageType",
6857
7298
  "theme",
6858
7299
  "pluginRow"
6859
7300
  ]),
@@ -6882,9 +7323,10 @@ var slugInputSchema = string2().min(1).max(120).regex(/^[a-z0-9][a-z0-9-]*$/, "l
6882
7323
  var createTemplateToolInput = object({
6883
7324
  slug: slugInputSchema,
6884
7325
  displayName: string2().min(1).max(256),
6885
- html: sourceTextArg(string2().min(1).max(2000000)),
6886
- css: sourceTextArg(string2().max(2000000)).default(""),
7326
+ html: sourceTextArg(string2().min(1).max(2000000), "html"),
7327
+ css: sourceTextArg(string2().max(2000000), "css").default(""),
6887
7328
  layoutId: string2().uuid().optional(),
7329
+ pageType: pageTypeSlugSchema.optional(),
6888
7330
  blocks: array(object({
6889
7331
  name: string2().min(1).max(80),
6890
7332
  displayName: string2().min(1).max(200),
@@ -6896,12 +7338,6 @@ var removeModuleFromToolInput = object({
6896
7338
  targetRef: string2().min(1).max(500),
6897
7339
  moduleId: string2().uuid()
6898
7340
  }).strict();
6899
- var setStructuredSetToolInput = object({
6900
- kind: _enum(["nav-menu", "taxonomy", "tags", "link-list"]),
6901
- slug: slugInputSchema,
6902
- displayName: string2().min(1).max(200),
6903
- items: array(unknown())
6904
- }).strict();
6905
7341
  var setTemplateLayoutToolInput = object({
6906
7342
  templateId: string2().uuid(),
6907
7343
  layoutSlug: slugInputSchema
@@ -6909,8 +7345,8 @@ var setTemplateLayoutToolInput = object({
6909
7345
  var createLayoutToolInput = object({
6910
7346
  slug: slugInputSchema,
6911
7347
  displayName: string2().min(1).max(200),
6912
- html: sourceTextArg(string2().min(1).max(50000)),
6913
- css: sourceTextArg(string2().max(50000)).optional(),
7348
+ html: sourceTextArg(string2().min(1).max(50000), "html"),
7349
+ css: sourceTextArg(string2().max(50000), "css").optional(),
6914
7350
  blocks: array(object({
6915
7351
  name: string2().min(1).max(80),
6916
7352
  displayName: string2().min(1).max(200),
@@ -6926,8 +7362,8 @@ var setSiteDefaultsToolInput = object({
6926
7362
  var duplicatePageToolInput = object({
6927
7363
  sourcePageId: string2().uuid(),
6928
7364
  newSlug: slugInputSchema,
6929
- newName: string2().min(1).max(256).optional(),
6930
7365
  newTitle: string2().min(1).max(256).optional(),
7366
+ newShortTitle: pageShortTitleSchema.optional(),
6931
7367
  targetTemplateId: string2().uuid().optional(),
6932
7368
  newParentPageId: string2().uuid().nullable().optional()
6933
7369
  }).strict();
@@ -6939,16 +7375,15 @@ var repointPageTemplateToolInput = object({
6939
7375
  object({ kind: literal("preserve-as-block"), blockName: slugInputSchema }).strict()
6940
7376
  ]).default({ kind: "drop" })
6941
7377
  }).strict();
7378
+ var movePositionInputSchema = preprocess(stripSurroundingQuotes, union([
7379
+ _enum(["top", "bottom", "up", "down"]),
7380
+ preprocess((v) => typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v, number2().int().min(0).max(1000))
7381
+ ]));
6942
7382
  var moveModuleToolInput = object({
6943
7383
  pageId: string2().uuid(),
6944
7384
  moduleId: string2().uuid(),
6945
- toBlockName: string2().min(1).max(80),
6946
- position: positionInputSchema
6947
- }).strict();
6948
- var reorderModuleToolInput = object({
6949
- pageId: string2().uuid(),
6950
- moduleId: string2().uuid(),
6951
- direction: union([_enum(["up", "down"]), number2().int().min(0).max(1000)])
7385
+ toBlockName: string2().min(1).max(80).optional(),
7386
+ position: movePositionInputSchema
6952
7387
  }).strict();
6953
7388
  var findMediaToolInput = object({
6954
7389
  query: string2().max(256).optional(),
@@ -6997,19 +7432,13 @@ var autofillPageSeoToolInput = object({
6997
7432
  metaDescription: string2().trim().min(1).max(320),
6998
7433
  ogImageAssetId: string2().uuid().nullable().optional()
6999
7434
  }).strict();
7000
- var optimizePageSeoToolInput = object({
7001
- pageId: string2().uuid(),
7002
- metaDescription: string2().min(1).max(320),
7003
- ogImageAssetId: string2().uuid().nullable().optional(),
7004
- context: string2().max(4000).optional()
7005
- }).strict();
7006
7435
  var findRedirectsToolInput = object({
7007
7436
  query: string2().max(500).optional(),
7008
7437
  statusCode: union([literal(301), literal(302), literal(307), literal(308), literal(410)]).optional(),
7009
7438
  limit: number2().int().min(1).max(200).default(50)
7010
7439
  }).strict();
7011
7440
  var bulkCreateRedirectsToolInput = object({
7012
- redirects: array(object({
7441
+ items: array(object({
7013
7442
  fromPath: string2().min(1).max(500),
7014
7443
  toPath: redirectTargetPathSchema,
7015
7444
  statusCode: union([
@@ -7032,7 +7461,7 @@ var bulkDeleteRedirectsToolInput = object({
7032
7461
  matches: string2().min(1).max(500).optional()
7033
7462
  }).strict();
7034
7463
  var bulkOptimizeSeoToolInput = object({
7035
- updates: array(object({
7464
+ items: array(object({
7036
7465
  pageId: string2().uuid(),
7037
7466
  metaDescription: string2().min(1).max(320),
7038
7467
  ogImageAssetId: string2().uuid().nullable().optional()
@@ -7263,7 +7692,7 @@ var buildPageContentSchema = discriminatedUnion("source", [
7263
7692
  var buildPageModuleSchema = object({
7264
7693
  blockName: slugSchema.optional(),
7265
7694
  position: positionInputSchema.optional(),
7266
- ref: string2().regex(/^[a-z][a-z0-9_-]{0,31}$/, "ref must be a short lowercase handle").optional(),
7695
+ ref: string2().regex(/^[A-Za-z][A-Za-z0-9_-]{0,31}$/, "ref must be a short handle: a letter, then letters, digits, _ or -").optional(),
7267
7696
  moduleId: union([
7268
7697
  string2().uuid('moduleId must be a UUID from list_modules \u2014 to re-place a module minted earlier in THIS call, pass {"$ref": "<its ref>"} instead'),
7269
7698
  object({ $ref: string2() }).strict()
@@ -7272,9 +7701,9 @@ var buildPageModuleSchema = object({
7272
7701
  description: string2().max(1000).optional(),
7273
7702
  kind: moduleKindSchema.optional(),
7274
7703
  type: slugSchema.optional(),
7275
- html: sourceTextArg(string2().min(1).max(MODULE_HTML_MAX)).optional(),
7276
- css: sourceTextArg(string2().max(MODULE_CSS_MAX)).optional(),
7277
- js: sourceTextArg(string2().max(MODULE_JS_MAX)).optional(),
7704
+ html: sourceTextArg(string2().min(1).max(MODULE_HTML_MAX), "html").optional(),
7705
+ css: sourceTextArg(string2().max(MODULE_CSS_MAX), "css").optional(),
7706
+ js: sourceTextArg(string2().max(MODULE_JS_MAX), "js").optional(),
7278
7707
  fields: array(moduleFieldSchema).max(64).optional(),
7279
7708
  bindThemeLiterals: boolean2().optional(),
7280
7709
  content: buildPageContentSchema.optional()
@@ -7304,14 +7733,27 @@ var buildPageTargetSchema = object({
7304
7733
  pageId: string2().uuid().optional(),
7305
7734
  slug: slugSchema.optional(),
7306
7735
  title: string2().min(1).max(256).optional(),
7307
- name: string2().min(1).max(256).optional(),
7736
+ shortTitle: pageCreateSchema.shape.shortTitle,
7308
7737
  templateId: string2().uuid().optional(),
7309
7738
  status: pageStatusSchema.optional(),
7310
- parentPageId: string2().uuid().optional(),
7739
+ parentPageId: string2().uuid().nullable().optional(),
7740
+ pageType: pageCreateSchema.shape.pageType,
7741
+ properties: pageCreateSchema.shape.properties,
7742
+ terms: pageCreateSchema.shape.terms,
7311
7743
  importPageId: string2().uuid().optional(),
7312
7744
  metaDescription: string2().trim().min(1).max(SEO_DESCRIPTION_HARD_MAX).optional()
7313
7745
  }).strict().superRefine((page, ctx) => {
7314
- const createKeys = ["slug", "title", "name", "templateId", "status", "parentPageId"].filter((k) => page[k] !== undefined);
7746
+ const createKeys = [
7747
+ "slug",
7748
+ "title",
7749
+ "shortTitle",
7750
+ "templateId",
7751
+ "status",
7752
+ "parentPageId",
7753
+ "pageType",
7754
+ "properties",
7755
+ "terms"
7756
+ ].filter((k) => page[k] !== undefined);
7315
7757
  if (page.pageId !== undefined) {
7316
7758
  if (createKeys.length > 0) {
7317
7759
  ctx.addIssue({
@@ -7386,7 +7828,7 @@ var INTERACTIONS = {
7386
7828
  carousel: { css: CAROUSEL_CSS, js: CAROUSEL_JS }
7387
7829
  };
7388
7830
  var INTERACTION_IDS = Object.keys(INTERACTIONS);
7389
- var INTERACTION_MARKUP_CONTRACT = '**Vetted interactions - use these instead of writing JS for them.** Add `data-caelo-ui="<id>"` to the markup and Caelo ships accessible, dependency-free behaviour once per page; style it freely in the module CSS. ' + '`accordion` (FAQ, collapsible sections): `<div data-caelo-ui="accordion" data-single>` containing `<details id="..."><summary>Question</summary><div>Answer</div></details>` items - `data-single` keeps one open; a link to `#<details id>` opens it. ' + '`tabs`: `<div data-caelo-ui="tabs"><div role="tablist" aria-label="..."><button type="button" role="tab" id="t1" aria-controls="p1" aria-selected="true">...</button><button type="button" role="tab" id="t2" aria-controls="p2" aria-selected="false" tabindex="-1">...</button></div><div role="tabpanel" id="p1" aria-labelledby="t1">...</div><div role="tabpanel" id="p2" aria-labelledby="t2" hidden>...</div></div>` - ids must be unique on the page. ' + '`carousel` (slider, testimonials): `<section data-caelo-ui="carousel" aria-roledescription="carousel" aria-label="..."><div data-caelo-carousel-track tabindex="0"><div role="group" aria-roledescription="slide" aria-label="1 of 3">...</div>...</div><button type="button" data-caelo-carousel-prev aria-label="Previous slide"><span aria-hidden="true">&lsaquo;</span></button><button type="button" data-caelo-carousel-next aria-label="Next slide"><span aria-hidden="true">&rsaquo;</span></button></section>` (an icon button keeps its visible content aria-hidden; a text button needs no aria-label) - one slide per view by default; set the slides\' `flex-basis` in module CSS to show more. ' + "`mobile-nav` is applied by the built-in nav-menu renderer; never add it by hand. " + "Write module JS only for behaviour the kit does not cover.";
7831
+ var INTERACTION_MARKUP_CONTRACT = "`data-caelo-ui` kit \u2014 use it instead of JS for these (accessible, shipped once per page, style freely in module CSS):\n" + '- `accordion` (FAQ, collapsible): `<div data-caelo-ui="accordion" data-single>` with `<details id="..."><summary>Question</summary><div>Answer</div></details>` items; `data-single` keeps one open; a link to `#<details id>` opens it.\n' + '- `tabs`: `<div data-caelo-ui="tabs"><div role="tablist" aria-label="..."><button type="button" role="tab" id="t1" aria-controls="p1" aria-selected="true">...</button><button type="button" role="tab" id="t2" aria-controls="p2" aria-selected="false" tabindex="-1">...</button></div><div role="tabpanel" id="p1" aria-labelledby="t1">...</div><div role="tabpanel" id="p2" aria-labelledby="t2" hidden>...</div></div>`; ids unique on the page.\n' + '- `carousel` (slider, testimonials): `<section data-caelo-ui="carousel" aria-roledescription="carousel" aria-label="..."><div data-caelo-carousel-track tabindex="0"><div role="group" aria-roledescription="slide" aria-label="1 of 3">...</div>...</div><button type="button" data-caelo-carousel-prev aria-label="Previous slide"><span aria-hidden="true">&lsaquo;</span></button><button type="button" data-caelo-carousel-next aria-label="Next slide"><span aria-hidden="true">&rsaquo;</span></button></section>`; icon buttons keep visible content aria-hidden, text buttons need no aria-label. One slide per view; set the slides\' `flex-basis` in module CSS to show more.\n' + '- `mobile-nav` (menu collapsing behind a toggle on small screens): `<nav class="caelo-nav-menu" data-caelo-ui="mobile-nav" data-nav-open="false"><button type="button" class="caelo-nav-toggle" aria-expanded="false" aria-label="Menu"><span class="caelo-nav-bar"></span><span class="caelo-nav-bar"></span><span class="caelo-nav-bar"></span></button><ul>{{#menu}}<li>\u2026</li>{{/menu}}</ul></nav>` around the module\'s menu `list` field.\n' + "Write module JS only for behaviour the kit does not cover.";
7390
7832
 
7391
7833
  // ../shared/src/media.ts
7392
7834
  var MEDIA_ALLOWED_MIMES = [
@@ -7967,6 +8409,9 @@ mustache.Context = Context;
7967
8409
  mustache.Writer = Writer;
7968
8410
  var mustache_default = mustache;
7969
8411
 
8412
+ // ../shared/src/template-sections.ts
8413
+ var CONTEXT_HEADS = new Set(["page", "site_text"]);
8414
+
7970
8415
  // ../shared/src/template-engine.ts
7971
8416
  mustache_default.escape = (s) => s === null || s === undefined ? "" : String(s);
7972
8417
  var SECTION_KINDS = new Set(MODULE_FIELD_SECTION_KINDS);
@@ -7979,7 +8424,7 @@ var THEME_ASSET_KEY_TO_SLOT = {
7979
8424
  var THEME_ASSET_KEYS = Object.keys(THEME_ASSET_KEY_TO_SLOT);
7980
8425
 
7981
8426
  // ../shared/src/template-balance.ts
7982
- var RAW_TEXT_ELEMENTS = new Set([
8427
+ var RAW_TEXT_ELEMENTS2 = new Set([
7983
8428
  "script",
7984
8429
  "style",
7985
8430
  "textarea",
@@ -8297,9 +8742,9 @@ var languageTagSchema = string2().max(35).regex(/^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8
8297
8742
  var HTML_WS = new Set(["\t", `
8298
8743
  `, "\f", "\r", " "]);
8299
8744
  // ../shared/src/edit-markers.ts
8300
- var EDITABLE_KINDS = new Set(["text", "text-list", "link-list", "url", "image"]);
8745
+ var EDITABLE_KINDS = new Set(["text", "url", "image", "list"]);
8301
8746
  var MARKABLE_ATTRIBUTES = new Set(["href", "src", "alt"]);
8302
- var RAW_TEXT_ELEMENTS2 = new Set(["script", "style", "title", "textarea"]);
8747
+ var RAW_TEXT_ELEMENTS3 = new Set(["script", "style", "title", "textarea"]);
8303
8748
  // ../shared/src/font-usage.ts
8304
8749
  var GLOBAL_KEYWORDS = new Set(["inherit", "initial", "unset", "revert", "revert-layer"]);
8305
8750
  var SYSTEM_FONTS = new Set([
@@ -8456,6 +8901,31 @@ var VARIANT_RE = LINK_REF_PART_PATTERNS.variant;
8456
8901
  var SITE_SERVED_FILES = new Set(["/robots.txt", "/sitemap.xml"]);
8457
8902
  var URL_KINDS = new Set(["url", "link", "image"]);
8458
8903
  var NESTED_KINDS = new Set(["module", "module-list"]);
8904
+ // ../shared/src/lists/address.ts
8905
+ var SEG = "[a-z0-9_-]+";
8906
+ var ADDRESS_RE = new RegExp(`^(?:list/(${SEG})/item/(${SEG})/(${SEG})` + `|instance/(${SEG})/(${SEG})/item/(${SEG})/(${SEG})` + `|page/(${SEG})/props/(${SEG})` + `|list_type/(${SEG})/node/(${SEG})/(${SEG}))$`);
8907
+ // ../shared/src/lists/page-type.ts
8908
+ var pageTypeDefSchema = object({
8909
+ slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
8910
+ name: string2().min(1).max(200),
8911
+ purpose: string2().min(1).max(1000),
8912
+ templates: array(string2().uuid()).min(1).max(64),
8913
+ defaultTemplate: string2().uuid(),
8914
+ properties: array(fieldDefSchema).max(32).default([]),
8915
+ vocabularies: array(object({
8916
+ list: string2().uuid(),
8917
+ cardinality: _enum(["one", "many"]),
8918
+ required: boolean2().optional()
8919
+ }).strict()).max(16).default([])
8920
+ }).strict();
8921
+ // ../shared/src/lists/resolve.ts
8922
+ var SEG2 = "[a-z0-9_-]+";
8923
+ var LIST_VALUE_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
8924
+ var INSTANCE_VALUE_RE = new RegExp(`^instance/(${SEG2})/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
8925
+ var PAGE_PROP_RE = new RegExp(`^page/(${SEG2})/props/(${SEG2})$`);
8926
+ var LIST_HIDDEN_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})$`);
8927
+ var INSTANCE_HIDDEN_RE = new RegExp(`^instance/(${SEG2})/(${SEG2})/item/(${SEG2})$`);
8928
+ var STRUCTURAL_TYPES = new Set(["module", "term"]);
8459
8929
  // ../shared/src/markup-structure.ts
8460
8930
  var LEAF_TAGS = new Set(["svg", "math", "script", "style", "noscript", "iframe"]);
8461
8931
  // ../shared/src/page-log.ts
@@ -8588,31 +9058,6 @@ var PINNED_FONT_SLICE_FILE_PATTERN = "[0-9a-f]{64}\\.[0-9a-f]{12}\\.[a-z]+(?:-[a
8588
9058
  var PINNED_FONT_SLICE_FILE_RE = new RegExp(`^${PINNED_FONT_SLICE_FILE_PATTERN}$`);
8589
9059
  var CONTENT_HASHED_PATH_PATTERN = `^/(?:_app/immutable/.+|_assets/fonts/[^/]+/[0-9a-f]{16}\\.woff2|_assets/fonts/pinned/${PINNED_FONT_SLICE_FILE_PATTERN}|_assets/[a-z0-9][a-z0-9-]*(?:/[a-z][a-z0-9-]*)?\\.[0-9a-f]{16}\\.[a-z0-9]{2,5}|_caelo/plugin/[^/]+/[^/]+\\.[0-9a-f]{12}\\.(?:js|css))$`;
8590
9060
  var CONTENT_HASHED_PATH_RE = new RegExp(CONTENT_HASHED_PATH_PATTERN);
8591
- // ../shared/src/structured-sets.ts
8592
- var navMenuItem = lazy(() => object({
8593
- label: string2().min(1).max(120),
8594
- href: string2().min(1).max(500),
8595
- target: _enum(["_self", "_blank"]).optional(),
8596
- children: array(navMenuItem).optional(),
8597
- adSlotId: string2().min(1).max(100).optional()
8598
- }).strict());
8599
- var taxonomyItem = object({
8600
- slug: string2().min(1).max(120),
8601
- displayName: string2().min(1).max(200),
8602
- parentSlug: string2().min(1).max(120).optional(),
8603
- description: string2().max(2000).optional()
8604
- }).strict();
8605
- var tagItem = object({
8606
- slug: string2().min(1).max(120).regex(/^[a-z0-9-]+$/, "lowercase letters/digits/hyphens"),
8607
- displayName: string2().min(1).max(200),
8608
- color: string2().regex(/^#[0-9a-fA-F]{3,8}$/, "hex color").optional()
8609
- }).strict();
8610
- var linkListItem = object({
8611
- label: string2().min(1).max(200),
8612
- href: string2().min(1).max(500),
8613
- description: string2().max(500).optional()
8614
- }).strict();
8615
- var structuredSetKind = _enum(["nav-menu", "taxonomy", "tags", "link-list"]);
8616
9061
  // ../shared/src/subagents.ts
8617
9062
  var expectedReturnShape = _enum(["verdict", "tree", "freeform", "rebuild"]);
8618
9063
  var EXPECTED_RETURN_SHAPES = expectedReturnShape.options;
@@ -18008,7 +18453,7 @@ async function recordAudit(tx, opts) {
18008
18453
  var SYSTEM_ACTOR_ID = "00000000-0000-0000-0000-00000000ffff";
18009
18454
 
18010
18455
  // ../query-api/src/branch-access.ts
18011
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18456
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18012
18457
  function namedBranches(ctx, input) {
18013
18458
  const out = new Set;
18014
18459
  if (ctx.chatBranchId)
@@ -18029,11 +18474,11 @@ function branchNotFound(operation, chatBranchId) {
18029
18474
  };
18030
18475
  }
18031
18476
  function firstMalformedBranch(branches) {
18032
- return branches.find((b) => !UUID_RE.test(b)) ?? null;
18477
+ return branches.find((b) => !UUID_RE2.test(b)) ?? null;
18033
18478
  }
18034
18479
  async function firstInvisibleBranch(tx, branches) {
18035
18480
  for (const branch of branches) {
18036
- if (!UUID_RE.test(branch))
18481
+ if (!UUID_RE2.test(branch))
18037
18482
  return branch;
18038
18483
  const rows = await tx.execute(sql`SELECT caelo_branch_visible(${branch}::uuid) AS visible`);
18039
18484
  if (rows[0]?.visible !== true)
@@ -18096,6 +18541,7 @@ function isRlsDenial(error) {
18096
18541
 
18097
18542
  // ../query-api/src/plugin-schema-provision.ts
18098
18543
  import { createHash } from "crypto";
18544
+ var DROPPED_COLUMNS_SETTING = "caelo.plugin_dropped_columns";
18099
18545
  var SCHEMA_HEADER_RE = /^CREATE SCHEMA IF NOT EXISTS "(plugin_[a-z][a-z0-9_]*)";/;
18100
18546
  function provisionedSchemaName(caller, ddl) {
18101
18547
  const name = SCHEMA_HEADER_RE.exec(ddl)?.[1];
@@ -18116,14 +18562,19 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
18116
18562
  AND to_regnamespace(${schemaName}) IS NOT NULL
18117
18563
  `);
18118
18564
  if (recorded.length > 0)
18119
- return;
18565
+ return { schemaName, droppedColumns: [] };
18120
18566
  await tx.execute(sql.raw(opts.sql));
18567
+ const dropped = await tx.execute(sql.raw(`SELECT coalesce(current_setting('${DROPPED_COLUMNS_SETTING}', true), '') AS v`));
18121
18568
  await tx.execute(sql`
18122
18569
  INSERT INTO plugin_schema_provisions (schema_name, sql_digest)
18123
18570
  VALUES (${schemaName}, ${digest})
18124
18571
  ON CONFLICT (schema_name) DO UPDATE
18125
18572
  SET sql_digest = EXCLUDED.sql_digest, provisioned_at = now()
18126
18573
  `);
18574
+ return {
18575
+ schemaName,
18576
+ droppedColumns: (dropped[0]?.v ?? "").split(",").filter((c) => c.length > 0)
18577
+ };
18127
18578
  }
18128
18579
 
18129
18580
  // ../query-api/src/post-commit.ts
@@ -18494,9 +18945,16 @@ class DatabaseAdapter {
18494
18945
  }
18495
18946
  if (!this.#skipVerify)
18496
18947
  await this.verifyRoles();
18497
- await this.#public.transaction(async (tx) => {
18498
- await provisionPluginSchemaInTx(tx, "provisionPluginPublicSchema", opts);
18948
+ const outcome = await this.#public.transaction(async (tx) => {
18949
+ return provisionPluginSchemaInTx(tx, "provisionPluginPublicSchema", opts);
18499
18950
  });
18951
+ if (outcome.droppedColumns.length > 0) {
18952
+ const admin = this.#adminDb("provisionPluginPublicSchema");
18953
+ await admin.transaction(async (tx) => {
18954
+ await tx.execute(sql.raw("SELECT set_config('caelo.actor_kind', 'system', true)"));
18955
+ await auditDroppedColumns(tx, "cms_public", opts.pluginId, outcome);
18956
+ });
18957
+ }
18500
18958
  }
18501
18959
  async provisionPluginAdminSchema(opts) {
18502
18960
  if (!/^[0-9a-f-]{36}$/i.test(opts.pluginId)) {
@@ -18506,7 +18964,10 @@ class DatabaseAdapter {
18506
18964
  if (!this.#skipVerify)
18507
18965
  await this.verifyRoles();
18508
18966
  await admin.transaction(async (tx) => {
18509
- await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", opts);
18967
+ const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", opts);
18968
+ if (outcome.droppedColumns.length > 0) {
18969
+ await auditDroppedColumns(tx, "cms_admin", opts.pluginId, outcome);
18970
+ }
18510
18971
  });
18511
18972
  }
18512
18973
  async dropPluginAdminSchema(opts) {
@@ -18541,6 +19002,16 @@ class DatabaseAdapter {
18541
19002
  return this.#public;
18542
19003
  }
18543
19004
  }
19005
+ async function auditDroppedColumns(tx, database, pluginId, outcome) {
19006
+ await recordAudit(tx, {
19007
+ actorId: SYSTEM_ACTOR_ID,
19008
+ operation: "plugins.schema_drop_columns",
19009
+ input: { database, schema: outcome.schemaName, columns: outcome.droppedColumns },
19010
+ succeeded: true,
19011
+ entityId: pluginId,
19012
+ resultSummary: `${database}.${outcome.schemaName}: dropped ${outcome.droppedColumns.join(", ")}`
19013
+ });
19014
+ }
18544
19015
  // ../query-api/src/execute.ts
18545
19016
  var allowAllRateLimiter = {
18546
19017
  async check() {