@vectoriox/iox-ui 5.60.0 → 5.61.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.
@@ -10818,23 +10818,23 @@ const RESERVED_PREFIX = '$';
10818
10818
  const ID_FIELD = '_id';
10819
10819
  /** The documented plan shape (contract `site.planShape`): its key lists are READ (`shape-check`), so a key
10820
10820
  * the plan documents is a key the gate validates — and an undocumented one is refused. */
10821
- const SHAPE$1 = SPEC_SITE_PLAN_SHAPE;
10822
- const PLAN_KEYS = shapeKeys(SHAPE$1['plan']);
10823
- const TEMPLATE_SOURCE_KEYS = shapeKeys(SHAPE$1['sourceTemplate']);
10824
- const AI_SOURCE_KEYS = shapeKeys(SHAPE$1['sourceAi']);
10825
- const BUSINESS_KEYS = shapeKeys(SHAPE$1['business']);
10826
- const DESIGN_REQUIRED = requiredShapeKeys(SHAPE$1['design']);
10827
- const DESIGN_KEYS = shapeKeys(SHAPE$1['design']);
10828
- const CONTENT_TYPE_KEYS = shapeKeys(SHAPE$1['contentType']);
10829
- const ROLE_KEYS = shapeKeys(SHAPE$1['roles']);
10830
- const PAGE_SEO_KEYS = shapeKeys(SHAPE$1['pageSeo']);
10821
+ const SHAPE = SPEC_SITE_PLAN_SHAPE;
10822
+ const PLAN_KEYS = shapeKeys(SHAPE['plan']);
10823
+ const TEMPLATE_SOURCE_KEYS = shapeKeys(SHAPE['sourceTemplate']);
10824
+ const AI_SOURCE_KEYS = shapeKeys(SHAPE['sourceAi']);
10825
+ const BUSINESS_KEYS = shapeKeys(SHAPE['business']);
10826
+ const DESIGN_REQUIRED = requiredShapeKeys(SHAPE['design']);
10827
+ const DESIGN_KEYS = shapeKeys(SHAPE['design']);
10828
+ const CONTENT_TYPE_KEYS = shapeKeys(SHAPE['contentType']);
10829
+ const ROLE_KEYS = shapeKeys(SHAPE['roles']);
10830
+ const PAGE_SEO_KEYS = shapeKeys(SHAPE['pageSeo']);
10831
10831
  const ARRAY_TYPE = 'array';
