@bettercms-ai/kit 2.0.0-next.1 → 2.0.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/lossy.d.ts +4 -0
- package/dist/adapters/lossy.js +8 -2
- package/dist/adapters/lossy.js.map +1 -1
- package/dist/adapters/schema.js +28 -0
- package/dist/adapters/schema.js.map +1 -1
- package/dist/controls/rich-text/blocks.d.ts +6 -0
- package/dist/controls/rich-text/blocks.js +187 -0
- package/dist/controls/rich-text/blocks.js.map +1 -1
- package/dist/controls/rich-text/catalog.d.ts +2 -5
- package/dist/controls/rich-text/catalog.js +104 -6
- package/dist/controls/rich-text/catalog.js.map +1 -1
- package/dist/controls/rich-text/codec.d.ts +10 -1
- package/dist/controls/rich-text/codec.js +7 -0
- package/dist/controls/rich-text/codec.js.map +1 -1
- package/dist/controls/rich-text/control.js +1 -0
- package/dist/controls/rich-text/control.js.map +1 -1
- package/dist/controls/rich-text/dom.d.ts +79 -0
- package/dist/controls/rich-text/dom.js +159 -7
- package/dist/controls/rich-text/dom.js.map +1 -1
- package/dist/controls/rich-text/editor.d.ts +4 -2
- package/dist/controls/rich-text/editor.js +93 -3
- package/dist/controls/rich-text/editor.js.map +1 -1
- package/dist/controls/rich-text/ops.js +6 -0
- package/dist/controls/rich-text/ops.js.map +1 -1
- package/dist/controls/rich-text/toc.d.ts +3 -2
- package/dist/controls/rich-text/toc.js +1 -2
- package/dist/controls/rich-text/toc.js.map +1 -1
- package/dist/core/core.d.ts +1 -0
- package/dist/core/core.js +2 -0
- package/dist/core/core.js.map +1 -1
- package/dist/core/facets.d.ts +8 -0
- package/dist/core/facets.js +8 -0
- package/dist/core/facets.js.map +1 -1
- package/dist/library/kit-catalog.json +4 -2
- package/dist/schema/types.d.ts +8 -0
- package/dist/schema/types.js.map +1 -1
- package/package.json +2 -2
package/dist/adapters/lossy.d.ts
CHANGED
|
@@ -169,6 +169,10 @@ export declare const FEATURES: {
|
|
|
169
169
|
readonly backlog: "corpus-fix";
|
|
170
170
|
readonly reason: "A field's time zone is true, false, \"site\" or an IANA zone name, on a date or datetime.";
|
|
171
171
|
};
|
|
172
|
+
readonly "richtext references": {
|
|
173
|
+
readonly backlog: "corpus-fix";
|
|
174
|
+
readonly reason: "A rich text's references (config.references) are { posts?, authors? }, each a content-model id (letters, digits, - and _, 1-64), on a rich text or document field only: anything else is not stored.";
|
|
175
|
+
};
|
|
172
176
|
readonly "richtext features": {
|
|
173
177
|
readonly backlog: "corpus-fix";
|
|
174
178
|
readonly reason: "A rich text's features (config.features) are distinct names from types RICHTEXT_FEATURES, on a rich text or document field only: a name outside it is not stored, and the rest are.";
|
package/dist/adapters/lossy.js
CHANGED
|
@@ -136,6 +136,10 @@ var FEATURES = {
|
|
|
136
136
|
backlog: "corpus-fix",
|
|
137
137
|
reason: "A field's time zone is true, false, \"site\" or an IANA zone name, on a date or datetime."
|
|
138
138
|
},
|
|
139
|
+
"richtext references": {
|
|
140
|
+
backlog: "corpus-fix",
|
|
141
|
+
reason: "A rich text's references (config.references) are { posts?, authors? }, each a content-model id (letters, digits, - and _, 1-64), on a rich text or document field only: anything else is not stored."
|
|
142
|
+
},
|
|
139
143
|
"richtext features": {
|
|
140
144
|
backlog: "corpus-fix",
|
|
141
145
|
reason: "A rich text's features (config.features) are distinct names from types RICHTEXT_FEATURES, on a rich text or document field only: a name outside it is not stored, and the rest are."
|
|
@@ -335,7 +339,8 @@ var KIT_READ_CONFIG_KEYS = {
|
|
|
335
339
|
"pattern",
|
|
336
340
|
"patternMessage",
|
|
337
341
|
"inputType",
|
|
338
|
-
"features"
|
|
342
|
+
"features",
|
|
343
|
+
"references"
|
|
339
344
|
],
|
|
340
345
|
document: [
|
|
341
346
|
"minLength",
|
|
@@ -343,7 +348,8 @@ var KIT_READ_CONFIG_KEYS = {
|
|
|
343
348
|
"pattern",
|
|
344
349
|
"patternMessage",
|
|
345
350
|
"inputType",
|
|
346
|
-
"features"
|
|
351
|
+
"features",
|
|
352
|
+
"references"
|
|
347
353
|
],
|
|
348
354
|
number: [
|
|
349
355
|
"min",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lossy.js","names":[],"sources":["../../src/adapters/lossy.ts"],"sourcesContent":["/**\n * LOSSY ITEMS — every V2 schema feature the production language cannot hold is named, never dropped silently.\n * CATALOG ITEMS (below) are the V2 metadata a host sets at seeding time, which the dock never stores.\n *\n * `path` is a JSON pointer into the V2 TypeSchema (\"/fields/3/of/0/fields/1/localized\"). `feature` is a stable name\n * from FEATURES (what the lossy ledger counts), `reason` says why prod cannot keep it, and FEATURES[feature].backlog\n * names what removes the loss.\n */\nexport interface LossyItem {\n path: string;\n feature: LossyFeature;\n reason: string;\n}\n\n/**\n * What removes a lossy feature: a named backend backlog item (its receipt is this feature's lossy count reaching 0), or\n * an explicit disposition that is not backend work: `corpus-fix` (the V2 schema breaks a prod rule the author can\n * satisfy: rename, shorten, reorder, drop a key the type cannot use), `dashboard:F12` (prod stores it; only the dock's\n * DockFieldTabs drops it, and the kit's GroupTabs replaces that at the dock cutover), `wontfix:depth` (a prod rule kept\n * on purpose: the backend refuses an 11th level with field_depth_exceeded), `wontfix:item-config` (the backend refuses\n * per-item config on a list of scalars on purpose, array_config_key_unknown), `wontfix:nesting` (the backend keeps a\n * slot's settings leaf fields, on purpose: slot_settings_invalid), `deferred:shopify` (owner 2026-10-11: a Shopify\n * reference stays locked text until the Shopify integration pipeline is built; allowed-deviations.json \"deferred\"). Owner 2026-10-11 (C11): the backend keeps a scalar list's\n * item settings (config.item), a list's bounds to 500, openMode on code, a slot's allow names (config.allow), a dotted\n * document preview path, the tabs no top-level prop sits in (components.tabs) and a nested field's tab (ui.tab { id }).\n * corpus-fix items are V2 corpus defects, an\n * owner note for the demo repo: the sandbox renders them from the V2 schema, seeding skips them.\n *\n * Landed on origin/dev and mapped here, so they name no feature any more: prop-reference (73a120c50), prop reference targets + filter (a4ce1ae95; shopify stays named), validation-parity\n * (dd021ceba), option-meta (ab15fe893), chrome-keys (98e48b9e3, 324580b2e), adapter:icons (the V2 icon vocabulary,\n * e126bf33d), localized-flag (279a10490), table / polymorphic list nesting (B1/B2: a data table or a polymorphic list at\n * any depth, in an object, a list's rows or an item type), slot nesting (C1/C2: a slot prop at any depth). What those\n * keys still cannot hold is a value the backend refuses, named below as corpus-fix.\n */\nexport type Backlog =\n | \"reference-targets\"\n | \"table\"\n | \"slot\"\n | \"block-model-fields\"\n | \"corpus-fix\"\n | \"dashboard:F12\"\n | \"wontfix:depth\"\n | \"wontfix:nesting\"\n | \"wontfix:item-config\"\n | \"deferred:shopify\";\n\nexport const FEATURES = {\n /* field types prod has no home for (the value is kept in a json field, or a near type) */\n \"type:table\": { backlog: \"table\", reason: \"Prod has no table field type (BE: a new type): the TableValue is stored verbatim in a json field.\" },\n \"type:slot\": { backlog: \"slot\", reason: \"Blocks inside a block on a content model: the backend's slot field (kind:'block' models by blockSlugs) is not mapped by this adapter yet, so it is stored as json. A component's top-level slot is a slot prop.\" },\n \"type:polymorphic-array\": { backlog: \"slot\", reason: \"An array of several item types: a model stores it as modular and a prop as a polymorphic list (table, config.mode \\\"types\\\"), both of object item types only. A list mixing scalar item types is stored as json.\" },\n \"item chrome (types)\": { backlog: \"slot\", reason: \"A polymorphic list's item type is { name, label?, fields, icon?, preview? } (item_types_invalid): it keeps its dock icon and row preview, and no other chrome.\" },\n \"type:primitive-array\": { backlog: \"wontfix:item-config\", reason: \"A list of this item type has no prod array form: config.itemType is text, number, date, image, link or richtext. Stored as json.\" },\n \"depth>10\": { backlog: \"wontfix:depth\", reason: \"Prod allows 10 field levels: the object at level 10 that would nest an 11th is stored as json.\" },\n /* document level */\n \"type name\": { backlog: \"corpus-fix\", reason: \"The schema name is stored as a kebab-case slug, which does not read back as this name.\" },\n /* tabs and sections */\n \"group name>40\": { backlog: \"corpus-fix\", reason: \"A tab name is at most 40 characters; truncated.\" },\n \"group title\": { backlog: \"corpus-fix\", reason: \"A prop tab's label is the group title trimmed (1–20 characters): a blank title defines no tab, so the group is not stored and its props sit in the default tab; spaces around a title are not kept.\" },\n \"group default\": { backlog: \"corpus-fix\", reason: \"The default tab is always the first one in prod.\" },\n \"group icon\": { backlog: \"corpus-fix\", reason: \"A tab icon is a dock icon name (DOCK_ICON_NAMES); this name is not one.\" },\n \"group (nested)\": { backlog: \"corpus-fix\", reason: \"A nested field names its tab as ui.tab { id } only when the tab is stored: this group is not (named on the group itself).\" },\n \"groups>3\": { backlog: \"dashboard:F12\", reason: \"The dock shows at most 3 tabs (DockFieldTabs), so the adapter keeps 3 until the kit GroupTabs replaces DockFieldTabs at the dock cutover: later groups' fields land in the default tab meanwhile.\" },\n \"group name collision\": { backlog: \"corpus-fix\", reason: \"Two tabs share an id, or a stored name (the first 20 characters, trimmed, any case): the backend refuses that (tab_name, fieldset ids), so the later tab is not stored.\" },\n \"prop tab limit\": { backlog: \"corpus-fix\", reason: \"A component holds at most 8 tabs, each with an id of 1–40 characters (the backend's ui.tab rule): this group is not stored and its props sit in the default tab.\" },\n \"fieldset spans groups\": { backlog: \"corpus-fix\", reason: \"A prod fieldset sits in one tab; this field's tab differs from its fieldset's.\" },\n \"fieldset (props)\": { backlog: \"corpus-fix\", reason: \"A prop group is defined on the first prop that uses it: a fieldset no prop uses is not stored (and the ones after it move up a place).\" },\n \"fieldset name>limit\": { backlog: \"corpus-fix\", reason: \"A fieldset name is at most 60 characters (a prop group label 32); truncated.\" },\n \"fieldset collision\": { backlog: \"corpus-fix\", reason: \"A fieldset shares its id with a group, or this field names a fieldset the schema does not define.\" },\n /* field chrome and rules */\n \"duplicate name\": { backlog: \"corpus-fix\", reason: \"Two sibling fields share a name; prod keys are unique per level, so the later one is renamed.\" },\n \"reserved name\": {\n backlog: \"corpus-fix\",\n reason: \"A model field's key is not one the API reserves (types' KEY_RESERVED: derived, layout, page, a forbidden key or a leading _) and is a letter, then letters, digits, - and _ (KEY_FORMAT): this one is stored renamed. id is allowed.\",\n },\n rows: { backlog: \"corpus-fix\", reason: \"ui.rows is a whole number from 1 to 40, on a text or long text field only.\" },\n synced: { backlog: \"corpus-fix\", reason: \"ui.synced names its global in 1–64 characters (trimmed).\" },\n valueShape: { backlog: \"corpus-fix\", reason: \"ui.valueShape is for a link, image or button field only.\" },\n \"default value\": { backlog: \"corpus-fix\", reason: \"This initial value holds what the prod value cannot keep (named under it): a crop preset with no image size, a datetime without seconds, a button icon outside the dock vocabulary…\" },\n slotSettings: { backlog: \"wontfix:nesting\", reason: \"A slot's per-child settings are at most 20 leaf fields (slot_settings_invalid): this setting is an object, a list, a table, a slot or a form, or past the 20th, or the field holding them is not a slot.\" },\n icon: { backlog: \"corpus-fix\", reason: \"A field icon is ui.icon, a dock icon name, on a nesting field only (an object, a list of objects, a modular list); this field is a leaf, or the name is not a dock icon.\" },\n layout: { backlog: \"corpus-fix\", reason: \"This layout does not fit the field in prod: ui.control is dropdown / radio / grid on a select, cards on a select or multiselect, checkbox on a boolean or multiselect, switch on a boolean, segmented on a select of 2–4 options, slider on a number with min < max (≤ 1000 stops); a list of scalars or entries lays out as rows or a gallery.\" },\n \"help>limit\": { backlog: \"corpus-fix\", reason: \"Help text is at most 500 characters on a field and 200 on a prop (a placeholder 120, a prop group description 200); truncated.\" },\n \"option icon\": { backlog: \"corpus-fix\", reason: \"Option icons are dock icon names, all or none per list: an icon in this list is not a dock name, so the list stores none (option_icon_unknown).\" },\n \"option description\": { backlog: \"corpus-fix\", reason: \"An option description is one line of 1–140 characters (option_description_invalid).\" },\n condition: { backlog: \"corpus-fix\", reason: \"This condition has no prod showIf form: it names something that is not a sibling (the block's _variant is one only for a top-level prop: show_if_variant_field), names it by `scope` (prod resolves a rule at the nearest level that declares its field), or tests a value showIf cannot.\" },\n access: { backlog: \"corpus-fix\", reason: \"ui.access takes view \\\"schema.view\\\" and edit \\\"schema.manage\\\" only (access_cap_unknown), and a developer lock (readOnly \\\"developer\\\") is not also a plain read-only flag.\" },\n timezone: { backlog: \"corpus-fix\", reason: \"A field's time zone is true, false, \\\"site\\\" or an IANA zone name, on a date or datetime.\" },\n \"richtext features\": {\n backlog: \"corpus-fix\",\n reason: \"A rich text's features (config.features) are distinct names from types RICHTEXT_FEATURES, on a rich text or document field only: a name outside it is not stored, and the rest are.\",\n },\n \"shopify reference\": {\n backlog: \"deferred:shopify\",\n reason:\n \"A prop reference to Shopify: no Shopify integration backs one (reference_target_unsupported), so a Shopify-only reference is locked text holding the product id (SHOPIFY_REF_LOCK) and a mixed one keeps its other targets. Owner 2026-10-11: deferred until the Shopify integration pipeline is built (allowed-deviations.json status deferred).\",\n },\n \"reference target\": { backlog: \"reference-targets\", reason: \"A prop reference keeps entry models (contentModelId / allowedModelSlugs) and page, form and collection (config.targets, backend a4ce1ae95); Shopify has no integration behind it (reference_target_unsupported), so a shopify target is dropped and a reference with only shopify is stored as json. A content-model field keeps entry models only.\" },\n \"reference order\": { backlog: \"corpus-fix\", reason: \"Prod keeps a reference's entry models apart from its page / form / collection targets: the list reads back entry models first, then the targets.\" },\n \"reference filter\": { backlog: \"reference-targets\", reason: \"A prop reference keeps its filter as config.filter (1–500 characters, no $parameters or ^ parent: reference_filter_invalid); a content-model field keeps none.\" },\n \"numeric unit\": { backlog: \"corpus-fix\", reason: \"config.unit is a number's (1–24 characters); a rating has no unit.\" },\n \"rating bounds\": { backlog: \"corpus-fix\", reason: \"A prod rating runs from config.min (≥ 0) to config.max (a whole 1–10) in steps of config.step, min < max and at most 100 whole steps apart.\" },\n \"scale rules\": { backlog: \"corpus-fix\", reason: \"This scale breaks prod's scale rules: token values 1–32 characters and distinct, labels 1–24, at most 50 steps and 20 presets, a token preset naming a step, a { value, unit } preset needing allowCustom.\" },\n \"validation.patternMessage\": { backlog: \"corpus-fix\", reason: \"A pattern message needs a stored pattern and is 1–200 characters.\" },\n \"validation.pattern\": { backlog: \"corpus-fix\", reason: \"A pattern is kept on text, long text and url values, a regex of at most 200 characters that compiles.\" },\n \"validation.length\": { backlog: \"corpus-fix\", reason: \"Length limits are whole numbers ≥ 0, on text, long text, url and rich text values.\" },\n \"validation.items\": { backlog: \"corpus-fix\", reason: \"Item limits are whole numbers ≥ 0 on a list, min ≤ max: a tags max 1–500 and min 0–500, a prop's own list (table, array) at most 500 (LIST_ITEMS_CAP), a slot's at most 50 (SLOT_ITEMS_CAP).\" },\n \"validation.unique\": { backlog: \"corpus-fix\", reason: \"unique is for text, long text, slug, email, phone, link, number, select, date and single-model reference fields, and lists; a polymorphic reference has no one id to compare.\" },\n \"validation.recommendedMax\": { backlog: \"corpus-fix\", reason: \"A soft limit is a positive whole number in prod.\" },\n \"item identity\": { backlog: \"corpus-fix\", reason: \"A list item's name is an identifier (a letter, then letters, digits or _; ≤ 64) and its title 1–60 characters (ui.itemName / ui.itemTitle); an object item keeps no other key.\" },\n \"item settings\": { backlog: \"corpus-fix\", reason: \"A list of scalars keeps its item's description (1–200 characters), localized flag, required, a text or rich text item's length and a text item's pattern (config.item, array_item_settings_invalid): this setting is none of those, or out of their bounds (a multi-line text item reads back single-line).\" },\n itemNoun: { backlog: \"corpus-fix\", reason: \"The item noun is ui.itemLabel, capitalised, at most 24 characters.\" },\n \"block model field\": { backlog: \"block-model-fields\", reason: \"A modular item type is a kind:'block' model, which holds only fields a block primitive renders (text, long text, rich text, image, link, slug, email, phone, color, select, number, rating, date, datetime, boolean): this field is not stored.\" },\n \"item icon\": { backlog: \"corpus-fix\", reason: \"Prod keeps one icon per list (ui.icon, a dock name) and one per polymorphic item type: the list has its own icon, or this name is not a dock icon.\" },\n \"preview icon\": { backlog: \"corpus-fix\", reason: \"A row preview's icon is a dock icon name.\" },\n \"preview path\": { backlog: \"corpus-fix\", reason: \"A row preview names a child key, or a dotted path whose later segments are children or keys of an image, video, link, button, file, location, json, code or reference value, or a data table's caption or notes (preview_path_unknown).\" },\n openMode: { backlog: \"corpus-fix\", reason: \"openMode is for object, array, table and code fields only (openMode_leaf).\" },\n \"unsupported key\": { backlog: \"corpus-fix\", reason: \"This V2 key has no prod mapping.\" },\n} as const satisfies Record<string, { reason: string; backlog: Backlog }>;\n\nexport type LossyFeature = keyof typeof FEATURES;\n\n/**\n * CATALOG ITEMS — V2 metadata the dock never stores, set when a schema is seeded or placed by its host: not a loss of\n * stored content. Reported apart from LossyItem so the lossy ledger counts only what prod drops.\n */\nexport interface CatalogItem {\n path: string;\n feature: CatalogFeature;\n reason: string;\n}\n\nexport const CATALOG = {\n \"block icon\": \"The block picker's icon belongs to the catalog entry; a model carries none (a component stores it as component.icon, a dock icon, so only one with no dock name for it lands here).\",\n category: \"The block picker's category is catalog metadata. A component takes the nearest prod category (CATEGORY_TO_PROD) when there is one; a model has none.\",\n \"document preview\": \"The document-level preview (how a row of this type reads in a parent's list) has no place on a model; a component stores it as component.preview.\",\n \"document preview path\": \"component.preview names a top-level prop key or a dotted path into one (`slides.0.heading`, componentPreviewRefusals); a key no prop has, or a path through a renamed value key (`image.alt`), is not stored (preview_key_unknown), so the slot row reads the child's first text instead.\",\n \"kind:block (fields)\": \"A section's fields are stored as a model (kind model): which host places it is set at seeding.\",\n \"kind:global\": \"A global (synced) block is a host-level placement; its fields are stored as a model or a component.\",\n \"kind:page\": \"A page schema is stored as a model; the host makes it a page.\",\n \"kind:object\": \"An object type is stored as a model.\",\n \"kind:entry (props)\": \"Only blocks become components; the host decides what an entry schema is placed as.\",\n variants: \"Variants are separate components sharing a sectionType in prod: the host seeds one per variant.\",\n ai: \"The AI-assist sparkle is a host setting, not stored on the field.\",\n \"item openMode\": \"Prod keeps one openMode per list, the array's own; the V2 engine reads only that one too (lib/schema.ts itemOpenMode ignores the item's), so the item's changes nothing the dock shows.\",\n \"itemNoun (derived)\": \"A list with no itemNoun whose item title is not the field title's singular: ui.itemLabel keeps the item title, and the read side names the noun from it, which is the noun the V2 engine already falls back to (lib/schema.ts itemNoun).\",\n \"fieldset (derived)\": \"A field names a fieldset the schema does not declare: the V2 engine draws it as a section titled by its name (engine/field-list.tsx arrange), so it is stored as that prop group and the read side declares it. Not a loss.\",\n \"prop target\": \"A V2 prop has no binding path, so fromTypeSchema('props') returns none: seeding binds each prop to its target. Not a loss.\",\n \"block placement\": \"Where a block fits (inline / section / page) is the host's: the block picker and a body's Insert menu place it; a model or component carries none.\",\n \"slot allow\": \"A slot keeps its V2 allow list as config.allow (1–50 block names of 1–64 characters, no repeats: slot_allow_invalid), which the dock resolves to the project's components; this list breaks those bounds, so the slot takes any component.\",\n \"segmented fallback\": \"A segmented select of 5+ options: V2 draws the dropdown (D46, choice.tsx fitsSegmented) and prod stores the select with no control (control_segmented holds 2–4), which the dock draws as the same dropdown. Nothing drawn differs.\",\n \"seeded instance\": \"A starting value the host writes into a new instance when it seeds or places one: a schema-level initialValue, a reference prop's default (a definition holds none: reference_in_definition), or a slot's children (they name V2 block types, which become component instances at seeding).\",\n \"not applicable (props)\": \"unique on a scalar prop, or on a sub-field outside a table: each placement holds its own value, so there is nothing to be unique across (unique_prop_scope). The instance is the whole document.\",\n \"host config\": \"A prod-only prop config key the host reads, not the V2 schema: a rich text's Accept list, its one-paragraph flags (allowMultipleParagraphs, the older inline), allowTargetBlank and component allowlist (the dock's rich-text codec and insert menu read them from the stored prop: dock-v2 richTextCodec configAt, kitServices insertableComponents), a section label, and the import's write-back metadata no editor reads (SERVER_CONFIG_KEYS). Read back, it names no loss: the kit never writes a definition, so the config rides untouched.\",\n} as const;\n\nexport type CatalogFeature = keyof typeof CATALOG;\n\n/**\n * Keys the import (BE derive-schema.ts, register-editable-pages.ts, template-copy.ts) writes onto a field for the\n * SERVER: how publish writes a value back into the site's own HTML (`attr`, `numeric`, `iconFont`, `htmlClasses`,\n * `units`, `lead`), which band a field belongs to (`band`, `family`, `shape`), and import markers (`seeded`, `shared`,\n * `sharedValue`). No form reads them, so no form loses anything by leaving them alone, and no agent may write them: the\n * backend MCP drops them from what an agent sends, and its contract test fails on any key the import writes that is\n * neither here, a host key, nor read by the kit (kit-catalog.json `fieldConfig`).\n */\nexport const SERVER_CONFIG_KEYS: ReadonlySet<string> = new Set([\n \"seeded\", \"shared\", \"sharedValue\", \"attr\", \"numeric\", \"iconFont\", \"htmlClasses\", \"units\", \"lead\", \"band\", \"family\", \"shape\",\n]);\n\n/** The prop config keys CATALOG \"host config\" covers: any type's, then a rich text's (richtext, document) own. */\nexport const HOST_CONFIG_KEYS: { any: ReadonlySet<string>; richtext: ReadonlySet<string> } = {\n // A band's name, its kind and its own heading, which the derive lane writes on the band's first field.\n // `format`: how a number is shown ($1,200+), which the canvas, the classic preview and publish read; the value stays a number\n any: new Set([\"sectionLabel\", \"sectionShape\", \"sectionTitle\", \"format\", ...SERVER_CONFIG_KEYS]),\n // `hostTag` is read from the stored field by the rich-text host, like the rest. `features` is a V2 facet now (RT0, facets.ts)\n richtext: new Set([\"accept\", \"allowMultipleParagraphs\", \"inline\", \"allowTargetBlank\", \"components\", \"hostTag\"]),\n};\n\nexport const isHostConfigKey = (type: string, key: string): boolean =>\n HOST_CONFIG_KEYS.any.has(key) || ((type === \"richtext\" || type === \"document\") && HOST_CONFIG_KEYS.richtext.has(key));\n\n/**\n * The prod config keys the kit adapter READS, per prod field type (`*` = every type): what an agent may write on a\n * field and still edit it in the kit. Exported to the backend with the host and server keys (kit-catalog.json\n * `fieldConfig`), where the MCP warns on any other key and its contract test checks what the import writes.\n * field-config-contract.test.ts proves each entry against `toTypeSchema`, so this list cannot claim a key the\n * adapter drops; a key missing here only costs a warning.\n */\nexport const KIT_READ_CONFIG_KEYS: Readonly<Record<string, readonly string[]>> = {\n \"*\": [\"placeholder\", \"zones\", \"options\", \"fields\", \"linkHref\"],\n text: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n longtext: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n url: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n richtext: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\", \"features\"],\n document: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\", \"features\"],\n number: [\"min\", \"max\", \"step\", \"scale\", \"unit\", \"validation\"],\n rating: [\"min\", \"max\", \"step\", \"scale\", \"unit\", \"validation\"],\n select: [\"multiSelect\"],\n multiselect: [\"min\", \"max\", \"allowCustom\"],\n tags: [\"min\", \"max\", \"allowCustom\"],\n code: [\"language\", \"languages\"],\n date: [\"timezone\", \"includeTime\"],\n datetime: [\"timezone\", \"includeTime\"],\n reference: [\"contentModelId\", \"polymorphic\", \"allowedModelSlugs\", \"targets\", \"filter\"],\n \"multi-reference\": [\"contentModelId\", \"polymorphic\", \"allowedModelSlugs\", \"targets\", \"filter\", \"min\", \"max\"],\n array: [\"itemType\", \"minItems\", \"maxItems\", \"item\"],\n image: [\"media\"],\n modular: [\"blockSlugs\", \"minItems\", \"maxItems\"],\n};\n\n/** The field-config contract the backend reads (kit-catalog.json `fieldConfig`). */\nexport const fieldConfigContract = () => ({\n read: KIT_READ_CONFIG_KEYS,\n host: { any: [...HOST_CONFIG_KEYS.any].filter((k) => !SERVER_CONFIG_KEYS.has(k)), richtext: [...HOST_CONFIG_KEYS.richtext] },\n server: [...SERVER_CONFIG_KEYS],\n});\n"],"mappings":";AA8CA,IAAa,WAAW;CAEtB,cAAc;EAAE,SAAS;EAAS,QAAQ;CAAoG;CAC9I,aAAa;EAAE,SAAS;EAAQ,QAAQ;CAAkN;CAC1P,0BAA0B;EAAE,SAAS;EAAQ,QAAQ;CAAmN;CACxQ,uBAAuB;EAAE,SAAS;EAAQ,QAAQ;CAAiK;CACnN,wBAAwB;EAAE,SAAS;EAAuB,QAAQ;CAAmI;CACrM,YAAY;EAAE,SAAS;EAAiB,QAAQ;CAAiG;CAEjJ,aAAa;EAAE,SAAS;EAAc,QAAQ;CAAyF;CAEvI,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAAkD;CACpG,eAAe;EAAE,SAAS;EAAc,QAAQ;CAAsM;CACtP,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAAmD;CACrG,cAAc;EAAE,SAAS;EAAc,QAAQ;CAA0E;CACzH,kBAAkB;EAAE,SAAS;EAAc,QAAQ;CAA4H;CAC/K,YAAY;EAAE,SAAS;EAAiB,QAAQ;CAAoM;CACpP,wBAAwB;EAAE,SAAS;EAAc,QAAQ;CAA0K;CACnO,kBAAkB;EAAE,SAAS;EAAc,QAAQ;CAAmK;CACtN,yBAAyB;EAAE,SAAS;EAAc,QAAQ;CAAiF;CAC3I,oBAAoB;EAAE,SAAS;EAAc,QAAQ;CAAyI;CAC9L,uBAAuB;EAAE,SAAS;EAAc,QAAQ;CAA+E;CACvI,sBAAsB;EAAE,SAAS;EAAc,QAAQ;CAAoG;CAE3J,kBAAkB;EAAE,SAAS;EAAc,QAAQ;CAAgG;CACnJ,iBAAiB;EACf,SAAS;EACT,QAAQ;CACV;CACA,MAAM;EAAE,SAAS;EAAc,QAAQ;CAA6E;CACpH,QAAQ;EAAE,SAAS;EAAc,QAAQ;CAA2D;CACpG,YAAY;EAAE,SAAS;EAAc,QAAQ;CAA2D;CACxG,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAAsL;CACxO,cAAc;EAAE,SAAS;EAAmB,QAAQ;CAA2M;CAC/P,MAAM;EAAE,SAAS;EAAc,QAAQ;CAA2K;CAClN,QAAQ;EAAE,SAAS;EAAc,QAAQ;CAAkV;CAC3X,cAAc;EAAE,SAAS;EAAc,QAAQ;CAAiI;CAChL,eAAe;EAAE,SAAS;EAAc,QAAQ;CAAkJ;CAClM,sBAAsB;EAAE,SAAS;EAAc,QAAQ;CAAsF;CAC7I,WAAW;EAAE,SAAS;EAAc,QAAQ;CAA4R;CACxU,QAAQ;EAAE,SAAS;EAAc,QAAQ;CAA+K;CACxN,UAAU;EAAE,SAAS;EAAc,QAAQ;CAA4F;CACvI,qBAAqB;EACnB,SAAS;EACT,QAAQ;CACV;CACA,qBAAqB;EACnB,SAAS;EACT,QACE;CACJ;CACA,oBAAoB;EAAE,SAAS;EAAqB,QAAQ;CAAsV;CAClZ,mBAAmB;EAAE,SAAS;EAAc,QAAQ;CAAmJ;CACvM,oBAAoB;EAAE,SAAS;EAAqB,QAAQ;CAAiK;CAC7N,gBAAgB;EAAE,SAAS;EAAc,QAAQ;CAAqE;CACtH,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAA8I;CAChM,eAAe;EAAE,SAAS;EAAc,QAAQ;CAA6M;CAC7P,6BAA6B;EAAE,SAAS;EAAc,QAAQ;CAAoE;CAClI,sBAAsB;EAAE,SAAS;EAAc,QAAQ;CAAwG;CAC/J,qBAAqB;EAAE,SAAS;EAAc,QAAQ;CAAqF;CAC3I,oBAAoB;EAAE,SAAS;EAAc,QAAQ;CAA+L;CACpP,qBAAqB;EAAE,SAAS;EAAc,QAAQ;CAAgL;CACtO,6BAA6B;EAAE,SAAS;EAAc,QAAQ;CAAmD;CACjH,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAAiL;CACnO,iBAAiB;EAAE,SAAS;EAAc,QAAQ;CAA8S;CAChW,UAAU;EAAE,SAAS;EAAc,QAAQ;CAAqE;CAChH,qBAAqB;EAAE,SAAS;EAAsB,QAAQ;CAAkP;CAChT,aAAa;EAAE,SAAS;EAAc,QAAQ;CAAqJ;CACnM,gBAAgB;EAAE,SAAS;EAAc,QAAQ;CAA4C;CAC7F,gBAAgB;EAAE,SAAS;EAAc,QAAQ;CAA0O;CAC3R,UAAU;EAAE,SAAS;EAAc,QAAQ;CAA6E;CACxH,mBAAmB;EAAE,SAAS;EAAc,QAAQ;CAAmC;AACzF;AAcA,IAAa,UAAU;CACrB,cAAc;CACd,UAAU;CACV,oBAAoB;CACpB,yBAAyB;CACzB,uBAAuB;CACvB,eAAe;CACf,aAAa;CACb,eAAe;CACf,sBAAsB;CACtB,UAAU;CACV,IAAI;CACJ,iBAAiB;CACjB,sBAAsB;CACtB,sBAAsB;CACtB,eAAe;CACf,mBAAmB;CACnB,cAAc;CACd,sBAAsB;CACtB,mBAAmB;CACnB,0BAA0B;CAC1B,eAAe;AACjB;;;;;;;;;AAYA,IAAa,qCAA0C,IAAI,IAAI;CAC7D;CAAU;CAAU;CAAe;CAAQ;CAAW;CAAY;CAAe;CAAS;CAAQ;CAAQ;CAAU;AACtH,CAAC;;AAGD,IAAa,mBAAgF;CAG3F,qBAAK,IAAI,IAAI;EAAC;EAAgB;EAAgB;EAAgB;EAAU,GAAG;CAAkB,CAAC;CAE9F,0BAAU,IAAI,IAAI;EAAC;EAAU;EAA2B;EAAU;EAAoB;EAAc;CAAS,CAAC;AAChH;AAEA,IAAa,mBAAmB,MAAc,QAC5C,iBAAiB,IAAI,IAAI,GAAG,MAAO,SAAS,cAAc,SAAS,eAAe,iBAAiB,SAAS,IAAI,GAAG;;;;;;;;AASrH,IAAa,uBAAoE;CAC/E,KAAK;EAAC;EAAe;EAAS;EAAW;EAAU;CAAU;CAC7D,MAAM;EAAC;EAAa;EAAa;EAAW;EAAkB;CAAW;CACzE,UAAU;EAAC;EAAa;EAAa;EAAW;EAAkB;CAAW;CAC7E,KAAK;EAAC;EAAa;EAAa;EAAW;EAAkB;CAAW;CACxE,UAAU;EAAC;EAAa;EAAa;EAAW;EAAkB;EAAa;CAAU;CACzF,UAAU;EAAC;EAAa;EAAa;EAAW;EAAkB;EAAa;CAAU;CACzF,QAAQ;EAAC;EAAO;EAAO;EAAQ;EAAS;EAAQ;CAAY;CAC5D,QAAQ;EAAC;EAAO;EAAO;EAAQ;EAAS;EAAQ;CAAY;CAC5D,QAAQ,CAAC,aAAa;CACtB,aAAa;EAAC;EAAO;EAAO;CAAa;CACzC,MAAM;EAAC;EAAO;EAAO;CAAa;CAClC,MAAM,CAAC,YAAY,WAAW;CAC9B,MAAM,CAAC,YAAY,aAAa;CAChC,UAAU,CAAC,YAAY,aAAa;CACpC,WAAW;EAAC;EAAkB;EAAe;EAAqB;EAAW;CAAQ;CACrF,mBAAmB;EAAC;EAAkB;EAAe;EAAqB;EAAW;EAAU;EAAO;CAAK;CAC3G,OAAO;EAAC;EAAY;EAAY;EAAY;CAAM;CAClD,OAAO,CAAC,OAAO;CACf,SAAS;EAAC;EAAc;EAAY;CAAU;AAChD;;AAGA,IAAa,6BAA6B;CACxC,MAAM;CACN,MAAM;EAAE,KAAK,CAAC,GAAG,iBAAiB,GAAG,CAAC,CAAC,QAAQ,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC;EAAG,UAAU,CAAC,GAAG,iBAAiB,QAAQ;CAAE;CAC3H,QAAQ,CAAC,GAAG,kBAAkB;AAChC"}
|
|
1
|
+
{"version":3,"file":"lossy.js","names":[],"sources":["../../src/adapters/lossy.ts"],"sourcesContent":["/**\n * LOSSY ITEMS — every V2 schema feature the production language cannot hold is named, never dropped silently.\n * CATALOG ITEMS (below) are the V2 metadata a host sets at seeding time, which the dock never stores.\n *\n * `path` is a JSON pointer into the V2 TypeSchema (\"/fields/3/of/0/fields/1/localized\"). `feature` is a stable name\n * from FEATURES (what the lossy ledger counts), `reason` says why prod cannot keep it, and FEATURES[feature].backlog\n * names what removes the loss.\n */\nexport interface LossyItem {\n path: string;\n feature: LossyFeature;\n reason: string;\n}\n\n/**\n * What removes a lossy feature: a named backend backlog item (its receipt is this feature's lossy count reaching 0), or\n * an explicit disposition that is not backend work: `corpus-fix` (the V2 schema breaks a prod rule the author can\n * satisfy: rename, shorten, reorder, drop a key the type cannot use), `dashboard:F12` (prod stores it; only the dock's\n * DockFieldTabs drops it, and the kit's GroupTabs replaces that at the dock cutover), `wontfix:depth` (a prod rule kept\n * on purpose: the backend refuses an 11th level with field_depth_exceeded), `wontfix:item-config` (the backend refuses\n * per-item config on a list of scalars on purpose, array_config_key_unknown), `wontfix:nesting` (the backend keeps a\n * slot's settings leaf fields, on purpose: slot_settings_invalid), `deferred:shopify` (owner 2026-10-11: a Shopify\n * reference stays locked text until the Shopify integration pipeline is built; allowed-deviations.json \"deferred\"). Owner 2026-10-11 (C11): the backend keeps a scalar list's\n * item settings (config.item), a list's bounds to 500, openMode on code, a slot's allow names (config.allow), a dotted\n * document preview path, the tabs no top-level prop sits in (components.tabs) and a nested field's tab (ui.tab { id }).\n * corpus-fix items are V2 corpus defects, an\n * owner note for the demo repo: the sandbox renders them from the V2 schema, seeding skips them.\n *\n * Landed on origin/dev and mapped here, so they name no feature any more: prop-reference (73a120c50), prop reference targets + filter (a4ce1ae95; shopify stays named), validation-parity\n * (dd021ceba), option-meta (ab15fe893), chrome-keys (98e48b9e3, 324580b2e), adapter:icons (the V2 icon vocabulary,\n * e126bf33d), localized-flag (279a10490), table / polymorphic list nesting (B1/B2: a data table or a polymorphic list at\n * any depth, in an object, a list's rows or an item type), slot nesting (C1/C2: a slot prop at any depth). What those\n * keys still cannot hold is a value the backend refuses, named below as corpus-fix.\n */\nexport type Backlog =\n | \"reference-targets\"\n | \"table\"\n | \"slot\"\n | \"block-model-fields\"\n | \"corpus-fix\"\n | \"dashboard:F12\"\n | \"wontfix:depth\"\n | \"wontfix:nesting\"\n | \"wontfix:item-config\"\n | \"deferred:shopify\";\n\nexport const FEATURES = {\n /* field types prod has no home for (the value is kept in a json field, or a near type) */\n \"type:table\": { backlog: \"table\", reason: \"Prod has no table field type (BE: a new type): the TableValue is stored verbatim in a json field.\" },\n \"type:slot\": { backlog: \"slot\", reason: \"Blocks inside a block on a content model: the backend's slot field (kind:'block' models by blockSlugs) is not mapped by this adapter yet, so it is stored as json. A component's top-level slot is a slot prop.\" },\n \"type:polymorphic-array\": { backlog: \"slot\", reason: \"An array of several item types: a model stores it as modular and a prop as a polymorphic list (table, config.mode \\\"types\\\"), both of object item types only. A list mixing scalar item types is stored as json.\" },\n \"item chrome (types)\": { backlog: \"slot\", reason: \"A polymorphic list's item type is { name, label?, fields, icon?, preview? } (item_types_invalid): it keeps its dock icon and row preview, and no other chrome.\" },\n \"type:primitive-array\": { backlog: \"wontfix:item-config\", reason: \"A list of this item type has no prod array form: config.itemType is text, number, date, image, link or richtext. Stored as json.\" },\n \"depth>10\": { backlog: \"wontfix:depth\", reason: \"Prod allows 10 field levels: the object at level 10 that would nest an 11th is stored as json.\" },\n /* document level */\n \"type name\": { backlog: \"corpus-fix\", reason: \"The schema name is stored as a kebab-case slug, which does not read back as this name.\" },\n /* tabs and sections */\n \"group name>40\": { backlog: \"corpus-fix\", reason: \"A tab name is at most 40 characters; truncated.\" },\n \"group title\": { backlog: \"corpus-fix\", reason: \"A prop tab's label is the group title trimmed (1–20 characters): a blank title defines no tab, so the group is not stored and its props sit in the default tab; spaces around a title are not kept.\" },\n \"group default\": { backlog: \"corpus-fix\", reason: \"The default tab is always the first one in prod.\" },\n \"group icon\": { backlog: \"corpus-fix\", reason: \"A tab icon is a dock icon name (DOCK_ICON_NAMES); this name is not one.\" },\n \"group (nested)\": { backlog: \"corpus-fix\", reason: \"A nested field names its tab as ui.tab { id } only when the tab is stored: this group is not (named on the group itself).\" },\n \"groups>3\": { backlog: \"dashboard:F12\", reason: \"The dock shows at most 3 tabs (DockFieldTabs), so the adapter keeps 3 until the kit GroupTabs replaces DockFieldTabs at the dock cutover: later groups' fields land in the default tab meanwhile.\" },\n \"group name collision\": { backlog: \"corpus-fix\", reason: \"Two tabs share an id, or a stored name (the first 20 characters, trimmed, any case): the backend refuses that (tab_name, fieldset ids), so the later tab is not stored.\" },\n \"prop tab limit\": { backlog: \"corpus-fix\", reason: \"A component holds at most 8 tabs, each with an id of 1–40 characters (the backend's ui.tab rule): this group is not stored and its props sit in the default tab.\" },\n \"fieldset spans groups\": { backlog: \"corpus-fix\", reason: \"A prod fieldset sits in one tab; this field's tab differs from its fieldset's.\" },\n \"fieldset (props)\": { backlog: \"corpus-fix\", reason: \"A prop group is defined on the first prop that uses it: a fieldset no prop uses is not stored (and the ones after it move up a place).\" },\n \"fieldset name>limit\": { backlog: \"corpus-fix\", reason: \"A fieldset name is at most 60 characters (a prop group label 32); truncated.\" },\n \"fieldset collision\": { backlog: \"corpus-fix\", reason: \"A fieldset shares its id with a group, or this field names a fieldset the schema does not define.\" },\n /* field chrome and rules */\n \"duplicate name\": { backlog: \"corpus-fix\", reason: \"Two sibling fields share a name; prod keys are unique per level, so the later one is renamed.\" },\n \"reserved name\": {\n backlog: \"corpus-fix\",\n reason: \"A model field's key is not one the API reserves (types' KEY_RESERVED: derived, layout, page, a forbidden key or a leading _) and is a letter, then letters, digits, - and _ (KEY_FORMAT): this one is stored renamed. id is allowed.\",\n },\n rows: { backlog: \"corpus-fix\", reason: \"ui.rows is a whole number from 1 to 40, on a text or long text field only.\" },\n synced: { backlog: \"corpus-fix\", reason: \"ui.synced names its global in 1–64 characters (trimmed).\" },\n valueShape: { backlog: \"corpus-fix\", reason: \"ui.valueShape is for a link, image or button field only.\" },\n \"default value\": { backlog: \"corpus-fix\", reason: \"This initial value holds what the prod value cannot keep (named under it): a crop preset with no image size, a datetime without seconds, a button icon outside the dock vocabulary…\" },\n slotSettings: { backlog: \"wontfix:nesting\", reason: \"A slot's per-child settings are at most 20 leaf fields (slot_settings_invalid): this setting is an object, a list, a table, a slot or a form, or past the 20th, or the field holding them is not a slot.\" },\n icon: { backlog: \"corpus-fix\", reason: \"A field icon is ui.icon, a dock icon name, on a nesting field only (an object, a list of objects, a modular list); this field is a leaf, or the name is not a dock icon.\" },\n layout: { backlog: \"corpus-fix\", reason: \"This layout does not fit the field in prod: ui.control is dropdown / radio / grid on a select, cards on a select or multiselect, checkbox on a boolean or multiselect, switch on a boolean, segmented on a select of 2–4 options, slider on a number with min < max (≤ 1000 stops); a list of scalars or entries lays out as rows or a gallery.\" },\n \"help>limit\": { backlog: \"corpus-fix\", reason: \"Help text is at most 500 characters on a field and 200 on a prop (a placeholder 120, a prop group description 200); truncated.\" },\n \"option icon\": { backlog: \"corpus-fix\", reason: \"Option icons are dock icon names, all or none per list: an icon in this list is not a dock name, so the list stores none (option_icon_unknown).\" },\n \"option description\": { backlog: \"corpus-fix\", reason: \"An option description is one line of 1–140 characters (option_description_invalid).\" },\n condition: { backlog: \"corpus-fix\", reason: \"This condition has no prod showIf form: it names something that is not a sibling (the block's _variant is one only for a top-level prop: show_if_variant_field), names it by `scope` (prod resolves a rule at the nearest level that declares its field), or tests a value showIf cannot.\" },\n access: { backlog: \"corpus-fix\", reason: \"ui.access takes view \\\"schema.view\\\" and edit \\\"schema.manage\\\" only (access_cap_unknown), and a developer lock (readOnly \\\"developer\\\") is not also a plain read-only flag.\" },\n timezone: { backlog: \"corpus-fix\", reason: \"A field's time zone is true, false, \\\"site\\\" or an IANA zone name, on a date or datetime.\" },\n \"richtext references\": {\n backlog: \"corpus-fix\",\n reason: \"A rich text's references (config.references) are { posts?, authors? }, each a content-model id (letters, digits, - and _, 1-64), on a rich text or document field only: anything else is not stored.\",\n },\n \"richtext features\": {\n backlog: \"corpus-fix\",\n reason: \"A rich text's features (config.features) are distinct names from types RICHTEXT_FEATURES, on a rich text or document field only: a name outside it is not stored, and the rest are.\",\n },\n \"shopify reference\": {\n backlog: \"deferred:shopify\",\n reason:\n \"A prop reference to Shopify: no Shopify integration backs one (reference_target_unsupported), so a Shopify-only reference is locked text holding the product id (SHOPIFY_REF_LOCK) and a mixed one keeps its other targets. Owner 2026-10-11: deferred until the Shopify integration pipeline is built (allowed-deviations.json status deferred).\",\n },\n \"reference target\": { backlog: \"reference-targets\", reason: \"A prop reference keeps entry models (contentModelId / allowedModelSlugs) and page, form and collection (config.targets, backend a4ce1ae95); Shopify has no integration behind it (reference_target_unsupported), so a shopify target is dropped and a reference with only shopify is stored as json. A content-model field keeps entry models only.\" },\n \"reference order\": { backlog: \"corpus-fix\", reason: \"Prod keeps a reference's entry models apart from its page / form / collection targets: the list reads back entry models first, then the targets.\" },\n \"reference filter\": { backlog: \"reference-targets\", reason: \"A prop reference keeps its filter as config.filter (1–500 characters, no $parameters or ^ parent: reference_filter_invalid); a content-model field keeps none.\" },\n \"numeric unit\": { backlog: \"corpus-fix\", reason: \"config.unit is a number's (1–24 characters); a rating has no unit.\" },\n \"rating bounds\": { backlog: \"corpus-fix\", reason: \"A prod rating runs from config.min (≥ 0) to config.max (a whole 1–10) in steps of config.step, min < max and at most 100 whole steps apart.\" },\n \"scale rules\": { backlog: \"corpus-fix\", reason: \"This scale breaks prod's scale rules: token values 1–32 characters and distinct, labels 1–24, at most 50 steps and 20 presets, a token preset naming a step, a { value, unit } preset needing allowCustom.\" },\n \"validation.patternMessage\": { backlog: \"corpus-fix\", reason: \"A pattern message needs a stored pattern and is 1–200 characters.\" },\n \"validation.pattern\": { backlog: \"corpus-fix\", reason: \"A pattern is kept on text, long text and url values, a regex of at most 200 characters that compiles.\" },\n \"validation.length\": { backlog: \"corpus-fix\", reason: \"Length limits are whole numbers ≥ 0, on text, long text, url and rich text values.\" },\n \"validation.items\": { backlog: \"corpus-fix\", reason: \"Item limits are whole numbers ≥ 0 on a list, min ≤ max: a tags max 1–500 and min 0–500, a prop's own list (table, array) at most 500 (LIST_ITEMS_CAP), a slot's at most 50 (SLOT_ITEMS_CAP).\" },\n \"validation.unique\": { backlog: \"corpus-fix\", reason: \"unique is for text, long text, slug, email, phone, link, number, select, date and single-model reference fields, and lists; a polymorphic reference has no one id to compare.\" },\n \"validation.recommendedMax\": { backlog: \"corpus-fix\", reason: \"A soft limit is a positive whole number in prod.\" },\n \"item identity\": { backlog: \"corpus-fix\", reason: \"A list item's name is an identifier (a letter, then letters, digits or _; ≤ 64) and its title 1–60 characters (ui.itemName / ui.itemTitle); an object item keeps no other key.\" },\n \"item settings\": { backlog: \"corpus-fix\", reason: \"A list of scalars keeps its item's description (1–200 characters), localized flag, required, a text or rich text item's length and a text item's pattern (config.item, array_item_settings_invalid): this setting is none of those, or out of their bounds (a multi-line text item reads back single-line).\" },\n itemNoun: { backlog: \"corpus-fix\", reason: \"The item noun is ui.itemLabel, capitalised, at most 24 characters.\" },\n \"block model field\": { backlog: \"block-model-fields\", reason: \"A modular item type is a kind:'block' model, which holds only fields a block primitive renders (text, long text, rich text, image, link, slug, email, phone, color, select, number, rating, date, datetime, boolean): this field is not stored.\" },\n \"item icon\": { backlog: \"corpus-fix\", reason: \"Prod keeps one icon per list (ui.icon, a dock name) and one per polymorphic item type: the list has its own icon, or this name is not a dock icon.\" },\n \"preview icon\": { backlog: \"corpus-fix\", reason: \"A row preview's icon is a dock icon name.\" },\n \"preview path\": { backlog: \"corpus-fix\", reason: \"A row preview names a child key, or a dotted path whose later segments are children or keys of an image, video, link, button, file, location, json, code or reference value, or a data table's caption or notes (preview_path_unknown).\" },\n openMode: { backlog: \"corpus-fix\", reason: \"openMode is for object, array, table and code fields only (openMode_leaf).\" },\n \"unsupported key\": { backlog: \"corpus-fix\", reason: \"This V2 key has no prod mapping.\" },\n} as const satisfies Record<string, { reason: string; backlog: Backlog }>;\n\nexport type LossyFeature = keyof typeof FEATURES;\n\n/**\n * CATALOG ITEMS — V2 metadata the dock never stores, set when a schema is seeded or placed by its host: not a loss of\n * stored content. Reported apart from LossyItem so the lossy ledger counts only what prod drops.\n */\nexport interface CatalogItem {\n path: string;\n feature: CatalogFeature;\n reason: string;\n}\n\nexport const CATALOG = {\n \"block icon\": \"The block picker's icon belongs to the catalog entry; a model carries none (a component stores it as component.icon, a dock icon, so only one with no dock name for it lands here).\",\n category: \"The block picker's category is catalog metadata. A component takes the nearest prod category (CATEGORY_TO_PROD) when there is one; a model has none.\",\n \"document preview\": \"The document-level preview (how a row of this type reads in a parent's list) has no place on a model; a component stores it as component.preview.\",\n \"document preview path\": \"component.preview names a top-level prop key or a dotted path into one (`slides.0.heading`, componentPreviewRefusals); a key no prop has, or a path through a renamed value key (`image.alt`), is not stored (preview_key_unknown), so the slot row reads the child's first text instead.\",\n \"kind:block (fields)\": \"A section's fields are stored as a model (kind model): which host places it is set at seeding.\",\n \"kind:global\": \"A global (synced) block is a host-level placement; its fields are stored as a model or a component.\",\n \"kind:page\": \"A page schema is stored as a model; the host makes it a page.\",\n \"kind:object\": \"An object type is stored as a model.\",\n \"kind:entry (props)\": \"Only blocks become components; the host decides what an entry schema is placed as.\",\n variants: \"Variants are separate components sharing a sectionType in prod: the host seeds one per variant.\",\n ai: \"The AI-assist sparkle is a host setting, not stored on the field.\",\n \"item openMode\": \"Prod keeps one openMode per list, the array's own; the V2 engine reads only that one too (lib/schema.ts itemOpenMode ignores the item's), so the item's changes nothing the dock shows.\",\n \"itemNoun (derived)\": \"A list with no itemNoun whose item title is not the field title's singular: ui.itemLabel keeps the item title, and the read side names the noun from it, which is the noun the V2 engine already falls back to (lib/schema.ts itemNoun).\",\n \"fieldset (derived)\": \"A field names a fieldset the schema does not declare: the V2 engine draws it as a section titled by its name (engine/field-list.tsx arrange), so it is stored as that prop group and the read side declares it. Not a loss.\",\n \"prop target\": \"A V2 prop has no binding path, so fromTypeSchema('props') returns none: seeding binds each prop to its target. Not a loss.\",\n \"block placement\": \"Where a block fits (inline / section / page) is the host's: the block picker and a body's Insert menu place it; a model or component carries none.\",\n \"slot allow\": \"A slot keeps its V2 allow list as config.allow (1–50 block names of 1–64 characters, no repeats: slot_allow_invalid), which the dock resolves to the project's components; this list breaks those bounds, so the slot takes any component.\",\n \"segmented fallback\": \"A segmented select of 5+ options: V2 draws the dropdown (D46, choice.tsx fitsSegmented) and prod stores the select with no control (control_segmented holds 2–4), which the dock draws as the same dropdown. Nothing drawn differs.\",\n \"seeded instance\": \"A starting value the host writes into a new instance when it seeds or places one: a schema-level initialValue, a reference prop's default (a definition holds none: reference_in_definition), or a slot's children (they name V2 block types, which become component instances at seeding).\",\n \"not applicable (props)\": \"unique on a scalar prop, or on a sub-field outside a table: each placement holds its own value, so there is nothing to be unique across (unique_prop_scope). The instance is the whole document.\",\n \"host config\": \"A prod-only prop config key the host reads, not the V2 schema: a rich text's Accept list, its one-paragraph flags (allowMultipleParagraphs, the older inline), allowTargetBlank and component allowlist (the dock's rich-text codec and insert menu read them from the stored prop: dock-v2 richTextCodec configAt, kitServices insertableComponents), a section label, and the import's write-back metadata no editor reads (SERVER_CONFIG_KEYS). Read back, it names no loss: the kit never writes a definition, so the config rides untouched.\",\n} as const;\n\nexport type CatalogFeature = keyof typeof CATALOG;\n\n/**\n * Keys the import (BE derive-schema.ts, register-editable-pages.ts, template-copy.ts) writes onto a field for the\n * SERVER: how publish writes a value back into the site's own HTML (`attr`, `numeric`, `iconFont`, `htmlClasses`,\n * `units`, `lead`), which band a field belongs to (`band`, `family`, `shape`), and import markers (`seeded`, `shared`,\n * `sharedValue`). No form reads them, so no form loses anything by leaving them alone, and no agent may write them: the\n * backend MCP drops them from what an agent sends, and its contract test fails on any key the import writes that is\n * neither here, a host key, nor read by the kit (kit-catalog.json `fieldConfig`).\n */\nexport const SERVER_CONFIG_KEYS: ReadonlySet<string> = new Set([\n \"seeded\", \"shared\", \"sharedValue\", \"attr\", \"numeric\", \"iconFont\", \"htmlClasses\", \"units\", \"lead\", \"band\", \"family\", \"shape\",\n]);\n\n/** The prop config keys CATALOG \"host config\" covers: any type's, then a rich text's (richtext, document) own. */\nexport const HOST_CONFIG_KEYS: { any: ReadonlySet<string>; richtext: ReadonlySet<string> } = {\n // A band's name, its kind and its own heading, which the derive lane writes on the band's first field.\n // `format`: how a number is shown ($1,200+), which the canvas, the classic preview and publish read; the value stays a number\n any: new Set([\"sectionLabel\", \"sectionShape\", \"sectionTitle\", \"format\", ...SERVER_CONFIG_KEYS]),\n // `hostTag` is read from the stored field by the rich-text host, like the rest. `features` is a V2 facet now (RT0, facets.ts)\n richtext: new Set([\"accept\", \"allowMultipleParagraphs\", \"inline\", \"allowTargetBlank\", \"components\", \"hostTag\"]),\n};\n\nexport const isHostConfigKey = (type: string, key: string): boolean =>\n HOST_CONFIG_KEYS.any.has(key) || ((type === \"richtext\" || type === \"document\") && HOST_CONFIG_KEYS.richtext.has(key));\n\n/**\n * The prod config keys the kit adapter READS, per prod field type (`*` = every type): what an agent may write on a\n * field and still edit it in the kit. Exported to the backend with the host and server keys (kit-catalog.json\n * `fieldConfig`), where the MCP warns on any other key and its contract test checks what the import writes.\n * field-config-contract.test.ts proves each entry against `toTypeSchema`, so this list cannot claim a key the\n * adapter drops; a key missing here only costs a warning.\n */\nexport const KIT_READ_CONFIG_KEYS: Readonly<Record<string, readonly string[]>> = {\n \"*\": [\"placeholder\", \"zones\", \"options\", \"fields\", \"linkHref\"],\n text: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n longtext: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n url: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\"],\n richtext: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\", \"features\", \"references\"],\n document: [\"minLength\", \"maxLength\", \"pattern\", \"patternMessage\", \"inputType\", \"features\", \"references\"],\n number: [\"min\", \"max\", \"step\", \"scale\", \"unit\", \"validation\"],\n rating: [\"min\", \"max\", \"step\", \"scale\", \"unit\", \"validation\"],\n select: [\"multiSelect\"],\n multiselect: [\"min\", \"max\", \"allowCustom\"],\n tags: [\"min\", \"max\", \"allowCustom\"],\n code: [\"language\", \"languages\"],\n date: [\"timezone\", \"includeTime\"],\n datetime: [\"timezone\", \"includeTime\"],\n reference: [\"contentModelId\", \"polymorphic\", \"allowedModelSlugs\", \"targets\", \"filter\"],\n \"multi-reference\": [\"contentModelId\", \"polymorphic\", \"allowedModelSlugs\", \"targets\", \"filter\", \"min\", \"max\"],\n array: [\"itemType\", \"minItems\", \"maxItems\", \"item\"],\n image: [\"media\"],\n modular: [\"blockSlugs\", \"minItems\", \"maxItems\"],\n};\n\n/** The field-config contract the backend reads (kit-catalog.json `fieldConfig`). */\nexport const fieldConfigContract = () => ({\n read: KIT_READ_CONFIG_KEYS,\n host: { any: [...HOST_CONFIG_KEYS.any].filter((k) => !SERVER_CONFIG_KEYS.has(k)), richtext: [...HOST_CONFIG_KEYS.richtext] },\n server: [...SERVER_CONFIG_KEYS],\n});\n"],"mappings":"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"}
|
package/dist/adapters/schema.js
CHANGED
|
@@ -492,6 +492,20 @@ var ALL_RICH_FEATURES = normaliseRichTextFeatures(RICHTEXT_FEATURES).features;
|
|
|
492
492
|
* A rich text's features through types normaliseRichTextFeatures (its aliases expand there: `headings` is h1–h6), distinct,
|
|
493
493
|
* in the order given. `named`: something was not carried (a name outside RICHTEXT_FEATURES, a repeat, a non-list).
|
|
494
494
|
*/
|
|
495
|
+
/** A content model's id, as the backend's config.references rule reads one. */
|
|
496
|
+
var MODEL_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
497
|
+
/** A rich text's references: posts and authors, each a model id; `named` when anything else was there. */
|
|
498
|
+
function richRefs(v) {
|
|
499
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { named: true };
|
|
500
|
+
const r = v;
|
|
501
|
+
const kept = {};
|
|
502
|
+
for (const k of ["posts", "authors"]) if (typeof r[k] === "string" && MODEL_ID.test(r[k])) kept[k] = r[k];
|
|
503
|
+
const named = Object.keys(r).some((k) => k !== "posts" && k !== "authors" || r[k] !== void 0 && kept[k] === void 0);
|
|
504
|
+
return {
|
|
505
|
+
kept: Object.keys(kept).length ? kept : void 0,
|
|
506
|
+
named
|
|
507
|
+
};
|
|
508
|
+
}
|
|
495
509
|
function richFeatures(v) {
|
|
496
510
|
const names = Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
497
511
|
const { features, unknown } = normaliseRichTextFeatures(names);
|
|
@@ -705,6 +719,14 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
|
|
|
705
719
|
if (shape.kind === "leaf" && shape.type === "table") Object.assign(node.config, tableConfigFromOptions(f.table));
|
|
706
720
|
else lose(ctx, p, "type:table");
|
|
707
721
|
break;
|
|
722
|
+
case "references":
|
|
723
|
+
if (!RICH_TYPES.has(shape.type)) lose(ctx, p, "unsupported key");
|
|
724
|
+
else {
|
|
725
|
+
const { kept, named } = richRefs(f.references);
|
|
726
|
+
if (named) lose(ctx, p, "richtext references");
|
|
727
|
+
if (kept) node.config.references = kept;
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
708
730
|
case "features":
|
|
709
731
|
if (!RICH_TYPES.has(shape.type)) lose(ctx, p, "unsupported key");
|
|
710
732
|
else {
|
|
@@ -2573,6 +2595,12 @@ function readToV2(n, at, siblings, ctx, top, lane) {
|
|
|
2573
2595
|
const useUi = (...k) => k.forEach((x) => usedUi.add(x));
|
|
2574
2596
|
if (lane === "props") useUi("group");
|
|
2575
2597
|
if (lane === "props") useUi("tab");
|
|
2598
|
+
if (RICH_TYPES.has(n.type) && cfg.references !== void 0) {
|
|
2599
|
+
use("references");
|
|
2600
|
+
const { kept, named } = richRefs(cfg.references);
|
|
2601
|
+
if (named) loss("config/references", "richtext references", FEATURES["richtext references"].reason);
|
|
2602
|
+
if (kept) f.references = kept;
|
|
2603
|
+
}
|
|
2576
2604
|
if (RICH_TYPES.has(n.type) && cfg.features !== void 0) {
|
|
2577
2605
|
use("features");
|
|
2578
2606
|
const { kept, named } = richFeatures(cfg.features);
|