@relyio/cli 0.8.0 → 0.9.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 (2) hide show
  1. package/dist/index.js +2448 -803
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -928,10 +928,10 @@ var init_exit_codes = __esm({
928
928
  }
929
929
  });
930
930
 
931
- // ../../platform/api-contract/dist/chunk-PPSLKRFU.js
931
+ // ../../platform/api-contract/dist/chunk-WE6ZXIJM.js
932
932
  var ERROR_CODES;
933
- var init_chunk_PPSLKRFU = __esm({
934
- "../../platform/api-contract/dist/chunk-PPSLKRFU.js"() {
933
+ var init_chunk_WE6ZXIJM = __esm({
934
+ "../../platform/api-contract/dist/chunk-WE6ZXIJM.js"() {
935
935
  ERROR_CODES = {
936
936
  MISSING_API_KEY: { status: 401 },
937
937
  INVALID_API_KEY: { status: 401 },
@@ -1088,6 +1088,23 @@ var init_chunk_PPSLKRFU = __esm({
1088
1088
  * approval (REL-2053).
1089
1089
  */
1090
1090
  ORGANIZATION_BEING_DELETED: { status: 409 },
1091
+ /**
1092
+ * Added by REL-2200, the ninth name added to §4's approved seventeen.
1093
+ *
1094
+ * The Project's data region promises residency — today the EU — and RelyIO has no AI that
1095
+ * processes inside it yet, so every AI request for the Project is refused before any provider is
1096
+ * called (`multi-region-spec.md` decision 17 as amended 2026-10-08, MR6a,
1097
+ * `INV-MR-AI-STAYS-IN-REGION`). `details.region` names the Project's data region. It passes
1098
+ * Decision 3's test: a client reading it stops offering AI for that Project and says why, which no
1099
+ * other code tells it — `PLAN_FEATURE_UNAVAILABLE` sends the customer to upgrade a Plan that would
1100
+ * change nothing, and `PROJECT_IN_ANOTHER_CELL` to a cell that would refuse the same way. 403
1101
+ * because the request is understood and refused for what the Project is, and no retry, credential
1102
+ * or Plan changes that; MR6 lifts it by giving the region an AI that stays inside it.
1103
+ *
1104
+ * A **customer-facing naming decision**, approved by the product owner on
1105
+ * 2026-10-08 (REL-2200, MR6a).
1106
+ */
1107
+ AI_UNAVAILABLE_IN_REGION: { status: 403 },
1091
1108
  RESOURCE_NOT_FOUND: { status: 404 },
1092
1109
  RESOURCE_CONFLICT: { status: 409 },
1093
1110
  RESOURCE_EXPIRED: { status: 410 },
@@ -1134,7 +1151,7 @@ function readError(body) {
1134
1151
  var ContractBodyError;
1135
1152
  var init_client2 = __esm({
1136
1153
  "../../platform/api-contract/dist/client/index.js"() {
1137
- init_chunk_PPSLKRFU();
1154
+ init_chunk_WE6ZXIJM();
1138
1155
  ContractBodyError = class extends Error {
1139
1156
  body;
1140
1157
  constructor(message, body) {
@@ -1276,15 +1293,89 @@ var init_manage = __esm({
1276
1293
  }
1277
1294
  });
1278
1295
 
1279
- // ../../domain/schema/dist/chunk-GCR57WZV.js
1280
- var TIME_ZONE_HANDLING;
1281
- var init_chunk_GCR57WZV = __esm({
1282
- "../../domain/schema/dist/chunk-GCR57WZV.js"() {
1296
+ // ../../domain/schema/dist/chunk-MGMW4ZMJ.js
1297
+ function slugProblems(value) {
1298
+ return [
1299
+ ...value.startsWith("/") ? ["it starts with a slash"] : [],
1300
+ ...value.endsWith("/") ? ["it ends with a slash"] : [],
1301
+ ...value.includes("//") ? ["it has an empty segment (two slashes in a row)"] : []
1302
+ ];
1303
+ }
1304
+ var SLUG_MAX_LENGTH, SLUG_DEFAULT_MAX_LENGTH, FIELD_FORMAT_CODE_KEY, URL_VALUE_FORMAT, SITE_RELATIVE_PATH, FieldFormats, ConfiguredFieldFormats, TIME_ZONE_HANDLING;
1305
+ var init_chunk_MGMW4ZMJ = __esm({
1306
+ "../../domain/schema/dist/chunk-MGMW4ZMJ.js"() {
1307
+ SLUG_MAX_LENGTH = 255;
1308
+ SLUG_DEFAULT_MAX_LENGTH = 100;
1309
+ FIELD_FORMAT_CODE_KEY = "x-formatCode";
1310
+ URL_VALUE_FORMAT = "url";
1311
+ SITE_RELATIVE_PATH = /^\/(?!\/)/;
1312
+ FieldFormats = {
1313
+ slug: {
1314
+ code: "slug",
1315
+ pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/,
1316
+ message: "can only contain lowercase letters, numbers and hyphens, in segments separated by a slash, with no leading, trailing or repeated hyphen or slash",
1317
+ problemsOf: slugProblems
1318
+ },
1319
+ email: {
1320
+ code: "email",
1321
+ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
1322
+ message: "must be a valid email address"
1323
+ },
1324
+ uuid: {
1325
+ code: "uuid",
1326
+ pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
1327
+ message: "must be a valid UUID"
1328
+ },
1329
+ phone: {
1330
+ code: "phone",
1331
+ pattern: /^[+]?[\d\s\-()]{7,15}$/,
1332
+ message: "must be a valid phone number"
1333
+ }
1334
+ };
1335
+ ConfiguredFieldFormats = {
1336
+ /** An email field restricted to a `config.domainWhitelist`. */
1337
+ emailDomain: {
1338
+ code: "emailDomain",
1339
+ message: "must be from an allowed domain"
1340
+ },
1341
+ /** A URL field restricted to `config.allowedProtocols`. */
1342
+ urlProtocol: {
1343
+ code: "urlProtocol",
1344
+ message: "must use an allowed protocol"
1345
+ }
1346
+ };
1283
1347
  TIME_ZONE_HANDLING = ["UTC", "local"];
1284
1348
  }
1285
1349
  });
1286
1350
 
1287
- // ../../domain/schema/dist/chunk-7HSDWMYK.js
1351
+ // ../../domain/schema/dist/chunk-DERETXFC.js
1352
+ var JSON_SCHEMA_TYPES, JSON_SCHEMA_FORMATS;
1353
+ var init_chunk_DERETXFC = __esm({
1354
+ "../../domain/schema/dist/chunk-DERETXFC.js"() {
1355
+ JSON_SCHEMA_TYPES = [
1356
+ "string",
1357
+ "number",
1358
+ "integer",
1359
+ "boolean",
1360
+ "object",
1361
+ "array",
1362
+ "null"
1363
+ ];
1364
+ JSON_SCHEMA_FORMATS = [
1365
+ "date",
1366
+ "time",
1367
+ "date-time",
1368
+ "email",
1369
+ "hostname",
1370
+ "ipv4",
1371
+ "ipv6",
1372
+ "uri",
1373
+ "uuid"
1374
+ ];
1375
+ }
1376
+ });
1377
+
1378
+ // ../../domain/schema/dist/chunk-VFLLYTDW.js
1288
1379
  function isFieldKind(type) {
1289
1380
  return KIND_SET.has(type);
1290
1381
  }
@@ -1295,17 +1386,13 @@ function parseFieldType(type) {
1295
1386
  function isKind(type, kind) {
1296
1387
  return type === kind;
1297
1388
  }
1298
- function holdsReferenceValue(type) {
1299
- const declaration = fieldKindDeclarationOf(type);
1300
- return declaration !== null && declaration.references === "value";
1301
- }
1302
1389
  function fieldKindDeclarationOf(type) {
1303
1390
  const parsed = parseFieldType(type);
1304
1391
  return parsed.key === null ? null : FIELD_KIND_DECLARATIONS[parsed.key];
1305
1392
  }
1306
- var FIELD_KINDS, KIND_SET, TEXT_OPERATORS, LONG_TEXT_OPERATORS, NUMERIC_OPERATORS, BOOLEAN_OPERATORS, ENUM_OPERATORS, DATE_OPERATORS, REFERENCE_OPERATORS, TAGS_OPERATORS, NO_AI_CONSTRAINT, EMPTY_RICH_TEXT, PLAIN_TEXT_LIVE, VALUE_LIVE, WHOLE_VALUE_LOCALE, QUESTIONED_LOCALE, NOT_FILTERED, NOT_SEARCHED, NO_CHANGE, ONE_OR_LIST, FIELD_KIND_DECLARATIONS;
1307
- var init_chunk_7HSDWMYK = __esm({
1308
- "../../domain/schema/dist/chunk-7HSDWMYK.js"() {
1393
+ var FIELD_KINDS, KIND_SET, TEXT_OPERATORS, LONG_TEXT_OPERATORS, NUMERIC_OPERATORS, BOOLEAN_OPERATORS, ENUM_OPERATORS, DATE_OPERATORS, REFERENCE_OPERATORS, TAGS_OPERATORS, EMPTY_RICH_TEXT, PLAIN_TEXT_LIVE, VALUE_LIVE, WHOLE_VALUE_LOCALE, QUESTIONED_LOCALE, NOT_FILTERED, NOT_SEARCHED, NO_CHANGE, ONE_OR_LIST, FIELD_KIND_DECLARATIONS;
1394
+ var init_chunk_VFLLYTDW = __esm({
1395
+ "../../domain/schema/dist/chunk-VFLLYTDW.js"() {
1309
1396
  FIELD_KINDS = [
1310
1397
  // Text
1311
1398
  "shortText",
@@ -1331,7 +1418,9 @@ var init_chunk_7HSDWMYK = __esm({
1331
1418
  // Composite
1332
1419
  "tags",
1333
1420
  "list",
1334
- "object"
1421
+ "object",
1422
+ // Data nobody models (REL-1811): any JSON value, held to the field's JSON Schema when it gives one
1423
+ "json"
1335
1424
  ];
1336
1425
  KIND_SET = new Set(FIELD_KINDS);
1337
1426
  TEXT_OPERATORS = [
@@ -1400,7 +1489,6 @@ var init_chunk_7HSDWMYK = __esm({
1400
1489
  "isEmpty",
1401
1490
  "hasValue"
1402
1491
  ];
1403
- NO_AI_CONSTRAINT = { jsonSchema: {} };
1404
1492
  EMPTY_RICH_TEXT = Object.freeze({
1405
1493
  _type: "richText",
1406
1494
  version: 1,
@@ -1425,7 +1513,7 @@ var init_chunk_7HSDWMYK = __esm({
1425
1513
  search: { embed: "prose", treatments: ["autocomplete", "string", "token"] },
1426
1514
  filter: { kind: "text", viewOperators: TEXT_OPERATORS },
1427
1515
  references: "none",
1428
- aiTool: NO_AI_CONSTRAINT
1516
+ aiTool: { write: { as: "text", text: "plain" } }
1429
1517
  },
1430
1518
  longText: {
1431
1519
  change: { convertsTo: ["shortText"], backfill: null },
@@ -1437,7 +1525,7 @@ var init_chunk_7HSDWMYK = __esm({
1437
1525
  search: { embed: "prose", treatments: ["string"] },
1438
1526
  filter: { kind: "text", viewOperators: LONG_TEXT_OPERATORS },
1439
1527
  references: "none",
1440
- aiTool: NO_AI_CONSTRAINT
1528
+ aiTool: { write: { as: "text", text: "plain" } }
1441
1529
  },
1442
1530
  richText: {
1443
1531
  change: NO_CHANGE,
@@ -1448,10 +1536,14 @@ var init_chunk_7HSDWMYK = __esm({
1448
1536
  // The tree holding nothing. Frozen, as every facet value is shared: a read fills a fresh one
1449
1537
  // (`./empty`'s `freshEmptyValue`)
1450
1538
  empty: { value: EMPTY_RICH_TEXT },
1451
- search: { embed: "prose", treatments: ["string"] },
1539
+ // The tree is an object, which Atlas indexes under no mapping, so its path is mapped with nothing:
1540
+ // keyword search asks its plain-text twin, which `entries_search` maps as `string` (REL-2089,
1541
+ // `@relyio/content-db`'s `search-text`)
1542
+ search: { embed: "prose", treatments: [] },
1452
1543
  filter: NOT_FILTERED,
1453
- references: "none",
1454
- aiTool: NO_AI_CONSTRAINT
1544
+ // Its entry links and its declared nodes' fields (REL-1808)
1545
+ references: "tree",
1546
+ aiTool: { write: { as: "markdown" } }
1455
1547
  },
1456
1548
  // A table as a field's whole value (REL-1806): exactly a rich-text `table` block's value, edited
1457
1549
  // through the same binding — a fragment holding that one table, each cell's text merging
@@ -1466,11 +1558,12 @@ var init_chunk_7HSDWMYK = __esm({
1466
1558
  // never mints a key (REL-1777) — so a required table is required from creation, as an object is
1467
1559
  // (design decision 9, REL-2034).
1468
1560
  empty: null,
1469
- // Its cells' text, a paragraph a line (`richTextFieldBlockPlainText`), as rich text's
1470
- search: { embed: "prose", treatments: ["string"] },
1561
+ // Its cells' text, a paragraph a line (`richTextFieldBlockPlainText`), as rich text's — searched
1562
+ // in its plain-text twin, so its path is mapped with nothing (REL-2089)
1563
+ search: { embed: "prose", treatments: [] },
1471
1564
  filter: NOT_FILTERED,
1472
1565
  references: "none",
1473
- aiTool: NO_AI_CONSTRAINT
1566
+ aiTool: { write: { as: "markdownBlock", block: "table" } }
1474
1567
  },
1475
1568
  // Code with tabs as a field's whole value (REL-1806): exactly a rich-text `codeGroup` block's
1476
1569
  // value — one tab or more, each with its label, language, filename and line range — so a code
@@ -1483,11 +1576,12 @@ var init_chunk_7HSDWMYK = __esm({
1483
1576
  synthesized: null,
1484
1577
  // No empty value, as a table's: a code group holds a keyed tab or more.
1485
1578
  empty: null,
1486
- // Its tabs' text, as rich text embeds its code blocks
1487
- search: { embed: "prose", treatments: ["string"] },
1579
+ // Its tabs' text, as rich text embeds its code blocks — searched in its plain-text twin, so its
1580
+ // path is mapped with nothing (REL-2089)
1581
+ search: { embed: "prose", treatments: [] },
1488
1582
  filter: NOT_FILTERED,
1489
1583
  references: "none",
1490
- aiTool: NO_AI_CONSTRAINT
1584
+ aiTool: { write: { as: "markdownBlock", block: "codeGroup" } }
1491
1585
  },
1492
1586
  email: {
1493
1587
  change: NO_CHANGE,
@@ -1499,7 +1593,7 @@ var init_chunk_7HSDWMYK = __esm({
1499
1593
  search: { embed: null, treatments: ["token", "string"] },
1500
1594
  filter: { kind: "text", viewOperators: TEXT_OPERATORS },
1501
1595
  references: "none",
1502
- aiTool: NO_AI_CONSTRAINT
1596
+ aiTool: { write: { as: "text", text: "email" } }
1503
1597
  },
1504
1598
  url: {
1505
1599
  change: NO_CHANGE,
@@ -1511,7 +1605,7 @@ var init_chunk_7HSDWMYK = __esm({
1511
1605
  search: { embed: null, treatments: ["token", "string"] },
1512
1606
  filter: { kind: "text", viewOperators: TEXT_OPERATORS },
1513
1607
  references: "none",
1514
- aiTool: NO_AI_CONSTRAINT
1608
+ aiTool: { write: { as: "text", text: "url" } }
1515
1609
  },
1516
1610
  slug: {
1517
1611
  change: NO_CHANGE,
@@ -1523,7 +1617,7 @@ var init_chunk_7HSDWMYK = __esm({
1523
1617
  search: { embed: null, treatments: ["token", "string"] },
1524
1618
  filter: { kind: "text", viewOperators: TEXT_OPERATORS },
1525
1619
  references: "none",
1526
- aiTool: NO_AI_CONSTRAINT
1620
+ aiTool: { write: { as: "text", text: "slug" } }
1527
1621
  },
1528
1622
  integer: {
1529
1623
  change: { convertsTo: [], backfill: { value: 0 } },
@@ -1535,7 +1629,7 @@ var init_chunk_7HSDWMYK = __esm({
1535
1629
  search: { embed: null, treatments: ["number"] },
1536
1630
  filter: { kind: "number", viewOperators: NUMERIC_OPERATORS },
1537
1631
  references: "none",
1538
- aiTool: NO_AI_CONSTRAINT
1632
+ aiTool: { write: { as: "integer" } }
1539
1633
  },
1540
1634
  decimal: {
1541
1635
  // `0`, as `integer`: the old backfill switch's `number` arm, which named no kind (REL-1937).
@@ -1548,7 +1642,7 @@ var init_chunk_7HSDWMYK = __esm({
1548
1642
  search: { embed: null, treatments: ["number"] },
1549
1643
  filter: { kind: "number", viewOperators: NUMERIC_OPERATORS },
1550
1644
  references: "none",
1551
- aiTool: NO_AI_CONSTRAINT
1645
+ aiTool: { write: { as: "number" } }
1552
1646
  },
1553
1647
  boolean: {
1554
1648
  change: { convertsTo: [], backfill: { value: false } },
@@ -1560,7 +1654,7 @@ var init_chunk_7HSDWMYK = __esm({
1560
1654
  search: { embed: null, treatments: ["boolean"] },
1561
1655
  filter: { kind: "boolean", viewOperators: BOOLEAN_OPERATORS },
1562
1656
  references: "none",
1563
- aiTool: NO_AI_CONSTRAINT
1657
+ aiTool: { write: { as: "boolean" } }
1564
1658
  },
1565
1659
  enum: {
1566
1660
  change: NO_CHANGE,
@@ -1572,7 +1666,7 @@ var init_chunk_7HSDWMYK = __esm({
1572
1666
  search: { embed: null, treatments: ["token", "string"] },
1573
1667
  filter: { kind: "choice", viewOperators: ENUM_OPERATORS },
1574
1668
  references: "none",
1575
- aiTool: NO_AI_CONSTRAINT
1669
+ aiTool: { write: { as: "option" } }
1576
1670
  },
1577
1671
  date: {
1578
1672
  change: { convertsTo: [], backfill: { now: "date" } },
@@ -1587,7 +1681,7 @@ var init_chunk_7HSDWMYK = __esm({
1587
1681
  search: { embed: null, treatments: ["token"] },
1588
1682
  filter: { kind: "date", viewOperators: DATE_OPERATORS },
1589
1683
  references: "none",
1590
- aiTool: NO_AI_CONSTRAINT
1684
+ aiTool: { write: { as: "text", text: "date" } }
1591
1685
  },
1592
1686
  dateTime: {
1593
1687
  // The instant, as `date` takes the day: the old backfill switch's `datetime` arm, which named
@@ -1601,7 +1695,7 @@ var init_chunk_7HSDWMYK = __esm({
1601
1695
  search: { embed: null, treatments: ["token"] },
1602
1696
  filter: { kind: "date", viewOperators: DATE_OPERATORS },
1603
1697
  references: "none",
1604
- aiTool: NO_AI_CONSTRAINT
1698
+ aiTool: { write: { as: "text", text: "date-time" } }
1605
1699
  },
1606
1700
  reference: {
1607
1701
  change: NO_CHANGE,
@@ -1613,7 +1707,7 @@ var init_chunk_7HSDWMYK = __esm({
1613
1707
  search: NOT_SEARCHED,
1614
1708
  filter: { kind: "reference", viewOperators: REFERENCE_OPERATORS },
1615
1709
  references: "value",
1616
- aiTool: NO_AI_CONSTRAINT
1710
+ aiTool: { write: { as: "entryId" } }
1617
1711
  },
1618
1712
  tags: {
1619
1713
  change: NO_CHANGE,
@@ -1625,7 +1719,7 @@ var init_chunk_7HSDWMYK = __esm({
1625
1719
  search: { embed: "label", treatments: ["token", "string", "autocomplete"] },
1626
1720
  filter: { kind: "text", viewOperators: TAGS_OPERATORS },
1627
1721
  references: "none",
1628
- aiTool: NO_AI_CONSTRAINT
1722
+ aiTool: { write: { as: "strings" } }
1629
1723
  },
1630
1724
  list: {
1631
1725
  change: NO_CHANGE,
@@ -1637,7 +1731,7 @@ var init_chunk_7HSDWMYK = __esm({
1637
1731
  search: NOT_SEARCHED,
1638
1732
  filter: NOT_FILTERED,
1639
1733
  references: "fields",
1640
- aiTool: NO_AI_CONSTRAINT
1734
+ aiTool: { write: { as: "items" } }
1641
1735
  },
1642
1736
  object: {
1643
1737
  change: NO_CHANGE,
@@ -1655,21 +1749,55 @@ var init_chunk_7HSDWMYK = __esm({
1655
1749
  search: NOT_SEARCHED,
1656
1750
  filter: NOT_FILTERED,
1657
1751
  references: "fields",
1658
- aiTool: NO_AI_CONSTRAINT
1752
+ aiTool: { write: { as: "item" } }
1753
+ },
1754
+ // Data nobody models (REL-1811, design decision 18): any JSON value, held to the JSON Schema its
1755
+ // config may give (`config.schema`). Its source is collaborative text that merges keystroke by
1756
+ // keystroke (ADR 0014), parsed on save and served parsed.
1757
+ json: {
1758
+ change: NO_CHANGE,
1759
+ value: { shape: "json" },
1760
+ live: { holds: "text", stored: "json" },
1761
+ locale: WHOLE_VALUE_LOCALE,
1762
+ synthesized: null,
1763
+ // No empty value: no one value is one every JSON Schema admits, so a required json field is
1764
+ // required from creation, as a number or an object is (design decision 9)
1765
+ empty: null,
1766
+ // Data, not prose: neither embedded nor keyword-searched, and its path is mapped with nothing
1767
+ search: NOT_SEARCHED,
1768
+ // Not filterable at first (design decision 18): a Filter on one is refused by name
1769
+ filter: NOT_FILTERED,
1770
+ references: "none",
1771
+ aiTool: { write: { as: "json" } }
1659
1772
  }
1660
1773
  };
1661
1774
  }
1662
1775
  });
1663
1776
 
1664
- // ../../domain/schema/dist/chunk-RDFZ6WP6.js
1777
+ // ../../domain/schema/dist/chunk-UBAXWQKU.js
1665
1778
  function itemSegmentOf(shapeId) {
1666
1779
  return `${LIST_ITEM_SEGMENT}${shapeId}`;
1667
1780
  }
1781
+ function readItemSegment(segment) {
1782
+ if (!segment.startsWith(LIST_ITEM_SEGMENT)) return null;
1783
+ return { shape: segment.length === LIST_ITEM_SEGMENT.length ? null : segment.slice(1) };
1784
+ }
1668
1785
  function isItemSegment(segment) {
1669
1786
  return segment.startsWith(LIST_ITEM_SEGMENT);
1670
1787
  }
1788
+ function isRichTextTreeSegment(segment) {
1789
+ return readRichTextTreeSegment(segment) !== null;
1790
+ }
1791
+ function richTextTreeSegmentOf(type) {
1792
+ return `${RICH_TEXT_TREE_SEGMENT}${type}`;
1793
+ }
1794
+ function readRichTextTreeSegment(segment) {
1795
+ return segment.startsWith(RICH_TEXT_TREE_SEGMENT) && segment.length > 1 ? segment.slice(RICH_TEXT_TREE_SEGMENT.length) : null;
1796
+ }
1671
1797
  function includePathOf(walkPath) {
1672
- return walkPath.split(".").filter((segment) => !isItemSegment(segment)).join(".");
1798
+ const segments = walkPath.split(".");
1799
+ const tree = segments.findIndex((segment) => isRichTextTreeSegment(segment));
1800
+ return (tree === -1 ? segments : segments.slice(0, tree)).filter((segment) => !isItemSegment(segment)).join(".");
1673
1801
  }
1674
1802
  function shapesOf(field) {
1675
1803
  return shapePlacesOf(field).map((place) => place.shape);
@@ -1684,6 +1812,31 @@ function shapePlacesOf(field) {
1684
1812
  if (!Array.isArray(of) || of.length === 0 || !of.every((item) => isShape(item))) return [];
1685
1813
  return of.map((shape, index) => ({ shape, at: `config.of[${index}]` }));
1686
1814
  }
1815
+ function richTextShapePlacesOf(field) {
1816
+ if (parseFieldType(field.type).key !== "richText") return [];
1817
+ const config = field.config;
1818
+ if (typeof config !== "object" || config === null) return [];
1819
+ const places = [];
1820
+ for (const option of RICH_TEXT_SHAPE_OPTIONS) {
1821
+ if (!(option in config)) continue;
1822
+ const held = config[option];
1823
+ if (!Array.isArray(held) || !held.every((item) => isShape(item))) continue;
1824
+ for (const [index, shape] of held.entries()) {
1825
+ places.push({ shape, at: `config.${option}[${index}]`, option });
1826
+ }
1827
+ }
1828
+ return places;
1829
+ }
1830
+ function richTextShapesOf(field) {
1831
+ const places = richTextShapePlacesOf(field);
1832
+ return {
1833
+ blocks: places.filter((place) => place.option === "blocks").map((place) => place.shape),
1834
+ inline: places.filter((place) => place.option === "inline").map((place) => place.shape)
1835
+ };
1836
+ }
1837
+ function fieldsetPlacesOf(field) {
1838
+ return [...shapePlacesOf(field), ...richTextShapePlacesOf(field)];
1839
+ }
1687
1840
  function isShape(value) {
1688
1841
  return typeof value === "object" && value !== null && !Array.isArray(value) && "id" in value && typeof value.id === "string" && isSection(value);
1689
1842
  }
@@ -1701,19 +1854,56 @@ function fieldsOfMembers(members) {
1701
1854
  visitMembers(members);
1702
1855
  return fields;
1703
1856
  }
1704
- function walkMembers(members) {
1857
+ function walkMembers(members, options = {}) {
1705
1858
  const walked = [];
1706
1859
  for (const field of fieldsOfMembers(members)) {
1707
- visit(field, [], [], walked);
1860
+ visit(field, [], [], walked, options);
1708
1861
  }
1709
1862
  return walked;
1710
1863
  }
1711
- function walkFieldKinds(field) {
1864
+ function walkReferences(members) {
1865
+ const references = [];
1866
+ for (const walked of walkMembers(members, { intoRichText: true })) {
1867
+ if (walked.status !== "declared") continue;
1868
+ const config = walked.field.config ?? {};
1869
+ if (walked.declaration.references === "value") {
1870
+ references.push({
1871
+ path: walked.path,
1872
+ field: walked.field,
1873
+ holds: "reference",
1874
+ to: typeIdsOf(config.to),
1875
+ multi: config.multi === true
1876
+ });
1877
+ continue;
1878
+ }
1879
+ if (walked.declaration.references !== "tree") continue;
1880
+ const links = config.links;
1881
+ const to = typeof links === "object" && links !== null && !Array.isArray(links) ? typeIdsOf(links.to) : [];
1882
+ if (to.length === 0) continue;
1883
+ references.push({
1884
+ path: [
1885
+ ...walked.path,
1886
+ richTextTreeSegmentOf(RICH_TEXT_ENTRY_LINK_TYPE),
1887
+ ENTRY_LINK_TARGET_MEMBER
1888
+ ],
1889
+ field: walked.field,
1890
+ holds: "entryLink",
1891
+ to,
1892
+ multi: true
1893
+ });
1894
+ }
1895
+ return references;
1896
+ }
1897
+ function typeIdsOf(to) {
1898
+ if (typeof to === "string") return to === "" ? [] : [to];
1899
+ return Array.isArray(to) ? to.filter((id) => typeof id === "string" && id !== "") : [];
1900
+ }
1901
+ function walkFieldKinds(field, options = {}) {
1712
1902
  const walked = [];
1713
- visit(field, [], [], walked);
1903
+ visit(field, [], [], walked, options);
1714
1904
  return walked;
1715
1905
  }
1716
- function visit(field, parentPath, shapeStack, walked) {
1906
+ function visit(field, parentPath, shapeStack, walked, options) {
1717
1907
  const path25 = [...parentPath, field.id];
1718
1908
  const parsed = parseFieldType(field.type);
1719
1909
  if (parsed.key === null) {
@@ -1721,7 +1911,10 @@ function visit(field, parentPath, shapeStack, walked) {
1721
1911
  return;
1722
1912
  }
1723
1913
  const shapes = shapesOf(field);
1724
- const cyclic = shapes.find((shape) => shapeStack.includes(shape.id));
1914
+ const treeShapes = options.intoRichText === true ? richTextShapePlacesOf(field) : [];
1915
+ const cyclic = [...shapes, ...treeShapes.map((place) => place.shape)].find(
1916
+ (shape) => shapeStack.includes(shape.id)
1917
+ );
1725
1918
  if (cyclic !== void 0) {
1726
1919
  walked.push({ status: "shapeCycle", path: path25, field, shapeId: cyclic.id });
1727
1920
  return;
@@ -1735,9 +1928,65 @@ function visit(field, parentPath, shapeStack, walked) {
1735
1928
  });
1736
1929
  for (const shape of shapes) {
1737
1930
  for (const child of fieldsOfMembers(shape.fields)) {
1738
- visit(child, [...path25, itemSegmentOf(shape.id)], [...shapeStack, shape.id], walked);
1931
+ visit(child, [...path25, itemSegmentOf(shape.id)], [...shapeStack, shape.id], walked, options);
1932
+ }
1933
+ }
1934
+ const seen = /* @__PURE__ */ new Set();
1935
+ for (const { shape } of treeShapes) {
1936
+ if (seen.has(shape.id)) continue;
1937
+ seen.add(shape.id);
1938
+ for (const child of fieldsOfMembers(shape.fields)) {
1939
+ const segment = richTextTreeSegmentOf(shape.id);
1940
+ visit(child, [...path25, segment], [...shapeStack, shape.id], walked, options);
1941
+ }
1942
+ }
1943
+ }
1944
+ function richTextTextPathsOf(field) {
1945
+ return walkFieldKinds(field, { intoRichText: true }).flatMap((walked) => {
1946
+ if (walked.status !== "declared" || walked.declaration.search.embed !== "prose") return [];
1947
+ const inside = walked.path.slice(1);
1948
+ if (inside.length === 0 || !isRichTextTreeSegment(inside[0])) return [];
1949
+ return [inside.join(".")];
1950
+ });
1951
+ }
1952
+ function richTextDeclaredTextOfPaths(paths) {
1953
+ const declared = {};
1954
+ const put = (shape, member, holds) => {
1955
+ const members = declared[shape] ??= {};
1956
+ if (members[member] === void 0) members[member] = holds;
1957
+ };
1958
+ for (const path25 of paths) {
1959
+ const segments = path25.split(".");
1960
+ let shape = null;
1961
+ for (const [index, segment] of segments.entries()) {
1962
+ const tree = readRichTextTreeSegment(segment);
1963
+ if (tree !== null) {
1964
+ shape = tree;
1965
+ continue;
1966
+ }
1967
+ const item = readItemSegment(segment);
1968
+ if (item !== null) {
1969
+ shape = item.shape;
1970
+ continue;
1971
+ }
1972
+ if (shape === null) break;
1973
+ const next = segments[index + 1];
1974
+ const holds = next === void 0 || isRichTextTreeSegment(next) ? "text" : isItemSegment(next) ? "items" : null;
1975
+ if (holds === null) break;
1976
+ put(shape, segment, holds);
1977
+ shape = null;
1739
1978
  }
1740
1979
  }
1980
+ return declared;
1981
+ }
1982
+ function richTextDeclaredTextOf(field) {
1983
+ return richTextDeclaredTextOfPaths(richTextTextPathsOf(field));
1984
+ }
1985
+ function isRichTextShapeType(type) {
1986
+ return typeof type === "string" && SHAPE_ID.test(type) && !RESERVED_SHAPE_IDS.has(type);
1987
+ }
1988
+ function isRichTextShapeNode(node) {
1989
+ return isRecord(node) && isRichTextShapeType(node._type);
1741
1990
  }
1742
1991
  function isRecord(value) {
1743
1992
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1817,6 +2066,10 @@ function checkNode(node, at, place, problems, check) {
1817
2066
  return;
1818
2067
  }
1819
2068
  const type = node._type;
2069
+ if (isRichTextShapeType(type)) {
2070
+ checkShapeNode(node, String(type), at, place, problems, check);
2071
+ return;
2072
+ }
1820
2073
  if (typeof type !== "string" || !Object.hasOwn(MEMBERS, type)) {
1821
2074
  problems.push({
1822
2075
  at,
@@ -1965,6 +2218,16 @@ function checkNode(node, at, place, problems, check) {
1965
2218
  }
1966
2219
  }
1967
2220
  }
2221
+ function checkShapeNode(node, type, at, place, problems, check) {
2222
+ if (place !== "block" && place !== "inline") {
2223
+ problems.push({
2224
+ at,
2225
+ problem: `is a ${type}, a declared block or inline element, which cannot stand ${PLACES[place].where}`
2226
+ });
2227
+ return;
2228
+ }
2229
+ checkKey(node._key, at, problems, check);
2230
+ }
1968
2231
  function isLineNumber(value) {
1969
2232
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
1970
2233
  }
@@ -2016,12 +2279,13 @@ function checkMarks(marks, at, problems) {
2016
2279
  if (order <= previous) {
2017
2280
  problems.push({
2018
2281
  at: markAt,
2019
- problem: `is out of order or repeated; marks go ${RICH_TEXT_MARK_TYPES.join(", ")}`
2282
+ problem: `is out of order or repeated; marks go ${RICH_TEXT_MARK_ORDER.join(", ")}`
2020
2283
  });
2021
2284
  }
2022
2285
  previous = order;
2286
+ const members = MARK_MEMBERS[mark._type] ?? [];
2023
2287
  for (const member of Object.keys(mark)) {
2024
- if (member !== "_type" && !(mark._type === "link" && member === "href")) {
2288
+ if (member !== "_type" && !members.includes(member)) {
2025
2289
  problems.push({
2026
2290
  at: `${markAt}.${member}`,
2027
2291
  problem: `is not a member of a ${mark._type} mark`
@@ -2031,8 +2295,28 @@ function checkMarks(marks, at, problems) {
2031
2295
  if (mark._type === "link" && (typeof mark.href !== "string" || mark.href === "")) {
2032
2296
  problems.push({ at: `${markAt}.href`, problem: "must be a non-empty string" });
2033
2297
  }
2298
+ if (mark._type === RICH_TEXT_ENTRY_LINK_TYPE) {
2299
+ if (typeof mark.to !== "string" || mark.to === "") {
2300
+ problems.push({
2301
+ at: `${markAt}.to`,
2302
+ problem: "must be the id of the entry it links to, a non-empty string"
2303
+ });
2304
+ }
2305
+ if (mark.anchor !== void 0 && (typeof mark.anchor !== "string" || mark.anchor === "")) {
2306
+ problems.push({
2307
+ at: `${markAt}.anchor`,
2308
+ problem: "must be a heading anchor on the linked entry, a non-empty string, or absent"
2309
+ });
2310
+ }
2311
+ }
2034
2312
  }
2035
2313
  const types = new Set(marks.map((mark) => isRecord(mark) ? mark._type : void 0));
2314
+ if (types.has("link") && types.has(RICH_TEXT_ENTRY_LINK_TYPE)) {
2315
+ problems.push({
2316
+ at,
2317
+ problem: "holds a link beside an entry link; a run links to one place"
2318
+ });
2319
+ }
2036
2320
  if (marks.length > 1 && types.has("code")) {
2037
2321
  problems.push({ at, problem: "holds code beside another mark; code stands alone" });
2038
2322
  }
@@ -2040,15 +2324,67 @@ function checkMarks(marks, at, problems) {
2040
2324
  problems.push({ at, problem: "holds subscript beside superscript; a run is one or the other" });
2041
2325
  }
2042
2326
  }
2327
+ function richTextLinkTargetsOf(links) {
2328
+ if (links === void 0 || links === null) return [];
2329
+ if (Array.isArray(links)) return links;
2330
+ if (!isRecord(links)) return [];
2331
+ const to = links.to;
2332
+ if (typeof to === "string") return [to];
2333
+ return Array.isArray(to) ? to : [];
2334
+ }
2335
+ function shapesAllowedBy(held) {
2336
+ const allowed = /* @__PURE__ */ new Map();
2337
+ for (const entry of held ?? []) {
2338
+ if (typeof entry === "string") {
2339
+ allowed.set(entry, null);
2340
+ continue;
2341
+ }
2342
+ if (!isRecord(entry) || typeof entry.id !== "string" || !Array.isArray(entry.fields)) continue;
2343
+ const fields = entry.fields.every((field) => typeof field === "string") ? entry.fields : fieldsOfMembers(entry.fields).map((field) => field.id);
2344
+ allowed.set(entry.id, new Set(fields));
2345
+ }
2346
+ return allowed;
2347
+ }
2043
2348
  function richTextNarrowingOf(config) {
2044
2349
  const multiline = config.multiline ?? true;
2045
2350
  return {
2046
2351
  // A one-line field holds one paragraph whatever `structure` says; deploy refuses the two together.
2047
2352
  structure: new Set(multiline ? config.structure ?? RICH_TEXT_STRUCTURES : []),
2048
2353
  marks: new Set(config.marks ?? RICH_TEXT_MARK_TYPES),
2049
- multiline
2354
+ multiline,
2355
+ // A one-line field holds one paragraph and no declared block either; deploy refuses the two together.
2356
+ blocks: multiline ? shapesAllowedBy(config.blocks) : /* @__PURE__ */ new Map(),
2357
+ inline: shapesAllowedBy(config.inline),
2358
+ links: new Set(richTextLinkTargetsOf(config.links))
2359
+ };
2360
+ }
2361
+ function spelledShapes(allowed) {
2362
+ return [...allowed].map(([id, fields]) => fields === null ? id : { id, fields: [...fields] });
2363
+ }
2364
+ function richTextNarrowingOptionsOf(narrowing) {
2365
+ return {
2366
+ structure: RICH_TEXT_STRUCTURES.filter((structure) => narrowing.structure.has(structure)),
2367
+ marks: RICH_TEXT_MARK_TYPES.filter((mark) => narrowing.marks.has(mark)),
2368
+ multiline: narrowing.multiline,
2369
+ blocks: spelledShapes(narrowing.blocks),
2370
+ inline: spelledShapes(narrowing.inline),
2371
+ links: [...narrowing.links]
2050
2372
  };
2051
2373
  }
2374
+ function shapesLost(before, after) {
2375
+ const lost = [];
2376
+ for (const [id, fields] of before) {
2377
+ const now = after.get(id);
2378
+ if (now === void 0) {
2379
+ lost.push(id);
2380
+ continue;
2381
+ }
2382
+ if (fields === null || now === null) continue;
2383
+ const gone = [...fields].filter((field) => !now.has(field));
2384
+ if (gone.length > 0) lost.push({ id, fields: gone });
2385
+ }
2386
+ return lost;
2387
+ }
2052
2388
  function richTextNarrowingLost(before, after) {
2053
2389
  const structure = RICH_TEXT_STRUCTURES.filter(
2054
2390
  (structure2) => before.structure.has(structure2) && !after.structure.has(structure2)
@@ -2057,10 +2393,10 @@ function richTextNarrowingLost(before, after) {
2057
2393
  (mark) => before.marks.has(mark) && !after.marks.has(mark)
2058
2394
  );
2059
2395
  const multiline = before.multiline && !after.multiline;
2060
- return structure.length === 0 && marks.length === 0 && !multiline ? null : { structure, marks, multiline };
2061
- }
2062
- function isFullRichTextNarrowing(narrowing) {
2063
- return narrowing.multiline && narrowing.structure.size === RICH_TEXT_STRUCTURES.length && narrowing.marks.size === RICH_TEXT_MARK_TYPES.length;
2396
+ const blocks = shapesLost(before.blocks, after.blocks);
2397
+ const inline = shapesLost(before.inline, after.inline);
2398
+ const links = [...before.links].filter((type) => !after.links.has(type));
2399
+ return structure.length === 0 && marks.length === 0 && !multiline && blocks.length === 0 && inline.length === 0 && links.length === 0 ? null : { structure, marks, multiline, blocks, inline, links };
2064
2400
  }
2065
2401
  function structureOfBlock(block) {
2066
2402
  switch (block._type) {
@@ -2080,7 +2416,13 @@ function structureOfBlock(block) {
2080
2416
  }
2081
2417
  }
2082
2418
  }
2083
- function richTextNarrowingProblems(value, narrowing) {
2419
+ function keepsEntryLink(link, narrowing) {
2420
+ if (narrowing.links.size === 0) return false;
2421
+ const type = typeof link.to === "string" ? narrowing.linkTargets?.get(link.to) : void 0;
2422
+ return type === void 0 || narrowing.links.has(type);
2423
+ }
2424
+ function richTextNarrowingProblems(value, narrowing, linkTargets) {
2425
+ const reading = { ...narrowing, linkTargets };
2084
2426
  const problems = [];
2085
2427
  if (!narrowing.multiline && value.children.length > 1) {
2086
2428
  problems.push({
@@ -2088,15 +2430,49 @@ function richTextNarrowingProblems(value, narrowing) {
2088
2430
  problem: `is a block after the first; this field is one line (multiline: false) and holds ${value.children.length}`
2089
2431
  });
2090
2432
  }
2091
- narrowingProblemsIn(value.children, "children", narrowing, problems);
2433
+ narrowingProblemsIn(value.children, "children", "block", reading, problems);
2092
2434
  return problems;
2093
2435
  }
2094
- function narrowingProblemsIn(nodes, at, narrowing, problems) {
2436
+ function shapeNarrowingProblems(node, here, place, narrowing, problems) {
2437
+ const option = place === "block" ? "blocks" : "inline";
2438
+ const allowed = narrowing[option];
2439
+ const fields = allowed.get(node._type);
2440
+ if (fields === void 0) {
2441
+ problems.push({
2442
+ at: here,
2443
+ problem: `is a ${node._type}, which this field does not declare as ${SHAPE_PLACE_NAMES[place]} (${option}: ${listOf(new Set(allowed.keys()))})`
2444
+ });
2445
+ return;
2446
+ }
2447
+ if (fields === null) return;
2448
+ for (const member of Object.keys(node)) {
2449
+ if (member === "_type" || member === "_key" || fields.has(member)) continue;
2450
+ problems.push({
2451
+ at: `${here}.${member}`,
2452
+ problem: `is not a field the ${node._type} fieldset declares`
2453
+ });
2454
+ }
2455
+ }
2456
+ function narrowingProblemsIn(nodes, at, place, narrowing, problems) {
2095
2457
  for (const [index, node] of nodes.entries()) {
2096
2458
  const here = `${at}[${index}]`;
2459
+ if (isRichTextShapeNode(node)) {
2460
+ shapeNarrowingProblems(node, here, place, narrowing, problems);
2461
+ continue;
2462
+ }
2097
2463
  switch (node._type) {
2098
2464
  case "text": {
2099
- for (const [markIndex, mark] of (node.marks ?? []).entries()) {
2465
+ for (const [markIndex, mark] of marksOfRun(node).entries()) {
2466
+ if (mark._type === RICH_TEXT_ENTRY_LINK_TYPE) {
2467
+ if (!keepsEntryLink(mark, narrowing)) {
2468
+ const type = typeof mark.to === "string" ? narrowing.linkTargets?.get(mark.to) : void 0;
2469
+ problems.push({
2470
+ at: `${here}.marks[${markIndex}]`,
2471
+ problem: narrowing.links.size === 0 ? "is an entry link, which this field does not allow (links: none)" : `is an entry link to a ${type}, which this field does not link to (links: ${listOf(narrowing.links)})`
2472
+ });
2473
+ }
2474
+ continue;
2475
+ }
2100
2476
  if (!narrowing.marks.has(mark._type)) {
2101
2477
  problems.push({
2102
2478
  at: `${here}.marks[${markIndex}]`,
@@ -2118,7 +2494,7 @@ function narrowingProblemsIn(nodes, at, narrowing, problems) {
2118
2494
  case "listItem":
2119
2495
  case "tableRow":
2120
2496
  case "tableCell": {
2121
- narrowingProblemsIn(node.children, `${here}.children`, narrowing, problems);
2497
+ narrowingProblemsIn(node.children, `${here}.children`, "block", narrowing, problems);
2122
2498
  continue;
2123
2499
  }
2124
2500
  case "codeBlock":
@@ -2137,24 +2513,32 @@ function narrowingProblemsIn(nodes, at, narrowing, problems) {
2137
2513
  });
2138
2514
  }
2139
2515
  if ("children" in node && node._type !== "codeGroup") {
2140
- narrowingProblemsIn(node.children, `${here}.children`, narrowing, problems);
2516
+ const inside = node._type === "paragraph" || node._type === "heading" ? "inline" : "block";
2517
+ narrowingProblemsIn(node.children, `${here}.children`, inside, narrowing, problems);
2141
2518
  }
2142
2519
  continue;
2143
2520
  }
2144
2521
  }
2145
2522
  }
2146
2523
  }
2524
+ function marksOfRun(run3) {
2525
+ return run3.marks ?? [];
2526
+ }
2147
2527
  function listOf(allowed) {
2148
2528
  return allowed.size === 0 ? "none" : [...allowed].join(", ");
2149
2529
  }
2150
- var LIST_ITEM_SEGMENT, LIST_ITEM_KEY, LIST_ITEM_TYPE, SHAPED_KINDS, RICH_TEXT_VERSION, RICH_TEXT_TYPE, RICH_TEXT_HEADING_LEVELS, RICH_TEXT_LIST_STYLES, RICH_TEXT_CELL_ALIGNMENTS, RICH_TEXT_MARK_TYPES, MEMBERS, PLACES, MARK_ORDER, RICH_TEXT_STRUCTURES, LIST_STRUCTURE, RICH_TEXT_RESERVED_OPTIONS, STRUCTURE_NAMES;
2151
- var init_chunk_RDFZ6WP6 = __esm({
2152
- "../../domain/schema/dist/chunk-RDFZ6WP6.js"() {
2153
- init_chunk_7HSDWMYK();
2530
+ var LIST_ITEM_SEGMENT, RICH_TEXT_TREE_SEGMENT, LIST_ITEM_KEY, LIST_ITEM_TYPE, SHAPED_KINDS, RICH_TEXT_SHAPE_OPTIONS, RICH_TEXT_ENTRY_LINK_TYPE, ENTRY_LINK_TARGET_MEMBER, RICH_TEXT_VERSION, RICH_TEXT_TYPE, RICH_TEXT_HEADING_LEVELS, RICH_TEXT_LIST_STYLES, RICH_TEXT_CELL_ALIGNMENTS, RICH_TEXT_MARK_TYPES, RICH_TEXT_MARK_ORDER, RICH_TEXT_KEYED_NODE_TYPES, RICH_TEXT_RESERVED_SHAPE_IDS, RESERVED_SHAPE_IDS, SHAPE_ID, MEMBERS, PLACES, MARK_ORDER, MARK_MEMBERS, RICH_TEXT_STRUCTURES, LIST_STRUCTURE, STRUCTURE_NAMES, SHAPE_PLACE_NAMES;
2531
+ var init_chunk_UBAXWQKU = __esm({
2532
+ "../../domain/schema/dist/chunk-UBAXWQKU.js"() {
2533
+ init_chunk_VFLLYTDW();
2154
2534
  LIST_ITEM_SEGMENT = "$";
2535
+ RICH_TEXT_TREE_SEGMENT = "~";
2155
2536
  LIST_ITEM_KEY = "_key";
2156
2537
  LIST_ITEM_TYPE = "_type";
2157
2538
  SHAPED_KINDS = /* @__PURE__ */ new Set(["list", "object"]);
2539
+ RICH_TEXT_SHAPE_OPTIONS = ["blocks", "inline"];
2540
+ RICH_TEXT_ENTRY_LINK_TYPE = "entryLink";
2541
+ ENTRY_LINK_TARGET_MEMBER = "to";
2158
2542
  RICH_TEXT_VERSION = 1;
2159
2543
  RICH_TEXT_TYPE = "richText";
2160
2544
  RICH_TEXT_HEADING_LEVELS = [1, 2, 3, 4, 5, 6];
@@ -2171,6 +2555,34 @@ var init_chunk_RDFZ6WP6 = __esm({
2171
2555
  "subscript",
2172
2556
  "superscript"
2173
2557
  ];
2558
+ RICH_TEXT_MARK_ORDER = [
2559
+ "link",
2560
+ RICH_TEXT_ENTRY_LINK_TYPE,
2561
+ ...RICH_TEXT_MARK_TYPES.filter((type) => type !== "link")
2562
+ ];
2563
+ RICH_TEXT_KEYED_NODE_TYPES = [
2564
+ "paragraph",
2565
+ "heading",
2566
+ "quote",
2567
+ "list",
2568
+ "listItem",
2569
+ "codeBlock",
2570
+ "codeGroup",
2571
+ "table",
2572
+ "tableRow",
2573
+ "tableCell",
2574
+ "divider",
2575
+ "lineBreak"
2576
+ ];
2577
+ RICH_TEXT_RESERVED_SHAPE_IDS = [
2578
+ RICH_TEXT_TYPE,
2579
+ "text",
2580
+ ...RICH_TEXT_KEYED_NODE_TYPES,
2581
+ ...RICH_TEXT_MARK_TYPES,
2582
+ RICH_TEXT_ENTRY_LINK_TYPE
2583
+ ];
2584
+ RESERVED_SHAPE_IDS = new Set(RICH_TEXT_RESERVED_SHAPE_IDS);
2585
+ SHAPE_ID = /^[a-zA-Z][a-zA-Z0-9_]*$/;
2174
2586
  MEMBERS = {
2175
2587
  paragraph: ["_key", "children"],
2176
2588
  heading: ["_key", "level", "children"],
@@ -2208,8 +2620,12 @@ var init_chunk_RDFZ6WP6 = __esm({
2208
2620
  cellBlock: { types: /* @__PURE__ */ new Set(["paragraph"]), where: "inside a table cell" }
2209
2621
  };
2210
2622
  MARK_ORDER = new Map(
2211
- RICH_TEXT_MARK_TYPES.map((type, index) => [type, index])
2623
+ RICH_TEXT_MARK_ORDER.map((type, index) => [type, index])
2212
2624
  );
2625
+ MARK_MEMBERS = {
2626
+ link: ["href"],
2627
+ [RICH_TEXT_ENTRY_LINK_TYPE]: ["to", "anchor"]
2628
+ };
2213
2629
  RICH_TEXT_STRUCTURES = [
2214
2630
  "heading",
2215
2631
  "quote",
@@ -2226,11 +2642,6 @@ var init_chunk_RDFZ6WP6 = __esm({
2226
2642
  numbered: "numberedList",
2227
2643
  task: "taskList"
2228
2644
  };
2229
- RICH_TEXT_RESERVED_OPTIONS = {
2230
- blocks: "REL-1807",
2231
- inline: "REL-1807",
2232
- links: "REL-1808"
2233
- };
2234
2645
  STRUCTURE_NAMES = {
2235
2646
  heading: "a heading",
2236
2647
  quote: "a quote",
@@ -2242,6 +2653,7 @@ var init_chunk_RDFZ6WP6 = __esm({
2242
2653
  table: "a table",
2243
2654
  divider: "a divider"
2244
2655
  };
2656
+ SHAPE_PLACE_NAMES = { block: "a block", inline: "an inline element" };
2245
2657
  }
2246
2658
  });
2247
2659
 
@@ -2310,55 +2722,6 @@ var init_chunk_IJSOONHT = __esm({
2310
2722
  DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
2311
2723
  }
2312
2724
  });
2313
- function distinct(values) {
2314
- return new Set(values).size === values.length;
2315
- }
2316
- function reserved(option) {
2317
- const message = `\`${option}\` is reserved for ${RICH_TEXT_RESERVED_OPTIONS[option]}, which implements it; this build does not accept it yet`;
2318
- return z.never({ errorMap: () => ({ message }) });
2319
- }
2320
- function distinct2(values) {
2321
- return new Set(values).size === values.length;
2322
- }
2323
- function isCalendarDate(value) {
2324
- if (!DATE_PATTERN.test(value)) return true;
2325
- const [year, month, day] = value.split("-").map(Number);
2326
- const date = new Date(Date.UTC(year, month - 1, day));
2327
- return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
2328
- }
2329
- function normalizeReferenceTargets(to) {
2330
- if (to == null) return [];
2331
- return Array.isArray(to) ? [...to] : [to];
2332
- }
2333
- function resolveReferenceConfigTargets(config, schema) {
2334
- const typeIds = normalizeReferenceTargets(config?.to);
2335
- const contentTypeIds = [];
2336
- const assetTypeIds = [];
2337
- const unknownTypeIds = [];
2338
- const contentTypes = schema.contentTypes ?? {};
2339
- const singletons = schema.singletonContentTypes ?? {};
2340
- const assetTypes = schema.assetTypes ?? {};
2341
- for (const id of typeIds) {
2342
- if (id in contentTypes || id in singletons) {
2343
- contentTypeIds.push(id);
2344
- } else if (id in assetTypes) {
2345
- assetTypeIds.push(id);
2346
- } else {
2347
- unknownTypeIds.push(id);
2348
- }
2349
- }
2350
- let family;
2351
- if (unknownTypeIds.length > 0 && contentTypeIds.length === 0 && assetTypeIds.length === 0) {
2352
- family = "unknown";
2353
- } else if (contentTypeIds.length > 0 && assetTypeIds.length > 0) {
2354
- family = "mixed";
2355
- } else if (assetTypeIds.length > 0) {
2356
- family = "asset";
2357
- } else {
2358
- family = "content";
2359
- }
2360
- return { family, typeIds, contentTypeIds, assetTypeIds, unknownTypeIds };
2361
- }
2362
2725
  function sortDependencyLevels(nodes, dependencies) {
2363
2726
  const nodeSet = new Set(nodes);
2364
2727
  const dependents = /* @__PURE__ */ new Map();
@@ -2495,22 +2858,23 @@ function refineDeclaredReadSets(fields, ctx) {
2495
2858
  }
2496
2859
  function refineShapes(of, kind, ctx) {
2497
2860
  if (of === void 0) return;
2861
+ const words2 = SHAPE_PLACE_WORDS[kind];
2498
2862
  const shapes = Array.isArray(of) ? of : [of];
2499
2863
  const seen = /* @__PURE__ */ new Set();
2500
2864
  for (const [index, shape] of shapes.entries()) {
2501
- const at = Array.isArray(of) ? ["of", index] : ["of"];
2865
+ const at = Array.isArray(of) ? [words2.option, index] : [words2.option];
2502
2866
  const ids = fieldsOfMembers(shape.fields).map((field) => field.id);
2503
2867
  if (ids.length !== new Set(ids).size) {
2504
2868
  ctx.addIssue({
2505
2869
  code: z.ZodIssueCode.custom,
2506
- message: `Duplicate field IDs are not allowed within ${kind === "list" ? "a list's" : "an object's"} shape`,
2870
+ message: `Duplicate field IDs are not allowed within ${words2.owner} shape`,
2507
2871
  path: at
2508
2872
  });
2509
2873
  }
2510
2874
  if (seen.has(shape.id)) {
2511
2875
  ctx.addIssue({
2512
2876
  code: z.ZodIssueCode.custom,
2513
- message: `${kind === "list" ? "A list" : "An object"} takes each shape once: '${shape.id}' is named twice in its 'of'`,
2877
+ message: `${words2.subject} takes each shape once: '${shape.id}' is named twice in its '${words2.option}'`,
2514
2878
  path: [...at, "id"]
2515
2879
  });
2516
2880
  }
@@ -2521,6 +2885,65 @@ function refineShapes(of, kind, ctx) {
2521
2885
  );
2522
2886
  }
2523
2887
  }
2888
+ function normalizeReferenceTargets(to) {
2889
+ if (to == null) return [];
2890
+ return Array.isArray(to) ? [...to] : [to];
2891
+ }
2892
+ function resolveReferenceConfigTargets(config, schema) {
2893
+ const typeIds = normalizeReferenceTargets(config?.to);
2894
+ const contentTypeIds = [];
2895
+ const assetTypeIds = [];
2896
+ const unknownTypeIds = [];
2897
+ const contentTypes = schema.contentTypes ?? {};
2898
+ const singletons = schema.singletonContentTypes ?? {};
2899
+ const assetTypes = schema.assetTypes ?? {};
2900
+ for (const id of typeIds) {
2901
+ if (id in contentTypes || id in singletons) {
2902
+ contentTypeIds.push(id);
2903
+ } else if (id in assetTypes) {
2904
+ assetTypeIds.push(id);
2905
+ } else {
2906
+ unknownTypeIds.push(id);
2907
+ }
2908
+ }
2909
+ let family;
2910
+ if (unknownTypeIds.length > 0 && contentTypeIds.length === 0 && assetTypeIds.length === 0) {
2911
+ family = "unknown";
2912
+ } else if (contentTypeIds.length > 0 && assetTypeIds.length > 0) {
2913
+ family = "mixed";
2914
+ } else if (assetTypeIds.length > 0) {
2915
+ family = "asset";
2916
+ } else {
2917
+ family = "content";
2918
+ }
2919
+ return { family, typeIds, contentTypeIds, assetTypeIds, unknownTypeIds };
2920
+ }
2921
+ function directiveNameable(id) {
2922
+ return !id.endsWith("_");
2923
+ }
2924
+ function distinct(values) {
2925
+ return new Set(values).size === values.length;
2926
+ }
2927
+ function distinct2(values) {
2928
+ return new Set(values).size === values.length;
2929
+ }
2930
+ function isCalendarDate(value) {
2931
+ if (!DATE_PATTERN.test(value)) return true;
2932
+ const [year, month, day] = value.split("-").map(Number);
2933
+ const date = new Date(Date.UTC(year, month - 1, day));
2934
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
2935
+ }
2936
+ function distinct3(values) {
2937
+ return new Set(values.map((value) => JSON.stringify(value))).size === values.length;
2938
+ }
2939
+ function compiles(pattern) {
2940
+ try {
2941
+ new RegExp(pattern, "u");
2942
+ return true;
2943
+ } catch {
2944
+ return false;
2945
+ }
2946
+ }
2524
2947
  function configDeclarationOf(key) {
2525
2948
  switch (key) {
2526
2949
  case "shortText": {
@@ -2577,6 +3000,9 @@ function configDeclarationOf(key) {
2577
3000
  case "object": {
2578
3001
  return ObjectConfigSchema;
2579
3002
  }
3003
+ case "json": {
3004
+ return JsonConfigSchema;
3005
+ }
2580
3006
  }
2581
3007
  }
2582
3008
  function getFieldConfigSchema(type) {
@@ -2666,13 +3092,14 @@ function filterCapabilitiesOf(kind) {
2666
3092
  const { filter } = FIELD_KIND_DECLARATIONS[kind];
2667
3093
  return { filterable: filter.kind !== null, filterOperators: [...filter.viewOperators] };
2668
3094
  }
2669
- var FallbackChainSchema, BaseFieldConfigSchema, ShortTextConfigSchema, LongTextConfigSchema, RichTextDefaultSchema, RichTextConfigSchema, TableConfigSchema, CodeConfigSchema, DOMAIN, EmailConfigSchema, UrlConfigSchema, SlugConfigSchema, IntegerConfigSchema, DecimalConfigSchema, BooleanConfigSchema, EnumConfigSchema, DATE_PATTERN, CalendarDateSchema, DateConfigSchema, InstantSchema, DateTimeConfigSchema, ReferenceStrengthSchema, OnTargetDeleteSchema, ReferenceTargetSchema, ReferenceConfigSchema, AI_INSTRUCTIONS_MAX_CHARS, AIConfigSchema, FieldAIConfigSchema, AssetAIConfigSchema, UNKNOWN_INPUT_MESSAGE, INPUT_CYCLE_MESSAGE, TagsConfigSchema, ofShapes, ListConfigSchema, ObjectConfigSchema, BaseUIConfigSchema, TextUIOptionsSchema, LongTextUIOptionsSchema, SlugUIOptionsSchema, NumericUIOptionsSchema, BooleanUIOptionsSchema, EnumUIOptionsSchema, DateUIOptionsSchema, DateTimeUIOptionsSchema, ReferenceUIOptionsSchema, TagsUIOptionsSchema, ListUIOptionsSchema, ObjectUIOptionsSchema, UI_OPTIONS_SCHEMAS, UIConfigSchema, RuleConditionSchema, RuleConditionsSchema, VisibilityRuleSchema, RequirementRuleSchema, ValidationRuleSchema, ReadOnlyRuleSchema, FieldRuleSchema, LocalizationConfigSchema, INVALID_FIELD_CONFIG, judgingFieldConfigs, FieldSchema, FIELDSET_ID, TypeMemberSchema, FieldsetSchema, BUILT_IN_ROLES, ROLE_ACTIONS, RoleActionSchema, BuiltInRoleSchema, roleIdPattern, RoleIdSchema, accessTagPattern, AccessTagSchema, AccessTagsArraySchema, ResourcePermissionSchema, FieldVisibilitySchema, RolePermissionsSchema, RoleSchema, RolesArraySchema, COLUMN_RENDERERS, ColumnRendererSchema, FIELD_CAPABILITIES, SYSTEM_FIELD_CAPABILITIES, SYSTEM_FIELD_IDS, CHOICE_OPERATORS, PERSON_OPERATORS, INSTANT_OPERATORS, STUDIO_SYSTEM_FIELD_CAPABILITIES, STUDIO_SYSTEM_FIELD_IDS, FIELD_ICONS;
2670
- var init_chunk_3FBPIG5X = __esm({
2671
- "../../domain/schema/dist/chunk-3FBPIG5X.js"() {
2672
- init_chunk_GCR57WZV();
2673
- init_chunk_RDFZ6WP6();
3095
+ var FallbackChainSchema, BaseFieldConfigSchema, ShortTextConfigSchema, LongTextConfigSchema, AI_INSTRUCTIONS_MAX_CHARS, AIConfigSchema, FieldAIConfigSchema, AssetAIConfigSchema, UNKNOWN_INPUT_MESSAGE, INPUT_CYCLE_MESSAGE, TagsConfigSchema, ofShapes, SHAPE_PLACE_WORDS, ListConfigSchema, ObjectConfigSchema, ReferenceStrengthSchema, OnTargetDeleteSchema, ReferenceTargetSchema, ReferenceConfigSchema, RichTextDefaultSchema, declaredShapes, RichTextLinksSchema, RichTextConfigSchema, TableConfigSchema, CodeConfigSchema, DOMAIN, EmailConfigSchema, UrlConfigSchema, SlugConfigSchema, IntegerConfigSchema, DecimalConfigSchema, BooleanConfigSchema, EnumConfigSchema, DATE_PATTERN, CalendarDateSchema, DateConfigSchema, InstantSchema, DateTimeConfigSchema, JsonValueSchema, count, bound, JsonFieldSchemaSchema, JsonConfigSchema, BaseUIConfigSchema, TextUIOptionsSchema, LongTextUIOptionsSchema, SlugUIOptionsSchema, NumericUIOptionsSchema, BooleanUIOptionsSchema, EnumUIOptionsSchema, DateUIOptionsSchema, DateTimeUIOptionsSchema, ReferenceUIOptionsSchema, TagsUIOptionsSchema, ListUIOptionsSchema, ObjectUIOptionsSchema, UI_OPTIONS_SCHEMAS, UIConfigSchema, RuleConditionSchema, RuleConditionsSchema, VisibilityRuleSchema, RequirementRuleSchema, ValidationRuleSchema, ReadOnlyRuleSchema, FieldRuleSchema, LocalizationConfigSchema, INVALID_FIELD_CONFIG, judgingFieldConfigs, FieldSchema, FIELDSET_ID, TypeMemberSchema, FieldsetSchema, BUILT_IN_ROLES, ROLE_ACTIONS, RoleActionSchema, BuiltInRoleSchema, roleIdPattern, RoleIdSchema, accessTagPattern, AccessTagSchema, AccessTagsArraySchema, ResourcePermissionSchema, FieldVisibilitySchema, RolePermissionsSchema, RoleSchema, RolesArraySchema, COLUMN_RENDERERS, ColumnRendererSchema, FIELD_CAPABILITIES, SYSTEM_FIELD_CAPABILITIES, SYSTEM_FIELD_IDS, CHOICE_OPERATORS, PERSON_OPERATORS, INSTANT_OPERATORS, STUDIO_SYSTEM_FIELD_CAPABILITIES, STUDIO_SYSTEM_FIELD_IDS, FIELD_ICONS;
3096
+ var init_chunk_QTFIQNJM = __esm({
3097
+ "../../domain/schema/dist/chunk-QTFIQNJM.js"() {
3098
+ init_chunk_MGMW4ZMJ();
3099
+ init_chunk_DERETXFC();
3100
+ init_chunk_UBAXWQKU();
2674
3101
  init_chunk_IJSOONHT();
2675
- init_chunk_7HSDWMYK();
3102
+ init_chunk_VFLLYTDW();
2676
3103
  FallbackChainSchema = z.object({
2677
3104
  chain: z.array(
2678
3105
  z.discriminatedUnion("method", [
@@ -2734,19 +3161,173 @@ var init_chunk_3FBPIG5X = __esm({
2734
3161
  path: ["maxLength"]
2735
3162
  }
2736
3163
  );
3164
+ AI_INSTRUCTIONS_MAX_CHARS = 1e3;
3165
+ AIConfigSchema = z.object({
3166
+ /**
3167
+ * The AI brief for this element: what it should contain and how to produce
3168
+ * it. Used by the Studio authoring agent, and — on asset types with
3169
+ * `ai.autoGenerate` — by the automatic post-upload run.
3170
+ *
3171
+ * Fallback chain for AI guidance: `ai.instructions` → `ui.description` →
3172
+ * displayName + type.
3173
+ */
3174
+ instructions: z.string().max(AI_INSTRUCTIONS_MAX_CHARS, "ai.instructions must be at most 1000 characters").optional()
3175
+ }).strict();
3176
+ FieldAIConfigSchema = AIConfigSchema.extend({
3177
+ /**
3178
+ * Field ids this instruction reads. Unknown ids and cycles are refused on
3179
+ * the owning type at parse time.
3180
+ */
3181
+ inputs: z.array(z.string().min(1)).optional()
3182
+ });
3183
+ AssetAIConfigSchema = AIConfigSchema.extend({
3184
+ /**
3185
+ * Master switch for automatic, post-upload field generation. When true, the
3186
+ * agent fills each field that has `ai.instructions` (as a proposal). Only
3187
+ * meaningful for image asset types in v1.
3188
+ */
3189
+ autoGenerate: z.boolean().optional()
3190
+ });
3191
+ UNKNOWN_INPUT_MESSAGE = (fieldId, inputId) => `ai.inputs on field '${fieldId}' names unknown field '${inputId}'`;
3192
+ INPUT_CYCLE_MESSAGE = (cycle) => `ai.inputs cycle: ${cycle.join(" \u2192 ")}`;
3193
+ TagsConfigSchema = BaseFieldConfigSchema.extend({
3194
+ allowCustom: z.boolean().optional(),
3195
+ predefinedTags: z.array(z.string()).optional(),
3196
+ maxTags: z.number().min(1).optional()
3197
+ }).refine((data) => data.allowCustom !== false || (data.predefinedTags?.length ?? 0) > 0, {
3198
+ message: "When custom tags are disabled, predefinedTags must list at least one tag",
3199
+ path: ["predefinedTags"]
3200
+ });
3201
+ ofShapes = (kind) => (
3202
+ // Lazy because `FieldsetSchema` reaches `FieldSchema`, which holds a field's config to this
3203
+ // declaration (`refineDeclaredConfig`), so the modules import each other, and whichever is
3204
+ // evaluated second must not read the first's schema while it is still being built.
3205
+ z.lazy(
3206
+ () => z.union([
3207
+ FieldsetSchema,
3208
+ z.array(FieldsetSchema).min(1, `${kind === "list" ? "A list's" : "An object's"} 'of' names at least one fieldset`)
3209
+ ])
3210
+ )
3211
+ );
3212
+ SHAPE_PLACE_WORDS = {
3213
+ list: { owner: "a list's", subject: "A list", option: "of" },
3214
+ object: { owner: "an object's", subject: "An object", option: "of" },
3215
+ blocks: { owner: "a rich-text field's block", subject: "A rich-text field", option: "blocks" },
3216
+ inline: {
3217
+ owner: "a rich-text field's inline element",
3218
+ subject: "A rich-text field",
3219
+ option: "inline"
3220
+ }
3221
+ };
3222
+ ListConfigSchema = BaseFieldConfigSchema.extend({
3223
+ dragAndDrop: z.boolean().optional(),
3224
+ minItems: z.number().min(0).optional(),
3225
+ maxItems: z.number().min(1).optional(),
3226
+ /**
3227
+ * The Shape each item takes: a fieldset, whose fields every item holds beside `_type` (the
3228
+ * fieldset's id) and `_key` (REL-1796, ADR 0012) — or several, a page builder (REL-1798), each
3229
+ * item naming in `_type` the one it takes. A list without one holds strings. Read it through
3230
+ * `shapesOf` (`./field-kinds`), never off the config.
3231
+ */
3232
+ of: ofShapes("list").optional()
3233
+ }).refine(
3234
+ (data) => {
3235
+ if (data.minItems !== void 0 && data.maxItems !== void 0) {
3236
+ return data.minItems <= data.maxItems;
3237
+ }
3238
+ return true;
3239
+ },
3240
+ { message: "minItems must be less than or equal to maxItems", path: ["maxItems"] }
3241
+ ).superRefine((data, ctx) => refineShapes(data.of, "list", ctx));
3242
+ ObjectConfigSchema = BaseFieldConfigSchema.extend({
3243
+ of: ofShapes("object")
3244
+ }).superRefine((data, ctx) => refineShapes(data.of, "object", ctx));
3245
+ ReferenceStrengthSchema = z.enum(["strong", "weak"]).default("strong");
3246
+ OnTargetDeleteSchema = z.enum(["block", "nullify"]).default("block");
3247
+ ReferenceTargetSchema = z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]);
3248
+ ReferenceConfigSchema = BaseFieldConfigSchema.extend({
3249
+ /**
3250
+ * Target entry type id(s) — content type or asset type ids. A single string
3251
+ * for one target, an array for several. All ids must resolve to the same
3252
+ * family (all content types or all asset types); cross-family targets are
3253
+ * rejected by the DataIntegrityValidator (which has schema access).
3254
+ */
3255
+ to: ReferenceTargetSchema,
3256
+ /** Whether the field holds a list of references (true) or a single one. */
3257
+ multi: z.boolean().optional(),
3258
+ /**
3259
+ * The fewest and the most entries a multi reference holds (REL-2033), as a list's `minItems` and
3260
+ * `maxItems`: the customer's limit, enforced on every write and publish. A single reference
3261
+ * holds one, so it takes neither.
3262
+ */
3263
+ minItems: z.number().int().min(0).optional(),
3264
+ maxItems: z.number().int().min(1).optional(),
3265
+ defaultValue: z.union([z.string(), z.array(z.string())]).optional(),
3266
+ /**
3267
+ * Reference strength for publish/unpublish behavior
3268
+ * - 'strong' (default): Hard dependency - blocks publish if target unpublished
3269
+ * - 'weak': Soft dependency - warns but allows, target unpublish nullifies ref
3270
+ */
3271
+ referenceStrength: ReferenceStrengthSchema.optional(),
3272
+ /**
3273
+ * Behavior when referenced entry is deleted
3274
+ * - 'block' (default): Prevents deletion until reference is removed
3275
+ * - 'nullify': Sets reference to null when target deleted
3276
+ */
3277
+ onTargetDelete: OnTargetDeleteSchema.optional()
3278
+ }).superRefine((data, ctx) => {
3279
+ for (const key of ["minItems", "maxItems"]) {
3280
+ if (data[key] !== void 0 && data.multi !== true) {
3281
+ ctx.addIssue({
3282
+ code: z.ZodIssueCode.custom,
3283
+ message: `${key} applies to a multi reference: set multi: true, or remove ${key}`,
3284
+ path: [key]
3285
+ });
3286
+ }
3287
+ }
3288
+ if (data.minItems !== void 0 && data.maxItems !== void 0 && data.minItems > data.maxItems) {
3289
+ ctx.addIssue({
3290
+ code: z.ZodIssueCode.custom,
3291
+ message: "minItems must be less than or equal to maxItems",
3292
+ path: ["maxItems"]
3293
+ });
3294
+ }
3295
+ if (data.defaultValue !== void 0) {
3296
+ const isMulti2 = data.multi === true;
3297
+ const isArrayDefault = Array.isArray(data.defaultValue);
3298
+ if (isMulti2 && !isArrayDefault) {
3299
+ ctx.addIssue({
3300
+ code: z.ZodIssueCode.custom,
3301
+ message: "defaultValue must be an array when multi is true",
3302
+ path: ["defaultValue"]
3303
+ });
3304
+ }
3305
+ if (!isMulti2 && isArrayDefault) {
3306
+ ctx.addIssue({
3307
+ code: z.ZodIssueCode.custom,
3308
+ message: "defaultValue must be a string when multi is false or undefined",
3309
+ path: ["defaultValue"]
3310
+ });
3311
+ }
3312
+ }
3313
+ });
2737
3314
  RichTextDefaultSchema = z.custom(
2738
3315
  (value) => richTextProblems(value).length === 0,
2739
3316
  'A rich-text default value is a version-1 tree ({ _type: "richText", version: 1, children }) with a _key on every node but a text run'
2740
3317
  );
3318
+ declaredShapes = (option) => z.lazy(
3319
+ () => z.array(FieldsetSchema).min(1, `${option} names at least one fieldset; leave it out to declare none`)
3320
+ );
3321
+ RichTextLinksSchema = z.object({ to: ReferenceTargetSchema }).strict();
2741
3322
  RichTextConfigSchema = BaseFieldConfigSchema.extend({
2742
3323
  maxLength: z.number().min(1).optional(),
2743
3324
  defaultValue: RichTextDefaultSchema.optional(),
2744
3325
  structure: z.array(z.enum(RICH_TEXT_STRUCTURES)).refine(distinct, "structure names each built-in block at most once").optional(),
2745
3326
  marks: z.array(z.enum(RICH_TEXT_MARK_TYPES)).refine(distinct, "marks names each mark at most once").optional(),
2746
3327
  multiline: z.boolean().optional(),
2747
- blocks: reserved("blocks").optional(),
2748
- inline: reserved("inline").optional(),
2749
- links: reserved("links").optional()
3328
+ blocks: declaredShapes("blocks").optional(),
3329
+ inline: declaredShapes("inline").optional(),
3330
+ links: RichTextLinksSchema.optional()
2750
3331
  }).superRefine((config, ctx) => {
2751
3332
  if (config.multiline === false && config.structure !== void 0 && config.structure.length > 0) {
2752
3333
  ctx.addIssue({
@@ -2755,6 +3336,43 @@ var init_chunk_3FBPIG5X = __esm({
2755
3336
  message: "A one-line field (multiline: false) holds one paragraph and no other block; drop structure or allow many lines"
2756
3337
  });
2757
3338
  }
3339
+ if (config.multiline === false && config.blocks !== void 0 && config.blocks.length > 0) {
3340
+ ctx.addIssue({
3341
+ code: z.ZodIssueCode.custom,
3342
+ path: ["blocks"],
3343
+ message: "A one-line field (multiline: false) holds one paragraph and no declared block; drop blocks or allow many lines"
3344
+ });
3345
+ }
3346
+ for (const option of ["blocks", "inline"]) {
3347
+ const shapes = config[option];
3348
+ if (shapes === void 0) continue;
3349
+ refineShapes(shapes, option, ctx);
3350
+ for (const [index, shape] of shapes.entries()) {
3351
+ if (RICH_TEXT_RESERVED_SHAPE_IDS.includes(shape.id)) {
3352
+ ctx.addIssue({
3353
+ code: z.ZodIssueCode.custom,
3354
+ path: [option, index, "id"],
3355
+ message: `A fieldset placed in rich text cannot take the name '${shape.id}', which the tree's built-ins and marks use; give it another id`
3356
+ });
3357
+ }
3358
+ if (!directiveNameable(shape.id)) {
3359
+ ctx.addIssue({
3360
+ code: z.ZodIssueCode.custom,
3361
+ path: [option, index, "id"],
3362
+ message: `A fieldset placed in rich text is named in RelyIO Markdown by its id, which cannot end in '_': rename '${shape.id}'`
3363
+ });
3364
+ }
3365
+ if (option !== "blocks") continue;
3366
+ for (const field of fieldsOfMembers(shape.fields)) {
3367
+ if (!isKind(field.type, "richText") || directiveNameable(field.id)) continue;
3368
+ ctx.addIssue({
3369
+ code: z.ZodIssueCode.custom,
3370
+ path: [option, index, "fields"],
3371
+ message: `A rich-text field of a block placed in rich text is named in RelyIO Markdown by its id, which cannot end in '_': rename '${shape.id}.${field.id}'`
3372
+ });
3373
+ }
3374
+ }
3375
+ }
2758
3376
  if (config.defaultValue !== void 0 && richTextProblems(config.defaultValue).length === 0) {
2759
3377
  for (const problem of richTextNarrowingProblems(
2760
3378
  config.defaultValue,
@@ -2818,7 +3436,12 @@ var init_chunk_3FBPIG5X = __esm({
2818
3436
  });
2819
3437
  SlugConfigSchema = BaseFieldConfigSchema.extend({
2820
3438
  sourceField: z.string().optional(),
2821
- maxLength: z.number().min(1).optional(),
3439
+ // Capped (REL-2149): a slug is read in a URL path, and the hosts that serve one there size their
3440
+ // router to `SLUG_MAX_LENGTH`. A longer `maxLength` would admit slugs the router answers 414.
3441
+ maxLength: z.number().min(1).max(
3442
+ SLUG_MAX_LENGTH,
3443
+ `A slug's maxLength may be at most ${SLUG_MAX_LENGTH}: a slug is read in a URL path, and no longer one is served`
3444
+ ).optional(),
2822
3445
  defaultValue: z.string().optional()
2823
3446
  });
2824
3447
  IntegerConfigSchema = BaseFieldConfigSchema.extend({
@@ -2947,146 +3570,55 @@ var init_chunk_3FBPIG5X = __esm({
2947
3570
  path: ["maxDateTime"]
2948
3571
  }
2949
3572
  );
2950
- ReferenceStrengthSchema = z.enum(["strong", "weak"]).default("strong");
2951
- OnTargetDeleteSchema = z.enum(["block", "nullify"]).default("block");
2952
- ReferenceTargetSchema = z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]);
2953
- ReferenceConfigSchema = BaseFieldConfigSchema.extend({
2954
- /**
2955
- * Target entry type id(s) — content type or asset type ids. A single string
2956
- * for one target, an array for several. All ids must resolve to the same
2957
- * family (all content types or all asset types); cross-family targets are
2958
- * rejected by the DataIntegrityValidator (which has schema access).
2959
- */
2960
- to: ReferenceTargetSchema,
2961
- /** Whether the field holds a list of references (true) or a single one. */
2962
- multi: z.boolean().optional(),
2963
- /**
2964
- * The fewest and the most entries a multi reference holds (REL-2033), as a list's `minItems` and
2965
- * `maxItems`: the customer's limit, enforced on every write and publish. A single reference
2966
- * holds one, so it takes neither.
2967
- */
2968
- minItems: z.number().int().min(0).optional(),
2969
- maxItems: z.number().int().min(1).optional(),
2970
- defaultValue: z.union([z.string(), z.array(z.string())]).optional(),
2971
- /**
2972
- * Reference strength for publish/unpublish behavior
2973
- * - 'strong' (default): Hard dependency - blocks publish if target unpublished
2974
- * - 'weak': Soft dependency - warns but allows, target unpublish nullifies ref
2975
- */
2976
- referenceStrength: ReferenceStrengthSchema.optional(),
2977
- /**
2978
- * Behavior when referenced entry is deleted
2979
- * - 'block' (default): Prevents deletion until reference is removed
2980
- * - 'nullify': Sets reference to null when target deleted
2981
- */
2982
- onTargetDelete: OnTargetDeleteSchema.optional()
2983
- }).superRefine((data, ctx) => {
2984
- for (const key of ["minItems", "maxItems"]) {
2985
- if (data[key] !== void 0 && data.multi !== true) {
2986
- ctx.addIssue({
2987
- code: z.ZodIssueCode.custom,
2988
- message: `${key} applies to a multi reference: set multi: true, or remove ${key}`,
2989
- path: [key]
2990
- });
2991
- }
2992
- }
2993
- if (data.minItems !== void 0 && data.maxItems !== void 0 && data.minItems > data.maxItems) {
2994
- ctx.addIssue({
2995
- code: z.ZodIssueCode.custom,
2996
- message: "minItems must be less than or equal to maxItems",
2997
- path: ["maxItems"]
2998
- });
2999
- }
3000
- if (data.defaultValue !== void 0) {
3001
- const isMulti2 = data.multi === true;
3002
- const isArrayDefault = Array.isArray(data.defaultValue);
3003
- if (isMulti2 && !isArrayDefault) {
3004
- ctx.addIssue({
3005
- code: z.ZodIssueCode.custom,
3006
- message: "defaultValue must be an array when multi is true",
3007
- path: ["defaultValue"]
3008
- });
3009
- }
3010
- if (!isMulti2 && isArrayDefault) {
3011
- ctx.addIssue({
3012
- code: z.ZodIssueCode.custom,
3013
- message: "defaultValue must be a string when multi is false or undefined",
3014
- path: ["defaultValue"]
3015
- });
3016
- }
3017
- }
3018
- });
3019
- AI_INSTRUCTIONS_MAX_CHARS = 1e3;
3020
- AIConfigSchema = z.object({
3021
- /**
3022
- * The AI brief for this element: what it should contain and how to produce
3023
- * it. Used by the Studio authoring agent, and — on asset types with
3024
- * `ai.autoGenerate` — by the automatic post-upload run.
3025
- *
3026
- * Fallback chain for AI guidance: `ai.instructions` → `ui.description` →
3027
- * displayName + type.
3028
- */
3029
- instructions: z.string().max(AI_INSTRUCTIONS_MAX_CHARS, "ai.instructions must be at most 1000 characters").optional()
3030
- }).strict();
3031
- FieldAIConfigSchema = AIConfigSchema.extend({
3032
- /**
3033
- * Field ids this instruction reads. Unknown ids and cycles are refused on
3034
- * the owning type at parse time.
3035
- */
3036
- inputs: z.array(z.string().min(1)).optional()
3037
- });
3038
- AssetAIConfigSchema = AIConfigSchema.extend({
3039
- /**
3040
- * Master switch for automatic, post-upload field generation. When true, the
3041
- * agent fills each field that has `ai.instructions` (as a proposal). Only
3042
- * meaningful for image asset types in v1.
3043
- */
3044
- autoGenerate: z.boolean().optional()
3045
- });
3046
- UNKNOWN_INPUT_MESSAGE = (fieldId, inputId) => `ai.inputs on field '${fieldId}' names unknown field '${inputId}'`;
3047
- INPUT_CYCLE_MESSAGE = (cycle) => `ai.inputs cycle: ${cycle.join(" \u2192 ")}`;
3048
- TagsConfigSchema = BaseFieldConfigSchema.extend({
3049
- allowCustom: z.boolean().optional(),
3050
- predefinedTags: z.array(z.string()).optional(),
3051
- maxTags: z.number().min(1).optional()
3052
- }).refine((data) => data.allowCustom !== false || (data.predefinedTags?.length ?? 0) > 0, {
3053
- message: "When custom tags are disabled, predefinedTags must list at least one tag",
3054
- path: ["predefinedTags"]
3055
- });
3056
- ofShapes = (kind) => (
3057
- // Lazy because `FieldsetSchema` reaches `FieldSchema`, which holds a field's config to this
3058
- // declaration (`refineDeclaredConfig`), so the modules import each other, and whichever is
3059
- // evaluated second must not read the first's schema while it is still being built.
3060
- z.lazy(
3061
- () => z.union([
3062
- FieldsetSchema,
3063
- z.array(FieldsetSchema).min(1, `${kind === "list" ? "A list's" : "An object's"} 'of' names at least one fieldset`)
3064
- ])
3065
- )
3573
+ JsonValueSchema = z.lazy(
3574
+ () => z.union([
3575
+ z.string(),
3576
+ z.number().finite(),
3577
+ z.boolean(),
3578
+ z.null(),
3579
+ z.array(JsonValueSchema),
3580
+ z.record(z.string(), JsonValueSchema)
3581
+ ])
3066
3582
  );
3067
- ListConfigSchema = BaseFieldConfigSchema.extend({
3068
- dragAndDrop: z.boolean().optional(),
3069
- minItems: z.number().min(0).optional(),
3070
- maxItems: z.number().min(1).optional(),
3071
- /**
3072
- * The Shape each item takes: a fieldset, whose fields every item holds beside `_type` (the
3073
- * fieldset's id) and `_key` (REL-1796, ADR 0012) — or several, a page builder (REL-1798), each
3074
- * item naming in `_type` the one it takes. A list without one holds strings. Read it through
3075
- * `shapesOf` (`./field-kinds`), never off the config.
3076
- */
3077
- of: ofShapes("list").optional()
3078
- }).refine(
3079
- (data) => {
3080
- if (data.minItems !== void 0 && data.maxItems !== void 0) {
3081
- return data.minItems <= data.maxItems;
3082
- }
3083
- return true;
3084
- },
3085
- { message: "minItems must be less than or equal to maxItems", path: ["maxItems"] }
3086
- ).superRefine((data, ctx) => refineShapes(data.of, "list", ctx));
3087
- ObjectConfigSchema = BaseFieldConfigSchema.extend({
3088
- of: ofShapes("object")
3089
- }).superRefine((data, ctx) => refineShapes(data.of, "object", ctx));
3583
+ count = z.number().int().min(0);
3584
+ bound = z.number().finite();
3585
+ JsonFieldSchemaSchema = z.lazy(
3586
+ () => z.object({
3587
+ type: z.union([
3588
+ z.enum(JSON_SCHEMA_TYPES),
3589
+ z.array(z.enum(JSON_SCHEMA_TYPES)).min(1, "type names at least one type").refine(distinct3, "type names each type at most once")
3590
+ ]).optional(),
3591
+ properties: z.record(z.string(), JsonFieldSchemaSchema).optional(),
3592
+ required: z.array(z.string()).refine(distinct3, "required names each property at most once").optional(),
3593
+ additionalProperties: z.union([z.boolean(), JsonFieldSchemaSchema]).optional(),
3594
+ items: JsonFieldSchemaSchema.optional(),
3595
+ enum: z.array(JsonValueSchema).min(1, "enum names at least one value; a schema no value passes is not one to deploy").refine(distinct3, "enum names each value at most once").optional(),
3596
+ const: JsonValueSchema.optional(),
3597
+ anyOf: z.array(JsonFieldSchemaSchema).min(1, "anyOf names at least one schema").optional(),
3598
+ oneOf: z.array(JsonFieldSchemaSchema).min(1, "oneOf names at least one schema").optional(),
3599
+ minLength: count.optional(),
3600
+ maxLength: count.optional(),
3601
+ pattern: z.string().refine(compiles, "pattern is not a regular expression (read with the u flag)").optional(),
3602
+ format: z.enum(JSON_SCHEMA_FORMATS).optional(),
3603
+ minimum: bound.optional(),
3604
+ maximum: bound.optional(),
3605
+ exclusiveMinimum: bound.optional(),
3606
+ exclusiveMaximum: bound.optional(),
3607
+ multipleOf: z.number().finite().positive().optional(),
3608
+ minItems: count.optional(),
3609
+ maxItems: count.optional(),
3610
+ uniqueItems: z.boolean().optional(),
3611
+ minProperties: count.optional(),
3612
+ maxProperties: count.optional(),
3613
+ title: z.string().optional(),
3614
+ description: z.string().optional(),
3615
+ default: JsonValueSchema.optional(),
3616
+ examples: z.array(JsonValueSchema).optional()
3617
+ }).strict()
3618
+ );
3619
+ JsonConfigSchema = BaseFieldConfigSchema.omit({ defaultValue: true }).extend({
3620
+ schema: JsonFieldSchemaSchema.optional()
3621
+ });
3090
3622
  BaseUIConfigSchema = z.object({
3091
3623
  displayName: z.string(),
3092
3624
  description: z.string().optional(),
@@ -3165,7 +3697,9 @@ var init_chunk_3FBPIG5X = __esm({
3165
3697
  reference: ReferenceUIOptionsSchema,
3166
3698
  tags: TagsUIOptionsSchema,
3167
3699
  list: ListUIOptionsSchema,
3168
- object: ObjectUIOptionsSchema
3700
+ object: ObjectUIOptionsSchema,
3701
+ // Its source is edited as lines of plain text (REL-1811): a placeholder and a height, as long text's
3702
+ json: LongTextUIOptionsSchema
3169
3703
  };
3170
3704
  UIConfigSchema = BaseUIConfigSchema;
3171
3705
  RuleConditionSchema = z.object({
@@ -3567,6 +4101,19 @@ var init_chunk_3FBPIG5X = __esm({
3567
4101
  metadataDisplayable: false,
3568
4102
  metadataRenderer: "none",
3569
4103
  sortable: false
4104
+ },
4105
+ // ─────────────────────────────────────────────────────────────────────────────
4106
+ // DATA
4107
+ // ─────────────────────────────────────────────────────────────────────────────
4108
+ json: {
4109
+ // Not filterable at first (REL-1811, design decision 18): the declaration's `filter` facet
4110
+ ...filterCapabilitiesOf("json"),
4111
+ columnDisplayable: false,
4112
+ // Any JSON value: nothing a column can show in one cell
4113
+ columnRenderer: "none",
4114
+ metadataDisplayable: false,
4115
+ metadataRenderer: "none",
4116
+ sortable: false
3570
4117
  }
3571
4118
  };
3572
4119
  SYSTEM_FIELD_CAPABILITIES = {
@@ -3885,7 +4432,9 @@ var init_chunk_3FBPIG5X = __esm({
3885
4432
  // Composite
3886
4433
  tags: "tags",
3887
4434
  list: "list-tree",
3888
- object: "box"
4435
+ object: "box",
4436
+ // Data
4437
+ json: "braces"
3889
4438
  };
3890
4439
  }
3891
4440
  });
@@ -3934,9 +4483,9 @@ function inSidebarOrder(views) {
3934
4483
  ).map(({ view }) => view);
3935
4484
  }
3936
4485
  var stageIdPattern, WorkflowEscalationSchema, WorkflowAutoAssignSchema, WorkflowStageSchema, WorkflowConfigSchema, ContentTypeWorkflowSchema, ASSET_KINDS, FORBIDDEN_ASSET_TYPE_FIELD_TYPES, IMAGE_OUTPUT_FORMATS, IMAGE_FIT_MODES, CF_IMAGES_LIMITS, DEFAULT_IMAGE_SOURCE, FitSchema, OutputFormatSchema, ImageTransformSchema, ImageUploadConfigSchema, VideoUploadConfigSchema, FileUploadConfigSchema, ImageExifConfigSchema, VariantDefinitionSchema, ImageSourceConfigSchema, ImageKindConfigSchema, VideoPosterConfigSchema, VideoCaptionsConfigSchema, VideoDownloadConfigSchema, VideoWatermarkConfigSchema, VideoKindConfigSchema, FileScanConfigSchema, FileKindConfigSchema, AssetTypeSchema;
3937
- var init_chunk_TVFFHTGZ = __esm({
3938
- "../../domain/schema/dist/chunk-TVFFHTGZ.js"() {
3939
- init_chunk_3FBPIG5X();
4486
+ var init_chunk_RFXTOU4J = __esm({
4487
+ "../../domain/schema/dist/chunk-RFXTOU4J.js"() {
4488
+ init_chunk_QTFIQNJM();
3940
4489
  stageIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$|^[a-z][a-zA-Z0-9]*$/;
3941
4490
  WorkflowEscalationSchema = z.object({
3942
4491
  /** Roles to notify when stage times out */
@@ -4539,13 +5088,13 @@ function filterValueWith(range, relativeDate) {
4539
5088
  ]);
4540
5089
  }
4541
5090
  var QUERY_BLOCKS, queryBlocksOf, QUERY_BLOCKS_SPELLED, queryRunsBlock, QUERY_FILTER_OPERATORS, isQueryFilterOperator, QUERY_FILTER_OPERATOR_NAMES, SPELLED, INCLUDE_MODIFIER_NAMES, INCLUDE_MODIFIERS, isIncludeModifier, FIELD_PATH, AGGREGATE_OPERATORS, isAggregateOperator, AGGREGATE_OPERATOR_NAMES, MAX_SELECT_OPERATION_ARGUMENT, withoutArguments, SELECT_STRING_OPERATIONS, isSelectStringOperationName, SELECT_STRING_OPERATION_NAMES, OPERATION_SPELLING, NON_NEGATIVE_INTEGER, VIEW_SLUG_PATTERN, VIEW_SLUG_MESSAGE, SCHEMA_VIEW_FILTER_GROUP_REFUSAL, SAVED_VIEW_FILTER_GROUP_REFUSAL, RangeValueSchema, ColumnConfigSchema, MetadataFieldConfigSchema, MetadataLineConfigSchema, TableSettingsSchema, ViewScopeSchema, contentTypeScope, contentTypesScope, allScope, entryTypeScope, DeclaredViewScopeSchema, DeclaredFilterValueSchema, SchemaViewFilterSchema, SchemaViewDefinitionSchema;
4542
- var init_chunk_VIVFUDPH = __esm({
4543
- "../../domain/schema/dist/chunk-VIVFUDPH.js"() {
5091
+ var init_chunk_AUX6CCB5 = __esm({
5092
+ "../../domain/schema/dist/chunk-AUX6CCB5.js"() {
4544
5093
  init_chunk_46JQ2NQ5();
4545
5094
  init_chunk_GR7UT3XN();
4546
- init_chunk_3FBPIG5X();
4547
- init_chunk_RDFZ6WP6();
4548
- init_chunk_7HSDWMYK();
5095
+ init_chunk_QTFIQNJM();
5096
+ init_chunk_UBAXWQKU();
5097
+ init_chunk_VFLLYTDW();
4549
5098
  QUERY_BLOCKS = ["fetch", "aggregate", "search"];
4550
5099
  queryBlocksOf = (query) => QUERY_BLOCKS.filter((block) => Boolean(query[block]));
4551
5100
  QUERY_BLOCKS_SPELLED = QUERY_BLOCKS.join(", ");
@@ -5093,15 +5642,16 @@ function undeclaredFieldTypeMessage(type) {
5093
5642
  return `Invalid field type '${type}'. Valid field types are: ${FIELD_KINDS.join(", ")}`;
5094
5643
  }
5095
5644
  var QUERY_ENDPOINT_SEGMENT, QUERY_ENDPOINT_PATTERN, PARAMETER_EXPRESSION_SOURCE, parameterExpressionMatcher, ANCHORED_PARAMETER_EXPRESSION, parseDefault, parseParameterExpression, collectParameterExpressions, endpointPathParameters, ParameterExpressionStringSchema, QUERY_PARAMETER_TYPES, QueryParameterTypeSchema, QueryParameterScalarSchema, QueryParameterSchema, typeOfDefault, declareQueryParameters, SEARCH_SCORE_SORT_KEYS, isSearchScoreSortKey, FIELD, PATH, SPELLING, Refused, QUERY_SYSTEM_FIELDS, isQuerySystemField, QUERY_STORAGE_PATH_PREFIX, QUERY_SYSTEM_ROOTS, SEARCH_SELECT_COLUMNS, isSearchSelectColumn, parseDynamicParameter, ParameterizedLimitSchema, ParameterizedOffsetSchema, ScalarSchema, WhereOperatorObjectSchema, FilterOperatorObjectSchema, HavingOperatorObjectSchema, WhereValueSchema, WhereConditionSchema, SortConditionSchema, RELATIONSHIP_NAME, IncludeRelationshipSchema, IncludeDefinitionSchema, SelectMappingSchema, FilterDefinitionSchema, FullTextSearchSchema, ContentTypesSchema, SearchBlockSchema, AggregationMetricSchema, QueryCardinalitySchema, QueryCachePolicySchema, FetchBlockSchema, AggregateBlockSchema, IncludeSubQueriesSchema, QueryDefinitionSchema, CACHE_POLICY_STRING, normalizeQueryCachePolicy, CONTENT_API_ROUTE_SEGMENTS, RESERVED_TYPE_IDS, ContentTypeSchema, SettingsGroupSchema, SchemaDefinitionSchema, SavedViewFilterSchema;
5096
- var init_chunk_7GULCWXY = __esm({
5097
- "../../domain/schema/dist/chunk-7GULCWXY.js"() {
5098
- init_chunk_TVFFHTGZ();
5099
- init_chunk_VIVFUDPH();
5645
+ var init_chunk_OUTLPFR2 = __esm({
5646
+ "../../domain/schema/dist/chunk-OUTLPFR2.js"() {
5647
+ init_chunk_RFXTOU4J();
5648
+ init_chunk_AUX6CCB5();
5100
5649
  init_chunk_LK3PZTVM();
5101
5650
  init_chunk_GR7UT3XN();
5102
- init_chunk_3FBPIG5X();
5103
- init_chunk_RDFZ6WP6();
5104
- init_chunk_7HSDWMYK();
5651
+ init_chunk_QTFIQNJM();
5652
+ init_chunk_MGMW4ZMJ();
5653
+ init_chunk_UBAXWQKU();
5654
+ init_chunk_VFLLYTDW();
5105
5655
  QUERY_ENDPOINT_SEGMENT = "api";
5106
5656
  QUERY_ENDPOINT_PATTERN = new RegExp(
5107
5657
  String.raw`^\/${QUERY_ENDPOINT_SEGMENT}\/([a-zA-Z0-9\-_/]|\{[a-zA-Z][a-zA-Z0-9_]*\})*$`
@@ -5709,6 +6259,17 @@ var init_chunk_7GULCWXY = __esm({
5709
6259
  });
5710
6260
  }
5711
6261
  }
6262
+ const routedIdCollections = [...typeCollections, ["settingsGroups", "Settings group"]];
6263
+ for (const [collection, label] of routedIdCollections) {
6264
+ for (const typeId of Object.keys(schema[collection] || {})) {
6265
+ if (typeId.length <= SLUG_MAX_LENGTH) continue;
6266
+ ctx.addIssue({
6267
+ code: z.ZodIssueCode.custom,
6268
+ message: `${label} ID '${typeId}' is ${typeId.length} characters; an id may be at most ${SLUG_MAX_LENGTH}, the longest path segment the Content API routes. Choose a shorter id.`,
6269
+ path: [collection, typeId]
6270
+ });
6271
+ }
6272
+ }
5712
6273
  const builtInRoles = /* @__PURE__ */ new Set(["admin", "editor", "viewer"]);
5713
6274
  const customRoleIds = new Set(schema.roles?.map((r) => r.id) ?? []);
5714
6275
  const allRoleIds = /* @__PURE__ */ new Set([...builtInRoles, ...customRoleIds]);
@@ -5830,7 +6391,153 @@ var init_chunk_7GULCWXY = __esm({
5830
6391
  }
5831
6392
  });
5832
6393
 
5833
- // ../../domain/schema/dist/chunk-5FYSBHHZ.js
6394
+ // ../../domain/schema/dist/chunk-NKUAK5EA.js
6395
+ function isContainer(value) {
6396
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6397
+ }
6398
+ function declaredFieldsOf(fields) {
6399
+ const root = { children: /* @__PURE__ */ new Map() };
6400
+ for (const walked of walkMembers(fields, { intoRichText: true })) {
6401
+ if (walked.path.some((segment) => typeof segment !== "string")) continue;
6402
+ let node = root;
6403
+ const way = [root];
6404
+ for (const segment of walked.path) {
6405
+ const treeType = readRichTextTreeSegment(segment);
6406
+ node = treeType === null ? childOf(node, segment) : treeShapeOf(node, treeType);
6407
+ way.push(node);
6408
+ }
6409
+ if (walked.status === "declared") {
6410
+ if (isKind(walked.key, "richText")) {
6411
+ node.richText = narrowingOfConfig(walked.field.config);
6412
+ if (node.richText.links.size > 0) for (const on of way) on.linksTypes = true;
6413
+ }
6414
+ const shapes = shapesOf(walked.field);
6415
+ if (shapes.length > 0) node.holds = walked.key === "object" ? "item" : "items";
6416
+ for (const shape of shapes) {
6417
+ const item = childOf(node, itemSegmentOf(shape.id));
6418
+ childOf(item, LIST_ITEM_TYPE);
6419
+ childOf(item, LIST_ITEM_KEY);
6420
+ }
6421
+ }
6422
+ }
6423
+ pruneTrees(root);
6424
+ return root;
6425
+ }
6426
+ function treeShapeOf(node, shapeId) {
6427
+ node.tree ??= /* @__PURE__ */ new Map();
6428
+ let shape = node.tree.get(shapeId);
6429
+ if (shape === void 0) {
6430
+ shape = { children: /* @__PURE__ */ new Map() };
6431
+ node.tree.set(shapeId, shape);
6432
+ }
6433
+ return shape;
6434
+ }
6435
+ function pruneTrees(node) {
6436
+ for (const child of node.children.values()) pruneTrees(child);
6437
+ if (node.tree === void 0) return;
6438
+ for (const [shapeId, shape] of node.tree) {
6439
+ pruneTrees(shape);
6440
+ if (![...shape.children.values()].some((field) => holdsBelow(field))) node.tree.delete(shapeId);
6441
+ }
6442
+ if (node.tree.size === 0) delete node.tree;
6443
+ }
6444
+ function holdsBelow(node) {
6445
+ return node.richText !== void 0 || node.children.size > 0 || node.tree !== void 0 || node.linksTypes === true;
6446
+ }
6447
+ function childOf(node, segment) {
6448
+ let child = node.children.get(segment);
6449
+ if (child === void 0) {
6450
+ child = { children: /* @__PURE__ */ new Map() };
6451
+ node.children.set(segment, child);
6452
+ }
6453
+ return child;
6454
+ }
6455
+ function narrowingOfConfig(config) {
6456
+ return richTextNarrowingOf(isContainer(config) ? config : {});
6457
+ }
6458
+ var init_chunk_NKUAK5EA = __esm({
6459
+ "../../domain/schema/dist/chunk-NKUAK5EA.js"() {
6460
+ init_chunk_UBAXWQKU();
6461
+ init_chunk_VFLLYTDW();
6462
+ }
6463
+ });
6464
+
6465
+ // ../../domain/schema/dist/chunk-YPAAGSVJ.js
6466
+ function isFieldLocalized(field) {
6467
+ const declared = field.localization;
6468
+ if (declared !== void 0 && declared !== null) {
6469
+ if (typeof declared === "boolean") return declared;
6470
+ if (Array.isArray(declared)) return declared.length > 0;
6471
+ if (typeof declared === "object") {
6472
+ return declared.enabled === true;
6473
+ }
6474
+ }
6475
+ return false;
6476
+ }
6477
+ function parseKeyedPath(path25) {
6478
+ const segments = [];
6479
+ let at = 0;
6480
+ let expectField = true;
6481
+ while (at < path25.length) {
6482
+ if (path25.startsWith(ITEM_SELECTOR, at)) {
6483
+ const previous = segments.at(-1);
6484
+ if (expectField || previous === void 0 || !("field" in previous)) return null;
6485
+ const quoted2 = readQuoted(path25, at + ITEM_SELECTOR.length);
6486
+ if (quoted2 === null || path25[quoted2.end] !== "]") return null;
6487
+ segments.push({ key: quoted2.value });
6488
+ at = quoted2.end + 1;
6489
+ if (at < path25.length && path25[at] !== "." && !path25.startsWith(ITEM_SELECTOR, at)) return null;
6490
+ if (path25[at] === ".") {
6491
+ at += 1;
6492
+ expectField = true;
6493
+ }
6494
+ continue;
6495
+ }
6496
+ let end = at;
6497
+ while (end < path25.length && path25[end] !== "." && path25[end] !== "[") end += 1;
6498
+ const field = path25.slice(at, end);
6499
+ if (field.length === 0) return null;
6500
+ segments.push({ field });
6501
+ expectField = false;
6502
+ at = end;
6503
+ if (path25[at] === ".") {
6504
+ at += 1;
6505
+ expectField = true;
6506
+ if (at === path25.length) return null;
6507
+ } else if (at < path25.length && !path25.startsWith(ITEM_SELECTOR, at)) {
6508
+ return null;
6509
+ }
6510
+ }
6511
+ return segments.length === 0 || expectField ? null : segments;
6512
+ }
6513
+ function readQuoted(path25, start) {
6514
+ if (path25[start] !== '"') return null;
6515
+ for (let end = start + 1; end < path25.length; end += 1) {
6516
+ if (path25[end] === "\\") {
6517
+ end += 1;
6518
+ continue;
6519
+ }
6520
+ if (path25[end] !== '"') continue;
6521
+ try {
6522
+ const value = JSON.parse(path25.slice(start, end + 1));
6523
+ return typeof value === "string" && value.length > 0 ? { value, end: end + 1 } : null;
6524
+ } catch {
6525
+ return null;
6526
+ }
6527
+ }
6528
+ return null;
6529
+ }
6530
+ var ITEM_SELECTOR;
6531
+ var init_chunk_YPAAGSVJ = __esm({
6532
+ "../../domain/schema/dist/chunk-YPAAGSVJ.js"() {
6533
+ init_chunk_NKUAK5EA();
6534
+ init_chunk_UBAXWQKU();
6535
+ init_chunk_VFLLYTDW();
6536
+ ITEM_SELECTOR = `[${LIST_ITEM_KEY}==`;
6537
+ }
6538
+ });
6539
+
6540
+ // ../../domain/schema/dist/chunk-JVCO3TTQ.js
5834
6541
  function kindConversionOf(from, to) {
5835
6542
  const source = parseFieldType(from);
5836
6543
  const target = parseFieldType(to);
@@ -5838,9 +6545,12 @@ function kindConversionOf(from, to) {
5838
6545
  const converts = FIELD_KIND_DECLARATIONS[source.key].change.convertsTo;
5839
6546
  return converts.includes(target.key) ? { status: "declared", from: source.key, to: target.key } : { status: "undeclared" };
5840
6547
  }
5841
- var init_chunk_5FYSBHHZ = __esm({
5842
- "../../domain/schema/dist/chunk-5FYSBHHZ.js"() {
5843
- init_chunk_7HSDWMYK();
6548
+ var init_chunk_JVCO3TTQ = __esm({
6549
+ "../../domain/schema/dist/chunk-JVCO3TTQ.js"() {
6550
+ init_chunk_YPAAGSVJ();
6551
+ init_chunk_NKUAK5EA();
6552
+ init_chunk_UBAXWQKU();
6553
+ init_chunk_VFLLYTDW();
5844
6554
  }
5845
6555
  });
5846
6556
  function emptyDefinition() {
@@ -5903,23 +6613,20 @@ function buildReferenceGraph(reader) {
5903
6613
  const reverse = {};
5904
6614
  for (const type of reader.getAllTypes()) {
5905
6615
  const edges = [];
5906
- for (const walked of walkMembers(reader.getFieldsForContentType(type.id))) {
5907
- if (walked.status !== "declared") continue;
5908
- const { field } = walked;
5909
- if (!holdsReferenceValue(field.type)) continue;
5910
- const config = field.config ?? {};
5911
- const target = reader.resolveReferenceTarget({ config: { to: config.to } });
6616
+ for (const reference of walkReferences(reader.getFieldsForContentType(type.id))) {
6617
+ const target = reader.resolveReferenceTarget({ config: { to: [...reference.to] } });
5912
6618
  if (target.types.length === 0) continue;
5913
6619
  const edge = {
5914
6620
  sourceType: type.id,
5915
- sourceField: field.id,
5916
- path: walked.path.join("."),
5917
- // Only a top-level field is addressable as a pipeline path; a nested one is
5918
- // reachable only through its logical path, and the caller decides how.
5919
- ...walked.path.length === 1 ? { mongoPath: `fields.${field.id}` } : {},
6621
+ sourceField: reference.field.id,
6622
+ path: reference.path.join("."),
6623
+ // Only a top-level reference field is addressable as a pipeline path; a nested one — and an
6624
+ // entry link, inside its field's tree — is reachable only through its logical path, and the
6625
+ // caller decides how.
6626
+ ...reference.path.length === 1 ? { mongoPath: `fields.${reference.field.id}` } : {},
5920
6627
  targetTypes: target.types,
5921
6628
  targetKind: target.kind,
5922
- multi: config.multi === true
6629
+ multi: reference.multi
5923
6630
  };
5924
6631
  edges.push(edge);
5925
6632
  for (const targetType of target.types) {
@@ -5932,10 +6639,9 @@ function buildReferenceGraph(reader) {
5932
6639
  return { forward, reverse };
5933
6640
  }
5934
6641
  var DEFINITION_SECTIONS, KEYED_SECTIONS, SCHEMA_HASH_OPTIONS;
5935
- var init_chunk_W5P6N7TE = __esm({
5936
- "../../domain/schema/dist/chunk-W5P6N7TE.js"() {
5937
- init_chunk_RDFZ6WP6();
5938
- init_chunk_7HSDWMYK();
6642
+ var init_chunk_MLQFI2H5 = __esm({
6643
+ "../../domain/schema/dist/chunk-MLQFI2H5.js"() {
6644
+ init_chunk_UBAXWQKU();
5939
6645
  DEFINITION_SECTIONS = {
5940
6646
  locales: "list",
5941
6647
  roles: "list",
@@ -5952,73 +6658,90 @@ var init_chunk_W5P6N7TE = __esm({
5952
6658
  }
5953
6659
  });
5954
6660
 
5955
- // ../../domain/schema/dist/chunk-OA32TBFV.js
6661
+ // ../../domain/schema/dist/chunk-CTS2X4ES.js
5956
6662
  function shapedValueFragment(shapes, nestedFields) {
5957
- const valueOf = (shape, type) => {
5958
- const value = nestedFields(fieldsOfMembers(shape.fields));
5959
- value.properties = {
5960
- ...value.properties,
5961
- [LIST_ITEM_TYPE]: type,
5962
- [LIST_ITEM_KEY]: { type: "string" }
5963
- };
5964
- return value;
5965
- };
5966
6663
  const [only] = shapes;
5967
6664
  if (shapes.length === 1 && only !== void 0) {
5968
- return valueOf(only, { type: "string", enum: [only.id] });
6665
+ return valueOf(only, { type: "string", enum: [only.id] }, nestedFields);
5969
6666
  }
5970
6667
  if (shapes.length > 1) {
5971
6668
  return {
5972
6669
  type: "object",
5973
6670
  discriminator: { propertyName: LIST_ITEM_TYPE },
5974
- oneOf: shapes.map((shape) => {
5975
- const value = valueOf(shape, { const: shape.id });
5976
- return { ...value, required: [...value.required ?? [], LIST_ITEM_TYPE] };
5977
- })
6671
+ oneOf: shapes.map((shape) => shapeFragmentOf(shape, nestedFields))
5978
6672
  };
5979
6673
  }
5980
6674
  return void 0;
5981
6675
  }
5982
- var FIELD_FORMAT_CODE_KEY, URL_VALUE_FORMAT, SITE_RELATIVE_PATH, FieldFormats, ConfiguredFieldFormats, StringFieldValidators, NumericFieldValidators, BooleanChoiceFieldValidators, DateTimeFieldValidators, ReferenceFieldValidators, CompositeFieldValidators, FIELD_KIND_VALUE_RULES;
5983
- var init_chunk_OA32TBFV = __esm({
5984
- "../../domain/schema/dist/chunk-OA32TBFV.js"() {
5985
- init_chunk_GCR57WZV();
5986
- init_chunk_RDFZ6WP6();
5987
- FIELD_FORMAT_CODE_KEY = "x-formatCode";
5988
- URL_VALUE_FORMAT = "url";
5989
- SITE_RELATIVE_PATH = /^\/(?!\/)/;
5990
- FieldFormats = {
5991
- slug: {
5992
- code: "slug",
5993
- pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
5994
- message: "can only contain lowercase letters, numbers and hyphens, with no leading, trailing or repeated hyphens"
5995
- },
5996
- email: {
5997
- code: "email",
5998
- pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
5999
- message: "must be a valid email address"
6000
- },
6001
- uuid: {
6002
- code: "uuid",
6003
- pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
6004
- message: "must be a valid UUID"
6676
+ function shapeFragmentOf(shape, nestedFields) {
6677
+ const value = valueOf(shape, { const: shape.id }, nestedFields);
6678
+ return { ...value, required: [...value.required ?? [], LIST_ITEM_TYPE] };
6679
+ }
6680
+ function valueOf(shape, type, nestedFields) {
6681
+ const value = nestedFields(fieldsOfMembers(shape.fields));
6682
+ value.properties = {
6683
+ ...value.properties,
6684
+ [LIST_ITEM_TYPE]: type,
6685
+ [LIST_ITEM_KEY]: { type: "string" }
6686
+ };
6687
+ return value;
6688
+ }
6689
+ var CompositeFieldValidators, StringFieldValidators, NumericFieldValidators, BooleanChoiceFieldValidators, DateTimeFieldValidators, ReferenceFieldValidators, DataFieldValidators, FIELD_KIND_VALUE_RULES;
6690
+ var init_chunk_CTS2X4ES = __esm({
6691
+ "../../domain/schema/dist/chunk-CTS2X4ES.js"() {
6692
+ init_chunk_MGMW4ZMJ();
6693
+ init_chunk_UBAXWQKU();
6694
+ CompositeFieldValidators = {
6695
+ tags: {
6696
+ type: "array",
6697
+ items: { type: "string" },
6698
+ uniqueItems: true,
6699
+ customValidator: (schema, field) => {
6700
+ if (field.config?.maxTags) {
6701
+ schema.maxItems = field.config.maxTags;
6702
+ }
6703
+ if (field.config?.allowCustom === false && schema.items) {
6704
+ if (!field.config.predefinedTags?.length) {
6705
+ throw new Error("Tags field with allowCustom: false must have predefinedTags defined");
6706
+ }
6707
+ schema.items.enum = field.config.predefinedTags;
6708
+ }
6709
+ }
6005
6710
  },
6006
- phone: {
6007
- code: "phone",
6008
- pattern: /^[+]?[\d\s\-()]{7,15}$/,
6009
- message: "must be a valid phone number"
6010
- }
6011
- };
6012
- ConfiguredFieldFormats = {
6013
- /** An email field restricted to a `config.domainWhitelist`. */
6014
- emailDomain: {
6015
- code: "emailDomain",
6016
- message: "must be from an allowed domain"
6711
+ list: {
6712
+ type: "array",
6713
+ items: { type: "string" },
6714
+ customValidator: (schema, field, nestedFields) => {
6715
+ if (field.config?.minItems) {
6716
+ schema.minItems = field.config.minItems;
6717
+ }
6718
+ if (field.config?.maxItems) {
6719
+ schema.maxItems = field.config.maxItems;
6720
+ }
6721
+ const shapes = shapesOf(field);
6722
+ if (shapes.length === 0 && field.config?.of !== void 0) {
6723
+ throw new Error(
6724
+ "A list field's `of` must be a fieldset, or a list of them: an id, a displayName and fields"
6725
+ );
6726
+ }
6727
+ const items = shapedValueFragment(shapes, nestedFields);
6728
+ if (items !== void 0) schema.items = items;
6729
+ if (field.config?.unique) {
6730
+ schema.uniqueItems = true;
6731
+ }
6732
+ }
6017
6733
  },
6018
- /** A URL field restricted to `config.allowedProtocols`. */
6019
- urlProtocol: {
6020
- code: "urlProtocol",
6021
- message: "must use an allowed protocol"
6734
+ object: {
6735
+ type: "object",
6736
+ customValidator: (schema, field, nestedFields) => {
6737
+ const value = shapedValueFragment(shapesOf(field), nestedFields);
6738
+ if (value === void 0) {
6739
+ throw new Error(
6740
+ "An object field's `of` must be a fieldset, or a list of them: an id, a displayName and fields"
6741
+ );
6742
+ }
6743
+ Object.assign(schema, value);
6744
+ }
6022
6745
  }
6023
6746
  };
6024
6747
  StringFieldValidators = {
@@ -6060,18 +6783,37 @@ var init_chunk_OA32TBFV = __esm({
6060
6783
  // the 50,000 characters of HTML this kept by default approximated — and an empty tree is refused
6061
6784
  // as an empty string was, by `minItems` on the root's blocks. A field that narrows the docs set
6062
6785
  // (REL-1805: `structure`, `marks`, `multiline`) carries its narrowing as `x-richTextNarrowing`, so
6063
- // a write holding what the field does not allow is refused naming it.
6786
+ // a write holding what the field does not allow is refused naming it. Every field carries it,
6787
+ // since every field refuses a declared block or inline element it does not declare (REL-1807) —
6788
+ // one that declares none refuses them all. The fields of each one it declares are held to the
6789
+ // shape's own fragment, the one a page builder's item of that fieldset is held to
6790
+ // (`x-richTextShapes`). Its `links` (REL-1808) rides the narrowing too: a field that names none
6791
+ // refuses an entry link; which type one points at is the write core's reference check.
6064
6792
  richText: {
6065
6793
  "x-richTextTree": 1,
6066
6794
  properties: { children: { type: "array", minItems: 1 } },
6067
- customValidator: (schema, field) => {
6068
- schema["x-richTextMaxLength"] = field.config?.maxLength ?? 5e4;
6795
+ customValidator: (schema, field, nestedFields) => {
6796
+ schema["x-richTextMaxLength"] = {
6797
+ limit: field.config?.maxLength ?? 5e4,
6798
+ declared: richTextDeclaredTextOf(field)
6799
+ };
6069
6800
  const narrowing = richTextNarrowingOf(field.config ?? {});
6070
- if (!isFullRichTextNarrowing(narrowing)) {
6071
- schema["x-richTextNarrowing"] = {
6072
- structure: [...narrowing.structure],
6073
- marks: [...narrowing.marks],
6074
- multiline: narrowing.multiline
6801
+ schema["x-richTextNarrowing"] = {
6802
+ structure: [...narrowing.structure],
6803
+ marks: [...narrowing.marks],
6804
+ multiline: narrowing.multiline,
6805
+ blocks: [...narrowing.blocks.keys()],
6806
+ inline: [...narrowing.inline.keys()],
6807
+ links: [...narrowing.links]
6808
+ };
6809
+ const shapes = richTextShapesOf(field);
6810
+ if (shapes.blocks.length > 0 || shapes.inline.length > 0) {
6811
+ const fragments = (declared) => Object.fromEntries(
6812
+ declared.map((shape) => [shape.id, shapeFragmentOf(shape, nestedFields)])
6813
+ );
6814
+ schema["x-richTextShapes"] = {
6815
+ blocks: fragments(shapes.blocks),
6816
+ inline: fragments(shapes.inline)
6075
6817
  };
6076
6818
  }
6077
6819
  if (field.config?.defaultValue) {
@@ -6158,7 +6900,7 @@ var init_chunk_OA32TBFV = __esm({
6158
6900
  schema.maxLength = field.config.maxLength;
6159
6901
  }
6160
6902
  if (!schema.maxLength) {
6161
- schema.maxLength = 100;
6903
+ schema.maxLength = SLUG_DEFAULT_MAX_LENGTH;
6162
6904
  }
6163
6905
  }
6164
6906
  }
@@ -6289,56 +7031,12 @@ var init_chunk_OA32TBFV = __esm({
6289
7031
  }
6290
7032
  }
6291
7033
  };
6292
- CompositeFieldValidators = {
6293
- tags: {
6294
- type: "array",
6295
- items: { type: "string" },
6296
- uniqueItems: true,
7034
+ DataFieldValidators = {
7035
+ json: {
7036
+ "x-json": {},
6297
7037
  customValidator: (schema, field) => {
6298
- if (field.config?.maxTags) {
6299
- schema.maxItems = field.config.maxTags;
6300
- }
6301
- if (field.config?.allowCustom === false && schema.items) {
6302
- if (!field.config.predefinedTags?.length) {
6303
- throw new Error("Tags field with allowCustom: false must have predefinedTags defined");
6304
- }
6305
- schema.items.enum = field.config.predefinedTags;
6306
- }
6307
- }
6308
- },
6309
- list: {
6310
- type: "array",
6311
- items: { type: "string" },
6312
- customValidator: (schema, field, nestedFields) => {
6313
- if (field.config?.minItems) {
6314
- schema.minItems = field.config.minItems;
6315
- }
6316
- if (field.config?.maxItems) {
6317
- schema.maxItems = field.config.maxItems;
6318
- }
6319
- const shapes = shapesOf(field);
6320
- if (shapes.length === 0 && field.config?.of !== void 0) {
6321
- throw new Error(
6322
- "A list field's `of` must be a fieldset, or a list of them: an id, a displayName and fields"
6323
- );
6324
- }
6325
- const items = shapedValueFragment(shapes, nestedFields);
6326
- if (items !== void 0) schema.items = items;
6327
- if (field.config?.unique) {
6328
- schema.uniqueItems = true;
6329
- }
6330
- }
6331
- },
6332
- object: {
6333
- type: "object",
6334
- customValidator: (schema, field, nestedFields) => {
6335
- const value = shapedValueFragment(shapesOf(field), nestedFields);
6336
- if (value === void 0) {
6337
- throw new Error(
6338
- "An object field's `of` must be a fieldset, or a list of them: an id, a displayName and fields"
6339
- );
6340
- }
6341
- Object.assign(schema, value);
7038
+ const declared = field.config?.schema;
7039
+ if (declared !== void 0) schema["x-json"] = { schema: declared };
6342
7040
  }
6343
7041
  }
6344
7042
  };
@@ -6360,7 +7058,8 @@ var init_chunk_OA32TBFV = __esm({
6360
7058
  reference: { fragment: ReferenceFieldValidators.reference },
6361
7059
  tags: { fragment: CompositeFieldValidators.tags },
6362
7060
  list: { fragment: CompositeFieldValidators.list },
6363
- object: { fragment: CompositeFieldValidators.object }
7061
+ object: { fragment: CompositeFieldValidators.object },
7062
+ json: { fragment: DataFieldValidators.json }
6364
7063
  };
6365
7064
  }
6366
7065
  });
@@ -6371,114 +7070,7 @@ var init_chunk_NGOOYRLJ = __esm({
6371
7070
  }
6372
7071
  });
6373
7072
 
6374
- // ../../domain/schema/dist/chunk-KDABWSAA.js
6375
- function declaredFieldsOf(fields) {
6376
- const root = { children: /* @__PURE__ */ new Map() };
6377
- for (const walked of walkMembers(fields)) {
6378
- let node = root;
6379
- for (const segment of walked.path) node = childOf(node, segment);
6380
- if (walked.status === "declared") {
6381
- const shapes = shapesOf(walked.field);
6382
- if (shapes.length > 0) node.holds = walked.key === "object" ? "item" : "items";
6383
- for (const shape of shapes) {
6384
- const item = childOf(node, itemSegmentOf(shape.id));
6385
- childOf(item, LIST_ITEM_TYPE);
6386
- childOf(item, LIST_ITEM_KEY);
6387
- }
6388
- }
6389
- }
6390
- return root;
6391
- }
6392
- function childOf(node, segment) {
6393
- let child = node.children.get(segment);
6394
- if (child === void 0) {
6395
- child = { children: /* @__PURE__ */ new Map() };
6396
- node.children.set(segment, child);
6397
- }
6398
- return child;
6399
- }
6400
- var init_chunk_KDABWSAA = __esm({
6401
- "../../domain/schema/dist/chunk-KDABWSAA.js"() {
6402
- init_chunk_RDFZ6WP6();
6403
- }
6404
- });
6405
-
6406
- // ../../domain/schema/dist/chunk-SXB4UYLV.js
6407
- function isFieldLocalized(field) {
6408
- const declared = field.localization;
6409
- if (declared !== void 0 && declared !== null) {
6410
- if (typeof declared === "boolean") return declared;
6411
- if (Array.isArray(declared)) return declared.length > 0;
6412
- if (typeof declared === "object") {
6413
- return declared.enabled === true;
6414
- }
6415
- }
6416
- return false;
6417
- }
6418
- function parseKeyedPath(path25) {
6419
- const segments = [];
6420
- let at = 0;
6421
- let expectField = true;
6422
- while (at < path25.length) {
6423
- if (path25.startsWith(ITEM_SELECTOR, at)) {
6424
- const previous = segments.at(-1);
6425
- if (expectField || previous === void 0 || !("field" in previous)) return null;
6426
- const quoted = readQuoted(path25, at + ITEM_SELECTOR.length);
6427
- if (quoted === null || path25[quoted.end] !== "]") return null;
6428
- segments.push({ key: quoted.value });
6429
- at = quoted.end + 1;
6430
- if (at < path25.length && path25[at] !== "." && !path25.startsWith(ITEM_SELECTOR, at)) return null;
6431
- if (path25[at] === ".") {
6432
- at += 1;
6433
- expectField = true;
6434
- }
6435
- continue;
6436
- }
6437
- let end = at;
6438
- while (end < path25.length && path25[end] !== "." && path25[end] !== "[") end += 1;
6439
- const field = path25.slice(at, end);
6440
- if (field.length === 0) return null;
6441
- segments.push({ field });
6442
- expectField = false;
6443
- at = end;
6444
- if (path25[at] === ".") {
6445
- at += 1;
6446
- expectField = true;
6447
- if (at === path25.length) return null;
6448
- } else if (at < path25.length && !path25.startsWith(ITEM_SELECTOR, at)) {
6449
- return null;
6450
- }
6451
- }
6452
- return segments.length === 0 || expectField ? null : segments;
6453
- }
6454
- function readQuoted(path25, start) {
6455
- if (path25[start] !== '"') return null;
6456
- for (let end = start + 1; end < path25.length; end += 1) {
6457
- if (path25[end] === "\\") {
6458
- end += 1;
6459
- continue;
6460
- }
6461
- if (path25[end] !== '"') continue;
6462
- try {
6463
- const value = JSON.parse(path25.slice(start, end + 1));
6464
- return typeof value === "string" && value.length > 0 ? { value, end: end + 1 } : null;
6465
- } catch {
6466
- return null;
6467
- }
6468
- }
6469
- return null;
6470
- }
6471
- var ITEM_SELECTOR;
6472
- var init_chunk_SXB4UYLV = __esm({
6473
- "../../domain/schema/dist/chunk-SXB4UYLV.js"() {
6474
- init_chunk_KDABWSAA();
6475
- init_chunk_RDFZ6WP6();
6476
- init_chunk_7HSDWMYK();
6477
- ITEM_SELECTOR = `[${LIST_ITEM_KEY}==`;
6478
- }
6479
- });
6480
-
6481
- // ../../domain/schema/dist/chunk-VXC2EEEM.js
7073
+ // ../../domain/schema/dist/chunk-AAH22U23.js
6482
7074
  function buildFieldPath(segments) {
6483
7075
  return segments.join(".");
6484
7076
  }
@@ -6501,6 +7093,36 @@ function getFieldRenderer(baseType) {
6501
7093
  function getReferenceFieldRenderer(targetKind) {
6502
7094
  return targetKind === "asset" ? "AssetReferenceField" : "ReferenceField";
6503
7095
  }
7096
+ function nestedRichTextFieldsOf(field) {
7097
+ const nested = /* @__PURE__ */ new Map();
7098
+ const read = /* @__PURE__ */ new Set();
7099
+ const visit2 = (holder) => {
7100
+ for (const shape of richTextShapesOf(holder).blocks) {
7101
+ if (read.has(shape.id)) continue;
7102
+ read.add(shape.id);
7103
+ const richText = fieldsOfMembers(shape.fields).filter(
7104
+ (member) => isKind(member.type, "richText")
7105
+ );
7106
+ if (richText.length === 0) continue;
7107
+ nested.set(shape.id, new Map(richText.map((member) => [member.id, member])));
7108
+ for (const member of richText) visit2(member);
7109
+ }
7110
+ };
7111
+ visit2(field);
7112
+ return nested;
7113
+ }
7114
+ function nestedRichTextNarrowingsOf(field) {
7115
+ const narrowings = {};
7116
+ for (const [shape, fields] of nestedRichTextFieldsOf(field)) {
7117
+ const spelled = {};
7118
+ for (const [id, member] of fields) {
7119
+ const config = member.config ?? {};
7120
+ spelled[id] = richTextNarrowingOptionsOf(richTextNarrowingOf(config));
7121
+ }
7122
+ narrowings[shape] = spelled;
7123
+ }
7124
+ return narrowings;
7125
+ }
6504
7126
  function isValidFieldType2(type) {
6505
7127
  return FIELD_KINDS.includes(type);
6506
7128
  }
@@ -6522,7 +7144,7 @@ function createSchemaReader(schemaDefinition) {
6522
7144
  }
6523
7145
  function findFieldset(members, id, enclosing = []) {
6524
7146
  for (const member of members) {
6525
- const candidates = isSection(member) ? [member] : shapesOf(member).filter((shape) => !enclosing.includes(shape.id));
7147
+ const candidates = isSection(member) ? [member] : fieldsetPlacesOf(member).map((place) => place.shape).filter((shape) => !enclosing.includes(shape.id));
6526
7148
  for (const fieldset of candidates) {
6527
7149
  if (fieldset.id === id) return fieldset;
6528
7150
  const inside = findFieldset(fieldset.fields, id, [...enclosing, fieldset.id]);
@@ -6531,21 +7153,38 @@ function findFieldset(members, id, enclosing = []) {
6531
7153
  }
6532
7154
  return void 0;
6533
7155
  }
6534
- function placedFieldsets(members, enclosing = []) {
7156
+ function placedFieldsets(members, enclosing = [], prefix = "") {
6535
7157
  const placed = [];
6536
7158
  for (const member of members) {
6537
7159
  if (isSection(member)) {
6538
7160
  placed.push(
6539
7161
  { fieldset: member, placement: "section" },
6540
- ...placedFieldsets(member.fields, enclosing)
7162
+ ...placedFieldsets(member.fields, enclosing, prefix)
6541
7163
  );
6542
7164
  continue;
6543
7165
  }
6544
- for (const shape of shapesOf(member)) {
6545
- if (enclosing.includes(shape.id)) continue;
7166
+ const path25 = prefix === "" ? member.id : `${prefix}.${member.id}`;
7167
+ const of = shapePlacesOf(member).map((place) => place.shape);
7168
+ const tree = richTextShapePlacesOf(member);
7169
+ const places = [
7170
+ ...of.map((fieldset) => ({
7171
+ fieldset,
7172
+ placement: "shape",
7173
+ shapes: of.map((shape) => shape.id),
7174
+ segment: itemSegmentOf(fieldset.id)
7175
+ })),
7176
+ ...tree.map(({ shape, option }) => ({
7177
+ fieldset: shape,
7178
+ placement: option === "blocks" ? "block" : "inline",
7179
+ shapes: tree.filter((place) => place.option === option).map((place) => place.shape.id),
7180
+ segment: richTextTreeSegmentOf(shape.id)
7181
+ }))
7182
+ ];
7183
+ for (const { fieldset, placement, shapes, segment } of places) {
7184
+ if (enclosing.includes(fieldset.id)) continue;
6546
7185
  placed.push(
6547
- { fieldset: shape, placement: "shape" },
6548
- ...placedFieldsets(shape.fields, [...enclosing, shape.id])
7186
+ { fieldset, placement, path: path25, fieldType: member.type, shapes },
7187
+ ...placedFieldsets(fieldset.fields, [...enclosing, fieldset.id], `${path25}.${segment}`)
6549
7188
  );
6550
7189
  }
6551
7190
  }
@@ -6576,13 +7215,13 @@ function ownConfigOf(field) {
6576
7215
  return typeof config === "object" && config !== null && !Array.isArray(config) ? { ...config } : {};
6577
7216
  }
6578
7217
  var DEFAULT_FIELD_ICON, FIELD_RENDERER_MAP, SYSTEM_FIELD_PRESENTATION, SchemaReader;
6579
- var init_chunk_VXC2EEEM = __esm({
6580
- "../../domain/schema/dist/chunk-VXC2EEEM.js"() {
6581
- init_chunk_TVFFHTGZ();
6582
- init_chunk_SXB4UYLV();
6583
- init_chunk_3FBPIG5X();
6584
- init_chunk_RDFZ6WP6();
6585
- init_chunk_7HSDWMYK();
7218
+ var init_chunk_AAH22U23 = __esm({
7219
+ "../../domain/schema/dist/chunk-AAH22U23.js"() {
7220
+ init_chunk_RFXTOU4J();
7221
+ init_chunk_YPAAGSVJ();
7222
+ init_chunk_QTFIQNJM();
7223
+ init_chunk_UBAXWQKU();
7224
+ init_chunk_VFLLYTDW();
6586
7225
  DEFAULT_FIELD_ICON = "circle";
6587
7226
  FIELD_RENDERER_MAP = {
6588
7227
  // Text-based fields
@@ -6610,7 +7249,9 @@ var init_chunk_VXC2EEEM = __esm({
6610
7249
  // Composite
6611
7250
  tags: "TagsField",
6612
7251
  list: "ListField",
6613
- object: "ObjectField"
7252
+ object: "ObjectField",
7253
+ // Data: a JSON value, edited as its source text (REL-1811)
7254
+ json: "JsonField"
6614
7255
  };
6615
7256
  SYSTEM_FIELD_PRESENTATION = {
6616
7257
  id: { displayName: "ID", icon: "user" },
@@ -7304,11 +7945,12 @@ var init_chunk_VXC2EEEM = __esm({
7304
7945
  // ═══════════════════════════════════════════════════════════════════════════════
7305
7946
  /**
7306
7947
  * Every fieldset the schema declares, once per id, in the order the types declare them (REL-1799):
7307
- * each section among a type's fields, and each shape an object's or a list's `of` names, at any
7308
- * depth — inside sections, shapes and their fields. A fieldset
7309
- * is reused by placing it, and its id names one shape across the schema (`FieldsetValidator`),
7310
- * so the first declaration found is its fields. A shape that is a shape of a value it sits in is
7311
- * not entered again (deploy refuses it).
7948
+ * each section among a type's fields, each shape an object's or a list's `of` names, and each block
7949
+ * and inline element a rich-text field's `blocks` and `inline` name (REL-1807, REL-1813), at any
7950
+ * depth — inside sections, shapes, blocks and their fields, as deploy's validators reach them
7951
+ * (`fieldsetPlacesOf`). A fieldset is reused by placing it, and its id names one shape across the
7952
+ * schema (`FieldsetValidator`), so the first declaration found is its fields. A shape that is a
7953
+ * shape of a value it sits in is not entered again (deploy refuses it).
7312
7954
  */
7313
7955
  getFieldsets() {
7314
7956
  const found = /* @__PURE__ */ new Map();
@@ -7318,7 +7960,8 @@ var init_chunk_VXC2EEEM = __esm({
7318
7960
  displayName: definition.displayName ?? typeId,
7319
7961
  category
7320
7962
  };
7321
- const record = (fieldset, placement) => {
7963
+ const record = (placed) => {
7964
+ const { fieldset, placement } = placed;
7322
7965
  let entry = found.get(fieldset.id);
7323
7966
  if (entry === void 0) {
7324
7967
  entry = {
@@ -7327,16 +7970,28 @@ var init_chunk_VXC2EEEM = __esm({
7327
7970
  ...fieldset.description === void 0 ? {} : { description: fieldset.description },
7328
7971
  fields: this.extractFieldsFromRaw(fieldsOfMembers(fieldset.fields), ref),
7329
7972
  placements: [],
7330
- usedIn: []
7973
+ usedIn: [],
7974
+ heldBy: []
7331
7975
  };
7332
7976
  found.set(fieldset.id, entry);
7333
7977
  }
7334
7978
  if (!entry.placements.includes(placement)) entry.placements.push(placement);
7335
7979
  if (!entry.usedIn.some((used) => used.id === typeId)) entry.usedIn.push(ref);
7980
+ if (placed.placement !== "section") {
7981
+ const holder = {
7982
+ type: typeId,
7983
+ path: placed.path,
7984
+ fieldType: placed.fieldType,
7985
+ placement: placed.placement,
7986
+ shapes: [...placed.shapes]
7987
+ };
7988
+ const known = entry.heldBy.some(
7989
+ (held) => held.type === holder.type && held.path === holder.path && held.placement === holder.placement
7990
+ );
7991
+ if (!known) entry.heldBy.push(holder);
7992
+ }
7336
7993
  };
7337
- for (const { fieldset, placement } of placedFieldsets(definition.fields ?? [])) {
7338
- record(fieldset, placement);
7339
- }
7994
+ for (const placed of placedFieldsets(definition.fields ?? [])) record(placed);
7340
7995
  }
7341
7996
  return [...found.values()];
7342
7997
  }
@@ -7517,18 +8172,16 @@ var init_chunk_VXC2EEEM = __esm({
7517
8172
  * Create an ExpandedField from a raw field.
7518
8173
  */
7519
8174
  createExpandedField(field, context, pathSegments) {
7520
- const baseType = field.type;
7521
8175
  const fieldPath = buildFieldPath(pathSegments);
7522
- const icon = isKind(baseType, "reference") ? getReferenceFieldIcon(
8176
+ const icon = isKind(field.type, "reference") ? getReferenceFieldIcon(
7523
8177
  this.resolveReferenceTarget({
7524
8178
  config: { to: field.config?.to }
7525
8179
  }).kind
7526
- ) : getFieldIcon(baseType);
8180
+ ) : getFieldIcon(field.type);
7527
8181
  return {
7528
8182
  id: field.id,
7529
8183
  displayName: field.ui?.displayName ?? field.id,
7530
8184
  type: field.type,
7531
- baseType,
7532
8185
  icon,
7533
8186
  description: field.ui?.description,
7534
8187
  required: field.config?.required === true,
@@ -7792,13 +8445,13 @@ var init_chunk_VXC2EEEM = __esm({
7792
8445
  ...Object.values(this.schema.assetTypes ?? {}),
7793
8446
  ...Object.values(this.schema.settingsGroups ?? {})
7794
8447
  ];
7795
- const distinct3 = /* @__PURE__ */ new Set();
8448
+ const distinct4 = /* @__PURE__ */ new Set();
7796
8449
  for (const owner of owners) {
7797
8450
  for (const walked of walkMembers(owner.fields ?? [])) {
7798
- distinct3.add(walked.path.join("."));
8451
+ distinct4.add(walked.path.join("."));
7799
8452
  }
7800
8453
  }
7801
- return distinct3.size;
8454
+ return distinct4.size;
7802
8455
  }
7803
8456
  // ═══════════════════════════════════════════════════════════════════════════════
7804
8457
  // PRIVATE HELPERS
@@ -8024,7 +8677,7 @@ var init_chunk_VXC2EEEM = __esm({
8024
8677
  * @returns Resolved renderable field
8025
8678
  */
8026
8679
  resolveField(field, context) {
8027
- const baseType = field.type;
8680
+ const { type } = field;
8028
8681
  const pathSegments = [...context.pathSegments, field.id];
8029
8682
  const path25 = buildFieldPath(pathSegments);
8030
8683
  const displayName = field.ui?.displayName ?? field.id;
@@ -8032,11 +8685,11 @@ var init_chunk_VXC2EEEM = __esm({
8032
8685
  const placeholder = field.ui?.options?.placeholder;
8033
8686
  const widgetConfig = this.extractWidgetConfig(field);
8034
8687
  const validation = this.extractFieldValidation(field);
8035
- const referenceTarget = isKind(baseType, "reference") ? this.resolveReferenceTarget({
8688
+ const referenceTarget = isKind(type, "reference") ? this.resolveReferenceTarget({
8036
8689
  config: { to: field.config?.to }
8037
8690
  }) : null;
8038
- const renderer = referenceTarget ? getReferenceFieldRenderer(referenceTarget.kind) : getFieldRenderer(baseType);
8039
- const icon = referenceTarget ? getReferenceFieldIcon(referenceTarget.kind) : getFieldIcon(baseType);
8691
+ const renderer = referenceTarget ? getReferenceFieldRenderer(referenceTarget.kind) : getFieldRenderer(type);
8692
+ const icon = referenceTarget ? getReferenceFieldIcon(referenceTarget.kind) : getFieldIcon(type);
8040
8693
  const renderableField = {
8041
8694
  id: field.id,
8042
8695
  path: path25,
@@ -8045,8 +8698,7 @@ var init_chunk_VXC2EEEM = __esm({
8045
8698
  description,
8046
8699
  placeholder,
8047
8700
  icon,
8048
- type: field.type,
8049
- baseType,
8701
+ type,
8050
8702
  renderer,
8051
8703
  widgetConfig,
8052
8704
  required: field.config?.required === true,
@@ -8058,26 +8710,30 @@ var init_chunk_VXC2EEEM = __esm({
8058
8710
  hasRules: Array.isArray(field.rules) && field.rules.length > 0,
8059
8711
  raw: field
8060
8712
  };
8061
- if (isKind(baseType, "richText")) {
8713
+ if (isKind(type, "richText")) {
8062
8714
  const narrowing = richTextNarrowingOf(field.config ?? {});
8063
8715
  renderableField.richText = {
8064
8716
  document: "blocks",
8065
- structure: [...narrowing.structure],
8066
- marks: [...narrowing.marks],
8067
- multiline: narrowing.multiline
8717
+ ...richTextNarrowingOptionsOf(narrowing),
8718
+ // Each declared Block's rich-text fields, held to their own configs (REL-2114)
8719
+ nestedRichText: nestedRichTextNarrowingsOf(field)
8068
8720
  };
8069
8721
  }
8070
- const holdsBlock = isKind(baseType, "table") ? "table" : isKind(baseType, "code") ? "codeGroup" : null;
8722
+ const holdsBlock = isKind(type, "table") ? "table" : isKind(type, "code") ? "codeGroup" : null;
8071
8723
  if (holdsBlock !== null) {
8072
8724
  renderableField.richText = {
8073
8725
  document: holdsBlock,
8074
8726
  structure: [holdsBlock],
8075
8727
  marks: [...RICH_TEXT_MARK_TYPES],
8076
- multiline: true
8728
+ multiline: true,
8729
+ blocks: [],
8730
+ inline: [],
8731
+ links: [],
8732
+ nestedRichText: {}
8077
8733
  };
8078
8734
  }
8079
8735
  const shapes = shapesOf(field);
8080
- const expand = isKind(baseType, "object") ? context.expandObjects : isKind(baseType, "list") && context.expandListItemFields;
8736
+ const expand = isKind(type, "object") ? context.expandObjects : isKind(type, "list") && context.expandListItemFields;
8081
8737
  if (shapes.length > 0 && expand && context.depth < context.maxDepth) {
8082
8738
  const nestedContext = {
8083
8739
  ...context,
@@ -8137,11 +8793,11 @@ var init_chunk_VXC2EEEM = __esm({
8137
8793
  const plan = this.getRenderPlan(contentTypeId, options);
8138
8794
  if (!plan) return null;
8139
8795
  if (fieldPath.includes("[")) {
8140
- const keyed = parseKeyedPath(fieldPath);
8141
- if (keyed === null) return null;
8796
+ const keyed2 = parseKeyedPath(fieldPath);
8797
+ if (keyed2 === null) return null;
8142
8798
  let scope = plan.fields;
8143
8799
  let current = null;
8144
- for (const segment of keyed) {
8800
+ for (const segment of keyed2) {
8145
8801
  if ("key" in segment) {
8146
8802
  if (current === null || !isKind(current.type, "list")) return null;
8147
8803
  scope = current.nestedFields ?? current.shapes?.flatMap((shape) => shape.fields);
@@ -8217,14 +8873,14 @@ var init_chunk_VXC2EEEM = __esm({
8217
8873
  return built;
8218
8874
  }
8219
8875
  countTotalFields(fields) {
8220
- let count = 0;
8876
+ let count2 = 0;
8221
8877
  for (const field of fields) {
8222
- count += 1;
8878
+ count2 += 1;
8223
8879
  if (field.nestedFields) {
8224
- count += this.countTotalFields(field.nestedFields);
8880
+ count2 += this.countTotalFields(field.nestedFields);
8225
8881
  }
8226
8882
  }
8227
- return count;
8883
+ return count2;
8228
8884
  }
8229
8885
  calculateRenderPlanSummary(members) {
8230
8886
  const allFields = renderFieldsOf(members);
@@ -8299,7 +8955,7 @@ var init_chunk_VXC2EEEM = __esm({
8299
8955
  }
8300
8956
  });
8301
8957
 
8302
- // ../../domain/schema/dist/chunk-GG33YIFS.js
8958
+ // ../../domain/schema/dist/chunk-Q6EBYJVQ.js
8303
8959
  function flattenTypeFields(detail) {
8304
8960
  return detail?.fields ?? [];
8305
8961
  }
@@ -8307,16 +8963,16 @@ function buildTypeDetail(reader, typeId) {
8307
8963
  return reader.getAssetType(typeId) ?? reader.getContentTypeDetails(typeId);
8308
8964
  }
8309
8965
  var TABLE_SYSTEM_FIELD_KINDS;
8310
- var init_chunk_GG33YIFS = __esm({
8311
- "../../domain/schema/dist/chunk-GG33YIFS.js"() {
8966
+ var init_chunk_Q6EBYJVQ = __esm({
8967
+ "../../domain/schema/dist/chunk-Q6EBYJVQ.js"() {
8312
8968
  init_chunk_NGOOYRLJ();
8313
- init_chunk_VXC2EEEM();
8314
- init_chunk_SXB4UYLV();
8315
- init_chunk_KDABWSAA();
8969
+ init_chunk_AAH22U23();
8970
+ init_chunk_YPAAGSVJ();
8971
+ init_chunk_NKUAK5EA();
8316
8972
  init_chunk_GR7UT3XN();
8317
- init_chunk_3FBPIG5X();
8318
- init_chunk_RDFZ6WP6();
8319
- init_chunk_7HSDWMYK();
8973
+ init_chunk_QTFIQNJM();
8974
+ init_chunk_UBAXWQKU();
8975
+ init_chunk_VFLLYTDW();
8320
8976
  TABLE_SYSTEM_FIELD_KINDS = {
8321
8977
  id: "text",
8322
8978
  contentType: "choice",
@@ -8380,7 +9036,7 @@ function buildSchemaIndex(schema) {
8380
9036
  };
8381
9037
  const recordShapeFields = (list, enclosing = []) => {
8382
9038
  if (!list.field?.type) return;
8383
- for (const place of shapePlacesOf(list.field)) {
9039
+ for (const place of fieldsetPlacesOf(list.field)) {
8384
9040
  const shape = place.shape;
8385
9041
  if (enclosing.includes(shape.id)) continue;
8386
9042
  const visit2 = (members, basePath) => {
@@ -8628,7 +9284,8 @@ function declarativePath(zodPath, definition) {
8628
9284
  typeof named2.field === "string" && named2.field ? `${named2.field}_condition_${part}` : `condition_${part}`
8629
9285
  );
8630
9286
  } else {
8631
- pathParts.push(part.toString());
9287
+ const last = pathParts.pop();
9288
+ pathParts.push(`${last ?? ""}[${part}]`);
8632
9289
  }
8633
9290
  currentContext = item;
8634
9291
  }
@@ -8727,16 +9384,31 @@ function noNestedFieldChanges() {
8727
9384
  removedOptions: [],
8728
9385
  narrowedRichText: [],
8729
9386
  removedShapes: [],
8730
- renamedShapes: []
9387
+ renamedShapes: [],
9388
+ changedJsonSchemas: []
8731
9389
  };
8732
9390
  }
9391
+ function jsonSchemaOf(config) {
9392
+ return typeof config === "object" && config !== null ? config.schema : void 0;
9393
+ }
9394
+ function jsonSchemaChangedBetween(before, after) {
9395
+ const now = jsonSchemaOf(after);
9396
+ if (typeof now !== "object" || now === null) return null;
9397
+ const was = jsonSchemaOf(before);
9398
+ if (JSON.stringify(normalizeSchemaValue(was ?? null)) === JSON.stringify(normalizeSchemaValue(now))) {
9399
+ return null;
9400
+ }
9401
+ return now;
9402
+ }
8733
9403
  function richTextNarrowingLostBetween(before, after) {
8734
- const options = (config) => typeof config === "object" && config !== null ? config : {};
8735
9404
  return richTextNarrowingLost(
8736
- richTextNarrowingOf(options(before)),
8737
- richTextNarrowingOf(options(after))
9405
+ richTextNarrowingOf(narrowingOptionsOf(before)),
9406
+ richTextNarrowingOf(narrowingOptionsOf(after))
8738
9407
  );
8739
9408
  }
9409
+ function narrowingOptionsOf(config) {
9410
+ return typeof config === "object" && config !== null ? config : {};
9411
+ }
8740
9412
  function nestedConfigOf(field) {
8741
9413
  const config = field.config;
8742
9414
  return typeof config === "object" && config !== null ? config : {};
@@ -8821,14 +9493,41 @@ function nestedFieldChangeSites(before, after) {
8821
9493
  const lost = richTextNarrowingLostBetween(was, is);
8822
9494
  if (lost !== null) sites.narrowedRichText.push({ ...kept, lost });
8823
9495
  }
9496
+ if (isKind(kept.field.type, "json")) {
9497
+ const schema = jsonSchemaChangedBetween(was, is);
9498
+ if (schema !== null) sites.changedJsonSchemas.push({ ...kept, schema });
9499
+ }
8824
9500
  }
8825
9501
  for (const [key, site] of now) {
8826
9502
  if (old.has(key) || below(key)) continue;
8827
9503
  if (!existedBefore(parentKey(site.path))) continue;
8828
9504
  if (nestedConfigOf(site.field).required === true) sites.addedRequired.push(site);
8829
9505
  }
9506
+ const nowInTree = fieldsInTree(after);
9507
+ for (const [key, site] of fieldsInTree(before)) {
9508
+ if (below(key)) continue;
9509
+ const kept = nowInTree.get(key);
9510
+ if (kept === void 0 || kept.field.type !== site.field.type) continue;
9511
+ if (isKind(kept.field.type, "richText")) {
9512
+ const lost = richTextNarrowingLostBetween(site.field.config, kept.field.config);
9513
+ if (lost !== null) sites.narrowedRichText.push({ ...kept, lost });
9514
+ }
9515
+ if (isKind(kept.field.type, "json")) {
9516
+ const schema = jsonSchemaChangedBetween(site.field.config, kept.field.config);
9517
+ if (schema !== null) sites.changedJsonSchemas.push({ ...kept, schema });
9518
+ }
9519
+ }
8830
9520
  return sites;
8831
9521
  }
9522
+ function fieldsInTree(field) {
9523
+ const found = /* @__PURE__ */ new Map();
9524
+ for (const walked of walkFieldKinds(field, { intoRichText: true })) {
9525
+ if (walked.status !== "declared") continue;
9526
+ if (!walked.path.some((segment) => isRichTextTreeSegment(segment))) continue;
9527
+ found.set(walked.path.join("."), { path: [...walked.path], field: walked.field });
9528
+ }
9529
+ return found;
9530
+ }
8832
9531
  function declaredOptionValues(options) {
8833
9532
  if (!Array.isArray(options)) return [];
8834
9533
  const values = [];
@@ -8839,13 +9538,17 @@ function declaredOptionValues(options) {
8839
9538
  }
8840
9539
  return values;
8841
9540
  }
8842
- function fieldChangeSites(changes) {
9541
+ function fieldChangeSites({
9542
+ changes,
9543
+ narrowedRichTextFields
9544
+ }) {
8843
9545
  const sites = {
8844
9546
  removedFields: [...changes.removedFields],
8845
9547
  typeChanges: [],
8846
9548
  requiredFlips: [],
8847
9549
  removedOptions: [],
8848
- narrowedRichText: [],
9550
+ // Read off both whole configs by the differ, never off the changed keys alone (REL-2093)
9551
+ narrowedRichText: [...narrowedRichTextFields],
8849
9552
  nested: noNestedFieldChanges()
8850
9553
  };
8851
9554
  for (const mod of changes.modifiedFields) {
@@ -8870,12 +9573,14 @@ function fieldChangeSites(changes) {
8870
9573
  (change) => RICH_TEXT_NARROWING_CHANGE_KEYS.includes(change.field)
8871
9574
  );
8872
9575
  if (narrowing.length > 0 && !typeChange) {
8873
- const side = (which) => Object.fromEntries(
8874
- narrowing.map((change) => [change.field.slice("config.".length), change[which]])
9576
+ const declaredChange = (which) => Object.fromEntries(
9577
+ narrowing.filter((change) => RICH_TEXT_DECLARED_CHANGE_KEYS.has(change.field)).map((change) => [change.field.slice("config.".length), change[which]])
8875
9578
  );
8876
- if (richTextNarrowingLostBetween(side("before"), side("after")) !== null) {
8877
- sites.narrowedRichText.push(mod.name);
8878
- }
9579
+ const inTree = nestedFieldChangeSites(
9580
+ { id: mod.name, type: "richText", config: declaredChange("before") },
9581
+ { id: mod.name, type: "richText", config: declaredChange("after") }
9582
+ );
9583
+ sites.nested.narrowedRichText.push(...inTree.narrowedRichText);
8879
9584
  }
8880
9585
  const shapeChange = mod.changes.find((c) => c.field === FIELD_SHAPE_CHANGE_KEY);
8881
9586
  if (shapeChange && !typeChange) {
@@ -8891,6 +9596,7 @@ function fieldChangeSites(changes) {
8891
9596
  sites.nested.narrowedRichText.push(...inside.narrowedRichText);
8892
9597
  sites.nested.removedShapes.push(...inside.removedShapes);
8893
9598
  sites.nested.renamedShapes.push(...inside.renamedShapes);
9599
+ sites.nested.changedJsonSchemas.push(...inside.changedJsonSchemas);
8894
9600
  }
8895
9601
  }
8896
9602
  return sites;
@@ -8898,6 +9604,16 @@ function fieldChangeSites(changes) {
8898
9604
  function shapeHolder(id, of) {
8899
9605
  return { id, type: "list", config: { of } };
8900
9606
  }
9607
+ function narrowedRichTextFieldsOf(changes, oldFields, newFields) {
9608
+ const before = new Map(oldFields.map((field) => [field.id, field]));
9609
+ const after = new Map(newFields.map((field) => [field.id, field]));
9610
+ return changes.modifiedFields.filter((mod) => !mod.changes.some((change) => change.field === "type")).map((mod) => mod.name).filter((id) => {
9611
+ const was = before.get(id);
9612
+ const is = after.get(id);
9613
+ if (was === void 0 || is === void 0 || !isKind(is.type, "richText")) return false;
9614
+ return richTextNarrowingLostBetween(was.config, is.config) !== null;
9615
+ });
9616
+ }
8901
9617
  function addedRequiredFieldsOf(changes, newFields) {
8902
9618
  const added = new Set(changes.addedFields);
8903
9619
  return newFields.filter((field) => added.has(field.id) && nestedConfigOf(field).required === true).map((field) => field.id);
@@ -8962,7 +9678,7 @@ function classifyCompatibility(diff2) {
8962
9678
  }
8963
9679
  const classifyEntityFieldChanges = (entities, entityLabel) => {
8964
9680
  for (const entity of entities) {
8965
- const sites = fieldChangeSites(entity.changes);
9681
+ const sites = fieldChangeSites(entity);
8966
9682
  for (const field of sites.removedFields) {
8967
9683
  breakingChanges.push({
8968
9684
  category: "field_removed",
@@ -9107,6 +9823,48 @@ function sectionsOf2(members) {
9107
9823
  function isCrossVersionCheckCode(code) {
9108
9824
  return Object.hasOwn(CROSS_VERSION_CHECKS, code);
9109
9825
  }
9826
+ function shapeLocalizationConflictsOf(members) {
9827
+ const conflicts = [];
9828
+ const visit2 = (path25, placed, shapeStack) => {
9829
+ const localized = placed.filter((entry) => isFieldLocalized(entry.field));
9830
+ if (localized.length > 0) {
9831
+ const shared = placed.filter((entry) => !isFieldLocalized(entry.field));
9832
+ if (shared.length > 0) {
9833
+ conflicts.push({
9834
+ path: path25,
9835
+ at: localized[0].at,
9836
+ localizedIn: localized.map((entry) => entry.shape ?? ""),
9837
+ sharedIn: shared.map((entry) => entry.shape ?? "")
9838
+ });
9839
+ }
9840
+ return;
9841
+ }
9842
+ const byId = /* @__PURE__ */ new Map();
9843
+ const shapesHere = [];
9844
+ for (const entry of placed) {
9845
+ if (!isKind(entry.field.type, "object")) continue;
9846
+ for (const { shape, at } of shapePlacesOf(entry.field)) {
9847
+ if (shapeStack.includes(shape.id)) continue;
9848
+ shapesHere.push(shape.id);
9849
+ for (const child of fieldsOfMembers(shape.fields)) {
9850
+ const field = child;
9851
+ const group = byId.get(field.id) ?? [];
9852
+ group.push({ field, shape: shape.id, at: `${entry.at}.${at}.fields.${field.id}` });
9853
+ byId.set(field.id, group);
9854
+ }
9855
+ }
9856
+ }
9857
+ for (const [id, group] of byId) visit2(`${path25}.${id}`, group, [...shapeStack, ...shapesHere]);
9858
+ };
9859
+ for (const member of fieldsOfMembers(members)) {
9860
+ const field = member;
9861
+ visit2(field.id, [{ field, shape: null, at: field.id }], []);
9862
+ }
9863
+ return conflicts;
9864
+ }
9865
+ function quoted(ids) {
9866
+ return ids.map((id) => `'${id}'`).join(", ");
9867
+ }
9110
9868
  function listItemFieldsOf(field) {
9111
9869
  return shapesOf(field).flatMap((shape) => fieldsOfMembers(shape.fields));
9112
9870
  }
@@ -9249,29 +10007,30 @@ function notComparedWarning(part, refusals) {
9249
10007
  suggestion: "Nothing to change for it: this deploy replaces the deployed definition, and the next deploy is compared against all of it"
9250
10008
  };
9251
10009
  }
9252
- var USAGE_CONTAINERS, BaseValidator, ValidationUtils, NamingValidator, StructuralValidator, nestedLists, slugUniqueness, referenceTargetsResolve, NO_CHECKS, FIELD_CONFIG_CHECKS, FIELD_KIND_RULES, FieldConfigValidator, CrossReferenceValidator, BASIC_KINDS, NUMERIC_KINDS, DATE_KINDS, FieldsetValidator, CONTENT_TYPE_PROPS, GROUP_PROPS, QUERY_BASIC_PROPS, QUERY_OPTIONAL_PROPS, QUERY_DSL_BLOCKS, LOCALE_PROPS, ROLE_PROPS, WORKFLOW_STAGE_PROPS, VIEW_PROPS, FIELDSET_PROPS, ASSET_TYPE_PROPS, ASSET_KIND_BLOCKS, ASSET_KIND_KEY_MARKER, FILE_LOCALIZATION_MARKER, FIELD_REQUIRED_CHANGE_KEY, RICH_TEXT_NARROWING_CHANGE_KEYS, FIELD_SHAPE_CHANGE_KEY, FIELD_OPTIONS_CHANGE_KEY, SchemaDiffer, ADDED_LABEL, CROSS_VERSION_CHECKS, CROSS_VERSION_CODES, EvolutionValidator, NAVIGATION_KINDS, HierarchicalContentValidator, FieldRulesValidator, LOCALIZATION_FRIENDLY_KINDS, LocalizationValidator, LocaleConfigValidator, RECOMMENDED_SETTINGS_KINDS, DISCOURAGED_SETTINGS_KINDS, SettingsGroupsValidator, CONFIGURATION_KINDS, NOT_A_KIND_CONFIGURATION_TYPES, SingletonContentTypesValidator, FORBIDDEN_TYPES, AssetTypesValidator, COUNTED_TEXT_KINDS, WIDGET_KINDS, SUGGESTED_WIDGETS, HIGH_BENEFIT_UI_KINDS, DESCRIBED_KINDS, FieldUIValidator, DslQueryValidator, SLUG_SOURCE_KINDS, COMPLEX_SETTINGS_KINDS, SIMPLE_KINDS, LOCALIZABLE_TEXT_KINDS, DataIntegrityValidator, RolesValidator, EVOLUTION_VALIDATOR_KEY, CROSS_VERSION_CHECKS_SKIPPED, ValidationPipeline, SchemaValidator, FIELD_SECTIONS, OPERATION_TIER, DEPLOY_VALIDATION_CONFIG;
10010
+ var USAGE_CONTAINERS, BaseValidator, ValidationUtils, NamingValidator, StructuralValidator, nestedLists, slugUniqueness, referenceTargetsResolve, richTextLinkTargetsResolve, NO_CHECKS, FIELD_CONFIG_CHECKS, FIELD_KIND_RULES, FieldConfigValidator, CrossReferenceValidator, BASIC_KINDS, NUMERIC_KINDS, DATE_KINDS, FieldsetValidator, CONTENT_TYPE_PROPS, GROUP_PROPS, QUERY_BASIC_PROPS, QUERY_OPTIONAL_PROPS, QUERY_DSL_BLOCKS, LOCALE_PROPS, ROLE_PROPS, WORKFLOW_STAGE_PROPS, VIEW_PROPS, FIELDSET_PROPS, ASSET_TYPE_PROPS, ASSET_KIND_BLOCKS, ASSET_KIND_KEY_MARKER, FILE_LOCALIZATION_MARKER, FIELD_REQUIRED_CHANGE_KEY, RICH_TEXT_NARROWING_CHANGE_KEYS, RICH_TEXT_DECLARED_CHANGE_KEYS, FIELD_SHAPE_CHANGE_KEY, FIELD_OPTIONS_CHANGE_KEY, SchemaDiffer, ADDED_LABEL, CROSS_VERSION_CHECKS, CROSS_VERSION_CODES, EvolutionValidator, NAVIGATION_KINDS, HierarchicalContentValidator, FieldRulesValidator, LOCALIZATION_FRIENDLY_KINDS, LocalizationValidator, LocaleConfigValidator, RECOMMENDED_SETTINGS_KINDS, DISCOURAGED_SETTINGS_KINDS, SettingsGroupsValidator, CONFIGURATION_KINDS, NOT_A_KIND_CONFIGURATION_TYPES, SingletonContentTypesValidator, FORBIDDEN_TYPES, AssetTypesValidator, COUNTED_TEXT_KINDS, WIDGET_KINDS, SUGGESTED_WIDGETS, HIGH_BENEFIT_UI_KINDS, DESCRIBED_KINDS, FieldUIValidator, DslQueryValidator, SLUG_SOURCE_KINDS, COMPLEX_SETTINGS_KINDS, SIMPLE_KINDS, LOCALIZABLE_TEXT_KINDS, DataIntegrityValidator, RolesValidator, EVOLUTION_VALIDATOR_KEY, CROSS_VERSION_CHECKS_SKIPPED, ValidationPipeline, SchemaValidator, FIELD_SECTIONS, OPERATION_TIER, DEPLOY_VALIDATION_CONFIG;
9253
10011
  var init_dist2 = __esm({
9254
10012
  "../../domain/schema/dist/index.js"() {
9255
- init_chunk_7GULCWXY();
9256
- init_chunk_5FYSBHHZ();
9257
- init_chunk_W5P6N7TE();
9258
- init_chunk_OA32TBFV();
9259
- init_chunk_GG33YIFS();
10013
+ init_chunk_OUTLPFR2();
10014
+ init_chunk_JVCO3TTQ();
10015
+ init_chunk_MLQFI2H5();
10016
+ init_chunk_CTS2X4ES();
10017
+ init_chunk_Q6EBYJVQ();
9260
10018
  init_chunk_NGOOYRLJ();
9261
- init_chunk_VXC2EEEM();
9262
- init_chunk_TVFFHTGZ();
9263
- init_chunk_SXB4UYLV();
9264
- init_chunk_KDABWSAA();
9265
- init_chunk_VIVFUDPH();
10019
+ init_chunk_AAH22U23();
10020
+ init_chunk_RFXTOU4J();
10021
+ init_chunk_YPAAGSVJ();
10022
+ init_chunk_NKUAK5EA();
10023
+ init_chunk_AUX6CCB5();
9266
10024
  init_chunk_LK3PZTVM();
9267
10025
  init_chunk_46JQ2NQ5();
9268
10026
  init_chunk_GR7UT3XN();
9269
- init_chunk_3FBPIG5X();
9270
- init_chunk_GCR57WZV();
9271
- init_chunk_RDFZ6WP6();
10027
+ init_chunk_QTFIQNJM();
10028
+ init_chunk_MGMW4ZMJ();
10029
+ init_chunk_DERETXFC();
10030
+ init_chunk_UBAXWQKU();
9272
10031
  init_chunk_JH6U6ENN();
9273
10032
  init_chunk_IJSOONHT();
9274
- init_chunk_7HSDWMYK();
10033
+ init_chunk_VFLLYTDW();
9275
10034
  USAGE_CONTAINERS = [
9276
10035
  "contentTypes",
9277
10036
  "singletonContentTypes",
@@ -9752,11 +10511,33 @@ var init_dist2 = __esm({
9752
10511
  }
9753
10512
  }
9754
10513
  };
10514
+ richTextLinkTargetsResolve = ({ field, config, path: path25, index }, findings) => {
10515
+ const links = config.links;
10516
+ if (links === void 0 || links.to === void 0) return;
10517
+ const resolved = resolveReferenceConfigTargets({ to: links.to }, index.schema);
10518
+ for (const [position, typeId] of resolved.typeIds.entries()) {
10519
+ if (resolved.unknownTypeIds.includes(typeId)) {
10520
+ findings.error(
10521
+ "REFERENCE_TARGET_NOT_FOUND",
10522
+ `Field '${field.id}' links to non-existent type '${typeId}'`,
10523
+ `${path25}.config.links.to[${position}]`,
10524
+ "Link to an existing content type"
10525
+ );
10526
+ } else if (resolved.assetTypeIds.includes(typeId)) {
10527
+ findings.error(
10528
+ "RICH_TEXT_LINK_TO_ASSET_TYPE",
10529
+ `Field '${field.id}' links to the asset type '${typeId}'; an entry link points at a content type`,
10530
+ `${path25}.config.links.to[${position}]`,
10531
+ "Link to a content type, or hold the asset in a reference field or a declared block"
10532
+ );
10533
+ }
10534
+ }
10535
+ };
9755
10536
  NO_CHECKS = { declared: [], integrity: [] };
9756
10537
  FIELD_CONFIG_CHECKS = {
9757
10538
  shortText: NO_CHECKS,
9758
10539
  longText: NO_CHECKS,
9759
- richText: NO_CHECKS,
10540
+ richText: { declared: [], integrity: [richTextLinkTargetsResolve] },
9760
10541
  table: NO_CHECKS,
9761
10542
  code: NO_CHECKS,
9762
10543
  email: NO_CHECKS,
@@ -9772,7 +10553,9 @@ var init_dist2 = __esm({
9772
10553
  tags: NO_CHECKS,
9773
10554
  list: { declared: [nestedLists], integrity: [] },
9774
10555
  // Its shapes' fields are checked where `SchemaIndex` indexes them, as any field is
9775
- object: NO_CHECKS
10556
+ object: NO_CHECKS,
10557
+ // Its JSON Schema is the declaration's to judge, keyword by keyword (REL-1811)
10558
+ json: NO_CHECKS
9776
10559
  };
9777
10560
  FIELD_KIND_RULES = {
9778
10561
  shortText: {
@@ -9864,6 +10647,11 @@ var init_dist2 = __esm({
9864
10647
  config: configDeclarationOf("object"),
9865
10648
  configChecks: FIELD_CONFIG_CHECKS.object,
9866
10649
  valueValidation: FIELD_KIND_VALUE_RULES.object
10650
+ },
10651
+ json: {
10652
+ config: configDeclarationOf("json"),
10653
+ configChecks: FIELD_CONFIG_CHECKS.json,
10654
+ valueValidation: FIELD_KIND_VALUE_RULES.json
9867
10655
  }
9868
10656
  };
9869
10657
  FieldConfigValidator = class extends BaseValidator {
@@ -9943,7 +10731,7 @@ var init_dist2 = __esm({
9943
10731
  this.traverseFields(index, (field, path25, _context) => {
9944
10732
  const contentTypeId = this.extractContentTypeIdFromPath(path25);
9945
10733
  if (!contentTypeId) return;
9946
- for (const { shape, at } of shapePlacesOf(field)) {
10734
+ for (const { shape, at } of fieldsetPlacesOf(field)) {
9947
10735
  const itemFields = fieldsOfType(shape);
9948
10736
  const itemFieldIds = itemFields.map(({ field: itemField }) => itemField.id);
9949
10737
  for (const { field: itemField, path: itemPath } of itemFields) {
@@ -10059,7 +10847,7 @@ var init_dist2 = __esm({
10059
10847
  visitFieldset(member, `${basePath}.${member.id}`, ancestors);
10060
10848
  continue;
10061
10849
  }
10062
- for (const { shape, at } of shapePlacesOf(member)) {
10850
+ for (const { shape, at } of fieldsetPlacesOf(member)) {
10063
10851
  visitFieldset(shape, `${basePath}.${member.id}.${at}`, ancestors);
10064
10852
  }
10065
10853
  }
@@ -10301,8 +11089,16 @@ var init_dist2 = __esm({
10301
11089
  RICH_TEXT_NARROWING_CHANGE_KEYS = [
10302
11090
  "config.structure",
10303
11091
  "config.marks",
10304
- "config.multiline"
11092
+ "config.multiline",
11093
+ "config.blocks",
11094
+ "config.inline",
11095
+ // The types its entry links point at (REL-1808): fewer is a narrowing, as fewer blocks is
11096
+ "config.links"
10305
11097
  ];
11098
+ RICH_TEXT_DECLARED_CHANGE_KEYS = /* @__PURE__ */ new Set([
11099
+ "config.blocks",
11100
+ "config.inline"
11101
+ ]);
10306
11102
  FIELD_SHAPE_CHANGE_KEY = "config.of";
10307
11103
  FIELD_OPTIONS_CHANGE_KEY = "config.options";
10308
11104
  SchemaDiffer = class {
@@ -10585,6 +11381,11 @@ var init_dist2 = __esm({
10585
11381
  addedRequiredFields: addedRequiredFieldsOf(
10586
11382
  fieldChanges,
10587
11383
  this.extractAllFields(newItem)
11384
+ ),
11385
+ narrowedRichTextFields: narrowedRichTextFieldsOf(
11386
+ fieldChanges,
11387
+ this.extractAllFields(oldItem),
11388
+ this.extractAllFields(newItem)
10588
11389
  )
10589
11390
  });
10590
11391
  }
@@ -10725,6 +11526,11 @@ var init_dist2 = __esm({
10725
11526
  addedRequiredFields: addedRequiredFieldsOf(
10726
11527
  fieldChanges,
10727
11528
  this.extractAllFields(newItem)
11529
+ ),
11530
+ narrowedRichTextFields: narrowedRichTextFieldsOf(
11531
+ fieldChanges,
11532
+ this.extractAllFields(oldItem),
11533
+ this.extractAllFields(newItem)
10728
11534
  )
10729
11535
  });
10730
11536
  }
@@ -10769,6 +11575,11 @@ var init_dist2 = __esm({
10769
11575
  addedRequiredFields: addedRequiredFieldsOf(
10770
11576
  fieldChanges,
10771
11577
  newItem.fields
11578
+ ),
11579
+ narrowedRichTextFields: narrowedRichTextFieldsOf(
11580
+ fieldChanges,
11581
+ oldItem.fields,
11582
+ newItem.fields
10772
11583
  )
10773
11584
  });
10774
11585
  }
@@ -11678,9 +12489,40 @@ var init_dist2 = __esm({
11678
12489
  this.validateFieldFallback(field, path25, result, allFieldIds, context);
11679
12490
  }
11680
12491
  });
12492
+ this.validateShapeLocalization(index, result);
11681
12493
  this.addLocalizationSummary(result, localizationStats);
11682
12494
  return result;
11683
12495
  }
12496
+ /**
12497
+ * One field id localized in one shape of an object and shared in another, outside every list
12498
+ * (REL-2038 item 4): the localized path (`hero.alt`) does not name the shape there, so it would
12499
+ * carry both meanings. Refused at the localized declaration, naming the path and the shapes.
12500
+ */
12501
+ validateShapeLocalization(index, result) {
12502
+ const records = [
12503
+ ["contentTypes", index.contentTypes.entries],
12504
+ ["singletonContentTypes", index.singletonContentTypes.entries],
12505
+ ["settingsGroups", index.settingsGroups.entries],
12506
+ ["assetTypes", index.assetTypes.entries]
12507
+ ];
12508
+ for (const [container, entries] of records) {
12509
+ for (const [id, record] of entries) {
12510
+ const members = record.fields;
12511
+ if (!Array.isArray(members)) continue;
12512
+ for (const conflict of shapeLocalizationConflictsOf(members)) {
12513
+ result.errors.push(
12514
+ this.createError(
12515
+ "AMBIGUOUS_SHAPE_LOCALIZATION",
12516
+ `Field '${conflict.path}' is localized in shape ${quoted(conflict.localizedIn)} and shared in shape ${quoted(conflict.sharedIn)}: outside a list, a localized path does not name the shape its value takes, so it cannot mean both`,
12517
+ `${container}.${id}.fields.${conflict.at}`,
12518
+ "error",
12519
+ "Declare the field localized in every shape of the object, or in none, or give it a different id in one of them"
12520
+ )
12521
+ );
12522
+ }
12523
+ }
12524
+ }
12525
+ }
11684
12526
  collectAllFieldIds(index) {
11685
12527
  const fieldIds = /* @__PURE__ */ new Set();
11686
12528
  this.traverseFields(index, (field) => {
@@ -11708,8 +12550,31 @@ var init_dist2 = __esm({
11708
12550
  this.validateArrayLocalization(localization, localizationPath, result, field);
11709
12551
  } else if (localization !== void 0) {
11710
12552
  this.validateRequiredLocales(localization.requiredLocales, localizationPath, result);
12553
+ this.refuseRequiredLocalesOnSharedField(field, localizationPath, result);
11711
12554
  }
11712
12555
  }
12556
+ /**
12557
+ * `requiredLocales` on a field that is not localized (REL-2196): a shared value is one value every
12558
+ * locale shows, never a locale's own, so the publish check — an own value in each named locale —
12559
+ * could never pass, and every publish of a named locale was refused for a value no locale holds.
12560
+ * Refused by name, as a declaration that cannot mean anything.
12561
+ */
12562
+ refuseRequiredLocalesOnSharedField(field, localizationPath, result) {
12563
+ if (isFieldLocalized(field)) return;
12564
+ const declared = field.localization;
12565
+ if (typeof declared !== "object" || declared === null || Array.isArray(declared)) return;
12566
+ const required = declared.requiredLocales ?? [];
12567
+ if (required.length === 0) return;
12568
+ result.errors.push(
12569
+ this.createError(
12570
+ "REQUIRED_LOCALES_ON_SHARED_FIELD",
12571
+ `Field '${field.id}' requires its own value in ${quoted(required)} but is not localized: a shared value is one value every locale shows, so no locale holds one of its own`,
12572
+ `${localizationPath}.requiredLocales`,
12573
+ "error",
12574
+ "Enable localization on the field, or remove requiredLocales"
12575
+ )
12576
+ );
12577
+ }
11713
12578
  validateBooleanLocalization(enabled, localizationPath, result, field) {
11714
12579
  if (enabled) {
11715
12580
  result.info.push(
@@ -13487,7 +14352,7 @@ var init_dist2 = __esm({
13487
14352
  this.validateFieldTypeSpecificUI(field.ui, fieldType, path25, result, stats);
13488
14353
  }
13489
14354
  }
13490
- for (const { shape, at } of shapePlacesOf(field)) {
14355
+ for (const { shape, at } of fieldsetPlacesOf(field)) {
13491
14356
  for (const { field: itemField, path: itemPath } of fieldsOfType(shape)) {
13492
14357
  this.validateFieldUI(
13493
14358
  itemField,
@@ -13915,7 +14780,7 @@ var init_dist2 = __esm({
13915
14780
  );
13916
14781
  }
13917
14782
  const fieldsWithWidgets = Object.values(stats.widgetUsageBreakdown).reduce(
13918
- (sum, count) => sum + count,
14783
+ (sum, count2) => sum + count2,
13919
14784
  0
13920
14785
  );
13921
14786
  if (fieldsWithWidgets > 0) {
@@ -16346,14 +17211,14 @@ var init_dist2 = __esm({
16346
17211
  * was tuned against.
16347
17212
  */
16348
17213
  countTotalFields(index) {
16349
- let count = 0;
17214
+ let count2 = 0;
16350
17215
  for (const ct of index.contentTypes.values) {
16351
- count += ct.fields?.length || 0;
17216
+ count2 += ct.fields?.length || 0;
16352
17217
  }
16353
17218
  for (const sct of index.singletonContentTypes.values) {
16354
- count += sct.fields?.length || 0;
17219
+ count2 += sct.fields?.length || 0;
16355
17220
  }
16356
- return count;
17221
+ return count2;
16357
17222
  }
16358
17223
  createEmptyResult() {
16359
17224
  return {
@@ -17790,13 +18655,16 @@ function planPathLabel(path25) {
17790
18655
  const shape = path25.shape === void 0 ? "" : `.$${path25.shape}`;
17791
18656
  return `${path25.section}.${path25.type}${field}${subpath}${option}${shape}`;
17792
18657
  }
17793
- function plural(count, noun, nouns = `${noun}s`) {
17794
- return `${count} ${count === 1 ? noun : nouns}`;
18658
+ function plural(count2, noun, nouns = `${noun}s`) {
18659
+ return `${count2} ${count2 === 1 ? noun : nouns}`;
17795
18660
  }
17796
- function formatAdaptationCount(count) {
17797
- if (count.state === "uncountable") return `could not be counted (${count.reason})`;
17798
- const counted = `${plural(count.entries, "entry", "entries")}, ${plural(count.locales, "locale")}`;
17799
- return count.items === void 0 ? counted : `${counted}, ${plural(count.items, "item")}`;
18661
+ function formatAdaptationCount(count2) {
18662
+ if (count2.state === "uncountable") return `could not be counted (${count2.reason})`;
18663
+ const parts = [plural(count2.entries, "entry", "entries"), plural(count2.locales, "locale")];
18664
+ if (count2.items !== void 0) parts.push(plural(count2.items, "item"));
18665
+ if (count2.blocks !== void 0) parts.push(plural(count2.blocks, "block"));
18666
+ if (count2.links !== void 0) parts.push(plural(count2.links, "entry link"));
18667
+ return parts.join(", ");
17800
18668
  }
17801
18669
  function datasetLines(datasets) {
17802
18670
  if (datasets.length <= 1 && datasets.every((dataset) => dataset.inherited === null)) return [];
@@ -17831,7 +18699,8 @@ function residueLines(residue) {
17831
18699
  ];
17832
18700
  }
17833
18701
  function refusalLines(refusal) {
17834
- const items = refusal.items === void 0 ? "" : `, ${plural(refusal.items, "item")}`;
18702
+ const counted = refusal.code === "LIST_BELOW_MIN_ITEMS" ? "list" : "item";
18703
+ const items = refusal.items === void 0 ? "" : `, ${plural(refusal.items, counted)}`;
17835
18704
  return [
17836
18705
  ` ${chalk13.red("Refused".padEnd(16))}${chalk13.bold(planPathLabel(refusal.path))} ${chalk13.gray(`${refusal.code}, ${plural(refusal.entries, "entry", "entries")}${items}`)}`,
17837
18706
  ` ${" ".repeat(16)}${refusal.message}`
@@ -17873,14 +18742,16 @@ var init_adaptation_plan = __esm({
17873
18742
  archives: "Archives",
17874
18743
  backfills: "Backfills",
17875
18744
  renames: "Renames",
17876
- nothing: "Changes nothing"
18745
+ nothing: "Changes nothing",
18746
+ "leaves-to-author": "Left to author"
17877
18747
  };
17878
18748
  ACTION_COLOR = {
17879
18749
  deletes: chalk13.red,
17880
18750
  archives: chalk13.yellow,
17881
18751
  backfills: chalk13.cyan,
17882
18752
  renames: chalk13.yellow,
17883
- nothing: chalk13.gray
18753
+ nothing: chalk13.gray,
18754
+ "leaves-to-author": chalk13.magenta
17884
18755
  };
17885
18756
  }
17886
18757
  });
@@ -18025,12 +18896,11 @@ function collectFieldsets(elements) {
18025
18896
  visitFieldset(member, owner, false);
18026
18897
  continue;
18027
18898
  }
18028
- const parsed = typeof member.type === "string" ? parseFieldType(member.type) : { key: null };
18029
- if (parsed.key === null || CONFIG_WRITTEN_AS[parsed.key] !== "shapeBuilder") continue;
18899
+ if (typeof member.type !== "string") continue;
18030
18900
  const field = `${owner}, field '${String(member.id)}'`;
18031
- for (const { shape } of shapePlacesOf({
18901
+ for (const { shape } of fieldsetPlacesOf({
18032
18902
  id: String(member.id),
18033
- type: parsed.key,
18903
+ type: member.type,
18034
18904
  config: member.config
18035
18905
  })) {
18036
18906
  visitFieldset(shape, field, true);
@@ -18390,7 +19260,7 @@ ${prefix}: ${error2.path}`, ` ${error2.message}`);
18390
19260
  CONFIG_WRITTEN_AS = {
18391
19261
  shortText: "verbatim",
18392
19262
  longText: "verbatim",
18393
- richText: "verbatim",
19263
+ richText: "richTextShapes",
18394
19264
  table: "verbatim",
18395
19265
  code: "verbatim",
18396
19266
  email: "verbatim",
@@ -18405,7 +19275,8 @@ ${prefix}: ${error2.path}`, ` ${error2.message}`);
18405
19275
  reference: "verbatim",
18406
19276
  tags: "verbatim",
18407
19277
  list: "shapeBuilder",
18408
- object: "shapeBuilder"
19278
+ object: "shapeBuilder",
19279
+ json: "jsonSchemaConst"
18409
19280
  };
18410
19281
  TypeScriptGenerator = class {
18411
19282
  schema;
@@ -18536,7 +19407,7 @@ ${this.usageExample().map((line) => line ? ` * ${line}` : " *").join("\n")}
18536
19407
  const typeImports = [
18537
19408
  "InferContentType",
18538
19409
  "InferSingleton",
18539
- "InferAssetType",
19410
+ "InferAssetEntry",
18540
19411
  "InferSettingsGroup",
18541
19412
  "ShapeValue"
18542
19413
  ].filter((name) => used(name));
@@ -18567,9 +19438,10 @@ import type {} from '@relyio/sdk'
18567
19438
  /**
18568
19439
  * Every fieldset the schema places, once per id, as a `fieldset(…)` const — the one type per
18569
19440
  * fieldset id (REL-1799). A fieldset id names one shape across the whole schema, whether it is a
18570
- * section in one place and a list's or an object's shape in another (ADR 0012), so the file
18571
- * declares it once and every place it stands names the const, as a hand-written schema passes the
18572
- * same value. A fieldset that holds another comes after it.
19441
+ * section in one place, a list's or an object's shape in another and a rich-text field's Block or
19442
+ * Inline element in a third (ADR 0012, REL-1812), so the file declares it once and every place it
19443
+ * stands names the const, as a hand-written schema passes the same value. A fieldset that holds
19444
+ * another comes after it.
18573
19445
  */
18574
19446
  addFieldsets() {
18575
19447
  this.fieldsets = collectFieldsets([
@@ -18580,7 +19452,8 @@ import type {} from '@relyio/sdk'
18580
19452
  ]);
18581
19453
  if (this.fieldsets.size === 0) return;
18582
19454
  this.output.push(`// ============================================================================
18583
- // \u{1F9E9} Fieldsets (a section where they stand among fields, a shape as a list's or object's \`of\`)
19455
+ // \u{1F9E9} Fieldsets (a section where they stand among fields; a shape as a list's or object's \`of\`,
19456
+ // and as a rich-text field's \`blocks\` or \`inline\`)
18584
19457
  // ============================================================================
18585
19458
  `);
18586
19459
  for (const fieldset of this.fieldsets.values()) {
@@ -18784,6 +19657,12 @@ import type {} from '@relyio/sdk'
18784
19657
  if (key === "config" && parsed.key !== null && CONFIG_WRITTEN_AS[parsed.key] === "shapeBuilder" && this.isRecord(value)) {
18785
19658
  return [`config: ${this.shapesConfig(parsed.key, value, where)}`];
18786
19659
  }
19660
+ if (key === "config" && parsed.key !== null && CONFIG_WRITTEN_AS[parsed.key] === "richTextShapes" && this.isRecord(value)) {
19661
+ return [`config: ${this.richTextShapesConfig(parsed.key, value, where)}`];
19662
+ }
19663
+ if (key === "config" && parsed.key !== null && CONFIG_WRITTEN_AS[parsed.key] === "jsonSchemaConst" && this.isRecord(value)) {
19664
+ return [`config: ${this.jsonSchemaConfig(value)}`];
19665
+ }
18787
19666
  return [`${this.propertyKey(key)}: ${this.literal(value)}`];
18788
19667
  });
18789
19668
  const optionsArgument = options.length > 0 ? [`{ ${options.join(", ")} }`] : [];
@@ -18812,6 +19691,43 @@ import type {} from '@relyio/sdk'
18812
19691
  });
18813
19692
  return `{ ${entries.join(", ")} }`;
18814
19693
  }
19694
+ /**
19695
+ * A rich-text field's config, verbatim but for its declared Blocks and Inline elements (REL-1812):
19696
+ * each of `blocks` and `inline` a list of its fieldsets' consts, read through
19697
+ * `richTextShapePlacesOf`, the one reading of them. Written inline, a fieldset was a literal the
19698
+ * SDK could read no field type from — every field of a callout read as `unknown` — and a fieldset
19699
+ * declared in two fields was two definitions. `links` names types, not fieldsets, so it is written
19700
+ * as declared; an option holding no fieldset is written verbatim, for deploy to refuse.
19701
+ */
19702
+ richTextShapesConfig(kind, config, owner) {
19703
+ const places = richTextShapePlacesOf({ type: kind, config });
19704
+ const options = new Set(RICH_TEXT_SHAPE_OPTIONS);
19705
+ const entries = Object.entries(config).flatMap(([key, value]) => {
19706
+ if (value === void 0) return [];
19707
+ const shapes = places.filter((place) => place.option === key);
19708
+ if (options.has(key) && shapes.length > 0) {
19709
+ const written = shapes.map(
19710
+ ({ shape, at }) => this.generateFieldset(shape, `${owner}, ${at}`)
19711
+ );
19712
+ return [`${key}: [${written.join(", ")}]`];
19713
+ }
19714
+ return [`${this.propertyKey(key)}: ${this.literal(value)}`];
19715
+ });
19716
+ return `{ ${entries.join(", ")} }`;
19717
+ }
19718
+ /**
19719
+ * A json field's config, verbatim but for its JSON Schema, written `as const` (REL-1811): the SDK
19720
+ * infers the field's read from the schema literal, and without `as const` a `required` list and an
19721
+ * `enum` widen to `string[]`, so every property would read as optional.
19722
+ */
19723
+ jsonSchemaConfig(config) {
19724
+ const entries = Object.entries(config).flatMap(([key, value]) => {
19725
+ if (value === void 0) return [];
19726
+ if (key === "schema") return [`schema: ${this.literal(value)} as const`];
19727
+ return [`${this.propertyKey(key)}: ${this.literal(value)}`];
19728
+ });
19729
+ return `{ ${entries.join(", ")} }`;
19730
+ }
18815
19731
  /**
18816
19732
  * Add schema export
18817
19733
  */
@@ -18936,11 +19852,13 @@ import type {} from '@relyio/sdk'
18936
19852
  * `content('post')` reads. Without it a reference would read as `UnresolvedEntry`, though the
18937
19853
  * client resolves it.
18938
19854
  *
18939
- * A fieldset placed as a shape — a list's or an object's `of` — has one alias for its id,
18940
- * `<Id>Fieldset`: `ShapeValue<typeof <id>Fieldset, SchemaTypes>`, the `_type`, `_key` and fields
18941
- * an item or an object's value of it holds, which is what the client types it as (REL-1799).
18942
- * Suffixed, as its const is, because a fieldset id may be a type's id too. A fieldset that is only
18943
- * ever a section has no alias: its fields join the record it sits in, and it holds no value.
19855
+ * A fieldset placed as a shape — a list's or an object's `of`, or a rich-text field's `blocks` or
19856
+ * `inline` — has one alias for its id, `<Id>Fieldset`: `ShapeValue<typeof <id>Fieldset,
19857
+ * SchemaTypes>`, the `_type`, `_key` and fields an item, an object's value or a node of rich text
19858
+ * of it holds, which is what the client types it as (REL-1799, REL-1812). One alias wherever it
19859
+ * stands: a `link` in four lists is one `LinkFieldset`. Suffixed, as its const is, because a
19860
+ * fieldset id may be a type's id too. A fieldset that is only ever a section has no alias: its
19861
+ * fields join the record it sits in, and it holds no value.
18944
19862
  *
18945
19863
  * Every kind's alias is its id in PascalCase, as a content type's is. Ids are unique across kinds
18946
19864
  * (case-insensitively, the schema's `NAME_CONFLICT`), so two aliases collide only where the recipe
@@ -18967,15 +19885,15 @@ export type ${this.declare("SchemaTypes", "type", "the SchemaTypes export")} = t
18967
19885
  for (const [, assetType] of this.schema.assetTypes) {
18968
19886
  const varName = naming.camel(assetType.id);
18969
19887
  const typeName = this.aliasOf(assetType.id, `asset type '${assetType.id}'`);
18970
- lines.push(`export type ${typeName} = InferAssetType<typeof ${varName}, SchemaTypes>`);
19888
+ lines.push(`export type ${typeName} = InferAssetEntry<typeof ${varName}, SchemaTypes>`);
18971
19889
  if (assetType.variantIds && assetType.variantIds.length > 0) {
18972
- const union2 = assetType.variantIds.map((vid) => this.literal(vid)).join(" | ");
19890
+ const union3 = assetType.variantIds.map((vid) => this.literal(vid)).join(" | ");
18973
19891
  const variantName = this.declare(
18974
19892
  `${typeName}Variant`,
18975
19893
  "type",
18976
19894
  `asset type '${assetType.id}' (its variant ids)`
18977
19895
  );
18978
- lines.push(`export type ${variantName} = ${union2}`);
19896
+ lines.push(`export type ${variantName} = ${union3}`);
18979
19897
  }
18980
19898
  }
18981
19899
  for (const [id] of this.schema.singletons) {
@@ -19140,11 +20058,14 @@ __export(dist_exports2, {
19140
20058
  function className(id) {
19141
20059
  return escape(naming.pascal(id), (name) => PYTHON_KEYWORDS.has(name) || IMPORTED_NAMES.has(name));
19142
20060
  }
20061
+ function expandedClassName(id) {
20062
+ return escape(`Expanded${naming.pascal(id)}`, (name) => IMPORTED_NAMES.has(name));
20063
+ }
19143
20064
  function moduleName(id) {
19144
20065
  return escape(naming.snake(id), (name) => PYTHON_KEYWORDS.has(name));
19145
20066
  }
19146
20067
  function attributeName(key, taken) {
19147
- const snake = naming.snake(key);
20068
+ const snake = identifierOf(naming.snake(key));
19148
20069
  const name = escape(
19149
20070
  snake.startsWith(MODEL_NAMESPACE) ? `${snake}_` : snake,
19150
20071
  (candidate) => PYTHON_KEYWORDS.has(candidate) || SHADOWED_IN_CLASS_BODY.has(candidate) || MODEL_MEMBERS.has(candidate) || taken.has(candidate)
@@ -19152,6 +20073,13 @@ function attributeName(key, taken) {
19152
20073
  taken.add(name);
19153
20074
  return name;
19154
20075
  }
20076
+ function identifierOf(snake) {
20077
+ const word = snake.replaceAll(/[^A-Za-z0-9_]/g, "_").replace(/^_+/, "");
20078
+ return /^[A-Za-z]/.test(word) ? word : `key_${word}`;
20079
+ }
20080
+ function memberClassName(owner, key) {
20081
+ return `${owner}${naming.pascal(key).replaceAll(/[^A-Za-z0-9_]/g, "")}`;
20082
+ }
19155
20083
  function uniqueName(name, taken) {
19156
20084
  const unique = escape(name, (candidate) => taken.has(candidate));
19157
20085
  taken.add(unique);
@@ -19163,6 +20091,62 @@ function pyString(value) {
19163
20091
  function pyDocstring(text) {
19164
20092
  return `"""${text.replaceAll("\\", "\\\\").replaceAll('"', String.raw`\"`)}"""`;
19165
20093
  }
20094
+ function literal(values) {
20095
+ const members = values.map(
20096
+ (value) => typeof value === "string" ? pyString(value) : typeof value === "boolean" ? value ? "True" : "False" : String(value)
20097
+ );
20098
+ return `Literal[${members.join(", ")}]`;
20099
+ }
20100
+ function blockOf(family) {
20101
+ return union([
20102
+ family.name("paragraph"),
20103
+ family.name("heading"),
20104
+ family.name("quote"),
20105
+ family.name("list"),
20106
+ family.name("codeBlock"),
20107
+ family.name("codeGroup"),
20108
+ family.name("table"),
20109
+ family.name("divider"),
20110
+ ...family.blocks
20111
+ ]);
20112
+ }
20113
+ function inlineOf(family) {
20114
+ return union([family.name("text"), family.name("lineBreak"), ...family.inline]);
20115
+ }
20116
+ function markOf(family) {
20117
+ return union([
20118
+ family.name("link"),
20119
+ family.name("mark"),
20120
+ ...family.links ? [family.name("entryLink")] : []
20121
+ ]);
20122
+ }
20123
+ function familyNodes(links) {
20124
+ return Object.keys(RICH_TEXT_NODES).filter(
20125
+ (node) => RICH_TEXT_NODES[node].perField && (node !== "entryLink" || links)
20126
+ );
20127
+ }
20128
+ function builtInNodes() {
20129
+ return Object.keys(RICH_TEXT_NODES).filter((node) => node !== "entryLink");
20130
+ }
20131
+ function familyName(prefix, node) {
20132
+ return `${prefix}${RICH_TEXT_NODES[node].name.replace(/^RichText/, "")}`;
20133
+ }
20134
+ function nodeAttributes(node, family) {
20135
+ return Object.entries(RICH_TEXT_NODES[node].attributes).map(([key, attribute]) => ({
20136
+ key,
20137
+ required: attribute.required,
20138
+ annotation: attribute.annotation(family)
20139
+ }));
20140
+ }
20141
+ function referenceOf(field, to, scope) {
20142
+ const targets = typeof to === "string" || Array.isArray(to) && to.every((target) => typeof target === "string") ? normalizeReferenceTargets(to) : [];
20143
+ if (targets.length === 0) {
20144
+ throw new Error(
20145
+ `${field.id} names no type it references, which deploy refuses and the Python target has no type for.`
20146
+ );
20147
+ }
20148
+ return `Union[${["str", ...targets.map((target) => scope.entry(target, field))].join(", ")}]`;
20149
+ }
19166
20150
  function isRecord6(value) {
19167
20151
  return value !== null && typeof value === "object" && !Array.isArray(value);
19168
20152
  }
@@ -19170,12 +20154,183 @@ function arrayAt(field, key) {
19170
20154
  const value = field.config?.[key];
19171
20155
  return Array.isArray(value) ? value : [];
19172
20156
  }
19173
- function union(classes) {
20157
+ function union2(classes) {
19174
20158
  return `Union[${classes.join(", ")}]`;
19175
20159
  }
20160
+ function unionOf(types) {
20161
+ const distinct4 = [...new Set(types)];
20162
+ if (distinct4.includes("Any")) return "Any";
20163
+ const others = distinct4.filter((type) => type !== "None");
20164
+ const inner = others.length === 1 ? others[0] ?? "Any" : union2(others);
20165
+ if (others.length === 0) return "None";
20166
+ return distinct4.includes("None") ? `Optional[${inner}]` : inner;
20167
+ }
20168
+ function literalOf(value) {
20169
+ if (typeof value === "string") return pyString(value);
20170
+ if (typeof value === "boolean") return value ? "True" : "False";
20171
+ if (value === null) return "None";
20172
+ if (typeof value === "number" && Number.isInteger(value)) return String(value);
20173
+ return null;
20174
+ }
20175
+ function literalsOf(values) {
20176
+ const members = values.map((value) => literalOf(value));
20177
+ if (members.length === 0 || members.some((member) => member === null)) return "Any";
20178
+ const present = members.filter((member) => member !== null && member !== "None");
20179
+ if (present.length === 0) return "None";
20180
+ const literal2 = `Literal[${present.join(", ")}]`;
20181
+ return members.includes("None") ? `Optional[${literal2}]` : literal2;
20182
+ }
20183
+ function jsonSchemaClasses(schema, name, doc, naming3) {
20184
+ const classes = [];
20185
+ const annotation = jsonSchemaType(schema, name, doc, naming3, classes);
20186
+ return { annotation, classes };
20187
+ }
20188
+ function jsonSchemaType(schema, name, doc, naming3, classes) {
20189
+ if (!isRecord6(schema)) return "Any";
20190
+ if (typeof schema.$ref === "string") return naming3.ref?.(schema.$ref) ?? "Any";
20191
+ if (Object.hasOwn(schema, "const")) return literalsOf([schema.const]);
20192
+ if (Array.isArray(schema.enum)) return literalsOf(schema.enum);
20193
+ for (const combinator of ["anyOf", "oneOf"]) {
20194
+ const branches = schema[combinator];
20195
+ if (Array.isArray(branches)) {
20196
+ return unionOf(
20197
+ branches.map(
20198
+ (branch, index) => jsonSchemaType(
20199
+ branch,
20200
+ `${name}Option${index + 1}`,
20201
+ `One branch of ${name}.`,
20202
+ naming3,
20203
+ classes
20204
+ )
20205
+ )
20206
+ );
20207
+ }
20208
+ }
20209
+ const type = schema.type;
20210
+ const ofType = (each) => jsonType(each, schema, name, doc, naming3, classes);
20211
+ if (Array.isArray(type)) return unionOf(type.map((each) => ofType(each)));
20212
+ return type === void 0 ? "Any" : ofType(type);
20213
+ }
20214
+ function jsonType(type, schema, name, doc, naming3, classes) {
20215
+ switch (type) {
20216
+ case "string": {
20217
+ return "str";
20218
+ }
20219
+ case "integer": {
20220
+ return "int";
20221
+ }
20222
+ case "number": {
20223
+ return "float";
20224
+ }
20225
+ case "boolean": {
20226
+ return "bool";
20227
+ }
20228
+ case "null": {
20229
+ return "None";
20230
+ }
20231
+ case "array": {
20232
+ if (schema.items === void 0) return "List[Any]";
20233
+ const item = jsonSchemaType(
20234
+ schema.items,
20235
+ `${name}Item`,
20236
+ `One item of ${name}.`,
20237
+ naming3,
20238
+ classes
20239
+ );
20240
+ return `List[${item}]`;
20241
+ }
20242
+ case "object": {
20243
+ const properties = isRecord6(schema.properties) ? Object.entries(schema.properties) : [];
20244
+ if (properties.length > 0) return objectClass(schema, properties, name, doc, naming3, classes);
20245
+ const additional = schema.additionalProperties;
20246
+ if (!isRecord6(additional)) return "Dict[str, Any]";
20247
+ const value = jsonSchemaType(
20248
+ additional,
20249
+ `${name}Value`,
20250
+ `One value of ${name}.`,
20251
+ naming3,
20252
+ classes
20253
+ );
20254
+ return `Dict[str, ${value}]`;
20255
+ }
20256
+ default: {
20257
+ return "Any";
20258
+ }
20259
+ }
20260
+ }
20261
+ function objectClass(schema, properties, name, doc, naming3, classes) {
20262
+ const className2 = uniqueName(name, naming3.taken);
20263
+ const required = new Set(
20264
+ Array.isArray(schema.required) ? schema.required.filter((key) => typeof key === "string") : []
20265
+ );
20266
+ const attributes = [];
20267
+ const described = typeof schema.description === "string" ? schema.description : doc;
20268
+ const written = { name: className2, doc: described, attributes };
20269
+ classes.push(written);
20270
+ for (const [key, property] of properties) {
20271
+ attributes.push({
20272
+ key,
20273
+ annotation: jsonSchemaType(
20274
+ property,
20275
+ memberClassName(className2, key),
20276
+ `The \`${key}\` of ${className2}.`,
20277
+ naming3,
20278
+ classes
20279
+ ),
20280
+ required: required.has(key)
20281
+ });
20282
+ }
20283
+ return className2;
20284
+ }
19176
20285
  function pythonTypeOf(field, kind, scope) {
19177
20286
  return ROWS[kind](field, scope);
19178
20287
  }
20288
+ function readBlockClasses() {
20289
+ const schemas = define_RELYIO_READ_BLOCK_SCHEMAS_default;
20290
+ const names = Object.keys(schemas);
20291
+ const blocks = [
20292
+ "EntryMetadata",
20293
+ "SingletonMetadata",
20294
+ "SettingsGroupMetadata",
20295
+ "AssetFile",
20296
+ "AssetUrls"
20297
+ ];
20298
+ for (const block of blocks) {
20299
+ if (!names.includes(block)) {
20300
+ throw new Error(`The Content API's document declares no ${block}, which a read carries.`);
20301
+ }
20302
+ }
20303
+ const ref = (target) => {
20304
+ const name = target.startsWith(REF_PREFIX) ? target.slice(REF_PREFIX.length) : "";
20305
+ if (!names.includes(name)) {
20306
+ throw new Error(
20307
+ `A read block names ${target}, which the Content API's document does not declare.`
20308
+ );
20309
+ }
20310
+ return name;
20311
+ };
20312
+ const taken = new Set(names);
20313
+ const classes = [];
20314
+ for (const name of names) {
20315
+ taken.delete(name);
20316
+ const written = jsonSchemaClasses(
20317
+ schemas[name],
20318
+ name,
20319
+ `${name}, as the Content API's document declares it.`,
20320
+ { taken, ref }
20321
+ );
20322
+ if (written.annotation !== name) {
20323
+ throw new Error(
20324
+ `The Content API's document declares ${name} as ${written.annotation}, not an object with properties.`
20325
+ );
20326
+ }
20327
+ classes.push(...written.classes);
20328
+ }
20329
+ return classes;
20330
+ }
20331
+ function metadataOf(block) {
20332
+ return { name: "metadata", key: "_metadata", block, required: true };
20333
+ }
19179
20334
  function itemKeys(shapeId) {
19180
20335
  return [
19181
20336
  { name: "type", key: "_type", annotation: `Literal[${pyString(shapeId)}]`, required: true },
@@ -19211,6 +20366,26 @@ function elementsOf(ir, kind, warnings) {
19211
20366
  };
19212
20367
  });
19213
20368
  }
20369
+ function classOf(spec) {
20370
+ const taken = /* @__PURE__ */ new Set();
20371
+ return {
20372
+ name: spec.name,
20373
+ doc: spec.doc,
20374
+ attributes: spec.attributes.map((attribute) => ({
20375
+ name: attributeName(attribute.key, taken),
20376
+ ...attribute
20377
+ }))
20378
+ };
20379
+ }
20380
+ function declaresAnything(field) {
20381
+ const { blocks, inline } = richTextShapesOf(field);
20382
+ return blocks.length > 0 || inline.length > 0 || linkTargetsOf(field).length > 0;
20383
+ }
20384
+ function linkTargetsOf(field) {
20385
+ const to = isRecord22(field.config?.links) ? field.config.links.to : void 0;
20386
+ const declared = typeof to === "string" || Array.isArray(to) && to.every((type) => typeof type === "string");
20387
+ return declared ? richTextLinkTargetsOf({ to }) : [];
20388
+ }
19214
20389
  function optional(attribute) {
19215
20390
  return { ...attribute, required: false };
19216
20391
  }
@@ -19237,7 +20412,7 @@ function renderAttribute(attribute, baseModel) {
19237
20412
  const alias = `metadata={${pyString("alias")}: ${pyString(key)}}`;
19238
20413
  return `${name}: ${annotation} = field(${required ? "" : "default=None, "}${alias})`;
19239
20414
  }
19240
- function renderModule(doc, imports, classes, baseModel) {
20415
+ function renderModule(doc, classes, imports, baseModel) {
19241
20416
  const header = baseModel === "pydantic" ? [
19242
20417
  "from typing import Any, Dict, List, Literal, Optional, Union",
19243
20418
  "",
@@ -19249,9 +20424,16 @@ function renderModule(doc, imports, classes, baseModel) {
19249
20424
  return [
19250
20425
  pyDocstring(doc),
19251
20426
  "",
20427
+ "from __future__ import annotations",
20428
+ "",
19252
20429
  ...header,
19253
- ...imports.length > 0 ? ["", ...imports] : [],
19254
20430
  ...classes.flatMap((spec) => ["", "", renderClass(spec, baseModel)]),
20431
+ ...imports.length > 0 ? [
20432
+ "",
20433
+ "",
20434
+ "# Imported after the classes above, so modules that name each other import each other",
20435
+ ...imports.map((line) => `${line} # noqa: E402`)
20436
+ ] : [],
19255
20437
  "",
19256
20438
  "",
19257
20439
  exportList(classes.map((spec) => spec.name))
@@ -19261,19 +20443,42 @@ function exportList(names) {
19261
20443
  return `__all__ = [${names.map((name) => pyString(name)).join(", ")}]
19262
20444
  `;
19263
20445
  }
19264
- function writeElement(element, kind, shapes, options) {
20446
+ function writeElement(element, kind, catalog, options) {
19265
20447
  const model = className(element.id);
20448
+ const expanded = kind.expanded === null ? null : expandedClassName(element.id);
19266
20449
  const create = `Create${naming.pascal(element.id)}Input`;
19267
20450
  const update = `Update${naming.pascal(element.id)}Input`;
19268
- const exports = [model, ...kind.create ? [create] : [], ...kind.update ? [update] : []];
20451
+ const exports = [
20452
+ model,
20453
+ ...kind.create ? [create] : [],
20454
+ ...kind.update ? [update] : [],
20455
+ ...expanded === null ? [] : [expanded]
20456
+ ];
19269
20457
  const display = element.displayName ?? `${model} ${kind.label}`;
19270
- const module = new ModuleWriter("..fieldsets.", shapes, exports);
19271
- const fields = module.attributes(element.fields, element.id, kind.envelope);
20458
+ const module = new ModuleWriter(
20459
+ { directory: kind.directory, module: moduleName(element.id) },
20460
+ model,
20461
+ catalog,
20462
+ exports
20463
+ );
20464
+ module.reserveFamilies(element.fields, element.id);
20465
+ const envelope = module.envelope(kind.envelope);
20466
+ const fields = module.attributes(element.fields, element.id, envelope);
19272
20467
  module.declare({
19273
20468
  name: model,
19274
20469
  doc: display,
19275
- attributes: [...kind.envelope, ...fields.map((attribute) => optional(attribute))]
20470
+ attributes: [...envelope, ...fields.map((attribute) => optional(attribute))]
19276
20471
  });
20472
+ if (expanded !== null && kind.expanded !== null) {
20473
+ module.declare({
20474
+ name: expanded,
20475
+ doc: `${display}, as an include expands it: its fields beside an entry's metadata.`,
20476
+ attributes: [
20477
+ ...module.envelope(kind.expanded),
20478
+ ...fields.map((attribute) => optional(attribute))
20479
+ ]
20480
+ });
20481
+ }
19277
20482
  if (kind.create) {
19278
20483
  module.declare({ name: create, doc: `What creating a ${display} sends.`, attributes: fields });
19279
20484
  }
@@ -19287,20 +20492,61 @@ function writeElement(element, kind, shapes, options) {
19287
20492
  const doc = `${display}: the ${kind.label} \`${element.id}\`, written by \`relyio generate-types\` from the schema.`;
19288
20493
  return { content: module.render(doc, options.baseModel ?? "dataclass"), exports };
19289
20494
  }
19290
- function writeFieldset(shape, shapes, options) {
20495
+ function writeFieldset(shape, catalog, options) {
19291
20496
  const name = className(shape.id);
19292
- const module = new ModuleWriter(".", shapes, [name]);
20497
+ const module = new ModuleWriter(
20498
+ { directory: FIELDSETS.directory, module: moduleName(shape.id) },
20499
+ name,
20500
+ catalog,
20501
+ [name]
20502
+ );
20503
+ module.reserveFamilies(shape.fields, shape.at);
19293
20504
  const keys = itemKeys(shape.id);
19294
20505
  const attributes = [...keys, ...module.attributes(shape.fields, shape.at, keys)];
19295
20506
  const display = shape.displayName ?? `${name} fieldset`;
19296
20507
  module.declare({
19297
20508
  name,
19298
- doc: `${display}: one value of the fieldset \`${shape.id}\`, as a list's item or an object's value.`,
20509
+ doc: `${display}: one value of the fieldset \`${shape.id}\`, as a list's item, an object's value or a node of rich text.`,
19299
20510
  attributes: attributes.map((attribute) => optional(attribute))
19300
20511
  });
19301
20512
  const doc = `${display}: the fieldset \`${shape.id}\`, written by \`relyio generate-types\` from the schema.`;
19302
20513
  return { content: module.render(doc, options.baseModel ?? "dataclass"), exports: [name] };
19303
20514
  }
20515
+ function writeRichText(options) {
20516
+ const nodes = builtInNodes();
20517
+ const family = {
20518
+ name: (node) => RICH_TEXT_NODES[node].name,
20519
+ blocks: [],
20520
+ inline: [],
20521
+ links: false,
20522
+ entryLinkTo: ""
20523
+ };
20524
+ const classes = nodes.map((node) => {
20525
+ const taken = /* @__PURE__ */ new Set();
20526
+ return {
20527
+ name: RICH_TEXT_NODES[node].name,
20528
+ doc: RICH_TEXT_NODES[node].doc,
20529
+ attributes: nodeAttributes(node, family).map((attribute) => ({
20530
+ name: attributeName(attribute.key, taken),
20531
+ ...attribute
20532
+ }))
20533
+ };
20534
+ });
20535
+ return renderModule(
20536
+ "The rich-text tree (version 1): one class per node, written by `relyio generate-types`.",
20537
+ classes,
20538
+ [],
20539
+ options.baseModel ?? "dataclass"
20540
+ );
20541
+ }
20542
+ function writeReadBlocks(options) {
20543
+ return renderModule(
20544
+ "What a Content API read carries beside its fields, from the API's document, written by `relyio generate-types`.",
20545
+ readBlockClasses().map((spec) => classOf(spec)),
20546
+ [],
20547
+ options.baseModel ?? "dataclass"
20548
+ );
20549
+ }
19304
20550
  function shapesIn(ir, warnings) {
19305
20551
  const shapes = /* @__PURE__ */ new Map();
19306
20552
  const written = /* @__PURE__ */ new Map();
@@ -19313,7 +20559,8 @@ function shapesIn(ir, warnings) {
19313
20559
  continue;
19314
20560
  }
19315
20561
  if (typeof member.type !== "string" || typeof member.id !== "string") continue;
19316
- for (const shape of shapesOf({ id: member.id, type: member.type, config: member.config })) {
20562
+ const places = fieldsetPlacesOf({ id: member.id, type: member.type, config: member.config });
20563
+ for (const { shape } of places) {
19317
20564
  const text = JSON.stringify(shape);
19318
20565
  const seen = written.get(shape.id);
19319
20566
  if (seen !== void 0) {
@@ -19345,15 +20592,32 @@ function shapesIn(ir, warnings) {
19345
20592
  }
19346
20593
  return shapes;
19347
20594
  }
20595
+ function entriesIn(ir) {
20596
+ const entries = /* @__PURE__ */ new Map();
20597
+ for (const kind of KINDS) {
20598
+ if (!TARGET_KINDS.has(kind.key)) continue;
20599
+ const declared = ir[kind.key];
20600
+ if (!isRecord22(declared)) continue;
20601
+ for (const id of Object.keys(declared)) {
20602
+ entries.set(id, {
20603
+ directory: kind.directory,
20604
+ module: moduleName(id),
20605
+ // What an include expands it into, where that is not its own read (`Kind['expanded']`)
20606
+ name: kind.expanded === null ? className(id) : expandedClassName(id)
20607
+ });
20608
+ }
20609
+ }
20610
+ return entries;
20611
+ }
19348
20612
  async function generatePython(ir, options) {
19349
20613
  const files = [];
19350
20614
  const warnings = [];
19351
- const shapes = shapesIn(ir, warnings);
20615
+ const catalog = { shapes: shapesIn(ir, warnings), entries: entriesIn(ir) };
19352
20616
  const fieldsetIndex = [];
19353
20617
  const fieldsetNames = [];
19354
- for (const shape of shapes.values()) {
20618
+ for (const shape of catalog.shapes.values()) {
19355
20619
  const module = moduleName(shape.id);
19356
- const { content, exports } = writeFieldset(shape, shapes, options);
20620
+ const { content, exports } = writeFieldset(shape, catalog, options);
19357
20621
  files.push({ path: `${FIELDSETS.directory}/${module}.py`, content });
19358
20622
  fieldsetIndex.push(`from .${module} import ${exports.join(", ")}`);
19359
20623
  fieldsetNames.push(...exports);
@@ -19372,7 +20636,7 @@ async function generatePython(ir, options) {
19372
20636
  const names = [];
19373
20637
  for (const element of elementsOf(ir, kind, warnings)) {
19374
20638
  const module = moduleName(element.id);
19375
- const { content, exports } = writeElement(element, kind, shapes, options);
20639
+ const { content, exports } = writeElement(element, kind, catalog, options);
19376
20640
  files.push({ path: `${kind.directory}/${module}.py`, content });
19377
20641
  index.push(`from .${module} import ${exports.join(", ")}`);
19378
20642
  names.push(...exports);
@@ -19387,15 +20651,25 @@ async function generatePython(ir, options) {
19387
20651
  ].join("\n")
19388
20652
  });
19389
20653
  }
19390
- const directories = [...KINDS.map((kind) => kind.directory), FIELDSETS.directory].sort();
20654
+ files.push(
20655
+ { path: `${RICH_TEXT_MODULE}.py`, content: writeRichText(options) },
20656
+ { path: `${READ_BLOCKS_MODULE}.py`, content: writeReadBlocks(options) }
20657
+ );
20658
+ const modules = [
20659
+ ...KINDS.map((kind) => kind.directory),
20660
+ FIELDSETS.directory,
20661
+ RICH_TEXT_MODULE,
20662
+ READ_BLOCKS_MODULE
20663
+ ].sort();
19391
20664
  files.unshift({
19392
20665
  path: "__init__.py",
19393
20666
  content: [
19394
20667
  pyDocstring("The types of a RelyIO schema, written by `relyio generate-types`."),
19395
20668
  "",
19396
- `from . import ${directories.join(", ")}`,
20669
+ `from . import ${modules.join(", ")}`,
20670
+ ...options.baseModel === "pydantic" ? [RESOLVE_POSTPONED_ANNOTATIONS.trimEnd()] : [],
19397
20671
  "",
19398
- exportList(directories)
20672
+ exportList(modules)
19399
20673
  ].join("\n")
19400
20674
  });
19401
20675
  if (options.includeApiTypes) {
@@ -19489,13 +20763,15 @@ def to_json(instance: Any, indent: Optional[int] = None) -> str:
19489
20763
 
19490
20764
  ${exportList(["validate_data", "to_dict", "to_json"])}`;
19491
20765
  }
19492
- var PYTHON_KEYWORDS, SHADOWED_IN_CLASS_BODY, MODEL_MEMBERS, MODEL_NAMESPACE, IMPORTED_NAMES, escape, REFERENCE, isMulti, ROWS, METADATA, ASSET_FILE, KINDS, ModuleWriter, FIELDSETS, plugin2, index_default2;
20766
+ var define_RELYIO_READ_BLOCK_SCHEMAS_default, PYTHON_KEYWORDS, SHADOWED_IN_CLASS_BODY, MODEL_MEMBERS, MODEL_NAMESPACE, IMPORTED_NAMES, escape, always, sometimes, union, keyed, RICH_TEXT_NODES, isMulti, ROWS, READ_BLOCKS_MODULE, REF_PREFIX, ASSET_FILE, KINDS, TARGET_KINDS, RICH_TEXT_MODULE, FIELDSETS, ALIAS_SUFFIX, ModuleWriter, RESOLVE_POSTPONED_ANNOTATIONS, plugin2, index_default2;
19493
20767
  var init_dist5 = __esm({
19494
20768
  "../codegen-python/dist/index.js"() {
19495
20769
  init_dist3();
19496
20770
  init_dist2();
19497
20771
  init_dist3();
19498
20772
  init_dist2();
20773
+ init_dist2();
20774
+ define_RELYIO_READ_BLOCK_SCHEMAS_default = { EntryMetadata: { type: "object", description: "What this service reports about an entry beside its fields.", required: ["id", "type", "entryType", "slug", "status", "version", "locale", "localeHasDraft", "draftLocales", "hasDraft", "defaultLocale", "availableLocales", "createdAt", "updatedAt", "refs"], additionalProperties: false, properties: { id: { type: "string" }, type: { type: "string", description: "The content type." }, entryType: { type: "string", enum: ["asset", "content", "settings", "singleton"] }, slug: { type: ["string", "null"], description: "The slug field\u2019s value at `locale`, or null when the type declares none." }, status: { type: "string", enum: ["active", "archived", "proposed"], description: "Container lifecycle only. Publication is per locale: `localeStatus`." }, version: { type: "integer", description: "The reported locale\u2019s own version counter \u2014 not the entry\u2019s shared-field counter. `0` when that locale has no content of its own. With `id` and `locale`, it is what `X-Entity-Tag` is minted from." }, locale: { type: "string", description: "The locale the fields beside this object were composed for." }, versionCount: { type: "integer", description: "How many versions `locale` has. Sent on a single-entry read; a list leaves it out." }, versions: { type: "array", items: { type: "integer" }, description: "`locale`\u2019s version numbers, newest first, at most 25. Sent on a single-entry read; a list leaves it out." }, localeStatus: { type: "string", enum: ["archived", "draft", "published"], description: "Publication state of `locale`. Absent when that locale has no content of its own: what is visible in it is inherited through the fallback chain." }, localeHasDraft: { type: "boolean", description: "Whether `locale` has unpublished changes of its own." }, draftLocales: { type: "array", items: { type: "string" }, description: "Every locale holding unpublished content of its own, sorted. A shared-field draft is not listed: it is `hasDraft`." }, hasDraft: { type: "boolean", description: "Whether the shared (non-localized) fields have unpublished changes. Publishing any locale makes them live, so they are unpublished work in every locale." }, requestedLocale: { type: "string", description: "The locale the request named, when it named one other than the default. On `?locales=a,b,c` it is always sent: the primary, or `*` when the list names as many locales as the entry has \u2014 as on `?locale=*`." }, defaultLocale: { type: "string", description: "The schema\u2019s default locale." }, localeFallbacksApplied: { type: "object", additionalProperties: { type: "string" }, description: "Each field whose value came from another locale, mapped to that locale. Absent when none did." }, emptyRequiredFields: { type: "array", items: { type: "string" }, description: 'Each required field the entry holds no value for, which this read filled with its kind\u2019s empty value \u2014 `""` for text, the tree holding nothing (`{ _type: "richText", version: 1, children: [] }`) for rich text, `[]` for a list, `false` for a boolean \u2014 so a required field is always present. A draft may leave these empty; publishing refuses it. Each is a value path, a list item named by its key: `title`, `seo.title`, `faq[_key=="k1"].question` \u2014 the spelling a validation error\u2019s path uses, so a path still finds its item after a reorder. Absent when none was filled.' }, availableLocales: { type: "array", items: { $ref: "#/components/schemas/EntryLocale" }, description: "Every locale the entry has content in, the default first." }, localizations: { type: "object", additionalProperties: { type: "object", additionalProperties: true }, description: "The fields of every other locale asked for, keyed by locale \u2014 on `?locales=a,b` and `?locale=*`. A locale with no visible fields is left out." }, createdAt: { type: "string", format: "date-time", description: "When the entry was created." }, updatedAt: { type: "string", format: "date-time", description: "When the entry was last written." }, publishedAt: { type: "string", format: "date-time", description: "When the entry was last published. Absent until it is." }, firstPublishedAt: { type: "string", format: "date-time", description: "When the entry was first published. Absent until it is." }, refs: { type: "array", items: { $ref: "#/components/schemas/EntryRef" }, description: "The entry\u2019s outgoing references, across every locale, each once." } } }, SingletonMetadata: { type: "object", description: "What this service reports about a singleton beside its fields. Narrower than an entry\u2019s: no slug, no default locale, no available locales, no references.", required: ["id", "type", "status", "version", "locale", "localeHasDraft", "draftLocales", "hasDraft", "versionCount", "versions", "createdAt", "updatedAt"], additionalProperties: false, properties: { id: { type: "string" }, type: { type: "string", description: "The singleton type." }, status: { type: "string", enum: ["active", "archived", "proposed"], description: "Container lifecycle only. Publication is per locale: `localeStatus`." }, version: { type: "integer", description: "The reported locale\u2019s own version counter \u2014 not the entry\u2019s shared-field counter. `0` when that locale has no content of its own. With `id` and `locale`, it is what `X-Entity-Tag` is minted from." }, locale: { type: "string", description: "The locale the fields beside this object were composed for." }, versionCount: { type: "integer", description: "How many versions `locale` has." }, versions: { type: "array", items: { type: "integer" }, description: "`locale`\u2019s version numbers, newest first, at most 25." }, localeStatus: { type: "string", enum: ["archived", "draft", "published"], description: "Publication state of `locale`. Absent when that locale has no content of its own: what is visible in it is inherited through the fallback chain." }, localeHasDraft: { type: "boolean", description: "Whether `locale` has unpublished changes of its own." }, draftLocales: { type: "array", items: { type: "string" }, description: "Every locale holding unpublished content of its own, sorted. A shared-field draft is not listed: it is `hasDraft`." }, hasDraft: { type: "boolean", description: "Whether the shared (non-localized) fields have unpublished changes. Publishing any locale makes them live, so they are unpublished work in every locale." }, createdAt: { type: "string", format: "date-time", description: "When the singleton was created." }, updatedAt: { type: "string", format: "date-time", description: "When the singleton was last written." }, publishedAt: { type: "string", format: "date-time", description: "When the singleton was last published. Absent until it is." }, firstPublishedAt: { type: "string", format: "date-time", description: "When the singleton was first published. Absent until it is." }, emptyRequiredFields: { type: "array", items: { type: "string" }, description: "Each required field the singleton holds no value for, which this read filled with its kind\u2019s empty value, as a value path naming a list item by its key \u2014 as on an entry. Absent when none was." } } }, SettingsGroupMetadata: { type: "object", description: "What this service reports about a settings group beside its fields: the group, the per-locale facts and when it was last written.", required: ["id", "groupId", "version", "locale", "localeHasDraft", "draftLocales", "hasDraft", "versionCount", "versions", "updatedAt"], additionalProperties: false, properties: { id: { type: "string" }, groupId: { type: "string", description: "The settings group." }, version: { type: "integer", description: "The reported locale\u2019s own version counter \u2014 not the entry\u2019s shared-field counter. `0` when that locale has no content of its own. With `id` and `locale`, it is what `X-Entity-Tag` is minted from." }, locale: { type: "string", description: "The locale the fields beside this object were composed for." }, versionCount: { type: "integer", description: "How many versions `locale` has." }, versions: { type: "array", items: { type: "integer" }, description: "`locale`\u2019s version numbers, newest first, at most 25." }, localeStatus: { type: "string", enum: ["archived", "draft", "published"], description: "Publication state of `locale`. Absent when that locale has no content of its own: what is visible in it is inherited through the fallback chain." }, localeHasDraft: { type: "boolean", description: "Whether `locale` has unpublished changes of its own." }, draftLocales: { type: "array", items: { type: "string" }, description: "Every locale holding unpublished content of its own, sorted. A shared-field draft is not listed: it is `hasDraft`." }, hasDraft: { type: "boolean", description: "Whether the shared (non-localized) fields have unpublished changes. Publishing any locale makes them live, so they are unpublished work in every locale." }, updatedAt: { type: "string", format: "date-time", description: "When the group was last written." } } }, AssetFile: { type: "object", description: "An asset entry\u2019s `file`: the binary\u2019s lifecycle and what was read from it, at the locale the entry\u2019s fields were composed for. Sent beside the asset\u2019s fields on every read of it, with `urls` once `status` is `ready`. How storage addresses the bytes is not part of it.", required: ["status", "backend", "fileName", "visibility"], additionalProperties: false, properties: { status: { type: "string", enum: ["failed", "pending_upload", "processing", "ready", "uploading"], description: "Where the binary is: `pending_upload` before any bytes, `uploading` once presigned, `processing` once the pipeline has it, then `ready` or `failed`. Independent of the entry\u2019s publication." }, backend: { type: "string", enum: ["r2", "stream"], description: "`r2` for image and file kinds, `stream` for video." }, fileName: { type: "string" }, mimeType: { type: ["string", "null"] }, size: { type: ["integer", "null"], description: "In bytes." }, hash: { type: ["string", "null"], description: "The SHA-256 the client declared: advisory, for duplicate detection only." }, visibility: { type: "string", enum: ["private", "protected", "public"], description: "Enforced by the delivery gateway; a `protected` or `private` URL carries a token." }, extracted: { type: "object", description: "What was read from the file. Enrichment adds keys as its stages finish, so the object is open beyond the ones named here.", additionalProperties: true, properties: { width: { type: "number" }, height: { type: "number" }, format: { type: "string" }, duration: { type: "number" }, hasAlpha: { type: "boolean" }, isAnimated: { type: "boolean" }, orientation: { type: "number" }, placeholder: { type: "string" }, dominantColor: { type: "string" }, palette: { type: "array", items: { type: "string" } }, exif: { type: "object", additionalProperties: true }, video: { type: "object", additionalProperties: true, properties: { codec: { type: "string" }, bitrate: { type: "number" }, framerate: { type: "number" }, audioTracks: { type: "array", items: {} } } }, pageCount: { type: "number" }, text: { type: "string" }, safety: { type: "object", additionalProperties: true, properties: { nsfwScore: { type: "number" }, flagged: { type: "boolean" }, categories: { type: "array", items: { type: "string" } } } } } }, focalPoint: { type: "object", description: "The smart-crop anchor, each axis `0`\u2013`1`.", required: ["x", "y"], additionalProperties: false, properties: { x: { type: "number" }, y: { type: "number" } } }, enrichment: { type: "object", description: "Where metadata enrichment got to. Independent of `status`.", required: ["status"], additionalProperties: false, properties: { status: { type: "string", enum: ["complete", "failed", "partial", "pending", "running"] } } }, scan: { type: "object", description: "The malware scan of a file-kind binding.", required: ["status"], additionalProperties: false, properties: { status: { type: "string", enum: ["clean", "error", "infected", "pending", "skipped"] } } }, processing: { $ref: "#/components/schemas/AssetFileProcessing" }, playback: { type: "object", description: "Whether a video can be played yet.", additionalProperties: false, properties: { ready: { type: "boolean" } } } } }, AssetUrls: { type: "object", description: "An asset entry\u2019s `urls`, sent beside its `file` once `file.status` is `ready`: an image\u2019s `original` and `variants`, a file\u2019s `original`, a video\u2019s `playback`, `poster` and `download`. Every URL is on the delivery gateway; a protected or private asset\u2019s carries a short-lived token minted for this request.", additionalProperties: false, properties: { original: { type: "string", description: "An image or a file: its original bytes." }, variants: { type: "object", description: "An image: each delivery variant\u2019s id, mapped to its URL.", additionalProperties: { type: "string" } }, playback: { type: "string", description: "A video: its HLS/DASH manifest." }, poster: { type: "string", description: "A video: its poster image." }, download: { type: "string", description: "A video: its downloadable MP4, when enabled." } } }, EntryLocale: { type: "object", description: "One locale an entry has content in.", required: ["code", "status", "version"], additionalProperties: false, properties: { code: { type: "string" }, status: { type: "string", enum: ["archived", "draft", "published"] }, version: { type: "integer", description: "That locale\u2019s own version counter." }, publishedAt: { type: "string", format: "date-time", description: "When that locale was last published. Absent until it is." } } }, EntryRef: { type: "object", description: "One outgoing reference.", required: ["path", "entryUid"], additionalProperties: false, properties: { path: { type: "string", description: "The declaring field\u2019s path, spelt as the include path that expands it: `sections.customers` for a reference inside a page builder\u2019s blocks, `sections.media.image` for one inside an object inside a block \u2014 no shape, index or key. Two block types that declare one field id share a path; each row still names its target." }, entryUid: { type: "string", description: "The referenced entry\u2019s id." } } }, AssetFileProcessing: { type: "object", description: "Where the pipeline got to with a file, and why it failed. Present when the pipeline recorded a stage, a progress or an error.", additionalProperties: false, properties: { stage: { type: "string", description: "The stage the pipeline last recorded." }, progress: { type: "number", description: "How far the pipeline got, `0`\u2013`100`, where it reports that (video encoding)." }, error: { type: "object", description: "Why the file failed. Present exactly while `status` is `failed`.", additionalProperties: false, properties: { code: { type: "string", description: "The reason, e.g. `UNSUPPORTED_MIME`, `UPLOAD_TOO_LARGE` or `INTERNAL`." }, message: { type: "string" }, retriable: { type: "boolean", description: "`true` only for `INTERNAL`, a failure no property of the file caused." } } } } } };
19499
20775
  PYTHON_KEYWORDS = /* @__PURE__ */ new Set([
19500
20776
  "False",
19501
20777
  "None",
@@ -19559,22 +20835,205 @@ var init_dist5 = __esm({
19559
20835
  "BaseModel",
19560
20836
  "Field"
19561
20837
  ]);
19562
- escape = (name, reserved2) => {
20838
+ escape = (name, reserved) => {
19563
20839
  let candidate = name;
19564
- while (reserved2(candidate)) candidate += "_";
20840
+ while (reserved(candidate)) candidate += "_";
19565
20841
  return candidate;
19566
20842
  };
19567
- REFERENCE = "Union[str, Dict[str, Any]]";
20843
+ always = (annotation) => ({ required: true, annotation });
20844
+ sometimes = (annotation) => ({ required: false, annotation });
20845
+ union = (members) => `Union[${members.join(", ")}]`;
20846
+ keyed = { _key: always(() => "str") };
20847
+ RICH_TEXT_NODES = {
20848
+ richText: {
20849
+ name: "RichText",
20850
+ perField: true,
20851
+ doc: "A rich-text value: the root of the tree, its blocks in `children`.",
20852
+ attributes: {
20853
+ _type: always(() => literal([RICH_TEXT_TYPE])),
20854
+ version: always(() => literal([RICH_TEXT_VERSION])),
20855
+ children: always((family) => `List[${blockOf(family)}]`)
20856
+ }
20857
+ },
20858
+ paragraph: {
20859
+ name: "RichTextParagraph",
20860
+ perField: true,
20861
+ doc: "A paragraph: inline content.",
20862
+ attributes: {
20863
+ _type: always(() => literal(["paragraph"])),
20864
+ ...keyed,
20865
+ children: always((family) => `List[${inlineOf(family)}]`)
20866
+ }
20867
+ },
20868
+ heading: {
20869
+ name: "RichTextHeading",
20870
+ perField: true,
20871
+ doc: "A heading of `level` 1 to 6: inline content.",
20872
+ attributes: {
20873
+ _type: always(() => literal(["heading"])),
20874
+ ...keyed,
20875
+ level: always(() => literal(RICH_TEXT_HEADING_LEVELS)),
20876
+ children: always((family) => `List[${inlineOf(family)}]`)
20877
+ }
20878
+ },
20879
+ quote: {
20880
+ name: "RichTextQuote",
20881
+ perField: true,
20882
+ doc: "A quote: one block or more.",
20883
+ attributes: {
20884
+ _type: always(() => literal(["quote"])),
20885
+ ...keyed,
20886
+ children: always((family) => `List[${blockOf(family)}]`)
20887
+ }
20888
+ },
20889
+ list: {
20890
+ name: "RichTextList",
20891
+ perField: true,
20892
+ doc: "A list: one item or more. `start` is a numbered list's first number when it is not 1.",
20893
+ attributes: {
20894
+ _type: always(() => literal(["list"])),
20895
+ ...keyed,
20896
+ style: always(() => literal(RICH_TEXT_LIST_STYLES)),
20897
+ start: sometimes(() => "int"),
20898
+ children: always((family) => `List[${family.name("listItem")}]`)
20899
+ }
20900
+ },
20901
+ listItem: {
20902
+ name: "RichTextListItem",
20903
+ perField: true,
20904
+ doc: "A list's item: blocks, the first a paragraph. `checked` is a task list's item's.",
20905
+ attributes: {
20906
+ _type: always(() => literal(["listItem"])),
20907
+ ...keyed,
20908
+ checked: sometimes(() => "bool"),
20909
+ children: always((family) => `List[${blockOf(family)}]`)
20910
+ }
20911
+ },
20912
+ codeBlock: {
20913
+ name: "RichTextCodeBlock",
20914
+ perField: false,
20915
+ doc: "A block of code: its text exactly, the language it is written in, and as a tab its label.",
20916
+ attributes: {
20917
+ _type: always(() => literal(["codeBlock"])),
20918
+ ...keyed,
20919
+ language: sometimes(() => "str"),
20920
+ label: sometimes(() => "str"),
20921
+ filename: sometimes(() => "str"),
20922
+ lines: sometimes((family) => family.name("codeLines")),
20923
+ text: always(() => "str")
20924
+ }
20925
+ },
20926
+ codeLines: {
20927
+ name: "RichTextCodeLines",
20928
+ perField: false,
20929
+ doc: "The lines of a file a code block is, 1-based and inclusive.",
20930
+ attributes: {
20931
+ start: always(() => "int"),
20932
+ end: always(() => "int")
20933
+ }
20934
+ },
20935
+ codeGroup: {
20936
+ name: "RichTextCodeGroup",
20937
+ perField: false,
20938
+ doc: "Code with tabs: one code block or more, each a tab. A `code` field's value.",
20939
+ attributes: {
20940
+ _type: always(() => literal(["codeGroup"])),
20941
+ ...keyed,
20942
+ children: always((family) => `List[${family.name("codeBlock")}]`)
20943
+ }
20944
+ },
20945
+ table: {
20946
+ name: "RichTextTable",
20947
+ perField: true,
20948
+ doc: "A table: one row or more, the first the header where `headerRow` says so. A `table` field's value.",
20949
+ attributes: {
20950
+ _type: always(() => literal(["table"])),
20951
+ ...keyed,
20952
+ headerRow: sometimes(() => literal([true])),
20953
+ children: always((family) => `List[${family.name("tableRow")}]`)
20954
+ }
20955
+ },
20956
+ tableRow: {
20957
+ name: "RichTextTableRow",
20958
+ perField: true,
20959
+ doc: "A table's row: one cell or more.",
20960
+ attributes: {
20961
+ _type: always(() => literal(["tableRow"])),
20962
+ ...keyed,
20963
+ children: always((family) => `List[${family.name("tableCell")}]`)
20964
+ }
20965
+ },
20966
+ tableCell: {
20967
+ name: "RichTextTableCell",
20968
+ perField: true,
20969
+ doc: "A table's cell: one paragraph or more, and its alignment when one is set.",
20970
+ attributes: {
20971
+ _type: always(() => literal(["tableCell"])),
20972
+ ...keyed,
20973
+ align: sometimes(() => literal(RICH_TEXT_CELL_ALIGNMENTS)),
20974
+ children: always((family) => `List[${family.name("paragraph")}]`)
20975
+ }
20976
+ },
20977
+ divider: {
20978
+ name: "RichTextDivider",
20979
+ perField: false,
20980
+ doc: "A horizontal rule between blocks.",
20981
+ attributes: { _type: always(() => literal(["divider"])), ...keyed }
20982
+ },
20983
+ text: {
20984
+ name: "RichTextSpan",
20985
+ perField: true,
20986
+ doc: "A run of text, and the marks on all of it. A run has no key.",
20987
+ attributes: {
20988
+ _type: always(() => literal(["text"])),
20989
+ text: always(() => "str"),
20990
+ marks: sometimes((family) => `List[${markOf(family)}]`)
20991
+ }
20992
+ },
20993
+ lineBreak: {
20994
+ name: "RichTextLineBreak",
20995
+ perField: false,
20996
+ doc: "A line break inside a paragraph or a heading.",
20997
+ attributes: { _type: always(() => literal(["lineBreak"])), ...keyed }
20998
+ },
20999
+ link: {
21000
+ name: "RichTextLinkMark",
21001
+ perField: false,
21002
+ doc: "A link to a URL.",
21003
+ attributes: {
21004
+ _type: always(() => literal(["link"])),
21005
+ href: always(() => "str")
21006
+ }
21007
+ },
21008
+ mark: {
21009
+ name: "RichTextFormatMark",
21010
+ perField: false,
21011
+ doc: "Formatting, by name.",
21012
+ attributes: {
21013
+ _type: always(() => literal(RICH_TEXT_MARK_TYPES.filter((type) => type !== "link")))
21014
+ }
21015
+ },
21016
+ entryLink: {
21017
+ name: "RichTextEntryLink",
21018
+ perField: true,
21019
+ doc: "A link to another entry: a reference, and a heading on it. Only in a field that declares `links`.",
21020
+ attributes: {
21021
+ _type: always(() => literal([RICH_TEXT_ENTRY_LINK_TYPE])),
21022
+ to: always((family) => family.entryLinkTo),
21023
+ anchor: sometimes(() => "str")
21024
+ }
21025
+ }
21026
+ };
19568
21027
  isMulti = (field) => field.config?.multi === true;
19569
21028
  ROWS = {
19570
21029
  shortText: () => "str",
19571
21030
  longText: () => "str",
19572
- // The version-1 tree (ADR 0013, REL-1803): `{ _type, version, children }`, as the wire sends it
19573
- richText: () => "Dict[str, Any]",
19574
- // One of the tree's built-ins as a field's value (REL-1806): `{ _type, _key, children }`, a `Dict`
19575
- // as the tree is
19576
- table: () => "Dict[str, Any]",
19577
- code: () => "Dict[str, Any]",
21031
+ // The version-1 tree (ADR 0013, REL-1803): `{ _type, version, children }`, as the wire sends it,
21032
+ // typed node by node (REL-1812)
21033
+ richText: (field, scope) => scope.richText(field),
21034
+ // One of the tree's built-ins as a field's value (REL-1806): exactly that block's class
21035
+ table: (_field, scope) => scope.builtIn("table"),
21036
+ code: (_field, scope) => scope.builtIn("codeGroup"),
19578
21037
  email: () => "str",
19579
21038
  url: () => "str",
19580
21039
  slug: () => "str",
@@ -19589,7 +21048,10 @@ var init_dist5 = __esm({
19589
21048
  date: () => "str",
19590
21049
  dateTime: () => "str",
19591
21050
  // A single reference whose target was deleted under `onTargetDelete: 'nullify'` reads as null
19592
- reference: (field) => isMulti(field) ? `List[${REFERENCE}]` : `Optional[${REFERENCE}]`,
21051
+ reference: (field, scope) => {
21052
+ const one = referenceOf(field, field.config?.to, scope);
21053
+ return isMulti(field) ? `List[${one}]` : `Optional[${one}]`;
21054
+ },
19593
21055
  tags: () => "List[str]",
19594
21056
  // A list of a shape (`of`, REL-1796) is a list of its items, each its fieldset's class; a page
19595
21057
  // builder's (REL-1798) a list of a Union of each shape's class; one without is a list of strings,
@@ -19597,7 +21059,7 @@ var init_dist5 = __esm({
19597
21059
  list: (field, scope) => {
19598
21060
  const items = shapesOf(field).map((shape) => scope.shape(shape.id));
19599
21061
  if (items.length === 0) return "List[str]";
19600
- return items.length === 1 ? `List[${items[0]}]` : `List[${union(items)}]`;
21062
+ return items.length === 1 ? `List[${items[0]}]` : `List[${union2(items)}]`;
19601
21063
  },
19602
21064
  // An object (REL-1799) is one value of its fieldset's class, or of several a Union, told apart by
19603
21065
  // `_type`. `of` is required, so one without is a schema deploy refused: refused here too, rather
@@ -19609,25 +21071,25 @@ var init_dist5 = __esm({
19609
21071
  `${field.id} is an object with no fieldset in its 'of', which deploy refuses and the Python target has no type for.`
19610
21072
  );
19611
21073
  }
19612
- return values.length === 1 ? values[0] ?? "" : union(values);
19613
- }
19614
- };
19615
- METADATA = {
19616
- name: "metadata",
19617
- key: "_metadata",
19618
- annotation: "Dict[str, Any]",
19619
- required: true
21074
+ return values.length === 1 ? values[0] ?? "" : union2(values);
21075
+ },
21076
+ // Data nobody models (REL-1811): what its JSON Schema describes — an object with properties a
21077
+ // class of its own (REL-2141) — and `Any`, any JSON value, without one
21078
+ json: (field, scope) => scope.json(field)
19620
21079
  };
21080
+ READ_BLOCKS_MODULE = "read_blocks";
21081
+ REF_PREFIX = "#/components/schemas/";
19621
21082
  ASSET_FILE = [
19622
- { name: "file", key: "file", annotation: "Dict[str, Any]", required: false },
19623
- { name: "urls", key: "urls", annotation: "Dict[str, Any]", required: false }
21083
+ { name: "file", key: "file", block: "AssetFile", required: false },
21084
+ { name: "urls", key: "urls", block: "AssetUrls", required: false }
19624
21085
  ];
19625
21086
  KINDS = [
19626
21087
  {
19627
21088
  key: "contentTypes",
19628
21089
  directory: "content_types",
19629
21090
  label: "content type",
19630
- envelope: [METADATA],
21091
+ envelope: [metadataOf("EntryMetadata")],
21092
+ expanded: null,
19631
21093
  create: true,
19632
21094
  update: true
19633
21095
  },
@@ -19635,7 +21097,8 @@ var init_dist5 = __esm({
19635
21097
  key: "singletonContentTypes",
19636
21098
  directory: "singletons",
19637
21099
  label: "singleton",
19638
- envelope: [METADATA],
21100
+ envelope: [metadataOf("SingletonMetadata")],
21101
+ expanded: [metadataOf("EntryMetadata")],
19639
21102
  create: false,
19640
21103
  update: true
19641
21104
  },
@@ -19643,7 +21106,8 @@ var init_dist5 = __esm({
19643
21106
  key: "settingsGroups",
19644
21107
  directory: "settings_groups",
19645
21108
  label: "settings group",
19646
- envelope: [METADATA],
21109
+ envelope: [metadataOf("SettingsGroupMetadata")],
21110
+ expanded: null,
19647
21111
  create: false,
19648
21112
  update: true
19649
21113
  },
@@ -19651,27 +21115,78 @@ var init_dist5 = __esm({
19651
21115
  key: "assetTypes",
19652
21116
  directory: "asset_types",
19653
21117
  label: "asset type",
19654
- envelope: [METADATA, ...ASSET_FILE],
21118
+ envelope: [metadataOf("EntryMetadata"), ...ASSET_FILE],
21119
+ expanded: null,
19655
21120
  create: true,
19656
21121
  update: true
19657
21122
  }
19658
21123
  ];
21124
+ TARGET_KINDS = /* @__PURE__ */ new Set([
21125
+ "contentTypes",
21126
+ "singletonContentTypes",
21127
+ "assetTypes"
21128
+ ]);
21129
+ RICH_TEXT_MODULE = "rich_text";
21130
+ FIELDSETS = { directory: "fieldsets", label: "fieldset" };
21131
+ ALIAS_SUFFIX = new Map([
21132
+ ...KINDS.map((kind) => [kind.directory, naming.pascal(kind.label)]),
21133
+ [FIELDSETS.directory, naming.pascal(FIELDSETS.label)],
21134
+ [RICH_TEXT_MODULE, "Node"],
21135
+ [READ_BLOCKS_MODULE, "Block"]
21136
+ ]);
19659
21137
  ModuleWriter = class {
19660
- constructor(fieldsetPackage, shapes, names) {
19661
- this.fieldsetPackage = fieldsetPackage;
19662
- this.shapes = shapes;
21138
+ constructor(home, owner, catalog, names) {
21139
+ this.home = home;
21140
+ this.owner = owner;
21141
+ this.catalog = catalog;
19663
21142
  this.taken = new Set(names);
19664
21143
  }
19665
21144
  imports = /* @__PURE__ */ new Set();
19666
21145
  declared = [];
21146
+ families = [];
19667
21147
  taken;
19668
21148
  imported = /* @__PURE__ */ new Map();
21149
+ /** Each rich-text field's family, by field id: the name of each of its nodes. */
21150
+ reserved = /* @__PURE__ */ new Map();
21151
+ /** Each json field's value, by field id: its annotation, its classes written with the families. */
21152
+ json = /* @__PURE__ */ new Map();
21153
+ written = /* @__PURE__ */ new Set();
21154
+ /**
21155
+ * Name the family of every rich-text field among `fields` that declares Blocks, Inline elements or
21156
+ * entry links — `<Owner><Field>` and `<Owner><Field><Node>`, each unique among the module's classes
21157
+ * in declared order — and write the classes of every json field's JSON Schema, its object
21158
+ * `<Owner><Field>` (REL-2141). Before any attribute is typed, so before anything is imported. `path`
21159
+ * is the element's or shape's, which a docstring names a field by.
21160
+ */
21161
+ reserveFamilies(fields, path25) {
21162
+ for (const field of fields) {
21163
+ if (parseFieldType(field.type).key === "json" && !this.json.has(field.id)) {
21164
+ const written = jsonSchemaClasses(
21165
+ field.config?.schema,
21166
+ `${this.owner}${naming.pascal(field.id)}`,
21167
+ `What the json field \`${path25}.${field.id}\` holds.`,
21168
+ { taken: this.taken }
21169
+ );
21170
+ this.json.set(field.id, written.annotation);
21171
+ this.families.push(...written.classes.map((spec) => classOf(spec)));
21172
+ continue;
21173
+ }
21174
+ if (parseFieldType(field.type).key !== "richText" || !declaresAnything(field)) continue;
21175
+ if (this.reserved.has(field.id)) continue;
21176
+ const prefix = `${this.owner}${naming.pascal(field.id)}`;
21177
+ const names = /* @__PURE__ */ new Map();
21178
+ for (const node of familyNodes(linkTargetsOf(field).length > 0)) {
21179
+ names.set(node, uniqueName(familyName(prefix, node), this.taken));
21180
+ }
21181
+ this.reserved.set(field.id, names);
21182
+ }
21183
+ }
19669
21184
  /**
19670
21185
  * Each field as an attribute: named once, so the read model and its write inputs agree, and typed
19671
21186
  * once. `reserved` are the names a read's envelope takes.
19672
21187
  */
19673
- attributes(fields, path25, reserved2) {
19674
- const taken = new Set(reserved2.map((attribute) => attribute.name));
21188
+ attributes(fields, path25, reserved) {
21189
+ const taken = new Set(reserved.map((attribute) => attribute.name));
19675
21190
  return fields.map((field) => {
19676
21191
  const name = attributeName(field.id, taken);
19677
21192
  return {
@@ -19685,8 +21200,20 @@ var init_dist5 = __esm({
19685
21200
  declare(spec) {
19686
21201
  this.declared.push(spec);
19687
21202
  }
21203
+ /** What a read carries beside its fields, each a class of `read_blocks.py`, imported. */
21204
+ envelope(attributes) {
21205
+ return attributes.map(({ block, ...attribute }) => ({
21206
+ ...attribute,
21207
+ annotation: this.importClass({ directory: "", module: READ_BLOCKS_MODULE, name: block })
21208
+ }));
21209
+ }
19688
21210
  render(doc, baseModel) {
19689
- return renderModule(doc, [...this.imports].sort(), this.declared, baseModel);
21211
+ return renderModule(
21212
+ doc,
21213
+ [...this.declared, ...this.families],
21214
+ [...this.imports].sort(),
21215
+ baseModel
21216
+ );
19690
21217
  }
19691
21218
  /**
19692
21219
  * A field's annotation, by what the field-kind declaration's parser reads its `type` as. A type it
@@ -19700,29 +21227,118 @@ var init_dist5 = __esm({
19700
21227
  `${path25}.${field.id} has type '${field.type}', which the Python target has no type for. Field types: ${FIELD_KINDS.join(", ")}.`
19701
21228
  );
19702
21229
  }
19703
- return pythonTypeOf(field, declared.key, this.scope);
19704
- }
19705
- scope = {
19706
- shape: (shapeId) => {
19707
- const known = this.imported.get(shapeId);
19708
- if (known !== void 0) return known;
19709
- if (!this.shapes.has(shapeId)) {
19710
- throw new Error(
19711
- `fieldset '${shapeId}' is named as a shape, but no fieldset class declares it`
19712
- );
21230
+ return pythonTypeOf(field, declared.key, this.scopeAt(path25));
21231
+ }
21232
+ /** Import `home`'s class into this module, once, and return the name the module knows it by. */
21233
+ importClass(home) {
21234
+ if (home.directory === this.home.directory && home.module === this.home.module) return home.name;
21235
+ const identity = `${home.directory}/${home.module}.${home.name}`;
21236
+ const known = this.imported.get(identity);
21237
+ if (known !== void 0) return known;
21238
+ const local = this.taken.has(home.name) ? uniqueName(
21239
+ `${home.name}${ALIAS_SUFFIX.get(home.directory === "" ? home.module : home.directory) ?? "Class"}`,
21240
+ this.taken
21241
+ ) : home.name;
21242
+ this.taken.add(local);
21243
+ const from = home.directory === this.home.directory ? `.${home.module}` : home.directory === "" ? `..${home.module}` : `..${home.directory}.${home.module}`;
21244
+ this.imports.add(`from ${from} import ${home.name}${local === home.name ? "" : ` as ${local}`}`);
21245
+ this.imported.set(identity, local);
21246
+ return local;
21247
+ }
21248
+ scopeAt(path25) {
21249
+ return {
21250
+ shape: (shapeId) => {
21251
+ if (!this.catalog.shapes.has(shapeId)) {
21252
+ throw new Error(
21253
+ `fieldset '${shapeId}' is named as a shape, but no fieldset class declares it`
21254
+ );
21255
+ }
21256
+ return this.importClass({
21257
+ directory: FIELDSETS.directory,
21258
+ module: moduleName(shapeId),
21259
+ name: className(shapeId)
21260
+ });
21261
+ },
21262
+ entry: (typeId, field) => {
21263
+ const home = this.catalog.entries.get(typeId);
21264
+ if (home === void 0) {
21265
+ throw new Error(
21266
+ `${path25}.${field.id} references '${typeId}', which the schema declares no content type, singleton or asset type for.`
21267
+ );
21268
+ }
21269
+ return this.importClass(home);
21270
+ },
21271
+ richText: (field) => this.richTextOf(field, path25),
21272
+ builtIn: (node) => this.builtIn(node),
21273
+ json: (field) => {
21274
+ const annotation = this.json.get(field.id);
21275
+ if (annotation === void 0) {
21276
+ throw new Error(`${path25}.${field.id} is a json field whose classes were never written`);
21277
+ }
21278
+ return annotation;
19713
21279
  }
19714
- const name = className(shapeId);
19715
- const local = this.taken.has(name) ? uniqueName(`${name}Fieldset`, this.taken) : name;
19716
- this.taken.add(local);
19717
- this.imports.add(
19718
- `from ${this.fieldsetPackage}${moduleName(shapeId)} import ${name}${local === name ? "" : ` as ${local}`}`
19719
- );
19720
- this.imported.set(shapeId, local);
19721
- return local;
21280
+ };
21281
+ }
21282
+ builtIn(node) {
21283
+ return this.importClass({
21284
+ directory: "",
21285
+ module: RICH_TEXT_MODULE,
21286
+ name: RICH_TEXT_NODES[node].name
21287
+ });
21288
+ }
21289
+ /**
21290
+ * A rich-text field's class: the built-in root for a field that declares nothing, and otherwise
21291
+ * its family's root, whose classes this writes the first time the field is typed — its read model
21292
+ * and its write inputs share them.
21293
+ */
21294
+ richTextOf(field, path25) {
21295
+ const names = this.reserved.get(field.id);
21296
+ if (names === void 0) return this.builtIn("richText");
21297
+ const root = names.get("richText");
21298
+ if (this.written.has(field.id)) return root;
21299
+ this.written.add(field.id);
21300
+ const scope = this.scopeAt(path25);
21301
+ const { blocks, inline } = richTextShapesOf(field);
21302
+ const targets = linkTargetsOf(field);
21303
+ const family = {
21304
+ name: (node) => names.get(node) ?? this.builtIn(node),
21305
+ blocks: blocks.map((shape) => scope.shape(shape.id)),
21306
+ inline: inline.map((shape) => scope.shape(shape.id)),
21307
+ links: targets.length > 0,
21308
+ entryLinkTo: targets.length > 0 ? referenceOf(field, targets, scope) : ""
21309
+ };
21310
+ for (const [node, name] of names) {
21311
+ const taken = /* @__PURE__ */ new Set();
21312
+ this.families.push({
21313
+ name,
21314
+ doc: `${RICH_TEXT_NODES[node].doc} Of the rich-text field \`${path25}.${field.id}\`.`,
21315
+ attributes: nodeAttributes(node, family).map((attribute) => ({
21316
+ name: attributeName(attribute.key, taken),
21317
+ ...attribute
21318
+ }))
21319
+ });
19722
21320
  }
19723
- };
21321
+ return root;
21322
+ }
19724
21323
  };
19725
- FIELDSETS = { directory: "fieldsets", label: "fieldset" };
21324
+ RESOLVE_POSTPONED_ANNOTATIONS = `
21325
+
21326
+ def _resolve_postponed_annotations() -> None:
21327
+ """Resolve every model's annotations, now that every module of the package is imported."""
21328
+ import sys
21329
+
21330
+ for name, module in list(sys.modules.items()):
21331
+ if module is None or not (name == __name__ or name.startswith(__name__ + ".")):
21332
+ continue
21333
+ for value in list(vars(module).values()):
21334
+ if isinstance(value, type) and value.__module__ == name:
21335
+ rebuild = getattr(value, "model_rebuild", None) or getattr(value, "update_forward_refs", None)
21336
+ if callable(rebuild):
21337
+ rebuild()
21338
+
21339
+
21340
+ _resolve_postponed_annotations()
21341
+ `;
19726
21342
  plugin2 = {
19727
21343
  language: "python",
19728
21344
  async generate(ir, options) {
@@ -19978,20 +21594,6 @@ var init_schema_validation = __esm({
19978
21594
  }
19979
21595
  });
19980
21596
 
19981
- // ../../platform/api-contract/dist/chunk-EHKJTVAS.js
19982
- var HEADER_ROUTING_PROJECT;
19983
- var init_chunk_EHKJTVAS = __esm({
19984
- "../../platform/api-contract/dist/chunk-EHKJTVAS.js"() {
19985
- HEADER_ROUTING_PROJECT = {
19986
- name: "X-RelyIO-Project",
19987
- in: "request",
19988
- // Required on every route that declares it: a route declares it exactly when its path names no
19989
- // project and its subject is one.
19990
- required: true
19991
- };
19992
- }
19993
- });
19994
-
19995
21597
  // ../../platform/api-contract/dist/chunk-46347LG2.js
19996
21598
  function defineRoute(route) {
19997
21599
  if (route.kind === "raw" && route.standardShape === void 0) {
@@ -20050,15 +21652,38 @@ var init_chunk_OWKTFXBY = __esm({
20050
21652
  }
20051
21653
  });
20052
21654
 
20053
- // ../../platform/api-contract/dist/chunk-GWMWA3NP.js
20054
- var init_chunk_GWMWA3NP = __esm({
20055
- "../../platform/api-contract/dist/chunk-GWMWA3NP.js"() {
21655
+ // ../../platform/api-contract/dist/chunk-2LUGULHW.js
21656
+ var init_chunk_2LUGULHW = __esm({
21657
+ "../../platform/api-contract/dist/chunk-2LUGULHW.js"() {
20056
21658
  init_chunk_OWKTFXBY();
20057
- init_chunk_PPSLKRFU();
21659
+ init_chunk_WE6ZXIJM();
20058
21660
  init_chunk_46347LG2();
20059
21661
  }
20060
21662
  });
20061
21663
 
21664
+ // ../../platform/api-contract/dist/chunk-FC62S3SW.js
21665
+ var HEADER_ROUTING_PROJECT, HEADER_ROUTING_ORGANIZATION;
21666
+ var init_chunk_FC62S3SW = __esm({
21667
+ "../../platform/api-contract/dist/chunk-FC62S3SW.js"() {
21668
+ HEADER_ROUTING_PROJECT = {
21669
+ name: "X-RelyIO-Project",
21670
+ in: "request",
21671
+ // Required on every route that declares it: a route declares it exactly when its path names no
21672
+ // project and its subject is one.
21673
+ required: true
21674
+ };
21675
+ HEADER_ROUTING_ORGANIZATION = {
21676
+ name: "X-RelyIO-Organization",
21677
+ in: "request",
21678
+ required: false
21679
+ };
21680
+ [
21681
+ { ...HEADER_ROUTING_PROJECT, required: false },
21682
+ HEADER_ROUTING_ORGANIZATION
21683
+ ];
21684
+ }
21685
+ });
21686
+
20062
21687
  // ../../platform/api-contract/dist/chunk-W2KXTDDN.js
20063
21688
  var init_chunk_W2KXTDDN = __esm({
20064
21689
  "../../platform/api-contract/dist/chunk-W2KXTDDN.js"() {
@@ -20069,11 +21694,11 @@ var init_chunk_W2KXTDDN = __esm({
20069
21694
  var HEADER_RETRY_AFTER, MODEL_SETTING_RATES;
20070
21695
  var init_dist6 = __esm({
20071
21696
  "../../platform/api-contract/dist/index.js"() {
20072
- init_chunk_EHKJTVAS();
20073
- init_chunk_GWMWA3NP();
21697
+ init_chunk_2LUGULHW();
21698
+ init_chunk_FC62S3SW();
20074
21699
  init_chunk_OWKTFXBY();
20075
21700
  init_chunk_W2KXTDDN();
20076
- init_chunk_PPSLKRFU();
21701
+ init_chunk_WE6ZXIJM();
20077
21702
  init_chunk_46347LG2();
20078
21703
  HEADER_RETRY_AFTER = { name: "Retry-After" };
20079
21704
  defineRoute({
@@ -20594,8 +22219,8 @@ function similarity(a, b) {
20594
22219
  if (ta.size === 0 && tb.size === 0) return 0;
20595
22220
  let shared = 0;
20596
22221
  for (const t of ta) if (tb.has(t)) shared++;
20597
- const union2 = (/* @__PURE__ */ new Set([...ta, ...tb])).size;
20598
- return union2 === 0 ? 0 : shared / union2;
22222
+ const union3 = (/* @__PURE__ */ new Set([...ta, ...tb])).size;
22223
+ return union3 === 0 ? 0 : shared / union3;
20599
22224
  }
20600
22225
  function countAddRemove(lines) {
20601
22226
  let added = 0;
@@ -20997,7 +22622,7 @@ var init_agent_loop = __esm({
20997
22622
  yield { type: "error", error: `Reached maximum turn limit (${MAX_TURNS})` };
20998
22623
  }
20999
22624
  /**
21000
- * Persist the assistant turn into message history in AI SDK v6 ModelMessage shape.
22625
+ * Persist the assistant turn into message history in the AI SDK ModelMessage shape.
21001
22626
  *
21002
22627
  * Structure preserved:
21003
22628
  * - assistant: [text?, ...all tool-call parts in original order]
@@ -23672,7 +25297,7 @@ var init_banner = __esm({
23672
25297
  init_dev_cockpit_context();
23673
25298
  init_dev_cockpit_format();
23674
25299
  init_startup_info();
23675
- CLI_VERSION2 = "0.8.0" ;
25300
+ CLI_VERSION2 = "0.9.0" ;
23676
25301
  BRAND_GLYPH = "\u25B6";
23677
25302
  BRAND_NAME = "RelyIO";
23678
25303
  LABEL_WIDTH = 9;
@@ -23863,22 +25488,22 @@ var init_markdown = __esm({
23863
25488
  init_syntax_highlight();
23864
25489
  }
23865
25490
  });
23866
- function clusterHeading(toolName, count) {
25491
+ function clusterHeading(toolName, count2) {
23867
25492
  switch (toolName) {
23868
25493
  case "readFile": {
23869
- return count === 1 ? "Read file" : `Read ${count} files`;
25494
+ return count2 === 1 ? "Read file" : `Read ${count2} files`;
23870
25495
  }
23871
25496
  case "writeFile": {
23872
- return count === 1 ? "Write file" : `Write ${count} files`;
25497
+ return count2 === 1 ? "Write file" : `Write ${count2} files`;
23873
25498
  }
23874
25499
  case "applyPatch": {
23875
- return count === 1 ? "Patch file" : `Patch ${count} files`;
25500
+ return count2 === 1 ? "Patch file" : `Patch ${count2} files`;
23876
25501
  }
23877
25502
  case "listFiles": {
23878
- return count === 1 ? "List directory" : `List ${count} directories`;
25503
+ return count2 === 1 ? "List directory" : `List ${count2} directories`;
23879
25504
  }
23880
25505
  case "runValidation": {
23881
- return count === 1 ? "Validate schema" : `Validate ${count} times`;
25506
+ return count2 === 1 ? "Validate schema" : `Validate ${count2} times`;
23882
25507
  }
23883
25508
  case "getSchemaReference": {
23884
25509
  return "Schema reference";
@@ -23887,37 +25512,37 @@ function clusterHeading(toolName, count) {
23887
25512
  return "Search docs";
23888
25513
  }
23889
25514
  case "explainContentType": {
23890
- return count === 1 ? "Explain content type" : `Explain ${count} content types`;
25515
+ return count2 === 1 ? "Explain content type" : `Explain ${count2} content types`;
23891
25516
  }
23892
25517
  case "explainFieldset": {
23893
- return count === 1 ? "Explain fieldset" : `Explain ${count} fieldsets`;
25518
+ return count2 === 1 ? "Explain fieldset" : `Explain ${count2} fieldsets`;
23894
25519
  }
23895
25520
  case "searchEntries": {
23896
- return count === 1 ? "Search entries" : `Search entries \xD7 ${count}`;
25521
+ return count2 === 1 ? "Search entries" : `Search entries \xD7 ${count2}`;
23897
25522
  }
23898
25523
  case "listDatasets": {
23899
25524
  return "List datasets";
23900
25525
  }
23901
25526
  case "getDataset": {
23902
- return count === 1 ? "Get dataset" : `Get ${count} datasets`;
25527
+ return count2 === 1 ? "Get dataset" : `Get ${count2} datasets`;
23903
25528
  }
23904
25529
  case "getSchema": {
23905
- return count === 1 ? "Get schema" : `Get schema \xD7 ${count}`;
25530
+ return count2 === 1 ? "Get schema" : `Get schema \xD7 ${count2}`;
23906
25531
  }
23907
25532
  case "proposeCreateEntries": {
23908
- return count === 1 ? "Propose new entries" : `Propose new entries \xD7 ${count}`;
25533
+ return count2 === 1 ? "Propose new entries" : `Propose new entries \xD7 ${count2}`;
23909
25534
  }
23910
25535
  case "proposeEntryUpdate": {
23911
- return count === 1 ? "Propose entry update" : `Propose ${count} entry updates`;
25536
+ return count2 === 1 ? "Propose entry update" : `Propose ${count2} entry updates`;
23912
25537
  }
23913
25538
  case "proposeDeleteEntry": {
23914
- return count === 1 ? "Propose entry delete" : `Propose ${count} entry deletes`;
25539
+ return count2 === 1 ? "Propose entry delete" : `Propose ${count2} entry deletes`;
23915
25540
  }
23916
25541
  case "proposePublishEntry": {
23917
- return count === 1 ? "Propose publish" : `Propose ${count} publishes`;
25542
+ return count2 === 1 ? "Propose publish" : `Propose ${count2} publishes`;
23918
25543
  }
23919
25544
  case "proposeUnpublishEntry": {
23920
- return count === 1 ? "Propose unpublish" : `Propose ${count} unpublishes`;
25545
+ return count2 === 1 ? "Propose unpublish" : `Propose ${count2} unpublishes`;
23921
25546
  }
23922
25547
  case "suggestAgent": {
23923
25548
  return "Suggest agent";
@@ -24371,10 +25996,10 @@ function StatusBar() {
24371
25996
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: sessionName || "new session" })
24372
25997
  ] });
24373
25998
  }
24374
- function formatTokens(count) {
24375
- if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
24376
- if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
24377
- return String(count);
25999
+ function formatTokens(count2) {
26000
+ if (count2 >= 1e6) return `${(count2 / 1e6).toFixed(1)}M`;
26001
+ if (count2 >= 1e3) return `${(count2 / 1e3).toFixed(1)}k`;
26002
+ return String(count2);
24378
26003
  }
24379
26004
  function formatModelLabel(preferredModelId, resolvedModelId) {
24380
26005
  const preferredShort = shortModelName(preferredModelId);
@@ -26224,8 +27849,8 @@ function BranchPicker() {
26224
27849
  }
26225
27850
  return fetch2.branches.map((b) => {
26226
27851
  const isCurrent = b.id === branchId;
26227
- const count = typeof b.datasetCount === "number" ? b.datasetCount : b.datasetAssignments?.length ?? 0;
26228
- const datasetSummary = count === 0 ? "no datasets" : `${count} dataset${count === 1 ? "" : "s"}`;
27852
+ const count2 = typeof b.datasetCount === "number" ? b.datasetCount : b.datasetAssignments?.length ?? 0;
27853
+ const datasetSummary = count2 === 0 ? "no datasets" : `${count2} dataset${count2 === 1 ? "" : "s"}`;
26229
27854
  const protectedMark = b.protected ? " \xB7 protected" : "";
26230
27855
  return {
26231
27856
  id: b.id,
@@ -27522,8 +29147,8 @@ var init_session_manager = __esm({
27522
29147
  return [];
27523
29148
  }
27524
29149
  }
27525
- listRecent(count = 10) {
27526
- return this.listAll().slice(0, count);
29150
+ listRecent(count2 = 10) {
29151
+ return this.listAll().slice(0, count2);
27527
29152
  }
27528
29153
  delete(sessionId) {
27529
29154
  const filePath = path5.join(this.sessionsDir, `${sessionId}.json`);
@@ -30445,8 +32070,8 @@ function upsertEnv(content, values) {
30445
32070
  return { content: `${lines.join(eol)}${eol}`, written, kept };
30446
32071
  }
30447
32072
  function renderNextTemplate(source, dataset) {
30448
- const literal = dataset.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`);
30449
- return source.replaceAll(DATASET_PLACEHOLDER, literal);
32073
+ const literal2 = dataset.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`);
32074
+ return source.replaceAll(DATASET_PLACEHOLDER, literal2);
30450
32075
  }
30451
32076
  function nextTemplateFiles(templatesDir = getTemplatesDir()) {
30452
32077
  const root = path5.join(templatesDir, FRAMEWORK_DIR);
@@ -31008,7 +32633,7 @@ function loginCommand(program2) {
31008
32633
  );
31009
32634
  cmd.option(
31010
32635
  "--token <apiKey>",
31011
- "API key for organization authentication (format: rly_org_v1_xxxxx)"
32636
+ "API key for organization authentication (format: rly_org_{region}_v2_xxxxx)"
31012
32637
  );
31013
32638
  }
31014
32639
 
@@ -31958,8 +33583,8 @@ function createDiffJsonOutput(params) {
31958
33583
  },
31959
33584
  diff: projectChangeBuckets(changes, (bucket, key) => ({
31960
33585
  added: bucket.added.map((name) => {
31961
- const count = fieldCount(key, name);
31962
- return { name, ...count !== void 0 && { fieldCount: count } };
33586
+ const count2 = fieldCount(key, name);
33587
+ return { name, ...count2 !== void 0 && { fieldCount: count2 } };
31963
33588
  }),
31964
33589
  modified: bucket.modified.map((item) => describeModified(item)),
31965
33590
  deleted: bucket.deleted,
@@ -31970,8 +33595,8 @@ function createDiffJsonOutput(params) {
31970
33595
  plan
31971
33596
  };
31972
33597
  }
31973
- function countOf(count, noun) {
31974
- return `${count} ${noun}${count === 1 ? "" : "s"}`;
33598
+ function countOf(count2, noun) {
33599
+ return `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
31975
33600
  }
31976
33601
  function createValidateJsonOutput(params) {
31977
33602
  const { check, durationMs } = params;
@@ -32200,10 +33825,30 @@ function planNotAcceptedMessage(blockage, command) {
32200
33825
  const accept = acceptPlanFlag(blockage.plan);
32201
33826
  return blockage.kind === "schema-plan-stale" ? `The plan ${blockage.confirmedPlanId} is not this ${command.verb}'s plan any more: the branch's active schema moved, or the schema differs. Nothing was ${command.done}. Read the plan now in effect; to apply it, ${command.verb} again with ${accept}.` : `This ${command.verb} changes stored content, and ${command.unattended}. Nothing was ${command.done}. Read the plan; to apply it, ${command.verb} again with ${accept}.`;
32202
33827
  }
33828
+ var REQUIRED_VALUE = {
33829
+ leaves: "would leave required values missing",
33830
+ clear: "declare a default, or keep the option"
33831
+ };
33832
+ var REFUSAL_KINDS = {
33833
+ REQUIRED_VALUE_MISSING: REQUIRED_VALUE,
33834
+ REQUIRED_OPTION_IN_USE: REQUIRED_VALUE,
33835
+ LIST_BELOW_MIN_ITEMS: {
33836
+ leaves: "would leave lists with fewer items than their minimum",
33837
+ clear: "give those entries other items, or lower minItems"
33838
+ }
33839
+ };
32203
33840
  function planRefusedMessage(plan, command) {
32204
- const count = plan.refusals.length;
32205
33841
  const verb = `${command.verb.charAt(0).toUpperCase()}${command.verb.slice(1)}`;
32206
- return `${verb} refused: ${count} change${count === 1 ? "" : "s"} would leave required values missing. Declare a default, or keep the option.`;
33842
+ const counts = /* @__PURE__ */ new Map();
33843
+ for (const kind of new Set(Object.values(REFUSAL_KINDS))) {
33844
+ const count2 = plan.refusals.filter((refusal) => REFUSAL_KINDS[refusal.code] === kind).length;
33845
+ if (count2 > 0) counts.set(kind, count2);
33846
+ }
33847
+ const said = [...counts].map(
33848
+ ([kind, count2], index) => index === 0 ? `${count2} change${count2 === 1 ? "" : "s"} ${kind.leaves}` : `${count2} ${kind.leaves}`
33849
+ );
33850
+ const advice = [...counts.keys()].map((kind) => kind.clear).join("; ");
33851
+ return `${verb} refused: ${said.join(", and ")}. ${advice.charAt(0).toUpperCase()}${advice.slice(1)}.`;
32207
33852
  }
32208
33853
 
32209
33854
  // src/commands/promote.ts
@@ -33604,7 +35249,7 @@ function sandboxCommands(program2) {
33604
35249
  }
33605
35250
 
33606
35251
  // src/commands/version.ts
33607
- var CLI_VERSION = "0.8.0" ;
35252
+ var CLI_VERSION = "0.9.0" ;
33608
35253
  async function handleVersion(_options) {
33609
35254
  console.log(heading("RelyIO CLI Version Information"));
33610
35255
  const versionInfo = [
@@ -33781,8 +35426,8 @@ function formatDuration2(ms) {
33781
35426
  }
33782
35427
 
33783
35428
  // src/commands/status.ts
33784
- function plural2(count, noun) {
33785
- return `${count} ${noun}${count === 1 ? "" : "s"}`;
35429
+ function plural2(count2, noun) {
35430
+ return `${count2} ${noun}${count2 === 1 ? "" : "s"}`;
33786
35431
  }
33787
35432
  function statusFailure(context) {
33788
35433
  if (context.checkError) {
@@ -33830,8 +35475,8 @@ function formatSchemaSource2(result) {
33830
35475
  return `TypeScript (${relativePath})`;
33831
35476
  }
33832
35477
  const files = result.sourcePath;
33833
- const count = files.length;
33834
- return `YAML (${count} file${count === 1 ? "" : "s"})`;
35478
+ const count2 = files.length;
35479
+ return `YAML (${count2} file${count2 === 1 ? "" : "s"})`;
33835
35480
  }
33836
35481
  async function performStatusCheck(branchName, options) {
33837
35482
  const { json = false, verbose = false, watch: watchMode = false } = options;
@@ -34126,10 +35771,10 @@ function displayCompactStatus(context, failure) {
34126
35771
  `"${parentName}" has changed but all changes are additive \u2014 safe to merge.`
34127
35772
  );
34128
35773
  } else if (context.divergence.compatibility?.breakingChanges?.length) {
34129
- const count = context.divergence.compatibility.breakingChanges.length;
35774
+ const count2 = context.divergence.compatibility.breakingChanges.length;
34130
35775
  statusLine(
34131
35776
  "warning",
34132
- `"${parentName}" has breaking changes (${count} issue${count === 1 ? "" : "s"}). Review with 'relyio diff --against ${parentName}'.`
35777
+ `"${parentName}" has breaking changes (${count2} issue${count2 === 1 ? "" : "s"}). Review with 'relyio diff --against ${parentName}'.`
34133
35778
  );
34134
35779
  } else {
34135
35780
  statusLine(
@@ -34596,8 +36241,8 @@ function formatSchemaSource3(result) {
34596
36241
  return `TypeScript (${relativePath})`;
34597
36242
  }
34598
36243
  const files = result.sourcePath;
34599
- const count = files.length;
34600
- return `YAML (${count} file${count === 1 ? "" : "s"})`;
36244
+ const count2 = files.length;
36245
+ return `YAML (${count2} file${count2 === 1 ? "" : "s"})`;
34601
36246
  }
34602
36247
  function handleGenerationError(error2) {
34603
36248
  if (error2 instanceof Error) {
@@ -36070,7 +37715,7 @@ function handleAliasSet(name, command) {
36070
37715
  console.error(THEME7.error("Invalid alias name. Use letters, numbers, and hyphens only."));
36071
37716
  process.exit(1);
36072
37717
  }
36073
- const reserved2 = [
37718
+ const reserved = [
36074
37719
  "init",
36075
37720
  "login",
36076
37721
  "logout",
@@ -36091,7 +37736,7 @@ function handleAliasSet(name, command) {
36091
37736
  "config",
36092
37737
  "help"
36093
37738
  ];
36094
- if (reserved2.includes(name.toLowerCase())) {
37739
+ if (reserved.includes(name.toLowerCase())) {
36095
37740
  console.error(THEME7.error(`Cannot override built-in command: ${name}`));
36096
37741
  process.exit(1);
36097
37742
  }
@@ -37907,7 +39552,7 @@ Examples:
37907
39552
  }
37908
39553
 
37909
39554
  // src/index.ts
37910
- var CLI_VERSION3 = "0.8.0" ;
39555
+ var CLI_VERSION3 = "0.9.0" ;
37911
39556
  var program = new Command();
37912
39557
  program.name("relyio").description("RelyIO CLI - Content Management, Simplified").version(CLI_VERSION3);
37913
39558
  registerGlobalOptions(program);