10832
- const FIELD_KEYS = shapeKeys(SHAPE$1['field']);
10833
- const SEED_FIXED_KEYS = shapeKeys(SHAPE$1['seed']);
10834
- const CMS_SOURCE_KEYS = shapeKeys(SHAPE$1['cmsDataSource']);
10835
- const RELATIVE_SOURCE_KEYS = shapeKeys(SHAPE$1['relativeDataSource']);
10836
- const PAGE_KEYS = shapeKeys(SHAPE$1['page']);
10837
- const SECTION_KEYS = shapeKeys(SHAPE$1['section']);
10832
+ const FIELD_KEYS = shapeKeys(SHAPE['field']);
10833
+ const SEED_FIXED_KEYS = shapeKeys(SHAPE['seed']);
10834
+ const CMS_SOURCE_KEYS = shapeKeys(SHAPE['cmsDataSource']);
10835
+ const RELATIVE_SOURCE_KEYS = shapeKeys(SHAPE['relativeDataSource']);
10836
+ const PAGE_KEYS = shapeKeys(SHAPE['page']);
10837
+ const SECTION_KEYS = shapeKeys(SHAPE['section']);
10838
10838
  const GLOBAL_POSITION = {
10839
10839
  [GlobalSlot.Header]: PatternGlobalPosition.Before,
10840
10840
  [GlobalSlot.Footer]: PatternGlobalPosition.After,
@@ -11348,13 +11348,13 @@ const ENTRANCE_TRIGGER = 'viewportEnter';
11348
11348
  const ENTRANCE_EASING = 'ease-out';
11349
11349
  const LEVELS = SPEC_AXES.animations;
11350
11350
  const FAMILIES = SPEC_AXES.animationFamilies;
11351
- const NOTHING$1 = { content: null, parts: {} };
11351
+ const NOTHING = { content: null, parts: {} };
11352
11352
  /** What a section of `family` moves at `level` — nothing for an unknown level or family. */
11353
11353
  function sectionEntrances(level, family) {
11354
11354
  const lv = level ? LEVELS[level] : undefined;
11355
11355
  const allow = FAMILIES[family] ?? [];
11356
11356
  if (!lv || !allow.length)
11357
- return NOTHING$1;
11357
+ return NOTHING;
11358
11358
  if (lv.parts.content && allow.includes(EntrancePart.Content))
11359
11359
  return { content: lv.parts.content, parts: {} };
11360
11360
  const parts = {};
@@ -11484,7 +11484,7 @@ const TEXT$1 = 'Text';
11484
11484
  const HEADING = 'Heading';
11485
11485
  const NAV_STYLE = 'nav';
11486
11486
  const DISPLAY = 'display';
11487
- const NONE = 'none';
11487
+ const NONE$1 = 'none';
11488
11488
  const CLICK = 'click';
11489
11489
  const HOME_ROUTE = '/';
11490
11490
  const COMPOSED$1 = { releaseLayoutDefaults: true };
@@ -11557,7 +11557,7 @@ function navShapeOf(globals) {
11557
11557
  return NavShape.Bar;
11558
11558
  return drawer ? NavShape.Drawer : null;
11559
11559
  }
11560
- const hiddenAt = (n) => Object.entries(n.responsive ?? {}).find(([, layer]) => layer?.styleProps?.[DISPLAY] === NONE)?.[0] ?? null;
11560
+ const hiddenAt = (n) => Object.entries(n.responsive ?? {}).find(([, layer]) => layer?.styleProps?.[DISPLAY] === NONE$1)?.[0] ?? null;
11561
11561
  /** The breakpoint the phone shape takes over at, read off the header's links row (default Mobile). */
11562
11562
  function navShapeBreakpoint(globals) {
11563
11563
  const links = find(globals, NavRole.Links);
@@ -11649,12 +11649,12 @@ function clickTo(kit, suffix, type, target) {
11649
11649
  }
11650
11650
  /** Show at the phone width, hidden above it (base `display: none`, the breakpoint's own `display`). */
11651
11651
  function phoneOnly(node, at, display) {
11652
- node.styleProps[DISPLAY] = NONE;
11652
+ node.styleProps[DISPLAY] = NONE$1;
11653
11653
  node.responsive = { ...(node.responsive ?? {}), [at]: { ...(node.responsive?.[at] ?? {}), styleProps: { ...(node.responsive?.[at]?.styleProps ?? {}), [DISPLAY]: display } } };
11654
11654
  }
11655
11655
  /** Hidden at the phone width: the header's links row. */
11656
11656
  function hideAt(node, at) {
11657
- node.responsive = { ...(node.responsive ?? {}), [at]: { ...(node.responsive?.[at] ?? {}), styleProps: { ...(node.responsive?.[at]?.styleProps ?? {}), [DISPLAY]: NONE } } };
11657
+ node.responsive = { ...(node.responsive ?? {}), [at]: { ...(node.responsive?.[at] ?? {}), styleProps: { ...(node.responsive?.[at]?.styleProps ?? {}), [DISPLAY]: NONE$1 } } };
11658
11658
  }
11659
11659
  /** Move every `display` override the shape owns to the new breakpoint. */
11660
11660
  function moveDisplayOverride(node, to) {
@@ -13544,229 +13544,492 @@ var AiCallStatus;
13544
13544
  })(AiCallStatus || (AiCallStatus = {}));
13545
13545
 
13546
13546
  /**
13547
- * The structured-output format a model writes a Site Plan in (AI plan §3d step 4, M1-3) — built from the SAME shape
13548
- * table `validateSitePlan` reads (`SPEC_SITE_PLAN_SHAPE`) and the SAME pattern library, so the plan a model is told
13549
- * about is the plan the gate refuses. ONE source: a key the table gains breaks this builder until it is written here.
13547
+ * The plan WIRE (AI plan M1-3, vectoriox/iox-cms#45): what a model writes when it plans a site — a compact format inside
13548
+ * the Claude API's structured-output limits (≤ 24 optional parameters and ≤ 16 unions per request; `strict-schema.ts`).
13549
+ * The Site Plan itself (`SPEC_SITE_PLAN_SHAPE`) has 213 optional keys once every pattern is a branch, so the model never
13550
+ * writes it: it writes this, and ONE pure `expandPlanWire` turns it into the Site Plan that `validateSitePlan` and the
13551
+ * stage's gate judge exactly as before.
13552
+ *
13553
+ * - **Every key required.** A plan key that may be absent is written with its stated "none" — `''`, `0`, `false`, `[]`.
13554
+ * - **Closed lists as enums** (patterns, page types, needs, slots, parts, forms, entrances, header/footer), from the
13555
+ * contract — never per-pattern branches: the gate refuses a slot or a part the chosen pattern does not have, with its path.
13556
+ * - **Maps as lists:** slots `[{ slot, text }]`, links `[{ slot, route }]`, seed values `[{ field, value }]`, roles fixed.
13557
+ * - **One data source object** for both kinds; `type` says which keys are read (the other kind's hold their none).
13558
+ * - **What the model does not decide is not on the wire** (`PLAN_WIRE_DERIVED`): the look (the owner's pick), the run,
13559
+ * the language (the reading's), the facts (the owner's own, verbatim), the version, the home page (the page at `/`).
13560
+ * So the format is the same for every studio and every run — one compiled grammar, cached by the provider.
13550
13561
  *
13551
- * Two maps of the shape are written as LISTS on the wire, because no strict mode can say "any key": a section's
13552
- * `slots` (`[{ slot, text }]`, the slot names closed per pattern) and a seed's field values (`values: [{ field,
13553
- * value }]`). `sitePlanFromWire` turns them back; nothing else differs. Every section is one `anyOf` branch per
13554
- * pattern — its id, its declared slots, its optional parts, a binding only when it takes one — and the design, the
13555
- * source and the version are PINNED to the owner's pick and this run (one-value enums), so the provider cannot let
13556
- * a model change the look. The gate still checks both (`plan.design` deep-equals the pick).
13562
+ * ONE source: every wire key names the plan keys it carries (`refs`) and is described in the plan table's own words, and
13563
+ * every plan key is either carried or derived (`planWireGaps()`); a key the plan shape gains breaks the builder until it
13564
+ * is placed. The expanders translate, never repair: a value the model got wrong stays wrong, for the gate to refuse.
13557
13565
  */
13558
- const SITE_PLAN_SCHEMA_NAME = 'site_plan';
13559
- /** The wire keys of the two maps written as lists. */
13560
- var SitePlanWireKey;
13561
- (function (SitePlanWireKey) {
13562
- SitePlanWireKey["SlotName"] = "slot";
13563
- SitePlanWireKey["SlotText"] = "text";
13564
- SitePlanWireKey["SeedValues"] = "values";
13565
- SitePlanWireKey["SeedField"] = "field";
13566
- SitePlanWireKey["SeedValue"] = "value";
13567
- })(SitePlanWireKey || (SitePlanWireKey = {}));
13568
- const SHAPE = SPEC_SITE_PLAN_SHAPE;
13569
- const one$1 = (value) => jsonOneOf([value]);
13570
- const oneBoolean = (value) => ({ type: 'boolean', enum: [value] });
13571
- /** The pick, key by key, as one-value enums; an optional key the pick leaves out may only be left out (or empty). */
13572
- function pinnedDesign(design) {
13573
- const entry = SHAPE['design'];
13574
- const values = design;
13575
- const properties = {};
13576
- for (const key of Object.keys(entry).map(k => k.replace(/\?$/, ''))) {
13577
- const v = values[key];
13578
- if (typeof v === 'string')
13579
- properties[key] = one$1(v);
13580
- else if (typeof v === 'boolean')
13581
- properties[key] = oneBoolean(v);
13582
- else if (isPlainObject(v))
13583
- properties[key] = jsonClosedObject(Object.fromEntries(Object.entries(v).map(([role, family]) => [role, one$1(String(family))])), Object.keys(v));
13584
- else
13585
- properties[key] = jsonClosedObject({}, []);
13586
- }
13587
- const shaped = jsonShapedObject(entry, properties, 'design');
13588
- // Everything the pick carries is required: a model may not leave a choice out and let a default stand in.
13589
- const given = Object.keys(values).filter(k => values[k] !== undefined && k in properties);
13590
- return { ...shaped, required: [...new Set([...shaped['required'], ...given])] };
13591
- }
13592
- function sourceSchema(source) {
13593
- if (source.kind === SitePlanSourceKind.Ai)
13594
- return jsonShapedObject(SHAPE['sourceAi'], { kind: one$1(source.kind), runId: one$1(source.runId) }, 'sourceAi');
13595
- return jsonShapedObject(SHAPE['sourceTemplate'], {
13596
- kind: one$1(source.kind), templateId: one$1(source.templateId), templateVersion: { type: 'integer', enum: [source.templateVersion] },
13597
- }, 'sourceTemplate');
13598
- }
13599
- function businessSchema() {
13600
- const facts = jsonClosedObject(Object.fromEntries(FACT_KEYS.map(key => [key, jsonText()])), []);
13601
- return jsonShapedObject(SHAPE['business'], { name: jsonText(), tagline: jsonText(), facts }, 'business');
13602
- }
13603
- /** A content type's field. */
13604
- function fieldJsonSchema() {
13605
- return jsonShapedObject(SHAPE['field'], { name: jsonText(), type: jsonText(), controller: jsonText(), required: jsonBoolean(), note: jsonText() }, 'field');
13606
- }
13607
- /** A content type, its seed's field values as a list (`values: [{ field, value }]`). */
13608
- function contentTypeJsonSchema() {
13609
- const value = jsonAnyOf([jsonText(), jsonListOf(jsonText())]);
13610
- const values = jsonListOf(jsonClosedObject({ [SitePlanWireKey.SeedField]: jsonText(), [SitePlanWireKey.SeedValue]: value }, [SitePlanWireKey.SeedField, SitePlanWireKey.SeedValue]));
13611
- const seed = jsonShapedObject(SHAPE['seed'], { count: jsonInteger(), sample: jsonBoolean(), isPublished: jsonBoolean() }, 'seed', { key: SitePlanWireKey.SeedValues, schema: values });
13612
- const roleKeys = Object.keys(SHAPE['roles']).map(k => k.replace(/\?$/, ''));
13613
- const roles = jsonShapedObject(SHAPE['roles'], Object.fromEntries(roleKeys.map(k => [k, jsonText()])), 'roles');
13614
- return jsonShapedObject(SHAPE['contentType'], {
13615
- name: jsonText(), label: jsonText(), roles, fields: jsonListOf(fieldJsonSchema()), seed,
13616
- }, 'contentType');
13617
- }
13618
- /** A data source: a cms list / single item, or a relative one. */
13619
- function dataSourceJsonSchema() {
13620
- const byRoute = jsonClosedObject({ field: jsonText(), routeParam: jsonText() }, ['field', 'routeParam']);
13621
- const cms = jsonShapedObject(SHAPE['cmsDataSource'], {
13622
- alias: jsonText(), type: one$1(PlanDataSourceType.Cms), mode: jsonOneOf(Object.values(PlanDataSourceMode)),
13623
- contentType: jsonText(), limit: jsonInteger(), filterByRouteParam: byRoute,
13624
- }, 'cmsDataSource');
13625
- const relative = jsonShapedObject(SHAPE['relativeDataSource'], {
13626
- alias: jsonText(), type: one$1(PlanDataSourceType.Relative), of: jsonText(), op: jsonOneOf(Object.values(RelativeOp)), within: jsonText(),
13627
- }, 'relativeDataSource');
13628
- return jsonAnyOf([cms, relative]);
13566
+ const PLAN_WIRE_SCHEMA_NAME = 'site_plan';
13567
+ var PlanWireEntry;
13568
+ (function (PlanWireEntry) {
13569
+ PlanWireEntry["Plan"] = "plan";
13570
+ PlanWireEntry["Business"] = "business";
13571
+ PlanWireEntry["ContentType"] = "contentType";
13572
+ PlanWireEntry["Roles"] = "roles";
13573
+ PlanWireEntry["Field"] = "field";
13574
+ PlanWireEntry["Seed"] = "seed";
13575
+ PlanWireEntry["SeedValue"] = "seedValue";
13576
+ PlanWireEntry["DataSource"] = "dataSource";
13577
+ PlanWireEntry["Page"] = "page";
13578
+ PlanWireEntry["PageSeo"] = "pageSeo";
13579
+ PlanWireEntry["Section"] = "section";
13580
+ PlanWireEntry["SlotText"] = "slotText";
13581
+ PlanWireEntry["SlotLink"] = "slotLink";
13582
+ PlanWireEntry["Globals"] = "globals";
13583
+ })(PlanWireEntry || (PlanWireEntry = {}));
13584
+ const PLAN_SHAPE = SPEC_SITE_PLAN_SHAPE;
13585
+ const key = (ref, none) => ({ refs: [ref], ...(none !== undefined ? { none } : {}) });
13586
+ const WIRE_KEYS = {
13587
+ plan: {
13588
+ business: key('plan.business'),
13589
+ contentTypes: key('plan.contentTypes'),
13590
+ dataSources: { refs: ['plan.dataSources'], text: 'dataSource[] — merged by alias into the org library' },
13591
+ pages: key('plan.pages'),
13592
+ globals: key('plan.globals'),
13593
+ },
13594
+ business: {
13595
+ name: key('business.name'),
13596
+ tagline: key('business.tagline', "''"),
13597
+ },
13598
+ contentType: {
13599
+ name: key('contentType.name'),
13600
+ label: key('contentType.label'),
13601
+ roles: key('contentType.roles'),
13602
+ fields: key('contentType.fields'),
13603
+ seed: { refs: ['contentType.seed'], text: 'seed — the sample items', none: 'count 0 and no values' },
13604
+ },
13605
+ roles: {
13606
+ title: key('roles.title'),
13607
+ summary: key('roles.summary', "''"),
13608
+ image: key('roles.image', "''"),
13609
+ meta: key('roles.meta', "''"),
13610
+ date: key('roles.date', "''"),
13611
+ gallery: key('roles.gallery', "''"),
13612
+ },
13613
+ field: {
13614
+ name: key('field.name'),
13615
+ type: key('field.type'),
13616
+ controller: key('field.controller'),
13617
+ required: { refs: ['field.required'], text: 'boolean — the field must be filled', none: 'false' },
13618
+ note: key('field.note', "''"),
13619
+ },
13620
+ seed: {
13621
+ count: key('seed.count'),
13622
+ sample: key('seed.sample'),
13623
+ isPublished: key('seed.isPublished'),
13624
+ values: { refs: ['seed.[field]'], text: "seedValue[] — one per field: the samples' value for it" },
13625
+ },
13626
+ seedValue: {
13627
+ field: { refs: ['seed.[field]'], text: 'string — a field of the type' },
13628
+ value: { refs: ['seed.[field]'], text: `string | string[] — ${describeShapeKey(PLAN_SHAPE['seed'], '[field]')} (a list for an array field)` },
13629
+ },
13630
+ dataSource: {
13631
+ alias: { refs: ['cmsDataSource.alias', 'relativeDataSource.alias'] },
13632
+ type: { refs: ['cmsDataSource.type', 'relativeDataSource.type'], text: "'cms' | 'relative' — which of the keys below are read" },
13633
+ mode: key('cmsDataSource.mode', "'' (relative)"),
13634
+ contentType: key('cmsDataSource.contentType', "'' (relative)"),
13635
+ limit: key('cmsDataSource.limit', '0 (all, or relative)'),
13636
+ filterField: { refs: ['cmsDataSource.filterByRouteParam'], text: "string — single only: the field that holds the route param's value (e.g. slug)", none: "''" },
13637
+ routeParam: { refs: ['cmsDataSource.filterByRouteParam'], text: 'string — single only: the route param that picks the item (the :param of its page route)', none: "''" },
13638
+ of: key('relativeDataSource.of', "'' (cms)"),
13639
+ op: key('relativeDataSource.op', "'' (cms)"),
13640
+ within: key('relativeDataSource.within', "'' (cms)"),
13641
+ },
13642
+ page: {
13643
+ key: key('page.key'),
13644
+ name: key('page.name'),
13645
+ route: { refs: ['page.route'], text: `${describeShapeKey(PLAN_SHAPE['page'], 'route')}; the page at "/" is the home page` },
13646
+ inNav: key('page.inNav', 'false'),
13647
+ archetype: key('page.archetype', "''"),
13648
+ need: key('page.need', "''"),
13649
+ template: key('page.template', "''"),
13650
+ routeSource: key('page.routeSource', "''"),
13651
+ seo: key('page.seo'),
13652
+ sections: key('page.sections'),
13653
+ },
13654
+ pageSeo: {
13655
+ title: key('pageSeo.title', "''"),
13656
+ description: key('pageSeo.description', "''"),
13657
+ },
13658
+ section: {
13659
+ pattern: key('section.pattern'),
13660
+ slots: { refs: ['section.slots'], text: "slotText[] — the pattern's declared slots it fills, each within its budget (site.patterns … max × site.copyLengths)", none: '[]' },
13661
+ links: { refs: ['section.links'], text: 'slotLink[] — re-points a linked slot (a button, a link) to another page route of the plan', none: '[]' },
13662
+ binding: key('section.binding', "''"),
13663
+ without: key('section.without', '[]'),
13664
+ form: key('section.form', "''"),
13665
+ enter: key('section.enter', "'' (the look's)"),
13666
+ },
13667
+ slotText: {
13668
+ slot: { refs: ['section.slots'], text: "string — one of the pattern's slots" },
13669
+ text: { refs: ['section.slots'], text: "string — the words, in the owner's language and voice" },
13670
+ },
13671
+ slotLink: {
13672
+ slot: { refs: ['section.links'], text: "string — one of the pattern's linked slots" },
13673
+ route: { refs: ['section.links'], text: 'string — a page route of the plan (no :param)' },
13674
+ },
13675
+ globals: {
13676
+ header: key('globals.header'),
13677
+ footer: key('globals.footer'),
13678
+ },
13679
+ };
13680
+ /** The plan keys the model does not write, and where the expander takes each from. */
13681
+ const PLAN_WIRE_DERIVED = {
13682
+ 'plan.version': `always '${SITE_PLAN_VERSION}'`,
13683
+ 'plan.source': 'this run (the context)',
13684
+ 'plan.locale': "the reading's language (the context)",
13685
+ 'plan.design': "the owner's pick (the context)",
13686
+ 'business.facts': "the owner's own facts, verbatim (the context)",
13687
+ 'page.isHome': 'the page at "/"',
13688
+ 'page.required': "a template page's (the wizard's Pages step), never the AI's",
13689
+ sourceTemplate: 'the AI never writes a template plan',
13690
+ sourceAi: 'this run (the context)',
13691
+ design: "the owner's pick (the context)",
13692
+ };
13693
+ const wireText = (spec) => {
13694
+ const [entry, planKey] = spec.refs[0].split('.');
13695
+ const base = spec.text ?? describeShapeKey(PLAN_SHAPE[entry], planKey);
13696
+ return spec.none === undefined ? base : `${base} — ${spec.none} = none`;
13697
+ };
13698
+ /** The wire's shape table, in the plan table's words — what the model reads, and what the format is built from. */
13699
+ const PLAN_WIRE_SHAPE = Object.fromEntries(Object.entries(WIRE_KEYS).map(([entry, keys]) => [entry, Object.fromEntries(Object.entries(keys).map(([k, spec]) => [k, wireText(spec)]))]));
13700
+ /** Plan keys neither carried by a wire key nor derived — always empty (a spec and the builder hold it). */
13701
+ function planWireGaps() {
13702
+ const carried = new Set(Object.values(WIRE_KEYS).flatMap(keys => Object.values(keys).flatMap(spec => spec.refs)));
13703
+ const gaps = [];
13704
+ for (const [entry, shape] of Object.entries(PLAN_SHAPE)) {
13705
+ if (PLAN_WIRE_DERIVED[entry])
13706
+ continue;
13707
+ for (const raw of Object.keys(shape)) {
13708
+ const ref = `${entry}.${raw.replace(/\?$/, '')}`;
13709
+ if (!carried.has(ref) && !PLAN_WIRE_DERIVED[ref])
13710
+ gaps.push(ref);
13711
+ }
13712
+ }
13713
+ return gaps;
13629
13714
  }
13715
+ class PlanWireShapeError extends Error {
13716
+ }
13717
+ // ── The format ─────────────────────────────────────────────────────────────────────────────────────────────────────
13718
+ const unique = (values) => [...new Set(values)];
13719
+ const NONE = '';
13720
+ const orNone = (values) => jsonOneOf([NONE, ...values]);
13721
+ const closedOrText = (values) => (values.length ? jsonOneOf(values) : jsonText());
13722
+ const shaped = (entry, properties) => jsonShapedObject(PLAN_WIRE_SHAPE[entry], properties, `wire ${entry}`);
13630
13723
  /** The patterns a page section may use (every non-global one), in library order. */
13631
13724
  function sectionPatternIds() {
13632
13725
  return Object.entries(SITE_PATTERNS).filter(([, p]) => !p.global).map(([id]) => id);
13633
13726
  }
13634
- /** A key a pattern has nothing for: only `{}` (or leaving it out) — the decoder drops it. */
13635
- const NOTHING = () => jsonClosedObject({}, []);
13636
- /** One section branch: this pattern's id, its declared slots (a list), the slots it may re-point (`links`, closed per
13637
- * pattern), its optional parts, a binding when it takes one, a form preset when it holds a form, its own entrance. */
13638
- function sectionBranch(id, pattern) {
13639
- const slots = patternSlots(pattern);
13640
- const parts = patternOptionalParts(pattern);
13641
- const linkSlots = patternLinkSlots(pattern);
13642
- const slotList = slots.length
13643
- ? jsonListOf(jsonClosedObject({ [SitePlanWireKey.SlotName]: jsonOneOf(slots), [SitePlanWireKey.SlotText]: jsonText() }, [SitePlanWireKey.SlotName, SitePlanWireKey.SlotText]))
13644
- : NOTHING();
13645
- const branch = jsonShapedObject(SHAPE['section'], {
13646
- pattern: one$1(id),
13647
- slots: slotList,
13648
- links: linkSlots.length ? jsonClosedObject(Object.fromEntries(linkSlots.map(slot => [slot, jsonText()])), []) : NOTHING(),
13649
- binding: patternTakesBinding(pattern) ? jsonText() : NOTHING(),
13650
- without: parts.length ? jsonListOf(jsonOneOf(parts)) : NOTHING(),
13651
- form: patternHasForm(pattern) ? jsonOneOf(FORM_PRESETS.map(p => p.id)) : NOTHING(),
13652
- enter: jsonOneOf(ANIMATE_IDS),
13653
- }, 'section');
13654
- return { ...branch, description: `${id}: ${pattern.intent}` };
13655
- }
13656
- /** A page section: one branch per pattern. */
13657
- function sectionJsonSchema() {
13658
- return jsonAnyOf(sectionPatternIds().map(id => sectionBranch(id, SITE_PATTERNS[id])));
13659
- }
13660
- /** A page. */
13661
- function pageJsonSchema() {
13662
- const seo = jsonShapedObject(SHAPE['pageSeo'], { title: jsonText(), description: jsonText() }, 'pageSeo');
13663
- return jsonShapedObject(SHAPE['page'], {
13664
- key: jsonText(), name: jsonText(), route: jsonText(), isHome: jsonBoolean(), required: jsonBoolean(), inNav: jsonBoolean(),
13665
- archetype: jsonOneOf(Object.keys(PAGE_ARCHETYPES)), need: jsonOneOf(SITE_NEED_IDS), template: jsonText(), routeSource: jsonText(), seo, sections: jsonListOf(sectionJsonSchema()),
13666
- }, 'page');
13667
- }
13727
+ const sectionPatterns = () => sectionPatternIds().map(id => SITE_PATTERNS[id]);
13668
13728
  const globalIds = (position) => Object.entries(SITE_PATTERNS).filter(([, p]) => p.global === position).map(([id]) => id);
13669
- /** The Site Plan format, pinned to the owner's pick and this run. */
13670
- function sitePlanJsonSchema(pin) {
13671
- const globals = jsonShapedObject(SHAPE['globals'], {
13672
- [GlobalSlot.Header]: jsonOneOf(globalIds(PatternGlobalPosition.Before)),
13673
- [GlobalSlot.Footer]: jsonOneOf(globalIds(PatternGlobalPosition.After)),
13674
- }, 'globals');
13675
- return jsonShapedObject(SHAPE['plan'], {
13676
- version: one$1(SITE_PLAN_VERSION),
13677
- source: sourceSchema(pin.source),
13678
- business: businessSchema(),
13679
- locale: jsonOneOf(Object.keys(SITE_LOCALES)),
13680
- design: pinnedDesign(pin.design),
13681
- contentTypes: jsonListOf(contentTypeJsonSchema()),
13682
- dataSources: jsonListOf(dataSourceJsonSchema()),
13683
- pages: jsonListOf(pageJsonSchema()),
13684
- globals,
13685
- }, 'plan');
13686
- }
13687
- // ── The wire → the plan ─────────────────────────────────────────────────────────────────────────────
13688
- const isEmptyObject = (v) => isPlainObject(v) && Object.keys(v).length === 0;
13689
- /** `[{ slot, text }]` → `{ slot: text }`; anything that is not that list is left for the gate to refuse. */
13690
- function slotsFromWire(slots) {
13691
- if (!Array.isArray(slots))
13692
- return slots;
13729
+ function wireFieldJsonSchema() {
13730
+ return shaped(PlanWireEntry.Field, { name: jsonText(), type: jsonText(), controller: jsonText(), required: jsonBoolean(), note: jsonText() });
13731
+ }
13732
+ function wireContentTypeJsonSchema() {
13733
+ const roleKeys = shapeKeys(PLAN_WIRE_SHAPE.roles);
13734
+ const value = shaped(PlanWireEntry.SeedValue, { field: jsonText(), value: jsonAnyOf([jsonText(), jsonListOf(jsonText())]) });
13735
+ return shaped(PlanWireEntry.ContentType, {
13736
+ name: jsonText(),
13737
+ label: jsonText(),
13738
+ roles: shaped(PlanWireEntry.Roles, Object.fromEntries(roleKeys.map(k => [k, jsonText()]))),
13739
+ fields: jsonListOf(wireFieldJsonSchema()),
13740
+ seed: shaped(PlanWireEntry.Seed, { count: jsonInteger(), sample: jsonBoolean(), isPublished: jsonBoolean(), values: jsonListOf(value) }),
13741
+ });
13742
+ }
13743
+ function wireDataSourceJsonSchema() {
13744
+ return shaped(PlanWireEntry.DataSource, {
13745
+ alias: jsonText(),
13746
+ type: jsonOneOf(Object.values(PlanDataSourceType)),
13747
+ mode: orNone(Object.values(PlanDataSourceMode)),
13748
+ contentType: jsonText(),
13749
+ limit: jsonInteger(),
13750
+ filterField: jsonText(),
13751
+ routeParam: jsonText(),
13752
+ of: jsonText(),
13753
+ op: orNone(Object.values(RelativeOp)),
13754
+ within: jsonText(),
13755
+ });
13756
+ }
13757
+ function wireSectionJsonSchema() {
13758
+ const patterns = sectionPatterns();
13759
+ return shaped(PlanWireEntry.Section, {
13760
+ pattern: jsonOneOf(sectionPatternIds()),
13761
+ slots: jsonListOf(shaped(PlanWireEntry.SlotText, { slot: closedOrText(unique(patterns.flatMap(patternSlots))), text: jsonText() })),
13762
+ links: jsonListOf(shaped(PlanWireEntry.SlotLink, { slot: closedOrText(unique(patterns.flatMap(patternLinkSlots))), route: jsonText() })),
13763
+ binding: jsonText(),
13764
+ without: jsonListOf(closedOrText(unique(patterns.flatMap(patternOptionalParts)))),
13765
+ form: orNone(FORM_PRESETS.map(p => p.id)),
13766
+ enter: orNone(ANIMATE_IDS),
13767
+ });
13768
+ }
13769
+ function wirePageJsonSchema() {
13770
+ return shaped(PlanWireEntry.Page, {
13771
+ key: jsonText(),
13772
+ name: jsonText(),
13773
+ route: jsonText(),
13774
+ inNav: jsonBoolean(),
13775
+ archetype: orNone(Object.keys(PAGE_ARCHETYPES)),
13776
+ need: orNone(SITE_NEED_IDS),
13777
+ template: jsonText(),
13778
+ routeSource: jsonText(),
13779
+ seo: shaped(PlanWireEntry.PageSeo, { title: jsonText(), description: jsonText() }),
13780
+ sections: jsonListOf(wireSectionJsonSchema()),
13781
+ });
13782
+ }
13783
+ /** The format a model writes a plan in. The same for every studio and run: nothing in it is pinned. */
13784
+ function planWireJsonSchema() {
13785
+ const gaps = planWireGaps();
13786
+ if (gaps.length)
13787
+ throw new PlanWireShapeError(`the plan wire neither carries nor derives ${gaps.join(', ')}`);
13788
+ return shaped(PlanWireEntry.Plan, {
13789
+ business: shaped(PlanWireEntry.Business, { name: jsonText(), tagline: jsonText() }),
13790
+ contentTypes: jsonListOf(wireContentTypeJsonSchema()),
13791
+ dataSources: jsonListOf(wireDataSourceJsonSchema()),
13792
+ pages: jsonListOf(wirePageJsonSchema()),
13793
+ globals: shaped(PlanWireEntry.Globals, {
13794
+ [GlobalSlot.Header]: jsonOneOf(globalIds(PatternGlobalPosition.Before)),
13795
+ [GlobalSlot.Footer]: jsonOneOf(globalIds(PatternGlobalPosition.After)),
13796
+ }),
13797
+ });
13798
+ }
13799
+ /** A value standing for "absent": `''` (or nothing at all). */
13800
+ const isNone = (v) => v === undefined || v === NONE;
13801
+ const optional = (name, v, none = isNone) => (none(v) ? {} : { [name]: v });
13802
+ const emptyList = (v) => v === undefined || (Array.isArray(v) && v.length === 0);
13803
+ /** `[{ <keyName>, <valueName> }]` → `{ key: value }`; anything else is left for the gate to refuse. */
13804
+ function listToMap(list, keyName, valueName) {
13805
+ if (!Array.isArray(list))
13806
+ return list;
13693
13807
  const out = {};
13694
- for (const entry of slots) {
13695
- if (!isPlainObject(entry) || typeof entry[SitePlanWireKey.SlotName] !== 'string')
13696
- return slots;
13697
- out[entry[SitePlanWireKey.SlotName]] = entry[SitePlanWireKey.SlotText];
13808
+ for (const entry of list) {
13809
+ if (!isPlainObject(entry) || typeof entry[keyName] !== 'string')
13810
+ return list;
13811
+ out[entry[keyName]] = entry[valueName];
13698
13812
  }
13699
13813
  return out;
13700
13814
  }
13701
- /** A section off the wire: its slots back to a map; a key the pattern has nothing for (`{}`) dropped. */
13702
- function sectionFromWire(section) {
13703
- if (!isPlainObject(section))
13704
- return section;
13705
- const out = { ...section };
13706
- for (const key of ['slots', 'links', 'binding', 'without', 'form'])
13707
- if (isEmptyObject(out[key]))
13708
- delete out[key];
13709
- if ('slots' in out)
13710
- out['slots'] = slotsFromWire(out['slots']);
13711
- return out;
13815
+ const mapToList = (map, keyName, valueName) => Object.entries(map ?? {}).map(([k, v]) => ({ [keyName]: k, [valueName]: v }));
13816
+ const each = (list, expand) => (Array.isArray(list) ? list.map(expand) : list);
13817
+ function expandWireField(wire) {
13818
+ if (!isPlainObject(wire))
13819
+ return wire;
13820
+ const { required, note, ...rest } = wire;
13821
+ return { ...rest, ...optional('required', required, v => v === undefined || v === false), ...optional('note', note) };
13712
13822
  }
13713
- /** A content type off the wire: its seed's `values` list back to field keys. */
13714
- function contentTypeFromWire(type) {
13715
- if (!isPlainObject(type) || !isPlainObject(type['seed']))
13716
- return type;
13717
- const { [SitePlanWireKey.SeedValues]: values, ...seed } = type['seed'];
13718
- if (Array.isArray(values)) {
13719
- for (const entry of values) {
13720
- if (isPlainObject(entry) && typeof entry[SitePlanWireKey.SeedField] === 'string')
13721
- seed[entry[SitePlanWireKey.SeedField]] = entry[SitePlanWireKey.SeedValue];
13722
- }
13823
+ function expandWireContentType(wire) {
13824
+ if (!isPlainObject(wire))
13825
+ return wire;
13826
+ const { roles, fields, seed, ...rest } = wire;
13827
+ const out = { ...rest, roles: isPlainObject(roles) ? Object.fromEntries(Object.entries(roles).filter(([, v]) => !isNone(v))) : roles, fields: each(fields, expandWireField) };
13828
+ if (!isPlainObject(seed))
13829
+ return seed === undefined ? out : { ...out, seed };
13830
+ const { values, ...fixed } = seed;
13831
+ if (fixed['count'] === 0 && emptyList(values))
13832
+ return out;
13833
+ const map = listToMap(values ?? [], 'field', 'value');
13834
+ return { ...out, seed: isPlainObject(map) ? { ...fixed, ...map } : { ...fixed, values } };
13835
+ }
13836
+ function expandWireDataSource(wire) {
13837
+ if (!isPlainObject(wire))
13838
+ return wire;
13839
+ const { alias, type, mode, contentType, limit, filterField, routeParam, of, op, within, ...rest } = wire;
13840
+ if (type === PlanDataSourceType.Cms) {
13841
+ const filter = isNone(filterField) && isNone(routeParam) ? {} : { filterByRouteParam: { field: filterField, routeParam } };
13842
+ return { ...rest, alias, type, mode, contentType, ...optional('limit', limit, v => v === undefined || v === 0), ...filter };
13723
13843
  }
13724
- else if (values !== undefined)
13725
- seed[SitePlanWireKey.SeedValues] = values;
13726
- return { ...type, seed };
13844
+ if (type === PlanDataSourceType.Relative)
13845
+ return { ...rest, alias, type, of, op, within };
13846
+ return wire;
13727
13847
  }
13728
- function pageFromWire(page) {
13729
- if (!isPlainObject(page) || !Array.isArray(page['sections']))
13730
- return page;
13731
- return { ...page, sections: page['sections'].map(sectionFromWire) };
13848
+ function expandWireSection(wire) {
13849
+ if (!isPlainObject(wire))
13850
+ return wire;
13851
+ const { slots, links, binding, without, form, enter, ...rest } = wire;
13852
+ return {
13853
+ ...rest,
13854
+ ...optional('slots', listToMap(slots, 'slot', 'text'), v => emptyList(slots) || v === undefined),
13855
+ ...optional('links', listToMap(links, 'slot', 'route'), v => emptyList(links) || v === undefined),
13856
+ ...optional('binding', binding),
13857
+ ...optional('without', without, emptyList),
13858
+ ...optional('form', form),
13859
+ ...optional('enter', enter),
13860
+ };
13732
13861
  }
13733
- /** The design off the wire: an empty optional object (a choice the pick does not carry) dropped. */
13734
- function designFromWire(design) {
13735
- if (!isPlainObject(design))
13736
- return design;
13737
- return Object.fromEntries(Object.entries(design).filter(([, v]) => !isEmptyObject(v)));
13862
+ function expandWirePage(wire) {
13863
+ if (!isPlainObject(wire))
13864
+ return wire;
13865
+ const { archetype, need, template, routeSource, seo, sections, ...rest } = wire;
13866
+ const words = isPlainObject(seo) ? { ...optional('title', seo['title']), ...optional('description', seo['description']) } : seo;
13867
+ return {
13868
+ ...rest,
13869
+ ...(wire['route'] === '/' ? { isHome: true } : {}),
13870
+ ...optional('archetype', archetype),
13871
+ ...optional('need', need),
13872
+ ...optional('template', template),
13873
+ ...optional('routeSource', routeSource),
13874
+ ...optional('seo', words, v => v === undefined || (isPlainObject(v) && Object.keys(v).length === 0)),
13875
+ sections: each(sections, expandWireSection),
13876
+ };
13877
+ }
13878
+ function expandWireBusiness(wire, facts) {
13879
+ if (!isPlainObject(wire))
13880
+ return wire;
13881
+ const { tagline, ...rest } = wire;
13882
+ return { ...rest, ...optional('tagline', tagline), ...(facts && Object.keys(facts).length ? { facts: { ...facts } } : {}) };
13738
13883
  }
13739
13884
  /**
13740
- * A model's answer → a Site Plan: the two listed maps back to maps, nothing else touched. Never repairs — whatever the
13741
- * answer got wrong is still wrong, for `validateSitePlan` to refuse with a path.
13885
+ * A model's plan (the wire) + what the generation decides (the look, the run, the language, the owner's facts) → the
13886
+ * Site Plan. Pure. Translates, never repairs: whatever the answer got wrong is still wrong, for `validateSitePlan`.
13742
13887
  */
13743
- function sitePlanFromWire(wire) {
13888
+ function expandPlanWire(wire, context) {
13744
13889
  if (!isPlainObject(wire))
13745
13890
  return wire;
13746
- const out = { ...wire };
13747
- if (Array.isArray(out['contentTypes']))
13748
- out['contentTypes'] = out['contentTypes'].map(contentTypeFromWire);
13749
- if (Array.isArray(out['pages']))
13750
- out['pages'] = out['pages'].map(pageFromWire);
13751
- if ('design' in out)
13752
- out['design'] = designFromWire(out['design']);
13753
- return out;
13891
+ const { business, contentTypes, dataSources, pages, ...rest } = wire;
13892
+ return {
13893
+ ...rest,
13894
+ version: SITE_PLAN_VERSION,
13895
+ source: { ...context.source },
13896
+ business: expandWireBusiness(business, context.facts),
13897
+ ...(context.locale ? { locale: context.locale } : {}),
13898
+ design: { ...context.design },
13899
+ contentTypes: each(contentTypes, expandWireContentType),
13900
+ dataSources: each(dataSources, expandWireDataSource),
13901
+ pages: each(pages, expandWirePage),
13902
+ };
13754
13903
  }
13755
- /** The plan → the wire (what a re-ask shows the model, and what the plan edits' prompt carries). */
13756
- function sitePlanToWire(plan) {
13757
- const slotsToWire = (slots) => Object.entries(slots ?? {}).map(([slot, text]) => ({ [SitePlanWireKey.SlotName]: slot, [SitePlanWireKey.SlotText]: text }));
13904
+ // ── The plan → the wire ────────────────────────────────────────────────────────────────────────────────────────────
13905
+ function fieldToWire(field) {
13906
+ return { name: field.name, type: field.type, controller: field.controller, required: field.required ?? false, note: field.note ?? NONE };
13907
+ }
13908
+ function contentTypeToWire(type) {
13909
+ const roles = Object.fromEntries(shapeKeys(PLAN_WIRE_SHAPE.roles).map(role => [role, type.roles[role] ?? NONE]));
13910
+ const { count = 0, sample = false, isPublished = false, ...values } = type.seed ?? {};
13758
13911
  return {
13759
- ...plan,
13760
- contentTypes: plan.contentTypes.map(t => {
13761
- if (!t.seed)
13762
- return t;
13763
- const { count, sample, isPublished, ...values } = t.seed;
13764
- return { ...t, seed: { count, sample, isPublished, [SitePlanWireKey.SeedValues]: Object.entries(values).map(([field, value]) => ({ [SitePlanWireKey.SeedField]: field, [SitePlanWireKey.SeedValue]: value })) } };
13765
- }),
13766
- pages: plan.pages.map(p => ({ ...p, sections: p.sections.map(s => (s.slots ? { ...s, slots: slotsToWire(s.slots) } : s)) })),
13912
+ name: type.name, label: type.label, roles, fields: type.fields.map(fieldToWire),
13913
+ seed: { count, sample, isPublished, values: mapToList(values, 'field', 'value') },
13914
+ };
13915
+ }
13916
+ function dataSourceToWire(source) {
13917
+ const blank = { alias: source.alias, type: source.type, mode: NONE, contentType: NONE, limit: 0, filterField: NONE, routeParam: NONE, of: NONE, op: NONE, within: NONE };
13918
+ if (source.type === PlanDataSourceType.Relative)
13919
+ return { ...blank, of: source.of, op: source.op, within: source.within };
13920
+ return {
13921
+ ...blank, mode: source.mode, contentType: source.contentType, limit: source.limit ?? 0,
13922
+ filterField: source.filterByRouteParam?.field ?? NONE, routeParam: source.filterByRouteParam?.routeParam ?? NONE,
13923
+ };
13924
+ }
13925
+ function sectionToWire(section) {
13926
+ return {
13927
+ pattern: section.pattern, slots: mapToList(section.slots, 'slot', 'text'), links: mapToList(section.links, 'slot', 'route'),
13928
+ binding: section.binding ?? NONE, without: [...(section.without ?? [])], form: section.form ?? NONE, enter: section.enter ?? NONE,
13929
+ };
13930
+ }
13931
+ function pageToWire(page) {
13932
+ return {
13933
+ key: page.key, name: page.name, route: page.route, inNav: page.inNav ?? false, archetype: page.archetype ?? NONE, need: page.need ?? NONE,
13934
+ template: page.template ?? NONE, routeSource: page.routeSource ?? NONE,
13935
+ seo: { title: page.seo?.title ?? NONE, description: page.seo?.description ?? NONE }, sections: page.sections.map(sectionToWire),
13936
+ };
13937
+ }
13938
+ /** The plan → the wire: what a model would have written (the plan edits' prompt shows it; fakes and evals write it). */
13939
+ function planToWire(plan) {
13940
+ return {
13941
+ business: { name: plan.business.name, tagline: plan.business.tagline ?? NONE },
13942
+ contentTypes: plan.contentTypes.map(contentTypeToWire),
13943
+ dataSources: plan.dataSources.map(dataSourceToWire),
13944
+ pages: plan.pages.map(pageToWire),
13945
+ globals: { ...plan.globals },
13767
13946
  };
13768
13947
  }
13769
13948
 
13949
+ /**
13950
+ * What a structured-output request costs the provider's grammar compiler, counted the way the Claude API counts it
13951
+ * (platform.claude.com/docs/en/build-with-claude/structured-outputs, "Schema complexity limits", read 2026-10-27):
13952
+ * "Total optional parameters across all strict tool schemas and JSON output schemas. Each parameter not listed in
13953
+ * `required` counts toward this limit" (24); "Total parameters that use `anyOf` or type arrays (for example,
13954
+ * `"type": ["string", "null"]`) across all strict schemas" (16); "Maximum number of tools with `strict: true`" (20).
13955
+ * The limits are per REQUEST — every strict schema in it together. The ONE counter: the specs hold every schema iox-cms
13956
+ * sends to it, and the fake Claude API refuses with it exactly as Claude does — the plan stage once sent 213 optional
13957
+ * parameters and every suite stayed green because the fake accepted anything (vectoriox/iox-cms#45).
13958
+ */
13959
+ const STRICT_SCHEMA_LIMITS = { strictTools: 20, optional: 24, unions: 16 };
13960
+ /** What a schema iox-cms sends may use — half of each limit, so a step can gain a field (or a strict tool) without a 400. */
13961
+ const STRICT_SCHEMA_BUDGET = { optional: 12, unions: 8 };
13962
+ /** The keywords the docs list as not supported ("Numerical constraints", "String constraints", "Array constraints beyond `minItems` of 0 or 1"). */
13963
+ const UNSUPPORTED_KEYWORDS = [
13964
+ 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'maxItems', 'uniqueItems', 'contains',
13965
+ 'minContains', 'maxContains', 'prefixItems', 'patternProperties', 'propertyNames', 'dependentSchemas', 'if', 'then', 'else', 'not',
13966
+ ];
13967
+ const NESTED_LISTS = ['anyOf', 'allOf', 'oneOf'];
13968
+ const NESTED_MAPS = ['properties', '$defs', 'definitions'];
13969
+ const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
13970
+ function count(node, path, out) {
13971
+ if (!isObject(node))
13972
+ return;
13973
+ const at = (key) => (path ? `${path}.${key}` : key);
13974
+ if (Array.isArray(node['anyOf']) || Array.isArray(node['type']))
13975
+ out.unions++;
13976
+ const properties = isObject(node['properties']) ? node['properties'] : null;
13977
+ if (node['type'] === 'object' || properties) {
13978
+ const required = new Set(Array.isArray(node['required']) ? node['required'] : []);
13979
+ out.optional += Object.keys(properties ?? {}).filter(key => !required.has(key)).length;
13980
+ if (node['additionalProperties'] !== false)
13981
+ out.unsupported.push(`${at('additionalProperties')}: must be false`);
13982
+ }
13983
+ for (const key of UNSUPPORTED_KEYWORDS)
13984
+ if (key in node)
13985
+ out.unsupported.push(`${at(key)}: not supported`);
13986
+ if ('minItems' in node && node['minItems'] !== 0 && node['minItems'] !== 1)
13987
+ out.unsupported.push(`${at('minItems')}: only 0 or 1`);
13988
+ if (Array.isArray(node['enum']) && node['enum'].some(v => v !== null && typeof v === 'object'))
13989
+ out.unsupported.push(`${at('enum')}: complex types`);
13990
+ if (typeof node['$ref'] === 'string' && !node['$ref'].startsWith('#'))
13991
+ out.unsupported.push(`${at('$ref')}: external`);
13992
+ for (const key of NESTED_MAPS) {
13993
+ if (isObject(node[key]))
13994
+ for (const [name, child] of Object.entries(node[key]))
13995
+ count(child, at(`${key}.${name}`), out);
13996
+ }
13997
+ for (const key of NESTED_LISTS) {
13998
+ if (Array.isArray(node[key]))
13999
+ node[key].forEach((child, i) => count(child, at(`${key}[${i}]`), out));
14000
+ }
14001
+ if (isObject(node['items']))
14002
+ count(node['items'], at('items'), out);
14003
+ if (isObject(node['additionalProperties']))
14004
+ count(node['additionalProperties'], at('additionalProperties'), out);
14005
+ }
14006
+ /** The cost of ONE request's strict schemas together: its JSON output format and its strict tools' input schemas. */
14007
+ function strictSchemaComplexity(schemas, strictTools = 0) {
14008
+ const out = { optional: 0, unions: 0, strictTools, unsupported: [] };
14009
+ schemas.forEach((schema, i) => count(schema, schemas.length > 1 ? `[${i}]` : '', out));
14010
+ return out;
14011
+ }
14012
+ /**
14013
+ * The 400 the Claude API answers a request over its limits with, or null. The optional-parameter message is the one
14014
+ * Claude answered on 2026-10-27, verbatim; the union, strict-tool and unsupported messages follow its shape (not yet
14015
+ * captured).
14016
+ */
14017
+ function strictSchemaRefusal(complexity) {
14018
+ const refuse = (message) => ({ status: 400, type: 'invalid_request_error', message });
14019
+ if (complexity.unsupported.length)
14020
+ return refuse(`Invalid schema: ${complexity.unsupported.join('; ')}`);
14021
+ if (complexity.strictTools > STRICT_SCHEMA_LIMITS.strictTools) {
14022
+ return refuse(`Too many strict tools (${complexity.strictTools}). Reduce the number of tools with strict: true (limit: ${STRICT_SCHEMA_LIMITS.strictTools}).`);
14023
+ }
14024
+ if (complexity.optional > STRICT_SCHEMA_LIMITS.optional) {
14025
+ return refuse(`Schemas contains too many optional parameters (${complexity.optional}), which would make grammar compilation inefficient. Reduce the number of optional parameters in your tool schemas (limit: ${STRICT_SCHEMA_LIMITS.optional}).`);
14026
+ }
14027
+ if (complexity.unions > STRICT_SCHEMA_LIMITS.unions) {
14028
+ return refuse(`Schemas contains too many parameters with union types (${complexity.unions}), which would make grammar compilation inefficient. Reduce the number of parameters with union types in your tool schemas (limit: ${STRICT_SCHEMA_LIMITS.unions}).`);
14029
+ }
14030
+ return null;
14031
+ }
14032
+
13770
14033
  /**
13771
14034
  * Changing a plan by sentence (AI plan §3d "The Plan prompt box", M1-5; reused by M2 after a build): the CLOSED list
13772
14035
  * of operations (N-D6) a model may write against the current plan, and the ONE pure `applyPlanEdits` that carries them
@@ -13933,7 +14196,7 @@ const setSections = (d, at, sections) => { d.pages[at] = { ...d.pages[at], secti
13933
14196
  /** One handler per op — a data table, never an if-chain. */
13934
14197
  const APPLY = {
13935
14198
  [PlanEditOp.AddPage]: (d, e, c, path, changed) => {
13936
- const page = { ...e.page, sections: (e.page.sections ?? []).map(sectionFromWire) };
14199
+ const page = e.page;
13937
14200
  if (pageIndex(d, page.key) >= 0)
13938
14201
  return c.add(PlanEditIssueCode.Duplicate, `${path}.page.key`, `page "${page.key}" is already in the plan`);
13939
14202
  const after = e.after === undefined ? d.pages.length - 1 : findPage(d, e.after, c, `${path}.after`);
@@ -13992,7 +14255,7 @@ const APPLY = {
13992
14255
  return;
13993
14256
  const sections = [...d.pages[at].sections];
13994
14257
  const index = e.at === undefined ? sections.length : clamp(e.at, sections.length);
13995
- sections.splice(index, 0, sectionFromWire(e.section));
14258
+ sections.splice(index, 0, e.section);
13996
14259
  setSections(d, at, sections);
13997
14260
  changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Added, page: d.pages[at].key, index });
13998
14261
  },
@@ -14045,7 +14308,7 @@ const APPLY = {
14045
14308
  changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Changed, page: d.pages[at].key, index: e.index });
14046
14309
  },
14047
14310
  [PlanEditOp.AddContentType]: (d, e, c, path, changed) => {
14048
- const type = contentTypeFromWire(e.contentType);
14311
+ const type = e.contentType;
14049
14312
  if (d.contentTypes.some(t => t.name === type.name))
14050
14313
  return c.add(PlanEditIssueCode.Duplicate, `${path}.contentType.name`, `content type "${type.name}" is already in the plan`);
14051
14314
  d.contentTypes.push(type);
@@ -14120,53 +14383,150 @@ const PLAN_EDITS_ANSWER_SHAPE = {
14120
14383
  edits: 'edit[] — applied in order, all or none; empty when cannot',
14121
14384
  reason: "string — when cannot, ONE short line for the owner in their language (what to do instead); else ''",
14122
14385
  };
14123
- const one = (value) => jsonOneOf([value]);
14124
- /** The schema of each op's value types (what `PLAN_EDIT_SHAPE` names). */
14125
- function opSchema(op) {
14126
- const entry = PLAN_EDIT_SHAPE[op];
14127
- const valueOf = (key) => {
14128
- const text = entry[key] ?? entry[`${key}?`];
14129
- if (key === 'op')
14130
- return one(op);
14131
- if (key === 'pattern')
14132
- return jsonOneOf(sectionPatternIds());
14133
- const head = text.split(' ')[0];
14134
- const table = {
14135
- page: pageJsonSchema, section: sectionJsonSchema, contentType: contentTypeJsonSchema, field: fieldJsonSchema,
14136
- 'dataSource[]': () => jsonListOf(dataSourceJsonSchema()), integer: jsonInteger, string: jsonText,
14137
- };
14138
- if (text.startsWith('string | string[]'))
14139
- return jsonAnyOf([jsonText(), jsonListOf(jsonText())]);
14140
- return (table[head] ?? jsonText)();
14141
- };
14142
- return jsonShapedObject(entry, Object.fromEntries(shapeKeys(entry).map(key => [key, valueOf(key)])), op);
14386
+ const NO_INDEX = -1;
14387
+ /** A list holding exactly one object: none → absent, one → it (expanded), more → the list (the gate refuses it). */
14388
+ const oneOf = (schema, expand, toWire, what) => ({
14389
+ text: `${what}[] — exactly one when the op takes it`,
14390
+ none: [],
14391
+ schema: () => jsonListOf(schema()),
14392
+ expand: v => (Array.isArray(v) ? (v.length === 0 ? undefined : v.length === 1 ? expand(v[0]) : v) : v),
14393
+ toWire: v => [toWire(v)],
14394
+ });
14395
+ const textKey = (text) => ({ text: `string — ${text}`, none: '', schema: jsonText });
14396
+ const indexKey = (text) => ({ text: `integer — ${text}`, none: NO_INDEX, schema: jsonInteger });
14397
+ const EDIT_WIRE = {
14398
+ page: textKey('a page key'),
14399
+ name: textKey('renamePage: its new name (the menu label)'),
14400
+ route: textKey('renamePage: its new route'),
14401
+ after: textKey('addPage: the key of the page it follows in the menu'),
14402
+ into: textKey('mergePages: the page that stays'),
14403
+ from: textKey('mergePages: the page whose sections move into it, then removed'),
14404
+ index: indexKey('the section, 0-based on its page'),
14405
+ to: indexKey('movePage / moveSection: the new 0-based place'),
14406
+ at: indexKey('addSection: its 0-based place on the page'),
14407
+ pattern: { text: 'string — swapPattern: the section pattern it becomes', none: '', schema: () => jsonOneOf(['', ...sectionPatternIds()]) },
14408
+ slot: textKey("setSlot: one of the pattern's slots"),
14409
+ text: textKey("setSlot: the words, in the owner's language and voice"),
14410
+ binding: textKey('setSectionData: the list source the section repeats'),
14411
+ contentType: textKey('addField: a content type of the plan'),
14412
+ newPage: oneOf(wirePageJsonSchema, expandWirePage, pageToWire, 'page'),
14413
+ newSection: oneOf(wireSectionJsonSchema, expandWireSection, sectionToWire, 'section'),
14414
+ newContentType: oneOf(wireContentTypeJsonSchema, expandWireContentType, contentTypeToWire, 'contentType'),
14415
+ dataSources: {
14416
+ text: 'dataSource[] — addContentType: the list / single sources its pages read', none: [],
14417
+ schema: () => jsonListOf(wireDataSourceJsonSchema()),
14418
+ expand: v => (Array.isArray(v) ? v.map(expandWireDataSource) : v),
14419
+ toWire: (v) => v.map(dataSourceToWire),
14420
+ },
14421
+ newField: oneOf(wireFieldJsonSchema, expandWireField, fieldToWire, 'field'),
14422
+ sample: { text: "string | string[] — addField: the samples' value for it", none: '', schema: () => jsonAnyOf([jsonText(), jsonListOf(jsonText())]) },
14423
+ };
14424
+ /** Each op's keys (`PLAN_EDIT_SHAPE`) → the wire key that carries it. */
14425
+ const PLAN_EDIT_WIRE_KEYS = {
14426
+ [PlanEditOp.AddPage]: { page: 'newPage', after: 'after' },
14427
+ [PlanEditOp.RemovePage]: { page: 'page' },
14428
+ [PlanEditOp.RenamePage]: { page: 'page', name: 'name', route: 'route' },
14429
+ [PlanEditOp.MergePages]: { into: 'into', from: 'from' },
14430
+ [PlanEditOp.MovePage]: { page: 'page', to: 'to' },
14431
+ [PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: 'at' },
14432
+ [PlanEditOp.RemoveSection]: { page: 'page', index: 'index' },
14433
+ [PlanEditOp.MoveSection]: { page: 'page', from: 'index', to: 'to' },
14434
+ [PlanEditOp.SwapPattern]: { page: 'page', index: 'index', pattern: 'pattern' },
14435
+ [PlanEditOp.SetSlot]: { page: 'page', index: 'index', slot: 'slot', text: 'text' },
14436
+ [PlanEditOp.AddContentType]: { contentType: 'newContentType', dataSources: 'dataSources' },
14437
+ [PlanEditOp.AddField]: { contentType: 'contentType', field: 'newField', sample: 'sample' },
14438
+ [PlanEditOp.SetSectionData]: { page: 'page', index: 'index', binding: 'binding' },
14439
+ };
14440
+ const isEditWireNone = (wireKey, v) => {
14441
+ const none = EDIT_WIRE[wireKey].none;
14442
+ return v === undefined || (Array.isArray(none) ? Array.isArray(v) && v.length === 0 : v === none) || (wireKey === 'sample' && Array.isArray(v) && v.length === 0);
14443
+ };
14444
+ const noneText = (none) => (Array.isArray(none) ? '[]' : typeof none === 'string' ? "''" : String(none));
14445
+ /** The flat edit's shape table: every key required, each with its none. */
14446
+ const PLAN_EDIT_WIRE_SHAPE = {
14447
+ op: `${OPS.map(op => `'${op}'`).join(' | ')} — which keys below are read`,
14448
+ ...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, `${spec.text} — ${noneText(spec.none)} when the op does not read it`])),
14449
+ };
14450
+ /** Per op, the wire keys it reads, in `PLAN_EDIT_SHAPE`'s words (`?` = may hold its none) — what the model reads. */
14451
+ function planEditWireOps() {
14452
+ return Object.fromEntries(OPS.map(op => {
14453
+ const entry = PLAN_EDIT_SHAPE[op];
14454
+ const keys = PLAN_EDIT_WIRE_KEYS[op];
14455
+ return [op, Object.fromEntries(Object.entries(keys).map(([planKey, wireKey]) => {
14456
+ const optionalKey = `${planKey}?` in entry;
14457
+ return [`${wireKey}${optionalKey ? '?' : ''}`, entry[optionalKey ? `${planKey}?` : planKey]];
14458
+ }))];
14459
+ }));
14143
14460
  }
14144
- /** The format a sentence's answer is written in: the outcome, the edits (one `anyOf` branch per op), the reason. */
14461
+ /** Op keys no wire key carries, or wire keys that do not exist — always empty (a spec and the builder hold it). */
14462
+ function planEditWireGaps() {
14463
+ const gaps = [];
14464
+ for (const op of OPS) {
14465
+ const keys = PLAN_EDIT_WIRE_KEYS[op] ?? {};
14466
+ for (const planKey of shapeKeys(PLAN_EDIT_SHAPE[op])) {
14467
+ if (planKey === 'op')
14468
+ continue;
14469
+ if (!keys[planKey])
14470
+ gaps.push(`${op}.${planKey}`);
14471
+ else if (!EDIT_WIRE[keys[planKey]])
14472
+ gaps.push(`${op}.${planKey} → ${keys[planKey]}`);
14473
+ }
14474
+ for (const planKey of Object.keys(keys))
14475
+ if (!shapeKeys(PLAN_EDIT_SHAPE[op]).includes(planKey))
14476
+ gaps.push(`${op}.${planKey} (not an op key)`);
14477
+ }
14478
+ return gaps;
14479
+ }
14480
+ /** The format a sentence's answer is written in: the outcome, the flat edits, the reason. */
14145
14481
  function planEditsJsonSchema() {
14482
+ const gaps = planEditWireGaps();
14483
+ if (gaps.length)
14484
+ throw new PlanWireShapeError(`the plan edits wire does not carry ${gaps.join(', ')}`);
14485
+ const edit = jsonShapedObject(PLAN_EDIT_WIRE_SHAPE, {
14486
+ op: jsonOneOf(OPS),
14487
+ ...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, spec.schema()])),
14488
+ }, 'edit');
14146
14489
  return jsonShapedObject(PLAN_EDITS_ANSWER_SHAPE, {
14147
14490
  outcome: jsonOneOf(Object.values(PlanEditsOutcome)),
14148
- edits: jsonListOf(jsonAnyOf(OPS.map(op => opSchema(op)))),
14491
+ edits: jsonListOf(edit),
14149
14492
  reason: jsonText(),
14150
14493
  }, 'planEdits');
14151
14494
  }
14152
- /** A model's answer → edits (sections and content types off the wire); never repaired. */
14495
+ /** One flat edit → the op's edit: the keys it reads, a none left out where the op may go without it. Never repaired. */
14496
+ function expandWireEdit(wire) {
14497
+ if (!isPlainObject(wire))
14498
+ return wire;
14499
+ const keys = PLAN_EDIT_WIRE_KEYS[wire['op']];
14500
+ if (!keys)
14501
+ return wire;
14502
+ const entry = PLAN_EDIT_SHAPE[wire['op']];
14503
+ const out = Object.fromEntries(Object.entries(wire).filter(([k]) => k === 'op' || !(k in EDIT_WIRE)));
14504
+ for (const [planKey, wireKey] of Object.entries(keys)) {
14505
+ const raw = wire[wireKey];
14506
+ if (`${planKey}?` in entry && isEditWireNone(wireKey, raw))
14507
+ continue;
14508
+ const value = EDIT_WIRE[wireKey].expand ? EDIT_WIRE[wireKey].expand(raw) : raw;
14509
+ if (value !== undefined)
14510
+ out[planKey] = value;
14511
+ }
14512
+ return out;
14513
+ }
14514
+ /** An edit → the flat wire edit (what a model would write — fakes and specs write it). */
14515
+ function planEditToWire(edit) {
14516
+ const out = { op: edit.op, ...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, Array.isArray(spec.none) ? [] : spec.none])) };
14517
+ for (const [planKey, wireKey] of Object.entries(PLAN_EDIT_WIRE_KEYS[edit.op])) {
14518
+ const value = edit[planKey];
14519
+ if (value === undefined)
14520
+ continue;
14521
+ out[wireKey] = EDIT_WIRE[wireKey].toWire ? EDIT_WIRE[wireKey].toWire(value) : value;
14522
+ }
14523
+ return out;
14524
+ }
14525
+ /** A model's answer → edits (each flat edit expanded); never repaired. */
14153
14526
  function planEditsFromWire(answer) {
14154
14527
  if (!isPlainObject(answer) || !Array.isArray(answer['edits']))
14155
14528
  return answer;
14156
- const edits = answer['edits'].map(raw => {
14157
- if (!isPlainObject(raw))
14158
- return raw;
14159
- const e = { ...raw };
14160
- if (isPlainObject(e['page']) && Array.isArray(e['page']['sections'])) {
14161
- e['page'] = { ...e['page'], sections: e['page']['sections'].map(sectionFromWire) };
14162
- }
14163
- if (isPlainObject(e['section']))
14164
- e['section'] = sectionFromWire(e['section']);
14165
- if (isPlainObject(e['contentType']))
14166
- e['contentType'] = contentTypeFromWire(e['contentType']);
14167
- return e;
14168
- });
14169
- return { ...answer, edits };
14529
+ return { ...answer, edits: answer['edits'].map(expandWireEdit) };
14170
14530
  }
14171
14531
 
14172
14532
  /**
@@ -14745,5 +15105,5 @@ function addStockCredits(footer, credits) {
14745
15105
  * Generated bundle index. Do not edit.
14746
15106
  */
14747
15107
 
14748
- export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPE, PLAN_EDITS_SCHEMA_NAME, PLAN_EDIT_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_SCHEMA_NAME, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SitePlanWireKey, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypeFromWire, contentTypeJsonSchema, contentTypePreviewFields, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceJsonSchema, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, failedBuildStep, fieldJsonSchema, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageJsonSchema, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditsFromWire, planEditsJsonSchema, planEntrances, planFromTemplate, planNavShape, planStockPhotos, planWordIssues, publishRefusal, publishVerdict, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionFromWire, sectionJsonSchema, sectionPatternIds, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, sitePlanFromWire, sitePlanJsonSchema, sitePlanToWire, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, validatePlanEdits, validateSitePlan, walkSerialized, wordPattern, writtenTexts };
15108
+ export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPE, PLAN_EDITS_SCHEMA_NAME, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_EDIT_WIRE_SHAPE, PLAN_WIRE_DERIVED, PLAN_WIRE_SCHEMA_NAME, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditToWire, planEditWireGaps, planEditWireOps, planEditsFromWire, planEditsJsonSchema, planEntrances, planFromTemplate, planNavShape, planStockPhotos, planToWire, planWireGaps, planWireJsonSchema, planWordIssues, publishRefusal, publishVerdict, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, wordPattern, writtenTexts };
14749
15109
  //# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map