@elytracms/studio 0.0.90 → 0.0.91
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/convex/asset-revalidate.test.ts +14 -1
- package/convex/cms.ts +3 -4
- package/convex/document-revalidate.test.ts +14 -1
- package/convex/redirect-revalidate.test.ts +14 -1
- package/dist/components/assets/asset-selection.d.ts +8 -9
- package/dist/components/assets/asset-selection.js +26 -16
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +7 -5
- package/dist/components/builder/blocks-model.js +11 -9
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +1 -1
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +11 -3
- package/dist/components/builder/builder-store.js +12 -5
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +7 -1
- package/dist/components/builder/canvas-dnd.js +6 -3
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +13 -1
- package/dist/components/builder/canvas-render-context.js +28 -18
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas.js +1 -1
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +0 -6
- package/dist/components/builder/clipboard-actions.js +9 -4
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +22 -0
- package/dist/components/builder/composition-field-editor.js +47 -8
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/inline-edit.d.ts +7 -4
- package/dist/components/builder/inline-edit.js +11 -8
- package/dist/components/builder/inline-edit.js.map +1 -1
- package/dist/components/builder/insert-context.js +9 -5
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/layout-inspector.d.ts +2 -2
- package/dist/components/builder/layout-inspector.js +8 -6
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/node-factory.d.ts +31 -14
- package/dist/components/builder/node-factory.js +73 -93
- package/dist/components/builder/node-factory.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +3 -1
- package/dist/components/builder/page-tree.js +6 -4
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +2 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +1 -1
- package/dist/components/builder/reuse.js +8 -5
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/selection-overlay.d.ts +2 -2
- package/dist/components/builder/selection-overlay.js +3 -3
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +2 -2
- package/dist/components/builder/spacing-handles.js +3 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +1 -1
- package/dist/components/cms/asset-field.js +15 -13
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.js +3 -4
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/detail-editor.js +10 -5
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +1 -2
- package/dist/components/cms/discard-draft.js +1 -1
- package/dist/components/cms/discard-draft.js.map +1 -1
- package/dist/components/cms/editable-cell.js +11 -7
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control-lib.d.ts +10 -0
- package/dist/components/cms/field-control-lib.js +20 -0
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +3 -3
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/object-array-field.js +3 -5
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.d.ts +16 -3
- package/dist/components/cms/prop-fields.js +57 -12
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
- package/dist/components/cms/rich-text/embed-props.js +7 -12
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +13 -6
- package/dist/components/cms/rich-text/insert.js +16 -17
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/use-cell-edit.js +7 -12
- package/dist/components/cms/use-cell-edit.js.map +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -2
- package/dist/components/cms/use-discard-controller.js +1 -2
- package/dist/components/cms/use-discard-controller.js.map +1 -1
- package/dist/components/cms/versions-pane.js +2 -2
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/components/component-detail.d.ts +1 -1
- package/dist/components/components/component-detail.js +2 -4
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +9 -4
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/components-index.d.ts +2 -2
- package/dist/components/components/components-index.js +3 -6
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +5 -2
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/registry-browser.d.ts +6 -5
- package/dist/components/components/registry-browser.js +11 -37
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +14 -21
- package/dist/components/components/taxonomy.js +5 -29
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -8
- package/dist/components/components/transfer.js +17 -13
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +16 -14
- package/dist/lib/ai/constraints.js +64 -48
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/update-fields.js +5 -3
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/assets/asset-placements.d.ts +3 -10
- package/dist/lib/assets/asset-placements.js +17 -8
- package/dist/lib/assets/asset-placements.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +3 -3
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms-views/view-model.js +11 -8
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +10 -7
- package/dist/lib/data/collection-content.js +34 -15
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +22 -7
- package/dist/lib/dnd/drop-rules.js +21 -14
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +25 -9
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +21 -7
- package/dist/lib/i18n/messages.en.js +28 -9
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/cms-persistence.js +7 -4
- package/dist/lib/operations/cms-persistence.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +4 -9
- package/dist/lib/persona/labels.js +5 -12
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/reference-model.d.ts +0 -2
- package/dist/lib/references/reference-model.js +3 -5
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
- package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
- package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
- package/dist/lib/registry/index.d.ts +9 -13
- package/dist/lib/registry/index.js +34 -61
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/preview.d.ts +22 -7
- package/dist/lib/registry/preview.js +26 -89
- package/dist/lib/registry/preview.js.map +1 -1
- package/dist/previews/enumerate.d.ts +23 -27
- package/dist/previews/enumerate.js +27 -48
- package/dist/previews/enumerate.js.map +1 -1
- package/dist/previews/harness.js +1 -1
- package/dist/previews/harness.js.map +1 -1
- package/package.json +4 -3
- package/dist/lib/registry/project-components.d.ts +0 -16
- package/dist/lib/registry/project-components.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constraints.js","sourceRoot":"","sources":["../../../src/lib/ai/constraints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model-facing constraint rendering (EC-473, tuned by EC-505).\n *\n * The engine has ONE constraint projection: `fieldJsonSchema` merges a field's\n * declarative rules (`validation.min`/`max`/`pattern`) with the fragment derived\n * from a schema-form `validation.validate` (EC-522: nested `items`/`properties`\n * bounds included, fitted to the field's type at config resolve). This module turns\n * that projection into what the MODEL should see — which is not the same artifact a\n * JSON-Schema consumer wants:\n *\n * - **a regex is a poor instruction for a model.** `z.string().email()` emits a\n * ~200-character pattern and `z.iso.date()` a leap-year monster (measured,\n * EC-474 §3); `format` + bounds says the same thing in four tokens. So on the AI\n * path a `pattern` is dropped wherever a `format` is present. The JSON Schema\n * OUTPUT artifact keeps it — different consumer, different needs.\n * - **a union `type` is a poor instruction too** (EC-505). A `date` field accepts\n * an ISO string OR an epoch number *at rest*, so the general projection honestly\n * says `type: ['string','number']`. The model does not need that latitude: it is\n * asked to AUTHOR a date, EC-472 ratified that a date is an ISO instant, and the\n * declared bounds this same projection carries (`formatMinimum`/`formatMaximum`)\n * are ISO strings that mean nothing against an epoch. Some structured-output\n * providers also reject a union `type` outright. So the model-facing shape says\n * `type: 'string'`; accepting a number stays an input-parsing detail of the\n * validator, not a shape the model is taught.\n * - **incompleteness must be stated.** When `validation.schemaComplete` is `false`\n * the projection is a partial view of the rule, and the model is told so rather\n * than being handed a half-truth as the whole rule. (An `ai.hint` is exactly the\n * right place to say what the dropped refinement wants — the note nudges toward\n * writing one.)\n */\nimport { fieldJsonSchema } from '@elytracms/core/cms-core'\nimport type { FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text'\n\n/** The note appended when a field's constraint projection is knowingly partial. */\nexport const INCOMPLETE_CONSTRAINTS_NOTE =\n 'further validation rules apply that are not expressible here — keep the value conventional and plausible'\n\n/**\n * The prose form of `format: 'date-time'` (EC-505). The JSON-Schema keyword rides\n * on the target shape for the provider's validator; the CATALOG has no JSON Schema\n * at all — it is prose only, and the chat update path authors date values through\n * it. So the format is said in words too, or the only channel that path has says\n * nothing about the one format the engine accepts.\n *\n * Kept to three words deliberately: measured at 6 tokens, against 20 for the same\n * note with a worked example — and it rides every date field of every turn.\n */\nexport const ISO_DATE_NOTE = 'ISO 8601 date'\n\nfunction isSchemaNode(value: unknown): value is JsonSchema {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * A rich-text field's WRITER form (EC-379, D2 of EC-556): a MARKDOWN string. The\n * description carries the allowed subset (from the engine converter — one source\n * of truth); the engine converts the markdown into the v2 storage value and\n * rejects out-of-subset constructs with explicit issues.\n *\n * The declared `min` / `max` / `pattern` are read straight off `field.validation`\n * — deliberately NOT spread from the projection. Since EC-556 the projection is\n * validator-facing: a rich-text value at rest is the `{ version: 2, content }`\n * object, where a string bound has no place. The model is still told the bounds\n * it was told before (whether anything enforces them on v2 is EC-563).\n */\nexport function richTextWriterSchema(field: FieldDef): JsonSchema {\n const schema: JsonSchema = { type: 'string' }\n if (typeof field.validation?.min === 'number') schema.minLength = field.validation.min\n if (typeof field.validation?.max === 'number') schema.maxLength = field.validation.max\n if (field.validation?.pattern !== undefined) schema.pattern = field.validation.pattern\n schema.description = `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.`\n return schema\n}\n\n/**\n * The WRITER-facing form of one field, at EVERY depth (EC-556): what a model should\n * hand in. Derived from the validator-facing projection (`fieldJsonSchema` — the\n * value at rest, every merged constraint included) by one rule: **the writer omits\n * exactly what the write-time lens (`selfTypeFieldValue`) restores.**\n *\n * - asset, no per-use sub-fields → the bare id (the projection's `id` property):\n * the lens wraps a bare id string into `{ type: 'asset', id }`;\n * - asset WITH sub-fields → the envelope itself, `type` included: the lens wraps a\n * string, never an object missing its `type`, and the sub-fields need a place;\n * - relation → `{ collection, id }` (the envelope minus `type`): the lens rebuilds\n * a bare reference into its envelope;\n * - rich text → a markdown string (`richTextWriterSchema`) — the engine converts it\n * wherever it sits (`apply-content.ts`);\n * - an `object` → its own projection with every subfield in ITS writer form.\n *\n * Writer objects are closed (`additionalProperties: false`): a writer inventing keys\n * is a fault, where a stored envelope carrying extra keys is drift the lens\n * tolerates (EC-556 §B) — which is why the projection itself stays open. The\n * top-level relation/asset id ENUM (EC-381) is the target shape's own override.\n */\nexport function writerFieldSchema(field: FieldDef): JsonSchema {\n return writerForm(field, fieldJsonSchema(field))\n}\n\nfunction writerForm(field: FieldDef, base: JsonSchema): JsonSchema {\n switch (field.type) {\n case 'richText':\n return richTextWriterSchema(field)\n case 'asset': {\n // `type`/`id` belong to the envelope; the projection never lets a sub-field\n // of that name shadow them, and neither does the writer form.\n const subfields = (field.fields ?? []).filter((sub) => sub.name !== 'type' && sub.name !== 'id')\n return perItem(field.multiple, base, (item) =>\n subfields.length === 0 ? bareAssetId(item) : writerObject(subfields, item),\n )\n }\n case 'relation':\n return perItem(field.multiple, base, bareReference)\n case 'object':\n return perItem(field.multiple, base, (item) => writerObject(field.fields, item))\n default:\n return base\n }\n}\n\n/** Apply `one` to the item schema of a `multiple` field, or to the field's own schema. */\nfunction perItem(multiple: boolean, base: JsonSchema, one: (item: JsonSchema) => JsonSchema): JsonSchema {\n if (!multiple) return one(base)\n return isSchemaNode(base.items) ? { ...base, items: one(base.items) } : base\n}\n\nfunction propertiesOf(node: JsonSchema): Record<string, JsonSchema> {\n const properties = node.properties\n return isSchemaNode(properties) ? (properties as Record<string, JsonSchema>) : {}\n}\n\n/** The envelope's `id` property — the bare id string the lens wraps. */\nfunction bareAssetId(envelope: JsonSchema): JsonSchema {\n return propertiesOf(envelope).id ?? { type: 'string' }\n}\n\n/** The reference envelope minus the `type` discriminator the lens restores. */\nfunction bareReference(envelope: JsonSchema): JsonSchema {\n const { type: _discriminator, ...properties } = propertiesOf(envelope)\n const required = Array.isArray(envelope.required)\n ? envelope.required.filter((key) => key !== 'type')\n : []\n return { ...envelope, properties, required, additionalProperties: false }\n}\n\n/** An object-shaped node with each declared subfield in its writer form, closed. */\nfunction writerObject(fields: readonly FieldDef[], node: JsonSchema): JsonSchema {\n const properties: Record<string, JsonSchema> = { ...propertiesOf(node) }\n for (const sub of fields) {\n const subBase = properties[sub.name]\n if (subBase !== undefined) properties[sub.name] = writerForm(sub, subBase)\n }\n return { ...node, properties, additionalProperties: false }\n}\n\n/**\n * Drop `pattern` wherever `format` is present (EC-473/EC-474 req 6). Returns the\n * same object when there is nothing to strip, so callers can rely on identity.\n */\nexport function stripRedundantPattern(schema: JsonSchema): JsonSchema {\n if (schema.format === undefined || schema.pattern === undefined) return schema\n const { pattern: _pattern, ...rest } = schema\n return rest\n}\n\n/**\n * Narrow a date field's `['string','number']` union to the ISO string the model\n * should author (EC-505 — see the module note). Keyed on the exact combination the\n * date branch of the projection emits (`format: 'date-time'` + an array `type`\n * containing `string`), so nothing else is touched. Identity-preserving.\n */\nexport function narrowDateUnion(schema: JsonSchema): JsonSchema {\n if (schema.format !== 'date-time') return schema\n const type = schema.type\n if (!Array.isArray(type) || !type.includes('string')) return schema\n return { ...schema, type: 'string' }\n}\n\n/**\n * Apply a node transform through a schema tree (`items` + `properties`), preserving\n * object identity wherever nothing changed — `buildTargetShape` relies on that to\n * avoid copying a property it did not touch.\n */\nfunction mapSchemaDeep(schema: JsonSchema, transform: (node: JsonSchema) => JsonSchema): JsonSchema {\n let out = transform(schema)\n const items = out.items\n if (typeof items === 'object' && items !== null && !Array.isArray(items)) {\n const next = mapSchemaDeep(items as JsonSchema, transform)\n if (next !== items) out = { ...out, items: next }\n }\n const properties = out.properties\n if (typeof properties === 'object' && properties !== null && !Array.isArray(properties)) {\n let changed = false\n const nextProps: Record<string, JsonSchema> = {}\n for (const [key, value] of Object.entries(properties as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const next = mapSchemaDeep(value as JsonSchema, transform)\n nextProps[key] = next\n if (next !== value) changed = true\n } else {\n nextProps[key] = value as JsonSchema\n }\n }\n if (changed) out = { ...out, properties: nextProps }\n }\n return out\n}\n\n/** Recursively strip redundant patterns through `items` / `properties`. */\nexport function stripRedundantPatternsDeep(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * The whole model-facing rewrite of a projected schema (EC-505): redundant patterns\n * stripped and date unions narrowed, everywhere in the tree — an `object` field's\n * date SUBfield gets the same treatment as a top-level one. Identity-preserving.\n */\nexport function toModelFacingSchema(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, (node) => narrowDateUnion(stripRedundantPattern(node)))\n}\n\n/**\n * Human-readable phrases for the constraint keywords of ONE schema node, in a stable\n * order — the form a model reads best. Node-level only: nested `items`/`properties`\n * are {@link describeSchemaDeep}'s business. Total over the projection's numeric,\n * string and array vocabulary; `enum`/`const` are deliberately not phrased — they\n * ride the target SHAPE as JSON Schema, and a `select`'s options or a relation's\n * `collection` const would otherwise repeat on every catalog line.\n */\nfunction describeSchemaConstraints(schema: JsonSchema): string[] {\n const out: string[] = []\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' ? value : undefined\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' ? value : undefined\n\n const minLength = num(schema.minLength)\n const maxLength = num(schema.maxLength)\n if (minLength !== undefined && maxLength !== undefined) {\n out.push(`${minLength}–${maxLength} characters`)\n } else if (minLength !== undefined) out.push(`at least ${minLength} characters`)\n else if (maxLength !== undefined) out.push(`at most ${maxLength} characters`)\n\n const minimum = num(schema.minimum)\n const maximum = num(schema.maximum)\n if (minimum !== undefined && maximum !== undefined) out.push(`between ${minimum} and ${maximum}`)\n else if (minimum !== undefined) out.push(`at least ${minimum}`)\n else if (maximum !== undefined) out.push(`at most ${maximum}`)\n\n // EC-522: the projection carried these already; the prose never said them.\n const exclusiveMinimum = num(schema.exclusiveMinimum)\n const exclusiveMaximum = num(schema.exclusiveMaximum)\n if (exclusiveMinimum !== undefined) out.push(`more than ${exclusiveMinimum}`)\n if (exclusiveMaximum !== undefined) out.push(`less than ${exclusiveMaximum}`)\n const multipleOf = num(schema.multipleOf)\n if (multipleOf !== undefined) out.push(`a multiple of ${multipleOf}`)\n\n const minItems = num(schema.minItems)\n const maxItems = num(schema.maxItems)\n if (minItems !== undefined) out.push(`at least ${minItems} items`)\n if (maxItems !== undefined) out.push(`at most ${maxItems} items`)\n if (schema.uniqueItems === true) out.push('no duplicate items')\n\n // EC-473: date bounds finally reach a consumer. `formatMinimum`/`formatMaximum`\n // is the projection's machine form; this is the form a model acts on.\n const from = text(schema.formatMinimum)\n const to = text(schema.formatMaximum)\n if (from !== undefined) out.push(`not earlier than ${from}`)\n if (to !== undefined) out.push(`not later than ${to}`)\n\n const format = text(schema.format)\n // EC-505: `date-time` is said in words rather than skipped — the catalog carries\n // no JSON Schema, so this is the only place the update path learns the format.\n if (format === 'date-time') out.push(ISO_DATE_NOTE)\n else if (format !== undefined) out.push(`format: ${format}`)\n // Only a pattern that is NOT explained by a format is worth the tokens.\n const pattern = text(schema.pattern)\n if (pattern !== undefined && format === undefined) out.push(`matching /${pattern}/`)\n\n return out\n}\n\n/**\n * The phrases of one projected schema node AND everything below it (EC-522): a\n * `multiple` field's per-item bounds become an `each item: …` clause, an object's\n * subfield bounds a `<name>: …` clause, recursively. The projection is the ONLY\n * input — since EC-522 the merge carries nested `items`/`properties` constraints\n * itself, so this module never looks past `fieldJsonSchema` (EC-505's direct read\n * of the derived fragment's `items` is gone, and with it the second place that\n * understood fragment structure).\n *\n * Which side a schema addresses is still the executor's call, not this module's:\n * `validateDocument` hands the validator the field's WHOLE value — a `multiple`\n * field's array — so the per-item form is `z.array(z.string().min(3))`, and a\n * bare `z.string().min(3)` there is a config error at load (EC-522), never a\n * silently mis-applied bound.\n */\nfunction describeSchemaDeep(schema: JsonSchema): string[] {\n const out = describeSchemaConstraints(schema)\n if (isSchemaNode(schema.items)) {\n const itemParts = describeSchemaDeep(schema.items)\n if (itemParts.length > 0) out.push(`each item: ${itemParts.join(', ')}`)\n }\n if (isSchemaNode(schema.properties)) {\n for (const [name, sub] of Object.entries(schema.properties)) {\n if (!isSchemaNode(sub)) continue\n const subParts = describeSchemaDeep(sub)\n if (subParts.length > 0) out.push(`${name}: ${subParts.join(', ')}`)\n }\n }\n return out\n}\n\n/**\n * Human-readable constraint phrases for one field, in a stable order — the form a\n * model reads best (and the form the collections catalog carries, where there is no\n * JSON Schema to hand the model at all). Empty when the field constrains nothing.\n */\nexport function describeFieldConstraints(field: FieldDef): string[] {\n const out: string[] = []\n if (field.validation?.required === true) out.push('required')\n // EC-556: the prose tells a WRITER what to write, so it reads the writer form —\n // a rich-text field's character bounds live there, not on the v2 value.\n out.push(...describeSchemaDeep(writerFieldSchema(field)))\n if (field.validation?.unique === true) out.push('unique across the collection')\n // The one thing the projection cannot say about itself: that it is partial (an\n // opaque vendor, a `.refine()`, or — EC-522 — a rule at a position the field's\n // shape has no counterpart for). Set at config resolve, read here, nowhere else.\n if (field.validation?.schemaComplete === false) out.push(INCOMPLETE_CONSTRAINTS_NOTE)\n return out\n}\n\n/** The constraint phrases as one clause (`min 2 characters; required`), or `undefined`. */\nexport function constraintSentence(field: FieldDef): string | undefined {\n const parts = describeFieldConstraints(field)\n return parts.length > 0 ? parts.join('; ') : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"constraints.js","sourceRoot":"","sources":["../../../src/lib/ai/constraints.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model-facing constraint rendering (EC-473, tuned by EC-505).\n *\n * The engine has ONE constraint projection: `fieldJsonSchema` merges a field's\n * declarative rules (`validation.min`/`max`/`pattern`) with the fragment derived\n * from a schema-form `validation.validate` (EC-522: nested `items`/`properties`\n * bounds included, fitted to the field's type at config resolve). This module turns\n * that projection into what the MODEL should see — which is not the same artifact a\n * JSON-Schema consumer wants:\n *\n * - **a regex is a poor instruction for a model.** `z.string().email()` emits a\n * ~200-character pattern and `z.iso.date()` a leap-year monster (measured,\n * EC-474 §3); `format` + bounds says the same thing in four tokens. So on the AI\n * path a `pattern` is dropped wherever a `format` is present. The JSON Schema\n * OUTPUT artifact keeps it — different consumer, different needs.\n * - **a date is one instant string** (EC-505 → EC-586). The projection used to say\n * `type: ['string','number']` (an epoch was stored as-is) and this module narrowed\n * it for the model; since EC-586 every write canonicalises a date to ONE ISO-8601\n * UTC instant, so the projection itself says `type: 'string'` and there is nothing\n * left to narrow.\n * - **incompleteness must be stated.** When `validation.schemaComplete` is `false`\n * the projection is a partial view of the rule, and the model is told so rather\n * than being handed a half-truth as the whole rule. (An `ai.hint` is exactly the\n * right place to say what the dropped refinement wants — the note nudges toward\n * writing one.)\n */\nimport { BOUND_UNIT_NOUN, fieldJsonSchema, fieldTypeOf } from '@elytracms/core/cms-core'\nimport type { FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text'\n\n/** The note appended when a field's constraint projection is knowingly partial. */\nexport const INCOMPLETE_CONSTRAINTS_NOTE =\n 'further validation rules apply that are not expressible here — keep the value conventional and plausible'\n\n/**\n * The prose form of `format: 'date-time'` (EC-505). The JSON-Schema keyword rides\n * on the target shape for the provider's validator; the CATALOG has no JSON Schema\n * at all — it is prose only, and the chat update path authors date values through\n * it. So the format is said in words too, or the only channel that path has says\n * nothing about the one format the engine accepts.\n *\n * Kept to three words deliberately: measured at 6 tokens, against 20 for the same\n * note with a worked example — and it rides every date field of every turn.\n */\nexport const ISO_DATE_NOTE = 'ISO 8601 date'\n\nfunction isSchemaNode(value: unknown): value is JsonSchema {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * The phrase for a field's declared `min`/`max` in the UNIT its type declares\n * (`FIELD_TYPES[type].bounds`, EC-581) — or `undefined` when the field declares none\n * or the unit has a JSON-Schema keyword the projection already carries. Today that is\n * rich text: its bounds count PLAIN-TEXT characters of the stored value (ratified\n * 2a(iii)), which no keyword on the markdown string the model writes can express.\n */\nexport function plainTextBoundPhrase(field: FieldDef): string | undefined {\n if (fieldTypeOf(field).bounds(field) !== 'plain-text') return undefined\n const min = typeof field.validation?.min === 'number' ? field.validation.min : undefined\n const max = typeof field.validation?.max === 'number' ? field.validation.max : undefined\n const noun = BOUND_UNIT_NOUN['plain-text']\n if (min !== undefined && max !== undefined) return `${min}–${max} ${noun}`\n if (min !== undefined) return `at least ${min} ${noun}`\n if (max !== undefined) return `at most ${max} ${noun}`\n return undefined\n}\n\n/** {@link plainTextBoundPhrase} for a field and every sub-field below it, name-prefixed. */\nfunction plainTextBoundClauses(field: FieldDef, prefix = ''): string[] {\n const own = plainTextBoundPhrase(field)\n const out = own !== undefined ? [`${prefix}${own}`] : []\n const subs = field.type === 'object' ? field.fields : field.type === 'asset' ? (field.fields ?? []) : []\n for (const sub of subs) out.push(...plainTextBoundClauses(sub, `${prefix}${sub.name}: `))\n return out\n}\n\n/**\n * A rich-text field's WRITER form (EC-379, D2 of EC-556): a MARKDOWN string. The\n * description carries the allowed subset (from the engine converter — one source\n * of truth); the engine converts the markdown into the v2 storage value and\n * rejects out-of-subset constructs with explicit issues.\n *\n * EC-563/EC-581: the declared bounds are said in their unit — plain-text characters,\n * markup not counted — in the description, read off the per-FieldType definition.\n * They used to ride as `minLength`/`maxLength` on the markdown string: a different\n * unit (markdown characters) than anything enforced, and a provider validating the\n * structured output would have enforced the wrong one.\n */\nexport function richTextWriterSchema(field: FieldDef): JsonSchema {\n const schema: JsonSchema = { type: 'string' }\n const bound = plainTextBoundPhrase(field)\n schema.description = `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.${bound ? ` Length: ${bound}.` : ''}`\n return schema\n}\n\n/**\n * The WRITER-facing form of one field, at EVERY depth (EC-556): what a model should\n * hand in. Derived from the validator-facing projection (`fieldJsonSchema` — the\n * value at rest, every merged constraint included) by one rule: **the writer omits\n * exactly what the write-time lens (`selfTypeFieldValue`) restores.**\n *\n * - asset, no per-use sub-fields → the bare id (the projection's `id` property):\n * the lens wraps a bare id string into `{ type: 'asset', id }`;\n * - asset WITH sub-fields → the envelope itself, `type` included: the lens wraps a\n * string, never an object missing its `type`, and the sub-fields need a place;\n * - relation → `{ collection, id }` (the envelope minus `type`): the lens rebuilds\n * a bare reference into its envelope;\n * - rich text → a markdown string (`richTextWriterSchema`) — the engine converts it\n * wherever it sits (`apply-content.ts`);\n * - an `object` → its own projection with every subfield in ITS writer form.\n *\n * Writer objects are closed (`additionalProperties: false`): a writer inventing keys\n * is a fault, where a stored envelope carrying extra keys is drift the lens\n * tolerates (EC-556 §B) — which is why the projection itself stays open. The\n * top-level relation/asset id ENUM (EC-381) is the target shape's own override.\n */\nexport function writerFieldSchema(field: FieldDef): JsonSchema {\n return writerForm(field, fieldJsonSchema(field))\n}\n\n/** EC-581: the writer form is DECLARED per type (`FIELD_TYPES[type].writer`); built here. */\nfunction writerForm(field: FieldDef, base: JsonSchema): JsonSchema {\n switch (fieldTypeOf(field).writer) {\n case 'markdown':\n return richTextWriterSchema(field)\n case 'placement': {\n if (field.type !== 'asset') return base\n // `type`/`id` belong to the envelope; the projection never lets a sub-field\n // of that name shadow them, and neither does the writer form.\n const subfields = (field.fields ?? []).filter((sub) => sub.name !== 'type' && sub.name !== 'id')\n return perItem(field.multiple, base, (item) =>\n subfields.length === 0 ? bareAssetId(item) : writerObject(subfields, item),\n )\n }\n case 'reference':\n return field.type === 'relation' ? perItem(field.multiple, base, bareReference) : base\n case 'object':\n return field.type === 'object'\n ? perItem(field.multiple, base, (item) => writerObject(field.fields, item))\n : base\n case 'value':\n return base\n }\n}\n\n/** Apply `one` to the item schema of a `multiple` field, or to the field's own schema. */\nfunction perItem(multiple: boolean, base: JsonSchema, one: (item: JsonSchema) => JsonSchema): JsonSchema {\n if (!multiple) return one(base)\n return isSchemaNode(base.items) ? { ...base, items: one(base.items) } : base\n}\n\nfunction propertiesOf(node: JsonSchema): Record<string, JsonSchema> {\n const properties = node.properties\n return isSchemaNode(properties) ? (properties as Record<string, JsonSchema>) : {}\n}\n\n/** The envelope's `id` property — the bare id string the lens wraps. */\nfunction bareAssetId(envelope: JsonSchema): JsonSchema {\n return propertiesOf(envelope).id ?? { type: 'string' }\n}\n\n/** The reference envelope minus the `type` discriminator the lens restores. */\nfunction bareReference(envelope: JsonSchema): JsonSchema {\n const { type: _discriminator, ...properties } = propertiesOf(envelope)\n const required = Array.isArray(envelope.required)\n ? envelope.required.filter((key) => key !== 'type')\n : []\n return { ...envelope, properties, required, additionalProperties: false }\n}\n\n/** An object-shaped node with each declared subfield in its writer form, closed. */\nfunction writerObject(fields: readonly FieldDef[], node: JsonSchema): JsonSchema {\n const properties: Record<string, JsonSchema> = { ...propertiesOf(node) }\n for (const sub of fields) {\n const subBase = properties[sub.name]\n if (subBase !== undefined) properties[sub.name] = writerForm(sub, subBase)\n }\n return { ...node, properties, additionalProperties: false }\n}\n\n/**\n * Drop `pattern` wherever `format` is present (EC-473/EC-474 req 6). Returns the\n * same object when there is nothing to strip, so callers can rely on identity.\n */\nexport function stripRedundantPattern(schema: JsonSchema): JsonSchema {\n if (schema.format === undefined || schema.pattern === undefined) return schema\n const { pattern: _pattern, ...rest } = schema\n return rest\n}\n\n/**\n * Apply a node transform through a schema tree (`items` + `properties`), preserving\n * object identity wherever nothing changed — `buildTargetShape` relies on that to\n * avoid copying a property it did not touch.\n */\nfunction mapSchemaDeep(schema: JsonSchema, transform: (node: JsonSchema) => JsonSchema): JsonSchema {\n let out = transform(schema)\n const items = out.items\n if (typeof items === 'object' && items !== null && !Array.isArray(items)) {\n const next = mapSchemaDeep(items as JsonSchema, transform)\n if (next !== items) out = { ...out, items: next }\n }\n const properties = out.properties\n if (typeof properties === 'object' && properties !== null && !Array.isArray(properties)) {\n let changed = false\n const nextProps: Record<string, JsonSchema> = {}\n for (const [key, value] of Object.entries(properties as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const next = mapSchemaDeep(value as JsonSchema, transform)\n nextProps[key] = next\n if (next !== value) changed = true\n } else {\n nextProps[key] = value as JsonSchema\n }\n }\n if (changed) out = { ...out, properties: nextProps }\n }\n return out\n}\n\n/** Recursively strip redundant patterns through `items` / `properties`. */\nexport function stripRedundantPatternsDeep(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * The whole model-facing rewrite of a projected schema (EC-505): redundant patterns\n * stripped everywhere in the tree — an `object` field's SUBfield gets the same\n * treatment as a top-level one. Identity-preserving. (The date-union narrowing that\n * also ran here is gone: a date is one instant string at rest since EC-586.)\n */\nexport function toModelFacingSchema(schema: JsonSchema): JsonSchema {\n return mapSchemaDeep(schema, stripRedundantPattern)\n}\n\n/**\n * Human-readable phrases for the constraint keywords of ONE schema node, in a stable\n * order — the form a model reads best. Node-level only: nested `items`/`properties`\n * are {@link describeSchemaDeep}'s business. Total over the projection's numeric,\n * string and array vocabulary; `enum`/`const` are deliberately not phrased — they\n * ride the target SHAPE as JSON Schema, and a `select`'s options or a relation's\n * `collection` const would otherwise repeat on every catalog line.\n */\nfunction describeSchemaConstraints(schema: JsonSchema): string[] {\n const out: string[] = []\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' ? value : undefined\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' ? value : undefined\n\n const minLength = num(schema.minLength)\n const maxLength = num(schema.maxLength)\n if (minLength !== undefined && maxLength !== undefined) {\n out.push(`${minLength}–${maxLength} characters`)\n } else if (minLength !== undefined) out.push(`at least ${minLength} characters`)\n else if (maxLength !== undefined) out.push(`at most ${maxLength} characters`)\n\n const minimum = num(schema.minimum)\n const maximum = num(schema.maximum)\n if (minimum !== undefined && maximum !== undefined) out.push(`between ${minimum} and ${maximum}`)\n else if (minimum !== undefined) out.push(`at least ${minimum}`)\n else if (maximum !== undefined) out.push(`at most ${maximum}`)\n\n // EC-522: the projection carried these already; the prose never said them.\n const exclusiveMinimum = num(schema.exclusiveMinimum)\n const exclusiveMaximum = num(schema.exclusiveMaximum)\n if (exclusiveMinimum !== undefined) out.push(`more than ${exclusiveMinimum}`)\n if (exclusiveMaximum !== undefined) out.push(`less than ${exclusiveMaximum}`)\n const multipleOf = num(schema.multipleOf)\n if (multipleOf !== undefined) out.push(`a multiple of ${multipleOf}`)\n\n const minItems = num(schema.minItems)\n const maxItems = num(schema.maxItems)\n if (minItems !== undefined) out.push(`at least ${minItems} items`)\n if (maxItems !== undefined) out.push(`at most ${maxItems} items`)\n if (schema.uniqueItems === true) out.push('no duplicate items')\n\n // EC-473: date bounds finally reach a consumer. `formatMinimum`/`formatMaximum`\n // is the projection's machine form; this is the form a model acts on.\n const from = text(schema.formatMinimum)\n const to = text(schema.formatMaximum)\n if (from !== undefined) out.push(`not earlier than ${from}`)\n if (to !== undefined) out.push(`not later than ${to}`)\n\n const format = text(schema.format)\n // EC-505: `date-time` is said in words rather than skipped — the catalog carries\n // no JSON Schema, so this is the only place the update path learns the format.\n if (format === 'date-time') out.push(ISO_DATE_NOTE)\n else if (format !== undefined) out.push(`format: ${format}`)\n // Only a pattern that is NOT explained by a format is worth the tokens.\n const pattern = text(schema.pattern)\n if (pattern !== undefined && format === undefined) out.push(`matching /${pattern}/`)\n\n return out\n}\n\n/**\n * The phrases of one projected schema node AND everything below it (EC-522): a\n * `multiple` field's per-item bounds become an `each item: …` clause, an object's\n * subfield bounds a `<name>: …` clause, recursively. The projection is the ONLY\n * input — since EC-522 the merge carries nested `items`/`properties` constraints\n * itself, so this module never looks past `fieldJsonSchema` (EC-505's direct read\n * of the derived fragment's `items` is gone, and with it the second place that\n * understood fragment structure).\n *\n * Which side a schema addresses is still the executor's call, not this module's:\n * `validateDocument` hands the validator the field's WHOLE value — a `multiple`\n * field's array — so the per-item form is `z.array(z.string().min(3))`, and a\n * bare `z.string().min(3)` there is a config error at load (EC-522), never a\n * silently mis-applied bound.\n */\nfunction describeSchemaDeep(schema: JsonSchema): string[] {\n const out = describeSchemaConstraints(schema)\n if (isSchemaNode(schema.items)) {\n const itemParts = describeSchemaDeep(schema.items)\n if (itemParts.length > 0) out.push(`each item: ${itemParts.join(', ')}`)\n }\n if (isSchemaNode(schema.properties)) {\n for (const [name, sub] of Object.entries(schema.properties)) {\n if (!isSchemaNode(sub)) continue\n const subParts = describeSchemaDeep(sub)\n if (subParts.length > 0) out.push(`${name}: ${subParts.join(', ')}`)\n }\n }\n return out\n}\n\n/**\n * Human-readable constraint phrases for one field, in a stable order — the form a\n * model reads best (and the form the collections catalog carries, where there is no\n * JSON Schema to hand the model at all). Empty when the field constrains nothing.\n */\nexport function describeFieldConstraints(field: FieldDef): string[] {\n const out: string[] = []\n if (field.validation?.required === true) out.push('required')\n // EC-556: the prose tells a WRITER what to write, so it reads the writer form.\n out.push(...describeSchemaDeep(writerFieldSchema(field)))\n // EC-581: a bound whose unit has no schema keyword — rich text's plain-text\n // characters — is said in its unit, read off the per-FieldType definition, at every\n // depth the writer form reaches (`note: at most 400 plain-text characters …`).\n out.push(...plainTextBoundClauses(field))\n if (field.validation?.unique === true) out.push('unique across the collection')\n // The one thing the projection cannot say about itself: that it is partial (an\n // opaque vendor, a `.refine()`, or — EC-522 — a rule at a position the field's\n // shape has no counterpart for). Set at config resolve, read here, nowhere else.\n if (field.validation?.schemaComplete === false) out.push(INCOMPLETE_CONSTRAINTS_NOTE)\n return out\n}\n\n/** The constraint phrases as one clause (`min 2 characters; required`), or `undefined`. */\nexport function constraintSentence(field: FieldDef): string | undefined {\n const parts = describeFieldConstraints(field)\n return parts.length > 0 ? parts.join('; ') : undefined\n}\n"]}
|
|
@@ -142,7 +142,7 @@ function mapUpdateValue(field, change, document, current, input, issue) {
|
|
|
142
142
|
// them to the new image, and a NEW id is an envelope (EC-381). Data about
|
|
143
143
|
// the image itself (alt, an AI flag) belongs on the asset's `meta`.
|
|
144
144
|
return {
|
|
145
|
-
value: carryAssetPlacements(current, field.multiple ? { multiple: true, ids } : { multiple: false, id: ids[0] ?? null }
|
|
145
|
+
value: carryAssetPlacements(current, field.multiple ? { multiple: true, ids } : { multiple: false, id: ids[0] ?? null }),
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
case 'richText': {
|
|
@@ -157,8 +157,10 @@ function mapUpdateValue(field, change, document, current, input, issue) {
|
|
|
157
157
|
return { value: converted.value };
|
|
158
158
|
}
|
|
159
159
|
default:
|
|
160
|
-
// text / number / boolean / date / select / object / blocks: as
|
|
161
|
-
// (
|
|
160
|
+
// text / number / boolean / date / select / object / blocks: as sent. The
|
|
161
|
+
// write below (`writeField`) canonicalises it (EC-586: a date into one UTC
|
|
162
|
+
// instant, a multi-select scalar into a list, an empty list absent), and the
|
|
163
|
+
// merged document's validateDocument run is the shape leash.
|
|
162
164
|
return { value: raw };
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-fields.js","sourceRoot":"","sources":["../../../src/lib/ai/update-fields.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `buildUpdateProposal` — the EC-378 update-proposal builder (the edit-flow\n * counterpart of `applyContentWithShape`, minus the generation step).\n *\n * The MODEL authors the field changes directly in the `updateFields` tool input\n * (it has the conversation + `getDocument` output as context); this module turns\n * those changes into a reviewable proposal with the SAME engine discipline the\n * create flow has:\n *\n * - **merge semantics**: changes apply per field over the EXISTING document —\n * never a whole-document rewrite; the patch is the merged document, the diff\n * (`diffDocuments`) is what the editor reviews (changed fields only).\n * - **targeting is addressed with the engine's existing conventions** — field\n * name + optional locale bucket (the diff / `RestoreScope` shape) and, inside\n * a `blocks` field, `blockId` + `prop` (the `BlockChange`/`PropChange` shape,\n * root blocks by stable node id). No new path grammar.\n * - **leashes hold, loudly**: `ai.exclude` fields are untouchable; relation AND\n * asset ids (EC-381) must come from searched candidates (`searchDocuments` /\n * `listAssets`) or already be on the document; every targeting failure is an\n * explicit issue — never a silent drop (unlike the create shape, which makes bad\n * ids structurally unrepresentable, an update change is imperative — so it FAILS\n * instead of being filtered).\n * - **validation parity**: the merged document runs through `validateDocument`\n * with the studio's injected leashes. Only errors the change INTRODUCES block\n * the proposal — pre-existing brokenness on the document is not the\n * assistant's doing and must not force a whole-document repair (the editor's\n * own save path tolerates it the same way; errors gate publish, not drafts).\n *\n * Locale-awareness: a localized field's change lands in the change's `locale`,\n * defaulting to the editor's working locale (`input.locale`). Non-localized\n * fields reject a `locale` loudly.\n *\n * Pure and deterministic — no provider, no I/O; the executor (`tools.ts`) wires\n * it to `getDocument` + the candidate store and hands the result to the review\n * sink.\n */\nimport {\n cmsIssue,\n isFieldLocalized,\n readAssetId,\n readDocumentRef,\n referenceNode,\n validateDocument,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CmsValidationIssue,\n CollectionDef,\n CollectionRegistry,\n FieldDef,\n FieldValues,\n ValidateDocumentOptions,\n} from '@elytracms/core/cms-core'\nimport { blockRootsOf, editBlockRoots, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport { diffDocuments } from '@elytracms/core/content'\nimport { carryAssetPlacements } from '../assets/asset-placements'\nimport { introducedErrors, warningsOf } from './proposal-validation'\nimport { richTextValueFromModelInput } from './rich-text-input'\nimport type { AssetCandidates, ContentPatch, RelationCandidates } from './types'\n\n/**\n * One field change of an `updateFields` proposal, addressed with the engine's\n * existing conventions (diff / restore-scope): `field` (+ `locale` bucket for a\n * localized field), and for a `blocks` field optionally `blockId` + `prop` to\n * change one component prop of one root block. `value` is the new value\n * (`null`/omitted clears the field or prop).\n */\nexport interface UpdateFieldChange {\n field: string\n /** Locale bucket for a LOCALIZED field (defaults to the editor's working locale). */\n locale?: string\n /** For a `blocks` field: the root block's stable node id (from `getDocument`). */\n blockId?: string\n /** With `blockId`: the component prop to change. */\n prop?: string\n /** The new value; `null` / omitted clears the field or prop. */\n value?: unknown\n}\n\nexport interface BuildUpdateProposalInput {\n registry: CollectionRegistry\n collection: CollectionDef\n /** The CURRENT document — the proposal's \"before\" side. Never mutated. */\n document: CmsDocument\n changes: readonly UpdateFieldChange[]\n /** The editor's working locale — the default bucket for localized fields. */\n locale?: string\n /** Relation candidates (searched ids) keyed by field name — the id leash. */\n candidates?: RelationCandidates\n /** Asset candidates (searched library ids) keyed by field name — the asset id leash (EC-381). */\n assetCandidates?: AssetCandidates\n /**\n * THE validation option bag (EC-497) — the studio session's\n * `gateValidateOptions(...)`, whole. It also carries the project's\n * `localeConfig`, which is what the unknown-locale targeting check reads: one\n * source for \"which locales exist\", not two that can disagree.\n */\n validateOptions?: ValidateDocumentOptions\n}\n\n/** The `before` snapshot the review diff renders against. */\nexport interface UpdateBeforeSnapshot {\n values: FieldValues\n localized?: Record<string, FieldValues>\n}\n\nexport type BuildUpdateProposalResult =\n | {\n ok: true\n kind: 'proposal'\n /** The MERGED document as a patch (full values; the diff shows changed fields only). */\n patch: ContentPatch\n before: UpdateBeforeSnapshot\n /** Human-readable descriptors of the changed fields (e.g. `title (de)`). */\n changedFields: string[]\n /** Non-error issues on the accepted result (warnings). */\n issues: CmsValidationIssue[]\n }\n | {\n /** Every change resolved to the current values — nothing to propose. */\n ok: true\n kind: 'no-change'\n }\n | { ok: false; issues: CmsValidationIssue[] }\n\ntype ChangeIssue = { issue: CmsValidationIssue }\ntype ChangeOutcome = { doc: CmsDocument } | ChangeIssue\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0\n}\n\n// The present/absent-key field write is core's `writeField` (EC-580) — this module\n// used to carry a local copy of the editor's `setFieldValue` (\"Local here (not\n// imported)\", MODE F5b.bkt). The locale is resolved per change below: a localized\n// field always arrives with one (a missing one is an explicit issue first).\n\n/**\n * The read twin of core's `writeField`: the field's value in the resolved\n * `locale` bucket, or in the shared `values` for a non-localized field. The exact\n * bucket only, with no default-locale fallback, because that bucket is the one the\n * write replaces.\n */\nfunction readFieldValue(doc: CmsDocument, fieldName: string, locale: string | undefined): unknown {\n return locale !== undefined ? doc.localized?.[locale]?.[fieldName] : doc.values[fieldName]\n}\n\n/** Relation ids CURRENTLY on the document for a field (any bucket) — re-usable without a search. */\nfunction currentRelationIds(doc: CmsDocument, fieldName: string): Set<string> {\n const ids = new Set<string>()\n const collect = (value: unknown) => {\n for (const entry of Array.isArray(value) ? value : [value]) {\n const ref = readDocumentRef(entry)\n if (ref) ids.add(ref.id)\n }\n }\n collect(doc.values[fieldName])\n for (const bucket of Object.values(doc.localized ?? {})) collect(bucket[fieldName])\n return ids\n}\n\n/** Asset ids CURRENTLY on the document for a field (any bucket) — re-usable without a search (EC-381). */\nfunction currentAssetIds(doc: CmsDocument, fieldName: string): Set<string> {\n const ids = new Set<string>()\n const collect = (value: unknown) => {\n for (const entry of Array.isArray(value) ? value : [value]) {\n const id = readAssetId(entry)\n if (id) ids.add(id)\n }\n }\n collect(doc.values[fieldName])\n for (const bucket of Object.values(doc.localized ?? {})) collect(bucket[fieldName])\n return ids\n}\n\n/**\n * Map a change's raw value into the stored value shape (envelopes; loud leash).\n * `document` is the ORIGINAL document, which is what the id leash reads. `current`\n * is the value this change replaces, taken from the resolved locale bucket of the\n * working copy. The asset sub-field carry reads it (EC-558).\n */\nfunction mapUpdateValue(\n field: FieldDef,\n change: UpdateFieldChange,\n document: CmsDocument,\n current: unknown,\n input: BuildUpdateProposalInput,\n issue: (code: Parameters<typeof cmsIssue>[0]['code'], message: string) => ChangeIssue,\n): { value: unknown } | ChangeIssue {\n const raw = change.value\n if (raw === undefined || raw === null) return { value: undefined } // clear\n switch (field.type) {\n case 'relation': {\n const ids = Array.isArray(raw) ? raw : [raw]\n if (!ids.every(isNonEmptyString)) {\n return issue(\n 'invalid-field-value',\n `Relation \"${field.name}\" takes document id string(s) from searchDocuments results.`,\n )\n }\n // The id leash (same stance as the create enum, but LOUD): only searched\n // candidate ids or ids already on the document are usable.\n const allowed = new Set([\n ...(input.candidates?.[field.name] ?? []).map((c) => c.id),\n ...currentRelationIds(document, field.name),\n ])\n const unknown = ids.filter((id) => !allowed.has(id))\n if (unknown.length > 0) {\n return issue(\n 'unknown-relation-target',\n `Relation \"${field.name}\" may only use ids from searchDocuments results — search \"${field.target}\" first (got: ${unknown.join(', ')}).`,\n )\n }\n if (!field.multiple && ids.length > 1) {\n return issue('invalid-field-value', `Field \"${field.name}\" holds a single reference — pass one id.`)\n }\n const refs = ids.map((id) => referenceNode({ collection: field.target, id }))\n return { value: field.multiple ? refs : refs[0] }\n }\n case 'asset': {\n const ids = Array.isArray(raw) ? raw : [raw]\n if (!ids.every(isNonEmptyString)) {\n return issue(\n 'invalid-field-value',\n `Asset field \"${field.name}\" takes asset id string(s) from listAssets results.`,\n )\n }\n // EC-381 — the asset id leash (same stance as relations, but LOUD): only\n // searched library candidate ids or ids already on the document are usable;\n // the assistant never invents or uploads an asset.\n const allowed = new Set([\n ...(input.assetCandidates?.[field.name] ?? []).map((c) => c.id),\n ...currentAssetIds(document, field.name),\n ])\n const unknown = ids.filter((id) => !allowed.has(id))\n if (unknown.length > 0) {\n return issue(\n 'unknown-asset',\n `Asset field \"${field.name}\" may only use ids from listAssets results — search the media library first (got: ${unknown.join(', ')}).`,\n )\n }\n if (!field.multiple && ids.length > 1) {\n return issue('invalid-field-value', `Field \"${field.name}\" holds a single asset — pass one id.`)\n }\n // EC-558: replace-with-list through the one carry rule. A held id keeps its\n // placement's per-use sub-fields (a caption, say), a single swap carries\n // them to the new image, and a NEW id is an envelope (EC-381). Data about\n // the image itself (alt, an AI flag) belongs on the asset's `meta`.\n return {\n value: carryAssetPlacements(\n current,\n field.multiple ? { multiple: true, ids } : { multiple: false, id: ids[0] ?? null },\n 'envelope',\n ),\n }\n }\n case 'richText': {\n // EC-379: a string value is MARKDOWN, converted through the engine's strict\n // subset converter; out-of-subset constructs fail LOUD (the model retries\n // in-conversation). A `{ version, doc }` envelope object stays accepted for\n // robustness (getDocument may have shown one) — see rich-text-input.ts.\n const converted = richTextValueFromModelInput(raw)\n if (!converted.ok) {\n return issue('invalid-field-value', `Field \"${field.name}\": ${converted.problems.join(' ')}`)\n }\n return { value: converted.value }\n }\n default:\n // text / number / boolean / date / select / object / blocks: as-is\n // (the merged document's validateDocument run is the shape leash).\n return { value: raw }\n }\n}\n\n/** Change one component prop of one root block (the `BlockChange`/`PropChange` address). */\nfunction applyBlockPropChange(\n collection: CollectionDef,\n doc: CmsDocument,\n field: FieldDef,\n change: UpdateFieldChange,\n locale: string | undefined,\n issue: (code: Parameters<typeof cmsIssue>[0]['code'], message: string) => ChangeIssue,\n): ChangeOutcome {\n if (field.type !== 'blocks') {\n return issue(\n 'invalid-field-value',\n `\"${change.field}\" is not a blocks field — omit \"blockId\"/\"prop\" (set the field value directly).`,\n )\n }\n if (!isNonEmptyString(change.blockId)) {\n return issue(\n 'invalid-field-value',\n `Changing a block on \"${change.field}\" needs \"blockId\" — block ids come from getDocument.`,\n )\n }\n if (!isNonEmptyString(change.prop)) {\n return issue(\n 'invalid-field-value',\n `Changing block \"${change.blockId}\" needs \"prop\" — the component property to change.`,\n )\n }\n const original = readFieldValue(doc, change.field, locale)\n const blocks = blockRootsOf(original)\n const index = blocks.findIndex((block) => block.id === change.blockId)\n if (index < 0) {\n return issue(\n 'invalid-field-value',\n `Block \"${change.blockId}\" was not found on \"${change.field}\" — read the document first; block ids come from getDocument.`,\n )\n }\n const block = blocks[index]!\n const props = { ...(block.props ?? {}) }\n if (change.value === undefined || change.value === null) delete props[change.prop]\n else props[change.prop] = staticProp(change.value as JsonValue)\n const nextBlock: ComponentNode = { ...block, props }\n // EC-576: through the core write path — the other roots and every typography\n // item keep their place, and the write is the v2 sequence. (`blocks` is\n // `editBlockRoots`' own root list, so `index` addresses the same root.)\n const value = editBlockRoots(original, (roots) => roots.map((b, i) => (i === index ? nextBlock : b)))\n return { doc: writeField(collection, doc, change.field, locale, value) }\n}\n\n/** Apply ONE change to a (copy of the) document, or report the targeting issue. */\nfunction applyOneChange(\n input: BuildUpdateProposalInput,\n doc: CmsDocument,\n change: UpdateFieldChange,\n): ChangeOutcome {\n const { collection } = input\n const issue = (\n code: Parameters<typeof cmsIssue>[0]['code'],\n message: string,\n ): ChangeIssue => ({\n issue: cmsIssue({\n code,\n message,\n path: ['documents', `${collection.id}:${doc.id}`, 'values', change.field],\n collectionId: collection.id,\n documentId: doc.id,\n meta: { field: change.field, ...(change.locale !== undefined ? { locale: change.locale } : {}) },\n }),\n })\n\n const field = collection.fields.find((f) => f.name === change.field)\n if (!field) {\n return issue(\n 'invalid-field-value',\n `Unknown field \"${change.field}\" on collection \"${collection.id}\" — use a field name from the schema.`,\n )\n }\n if (field.ai?.exclude) {\n return issue('invalid-field-value', `Field \"${change.field}\" must not be edited by the assistant.`)\n }\n\n const localized = isFieldLocalized(collection, change.field)\n if (!localized && change.locale !== undefined) {\n return issue('unknown-locale', `Field \"${change.field}\" is not localized — omit \"locale\".`)\n }\n if (\n change.locale !== undefined &&\n input.validateOptions?.localeConfig &&\n !input.validateOptions.localeConfig.locales.includes(change.locale)\n ) {\n return issue(\n 'unknown-locale',\n `Unknown locale \"${change.locale}\" — this project has: ${input.validateOptions?.localeConfig?.locales.join(', ')}.`,\n )\n }\n const locale = localized ? change.locale ?? input.locale : undefined\n if (localized && locale === undefined) {\n return issue('missing-localized-value', `Field \"${change.field}\" is localized — provide \"locale\".`)\n }\n\n if (change.blockId !== undefined || change.prop !== undefined) {\n return applyBlockPropChange(collection, doc, field, change, locale, issue)\n }\n\n const current = readFieldValue(doc, change.field, locale)\n const mapped = mapUpdateValue(field, change, input.document, current, input, issue)\n if ('issue' in mapped) return mapped\n return { doc: writeField(collection, doc, change.field, locale, mapped.value) }\n}\n\n\n/**\n * Build the reviewable update proposal: apply every change over a copy of the\n * document (loud targeting), validate the merged result with the injected\n * leashes (only errors the change INTRODUCES block), and report the semantic\n * diff. `no-change` when the changes resolve to the current values.\n */\nexport function buildUpdateProposal(input: BuildUpdateProposalInput): BuildUpdateProposalResult {\n const { registry, collection, document } = input\n\n let next: CmsDocument = structuredClone(document)\n const targetingIssues: CmsValidationIssue[] = []\n for (const change of input.changes) {\n const outcome = applyOneChange(input, next, change)\n if ('issue' in outcome) targetingIssues.push(outcome.issue)\n else next = outcome.doc\n }\n if (targetingIssues.length > 0) return { ok: false, issues: targetingIssues }\n\n // EC-497: THE bag, whole — no per-call-site reassembly.\n const validateOptions: ValidateDocumentOptions = input.validateOptions ?? {}\n const before = validateDocument(registry, document, validateOptions)\n const issues = validateDocument(registry, next, validateOptions)\n const introduced = introducedErrors(before, issues)\n if (introduced.length > 0) return { ok: false, issues: introduced }\n\n const diff = diffDocuments(collection, document, next)\n if (diff.changes.length === 0) return { ok: true, kind: 'no-change' }\n\n return {\n ok: true,\n kind: 'proposal',\n patch: {\n collection: collection.id,\n id: document.id,\n values: next.values,\n ...(next.localized && Object.keys(next.localized).length > 0\n ? { localized: next.localized }\n : {}),\n },\n before: {\n values: document.values,\n ...(document.localized ? { localized: document.localized } : {}),\n },\n changedFields: diff.changes.map((change) =>\n change.locale !== undefined ? `${change.field} (${change.locale})` : change.field,\n ),\n issues: warningsOf(issues),\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"update-fields.js","sourceRoot":"","sources":["../../../src/lib/ai/update-fields.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `buildUpdateProposal` — the EC-378 update-proposal builder (the edit-flow\n * counterpart of `applyContentWithShape`, minus the generation step).\n *\n * The MODEL authors the field changes directly in the `updateFields` tool input\n * (it has the conversation + `getDocument` output as context); this module turns\n * those changes into a reviewable proposal with the SAME engine discipline the\n * create flow has:\n *\n * - **merge semantics**: changes apply per field over the EXISTING document —\n * never a whole-document rewrite; the patch is the merged document, the diff\n * (`diffDocuments`) is what the editor reviews (changed fields only).\n * - **targeting is addressed with the engine's existing conventions** — field\n * name + optional locale bucket (the diff / `RestoreScope` shape) and, inside\n * a `blocks` field, `blockId` + `prop` (the `BlockChange`/`PropChange` shape,\n * root blocks by stable node id). No new path grammar.\n * - **leashes hold, loudly**: `ai.exclude` fields are untouchable; relation AND\n * asset ids (EC-381) must come from searched candidates (`searchDocuments` /\n * `listAssets`) or already be on the document; every targeting failure is an\n * explicit issue — never a silent drop (unlike the create shape, which makes bad\n * ids structurally unrepresentable, an update change is imperative — so it FAILS\n * instead of being filtered).\n * - **validation parity**: the merged document runs through `validateDocument`\n * with the studio's injected leashes. Only errors the change INTRODUCES block\n * the proposal — pre-existing brokenness on the document is not the\n * assistant's doing and must not force a whole-document repair (the editor's\n * own save path tolerates it the same way; errors gate publish, not drafts).\n *\n * Locale-awareness: a localized field's change lands in the change's `locale`,\n * defaulting to the editor's working locale (`input.locale`). Non-localized\n * fields reject a `locale` loudly.\n *\n * Pure and deterministic — no provider, no I/O; the executor (`tools.ts`) wires\n * it to `getDocument` + the candidate store and hands the result to the review\n * sink.\n */\nimport {\n cmsIssue,\n isFieldLocalized,\n readAssetId,\n readDocumentRef,\n referenceNode,\n validateDocument,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CmsValidationIssue,\n CollectionDef,\n CollectionRegistry,\n FieldDef,\n FieldValues,\n ValidateDocumentOptions,\n} from '@elytracms/core/cms-core'\nimport { blockRootsOf, editBlockRoots, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport { diffDocuments } from '@elytracms/core/content'\nimport { carryAssetPlacements } from '../assets/asset-placements'\nimport { introducedErrors, warningsOf } from './proposal-validation'\nimport { richTextValueFromModelInput } from './rich-text-input'\nimport type { AssetCandidates, ContentPatch, RelationCandidates } from './types'\n\n/**\n * One field change of an `updateFields` proposal, addressed with the engine's\n * existing conventions (diff / restore-scope): `field` (+ `locale` bucket for a\n * localized field), and for a `blocks` field optionally `blockId` + `prop` to\n * change one component prop of one root block. `value` is the new value\n * (`null`/omitted clears the field or prop).\n */\nexport interface UpdateFieldChange {\n field: string\n /** Locale bucket for a LOCALIZED field (defaults to the editor's working locale). */\n locale?: string\n /** For a `blocks` field: the root block's stable node id (from `getDocument`). */\n blockId?: string\n /** With `blockId`: the component prop to change. */\n prop?: string\n /** The new value; `null` / omitted clears the field or prop. */\n value?: unknown\n}\n\nexport interface BuildUpdateProposalInput {\n registry: CollectionRegistry\n collection: CollectionDef\n /** The CURRENT document — the proposal's \"before\" side. Never mutated. */\n document: CmsDocument\n changes: readonly UpdateFieldChange[]\n /** The editor's working locale — the default bucket for localized fields. */\n locale?: string\n /** Relation candidates (searched ids) keyed by field name — the id leash. */\n candidates?: RelationCandidates\n /** Asset candidates (searched library ids) keyed by field name — the asset id leash (EC-381). */\n assetCandidates?: AssetCandidates\n /**\n * THE validation option bag (EC-497) — the studio session's\n * `gateValidateOptions(...)`, whole. It also carries the project's\n * `localeConfig`, which is what the unknown-locale targeting check reads: one\n * source for \"which locales exist\", not two that can disagree.\n */\n validateOptions?: ValidateDocumentOptions\n}\n\n/** The `before` snapshot the review diff renders against. */\nexport interface UpdateBeforeSnapshot {\n values: FieldValues\n localized?: Record<string, FieldValues>\n}\n\nexport type BuildUpdateProposalResult =\n | {\n ok: true\n kind: 'proposal'\n /** The MERGED document as a patch (full values; the diff shows changed fields only). */\n patch: ContentPatch\n before: UpdateBeforeSnapshot\n /** Human-readable descriptors of the changed fields (e.g. `title (de)`). */\n changedFields: string[]\n /** Non-error issues on the accepted result (warnings). */\n issues: CmsValidationIssue[]\n }\n | {\n /** Every change resolved to the current values — nothing to propose. */\n ok: true\n kind: 'no-change'\n }\n | { ok: false; issues: CmsValidationIssue[] }\n\ntype ChangeIssue = { issue: CmsValidationIssue }\ntype ChangeOutcome = { doc: CmsDocument } | ChangeIssue\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0\n}\n\n// The present/absent-key field write is core's `writeField` (EC-580) — this module\n// used to carry a local copy of the editor's `setFieldValue` (\"Local here (not\n// imported)\", MODE F5b.bkt). The locale is resolved per change below: a localized\n// field always arrives with one (a missing one is an explicit issue first).\n\n/**\n * The read twin of core's `writeField`: the field's value in the resolved\n * `locale` bucket, or in the shared `values` for a non-localized field. The exact\n * bucket only, with no default-locale fallback, because that bucket is the one the\n * write replaces.\n */\nfunction readFieldValue(doc: CmsDocument, fieldName: string, locale: string | undefined): unknown {\n return locale !== undefined ? doc.localized?.[locale]?.[fieldName] : doc.values[fieldName]\n}\n\n/** Relation ids CURRENTLY on the document for a field (any bucket) — re-usable without a search. */\nfunction currentRelationIds(doc: CmsDocument, fieldName: string): Set<string> {\n const ids = new Set<string>()\n const collect = (value: unknown) => {\n for (const entry of Array.isArray(value) ? value : [value]) {\n const ref = readDocumentRef(entry)\n if (ref) ids.add(ref.id)\n }\n }\n collect(doc.values[fieldName])\n for (const bucket of Object.values(doc.localized ?? {})) collect(bucket[fieldName])\n return ids\n}\n\n/** Asset ids CURRENTLY on the document for a field (any bucket) — re-usable without a search (EC-381). */\nfunction currentAssetIds(doc: CmsDocument, fieldName: string): Set<string> {\n const ids = new Set<string>()\n const collect = (value: unknown) => {\n for (const entry of Array.isArray(value) ? value : [value]) {\n const id = readAssetId(entry)\n if (id) ids.add(id)\n }\n }\n collect(doc.values[fieldName])\n for (const bucket of Object.values(doc.localized ?? {})) collect(bucket[fieldName])\n return ids\n}\n\n/**\n * Map a change's raw value into the stored value shape (envelopes; loud leash).\n * `document` is the ORIGINAL document, which is what the id leash reads. `current`\n * is the value this change replaces, taken from the resolved locale bucket of the\n * working copy. The asset sub-field carry reads it (EC-558).\n */\nfunction mapUpdateValue(\n field: FieldDef,\n change: UpdateFieldChange,\n document: CmsDocument,\n current: unknown,\n input: BuildUpdateProposalInput,\n issue: (code: Parameters<typeof cmsIssue>[0]['code'], message: string) => ChangeIssue,\n): { value: unknown } | ChangeIssue {\n const raw = change.value\n if (raw === undefined || raw === null) return { value: undefined } // clear\n switch (field.type) {\n case 'relation': {\n const ids = Array.isArray(raw) ? raw : [raw]\n if (!ids.every(isNonEmptyString)) {\n return issue(\n 'invalid-field-value',\n `Relation \"${field.name}\" takes document id string(s) from searchDocuments results.`,\n )\n }\n // The id leash (same stance as the create enum, but LOUD): only searched\n // candidate ids or ids already on the document are usable.\n const allowed = new Set([\n ...(input.candidates?.[field.name] ?? []).map((c) => c.id),\n ...currentRelationIds(document, field.name),\n ])\n const unknown = ids.filter((id) => !allowed.has(id))\n if (unknown.length > 0) {\n return issue(\n 'unknown-relation-target',\n `Relation \"${field.name}\" may only use ids from searchDocuments results — search \"${field.target}\" first (got: ${unknown.join(', ')}).`,\n )\n }\n if (!field.multiple && ids.length > 1) {\n return issue('invalid-field-value', `Field \"${field.name}\" holds a single reference — pass one id.`)\n }\n const refs = ids.map((id) => referenceNode({ collection: field.target, id }))\n return { value: field.multiple ? refs : refs[0] }\n }\n case 'asset': {\n const ids = Array.isArray(raw) ? raw : [raw]\n if (!ids.every(isNonEmptyString)) {\n return issue(\n 'invalid-field-value',\n `Asset field \"${field.name}\" takes asset id string(s) from listAssets results.`,\n )\n }\n // EC-381 — the asset id leash (same stance as relations, but LOUD): only\n // searched library candidate ids or ids already on the document are usable;\n // the assistant never invents or uploads an asset.\n const allowed = new Set([\n ...(input.assetCandidates?.[field.name] ?? []).map((c) => c.id),\n ...currentAssetIds(document, field.name),\n ])\n const unknown = ids.filter((id) => !allowed.has(id))\n if (unknown.length > 0) {\n return issue(\n 'unknown-asset',\n `Asset field \"${field.name}\" may only use ids from listAssets results — search the media library first (got: ${unknown.join(', ')}).`,\n )\n }\n if (!field.multiple && ids.length > 1) {\n return issue('invalid-field-value', `Field \"${field.name}\" holds a single asset — pass one id.`)\n }\n // EC-558: replace-with-list through the one carry rule. A held id keeps its\n // placement's per-use sub-fields (a caption, say), a single swap carries\n // them to the new image, and a NEW id is an envelope (EC-381). Data about\n // the image itself (alt, an AI flag) belongs on the asset's `meta`.\n return {\n value: carryAssetPlacements(\n current,\n field.multiple ? { multiple: true, ids } : { multiple: false, id: ids[0] ?? null },\n ),\n }\n }\n case 'richText': {\n // EC-379: a string value is MARKDOWN, converted through the engine's strict\n // subset converter; out-of-subset constructs fail LOUD (the model retries\n // in-conversation). A `{ version, doc }` envelope object stays accepted for\n // robustness (getDocument may have shown one) — see rich-text-input.ts.\n const converted = richTextValueFromModelInput(raw)\n if (!converted.ok) {\n return issue('invalid-field-value', `Field \"${field.name}\": ${converted.problems.join(' ')}`)\n }\n return { value: converted.value }\n }\n default:\n // text / number / boolean / date / select / object / blocks: as sent. The\n // write below (`writeField`) canonicalises it (EC-586: a date into one UTC\n // instant, a multi-select scalar into a list, an empty list absent), and the\n // merged document's validateDocument run is the shape leash.\n return { value: raw }\n }\n}\n\n/** Change one component prop of one root block (the `BlockChange`/`PropChange` address). */\nfunction applyBlockPropChange(\n collection: CollectionDef,\n doc: CmsDocument,\n field: FieldDef,\n change: UpdateFieldChange,\n locale: string | undefined,\n issue: (code: Parameters<typeof cmsIssue>[0]['code'], message: string) => ChangeIssue,\n): ChangeOutcome {\n if (field.type !== 'blocks') {\n return issue(\n 'invalid-field-value',\n `\"${change.field}\" is not a blocks field — omit \"blockId\"/\"prop\" (set the field value directly).`,\n )\n }\n if (!isNonEmptyString(change.blockId)) {\n return issue(\n 'invalid-field-value',\n `Changing a block on \"${change.field}\" needs \"blockId\" — block ids come from getDocument.`,\n )\n }\n if (!isNonEmptyString(change.prop)) {\n return issue(\n 'invalid-field-value',\n `Changing block \"${change.blockId}\" needs \"prop\" — the component property to change.`,\n )\n }\n const original = readFieldValue(doc, change.field, locale)\n const blocks = blockRootsOf(original)\n const index = blocks.findIndex((block) => block.id === change.blockId)\n if (index < 0) {\n return issue(\n 'invalid-field-value',\n `Block \"${change.blockId}\" was not found on \"${change.field}\" — read the document first; block ids come from getDocument.`,\n )\n }\n const block = blocks[index]!\n const props = { ...(block.props ?? {}) }\n if (change.value === undefined || change.value === null) delete props[change.prop]\n else props[change.prop] = staticProp(change.value as JsonValue)\n const nextBlock: ComponentNode = { ...block, props }\n // EC-576: through the core write path — the other roots and every typography\n // item keep their place, and the write is the v2 sequence. (`blocks` is\n // `editBlockRoots`' own root list, so `index` addresses the same root.)\n const value = editBlockRoots(original, (roots) => roots.map((b, i) => (i === index ? nextBlock : b)))\n return { doc: writeField(collection, doc, change.field, locale, value) }\n}\n\n/** Apply ONE change to a (copy of the) document, or report the targeting issue. */\nfunction applyOneChange(\n input: BuildUpdateProposalInput,\n doc: CmsDocument,\n change: UpdateFieldChange,\n): ChangeOutcome {\n const { collection } = input\n const issue = (\n code: Parameters<typeof cmsIssue>[0]['code'],\n message: string,\n ): ChangeIssue => ({\n issue: cmsIssue({\n code,\n message,\n path: ['documents', `${collection.id}:${doc.id}`, 'values', change.field],\n collectionId: collection.id,\n documentId: doc.id,\n meta: { field: change.field, ...(change.locale !== undefined ? { locale: change.locale } : {}) },\n }),\n })\n\n const field = collection.fields.find((f) => f.name === change.field)\n if (!field) {\n return issue(\n 'invalid-field-value',\n `Unknown field \"${change.field}\" on collection \"${collection.id}\" — use a field name from the schema.`,\n )\n }\n if (field.ai?.exclude) {\n return issue('invalid-field-value', `Field \"${change.field}\" must not be edited by the assistant.`)\n }\n\n const localized = isFieldLocalized(collection, change.field)\n if (!localized && change.locale !== undefined) {\n return issue('unknown-locale', `Field \"${change.field}\" is not localized — omit \"locale\".`)\n }\n if (\n change.locale !== undefined &&\n input.validateOptions?.localeConfig &&\n !input.validateOptions.localeConfig.locales.includes(change.locale)\n ) {\n return issue(\n 'unknown-locale',\n `Unknown locale \"${change.locale}\" — this project has: ${input.validateOptions?.localeConfig?.locales.join(', ')}.`,\n )\n }\n const locale = localized ? change.locale ?? input.locale : undefined\n if (localized && locale === undefined) {\n return issue('missing-localized-value', `Field \"${change.field}\" is localized — provide \"locale\".`)\n }\n\n if (change.blockId !== undefined || change.prop !== undefined) {\n return applyBlockPropChange(collection, doc, field, change, locale, issue)\n }\n\n const current = readFieldValue(doc, change.field, locale)\n const mapped = mapUpdateValue(field, change, input.document, current, input, issue)\n if ('issue' in mapped) return mapped\n return { doc: writeField(collection, doc, change.field, locale, mapped.value) }\n}\n\n\n/**\n * Build the reviewable update proposal: apply every change over a copy of the\n * document (loud targeting), validate the merged result with the injected\n * leashes (only errors the change INTRODUCES block), and report the semantic\n * diff. `no-change` when the changes resolve to the current values.\n */\nexport function buildUpdateProposal(input: BuildUpdateProposalInput): BuildUpdateProposalResult {\n const { registry, collection, document } = input\n\n let next: CmsDocument = structuredClone(document)\n const targetingIssues: CmsValidationIssue[] = []\n for (const change of input.changes) {\n const outcome = applyOneChange(input, next, change)\n if ('issue' in outcome) targetingIssues.push(outcome.issue)\n else next = outcome.doc\n }\n if (targetingIssues.length > 0) return { ok: false, issues: targetingIssues }\n\n // EC-497: THE bag, whole — no per-call-site reassembly.\n const validateOptions: ValidateDocumentOptions = input.validateOptions ?? {}\n const before = validateDocument(registry, document, validateOptions)\n const issues = validateDocument(registry, next, validateOptions)\n const introduced = introducedErrors(before, issues)\n if (introduced.length > 0) return { ok: false, issues: introduced }\n\n const diff = diffDocuments(collection, document, next)\n if (diff.changes.length === 0) return { ok: true, kind: 'no-change' }\n\n return {\n ok: true,\n kind: 'proposal',\n patch: {\n collection: collection.id,\n id: document.id,\n values: next.values,\n ...(next.localized && Object.keys(next.localized).length > 0\n ? { localized: next.localized }\n : {}),\n },\n before: {\n values: document.values,\n ...(document.localized ? { localized: document.localized } : {}),\n },\n changedFields: diff.changes.map((change) =>\n change.locale !== undefined ? `${change.field} (${change.locale})` : change.field,\n ),\n issues: warningsOf(issues),\n }\n}\n"]}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a writer spells a BRAND-NEW placement, which has no sub-fields yet. This is
|
|
3
|
-
* the caller's choice, not part of the carry rule. The detail editor and the table
|
|
4
|
-
* cell use `'bare'` on a field without sub-fields and `'envelope'` on one with them
|
|
5
|
-
* (`assetPickForm`). AI always uses `'envelope'` (EC-381).
|
|
6
|
-
*/
|
|
7
|
-
export type NewPlacementForm = 'bare' | 'envelope';
|
|
8
1
|
/** The ids the field should hold next. The shape follows the field's cardinality. */
|
|
9
2
|
export type AssetPlacementTarget = {
|
|
10
3
|
multiple: false;
|
|
@@ -22,8 +15,8 @@ export type AssetPlacementTarget = {
|
|
|
22
15
|
* - **Multiple field.** An id the value already held keeps its entry verbatim, with
|
|
23
16
|
* its sub-fields and its form (a bare id stays bare). Entries are matched in order,
|
|
24
17
|
* so an image placed twice keeps both placements. An id the value did not hold
|
|
25
|
-
* becomes a new
|
|
26
|
-
*
|
|
18
|
+
* becomes a new envelope. A held entry whose id is not in `ids` is removed, and an
|
|
19
|
+
* emptied list is ABSENT (`undefined`, EC-586), never `[]`.
|
|
27
20
|
*
|
|
28
21
|
* The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`
|
|
29
22
|
* reads. That includes keys the config no longer declares (AD-14: the schema is a
|
|
@@ -32,4 +25,4 @@ export type AssetPlacementTarget = {
|
|
|
32
25
|
* When nothing changes it returns `current` itself (the same reference), so a caller
|
|
33
26
|
* can skip the write.
|
|
34
27
|
*/
|
|
35
|
-
export declare function carryAssetPlacements(current: unknown, next: AssetPlacementTarget
|
|
28
|
+
export declare function carryAssetPlacements(current: unknown, next: AssetPlacementTarget): unknown;
|
|
@@ -25,8 +25,15 @@
|
|
|
25
25
|
* (import, CLI, MCP) appears.
|
|
26
26
|
*/
|
|
27
27
|
import { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core';
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* A BRAND-NEW placement, which has no sub-fields yet: always the canonical envelope
|
|
30
|
+
* (EC-586, ratified 2c — an asset value is `{ type: 'asset', id, … }`). There used to be
|
|
31
|
+
* a second, bare spelling (`'bare'`, a plain id string) for fields without sub-fields;
|
|
32
|
+
* the write lens wrapped it on save, so the in-session draft held a shape the stored one
|
|
33
|
+
* did not, and a def-less "has unpublished changes" verdict read the two as different.
|
|
34
|
+
*/
|
|
35
|
+
function newPlacement(id) {
|
|
36
|
+
return assetNode(id);
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
32
39
|
* Return the field's next value with every placement's per-use sub-fields kept.
|
|
@@ -37,8 +44,8 @@ function newPlacement(id, form) {
|
|
|
37
44
|
* - **Multiple field.** An id the value already held keeps its entry verbatim, with
|
|
38
45
|
* its sub-fields and its form (a bare id stays bare). Entries are matched in order,
|
|
39
46
|
* so an image placed twice keeps both placements. An id the value did not hold
|
|
40
|
-
* becomes a new
|
|
41
|
-
*
|
|
47
|
+
* becomes a new envelope. A held entry whose id is not in `ids` is removed, and an
|
|
48
|
+
* emptied list is ABSENT (`undefined`, EC-586), never `[]`.
|
|
42
49
|
*
|
|
43
50
|
* The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`
|
|
44
51
|
* reads. That includes keys the config no longer declares (AD-14: the schema is a
|
|
@@ -47,7 +54,7 @@ function newPlacement(id, form) {
|
|
|
47
54
|
* When nothing changes it returns `current` itself (the same reference), so a caller
|
|
48
55
|
* can skip the write.
|
|
49
56
|
*/
|
|
50
|
-
export function carryAssetPlacements(current, next
|
|
57
|
+
export function carryAssetPlacements(current, next) {
|
|
51
58
|
if (!next.multiple) {
|
|
52
59
|
if (next.id === null)
|
|
53
60
|
return undefined;
|
|
@@ -57,18 +64,20 @@ export function carryAssetPlacements(current, next, form) {
|
|
|
57
64
|
if (readAssetId(held) === next.id)
|
|
58
65
|
return current;
|
|
59
66
|
const subfields = assetSubfieldsOf(held);
|
|
60
|
-
return Object.keys(subfields).length > 0 ? assetNode(next.id, subfields) : newPlacement(next.id
|
|
67
|
+
return Object.keys(subfields).length > 0 ? assetNode(next.id, subfields) : newPlacement(next.id);
|
|
61
68
|
}
|
|
62
69
|
const held = Array.isArray(current) ? current : [];
|
|
63
70
|
const used = new Set();
|
|
64
71
|
const out = next.ids.map((id) => {
|
|
65
72
|
const index = held.findIndex((entry, i) => !used.has(i) && readAssetId(entry) === id);
|
|
66
73
|
if (index < 0)
|
|
67
|
-
return newPlacement(id
|
|
74
|
+
return newPlacement(id);
|
|
68
75
|
used.add(index);
|
|
69
76
|
return held[index];
|
|
70
77
|
});
|
|
71
78
|
const unchanged = out.length === held.length && out.every((entry, i) => entry === held[i]);
|
|
72
|
-
|
|
79
|
+
if (unchanged && Array.isArray(current))
|
|
80
|
+
return current;
|
|
81
|
+
return out.length === 0 ? undefined : out;
|
|
73
82
|
}
|
|
74
83
|
//# sourceMappingURL=asset-placements.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-placements.js","sourceRoot":"","sources":["../../../src/lib/assets/asset-placements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"asset-placements.js","sourceRoot":"","sources":["../../../src/lib/assets/asset-placements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAOnF;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,SAAS,CAAC,EAAE,CAAC,CAAA;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB,EAAE,IAA0B;IAC/E,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,SAAS,CAAA;QACtC,iFAAiF;QACjF,8BAA8B;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QAC1D,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE;YAAE,OAAO,OAAO,CAAA;QACjD,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClG,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,OAAqB,CAAC,CAAC,CAAC,EAAE,CAAA;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACrF,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,YAAY,CAAC,EAAE,CAAC,CAAA;QACtC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC,CAAC,CAAA;IACF,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1F,IAAI,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IACvD,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA;AAC3C,CAAC","sourcesContent":["/**\n * The per-use sub-field carry rule for an asset field's value (EC-558).\n *\n * An asset field that declares per-use sub-fields (EC-301) stores envelopes such as\n * `{ type: 'asset', id, caption }`. Those sub-fields are PLACEMENT data: they\n * describe this use of the image, not the image itself (owner ruling 2026-09-26;\n * data that describes the image belongs on the asset record's `meta`). A writer\n * that changes which image a placement shows must therefore keep the placement's\n * sub-fields. Before EC-558 only the detail editor did that. The table cell and AI\n * `updateFields` rebuilt the value from bare ids and lost them.\n *\n * {@link carryAssetPlacements} is the one declaration of that rule. Two operations\n * go through it:\n * - **append / pick**: the table cell and the detail editor\n * (`applyAssetSelection` / `appendAssetSelections` in\n * `components/assets/asset-selection.ts`). The next list is the held list plus\n * the picked ids, and a single field swaps its one placement's image.\n * - **replace-with-list**: AI `updateFields` (`lib/ai/update-fields.ts`). The next\n * list is the list the model sent.\n *\n * It lives in `lib/` because `lib/ai` may not import from `components/`, and both\n * sides need it. Its only dependency is `@elytracms/core/cms-core`, so the AI host\n * route pulls in nothing from the browser. Its natural home is next to `assetNode`\n * in `core/cms-core/envelopes.ts`. Move it there if a writer outside the studio\n * (import, CLI, MCP) appears.\n */\nimport { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core'\n\n/** The ids the field should hold next. The shape follows the field's cardinality. */\nexport type AssetPlacementTarget =\n | { multiple: false; id: string | null }\n | { multiple: true; ids: readonly string[] }\n\n/**\n * A BRAND-NEW placement, which has no sub-fields yet: always the canonical envelope\n * (EC-586, ratified 2c — an asset value is `{ type: 'asset', id, … }`). There used to be\n * a second, bare spelling (`'bare'`, a plain id string) for fields without sub-fields;\n * the write lens wrapped it on save, so the in-session draft held a shape the stored one\n * did not, and a def-less \"has unpublished changes\" verdict read the two as different.\n */\nfunction newPlacement(id: string): unknown {\n return assetNode(id)\n}\n\n/**\n * Return the field's next value with every placement's per-use sub-fields kept.\n *\n * - **Single field.** The placement's sub-fields carry over to whatever id now fills\n * it, so swapping the image keeps the caption. Re-picking the same id is a no-op,\n * and `null` clears the field (`undefined`).\n * - **Multiple field.** An id the value already held keeps its entry verbatim, with\n * its sub-fields and its form (a bare id stays bare). Entries are matched in order,\n * so an image placed twice keeps both placements. An id the value did not hold\n * becomes a new envelope. A held entry whose id is not in `ids` is removed, and an\n * emptied list is ABSENT (`undefined`, EC-586), never `[]`.\n *\n * The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`\n * reads. That includes keys the config no longer declares (AD-14: the schema is a\n * lens, so a writer never deletes what the lens does not show).\n *\n * When nothing changes it returns `current` itself (the same reference), so a caller\n * can skip the write.\n */\nexport function carryAssetPlacements(current: unknown, next: AssetPlacementTarget): unknown {\n if (!next.multiple) {\n if (next.id === null) return undefined\n // `singleAssetIdOf` tolerates a legacy one-item array on a single field, so read\n // its placement the same way.\n const held = Array.isArray(current) ? current[0] : current\n if (readAssetId(held) === next.id) return current\n const subfields = assetSubfieldsOf(held)\n return Object.keys(subfields).length > 0 ? assetNode(next.id, subfields) : newPlacement(next.id)\n }\n\n const held = Array.isArray(current) ? (current as unknown[]) : []\n const used = new Set<number>()\n const out = next.ids.map((id) => {\n const index = held.findIndex((entry, i) => !used.has(i) && readAssetId(entry) === id)\n if (index < 0) return newPlacement(id)\n used.add(index)\n return held[index]\n })\n const unchanged = out.length === held.length && out.every((entry, i) => entry === held[i])\n if (unchanged && Array.isArray(current)) return current\n return out.length === 0 ? undefined : out\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getNode, getParentInfo } from '@elytracms/core/project-graph';
|
|
2
2
|
import { manifestRules, placementRefusal, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules';
|
|
3
|
-
import {
|
|
3
|
+
import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
|
|
4
4
|
export function resolvePasteTarget(args) {
|
|
5
5
|
const { root, componentId, getManifest, fieldAllow } = args;
|
|
6
6
|
const anchor = args.anchorId !== null ? getNode(root, args.anchorId) : undefined;
|
|
@@ -19,7 +19,7 @@ export function resolvePasteTarget(args) {
|
|
|
19
19
|
const manifest = getManifest(node.componentId);
|
|
20
20
|
const childSlot = primaryInsertSlot(manifestRules(manifest));
|
|
21
21
|
if (childSlot) {
|
|
22
|
-
return leashed(childSlot.name, childSlot, { parentId: node.id, slot: childSlot.name }, `${
|
|
22
|
+
return leashed(childSlot.name, childSlot, { parentId: node.id, slot: childSlot.name }, `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`);
|
|
23
23
|
}
|
|
24
24
|
const info = getParentInfo(root, node.id);
|
|
25
25
|
if (info) {
|
|
@@ -27,7 +27,7 @@ export function resolvePasteTarget(args) {
|
|
|
27
27
|
// before/after case). An unknown parent manifest imposes no allow-list;
|
|
28
28
|
// validation owns that issue.
|
|
29
29
|
const parentSlot = manifestRules(getManifest(info.parent.componentId))?.slots.find((s) => s.name === info.slot);
|
|
30
|
-
return leashed(info.slot, parentSlot, { parentId: info.parent.id, slot: info.slot, index: info.index + 1 }, `${
|
|
30
|
+
return leashed(info.slot, parentSlot, { parentId: info.parent.id, slot: info.slot, index: info.index + 1 }, `${componentDisplayName(info.parent.componentId, getManifest(info.parent.componentId))} · ${info.slot}`);
|
|
31
31
|
}
|
|
32
32
|
// Leaf root with no declared slots (degenerate graphs): append to `children`.
|
|
33
33
|
// No slot rule exists, but the paste still lands at the root — the field leash
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paste-target.js","sourceRoot":"","sources":["../../../src/lib/clipboard/paste-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGtE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAEzG,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"paste-target.js","sourceRoot":"","sources":["../../../src/lib/clipboard/paste-target.ts"],"names":[],"mappings":"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","sourcesContent":["import { getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget } from '@elytracms/core/project-graph'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { manifestRules, placementRefusal, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { SlotRule } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\n\n/**\n * Paste landing-spot resolution (EC-175) — the same placement rule the Insert\n * dialog uses (`insert-context.ts`): a pasted subtree lands *into*\n * the anchor node's child slot when it has one, else *after* the anchor as a\n * sibling, else appended to the page root. Pure and selection-driven so the\n * canvas, the page tree, and keyboard paste all share one rule.\n *\n * EC-545: the resolved slot's `allow` leash is checked HERE, for the pasted\n * subtree's root, through the same `slotAllows` the drop rule reads — so a\n * component drag-and-drop refuses is refused by clipboard too, and a caller\n * cannot reach an `InsertTarget` without passing the check (the rejection is a\n * separate member of the result union, not a flag beside the target). Nested\n * validity of the pasted subtree stays with the publish gate, exactly like a\n * drop.\n *\n * EC-554: the composition FIELD's top-level `allow` is checked here too, through\n * the same `placementRefusal` → `fieldAllowAt` the drop rule and the insert offer\n * read — a paste that lands as a direct child of the composition root must be in\n * the field's vocabulary.\n */\n\nexport interface PasteTarget {\n ok: true\n target: InsertTarget\n /** Human label of where the paste lands (notices/debugging). */\n destinationLabel: string\n}\n\n/** The resolved slot's own `allow` list excludes the pasted root (EC-545). */\nexport interface PasteSlotRejection {\n ok: false\n code: 'not-allowed'\n /** The pasted root the slot refuses. */\n componentId: string\n /** The resolved slot whose `allow` list excludes it. */\n slot: string\n /** Human label of the refused destination (`Section · body`). */\n destinationLabel: string\n}\n\n/**\n * The composition field's top-level `allow` excludes the pasted root (EC-554).\n * Deliberately carries NO slot/destination label: the refused slot is the\n * invisible synthetic root's (`Stack · children`), and the reason to show is the\n * FIELD — the notice names it from the editor's field scope.\n */\nexport interface PasteFieldRejection {\n ok: false\n code: 'field-not-allowed'\n /** The pasted root the field vocabulary refuses. */\n componentId: string\n}\n\nexport type PasteRejection = PasteSlotRejection | PasteFieldRejection\n\nexport type PasteResolution = PasteTarget | PasteRejection\n\nexport function resolvePasteTarget(args: {\n root: ComponentNode\n /** The anchor node (usually the selection); `null` falls back to the root. */\n anchorId: string | null\n /** The pasted subtree's root component — the id the slot leash judges. */\n componentId: string\n getManifest: (componentId: string) => ComponentManifest | undefined\n /**\n * The composition field's top-level `allow` (EC-554) — `undefined` when the\n * field declares none. Required so no caller can forget to pass it.\n */\n fieldAllow: readonly string[] | undefined\n}): PasteResolution {\n const { root, componentId, getManifest, fieldAllow } = args\n const anchor = args.anchorId !== null ? getNode(root, args.anchorId) : undefined\n const node = anchor ?? root\n\n const leashed = (\n slot: string,\n slotRule: SlotRule | undefined,\n target: InsertTarget,\n destinationLabel: string,\n ): PasteResolution => {\n const refusal = placementRefusal({ root, parentId: target.parentId, slot: slotRule, fieldAllow, componentId })\n if (refusal === 'field') return { ok: false, code: 'field-not-allowed', componentId }\n if (refusal === 'slot') return { ok: false, code: 'not-allowed', componentId, slot, destinationLabel }\n return { ok: true, target, destinationLabel }\n }\n\n // EC-386: the shared slot rule (`primaryInsertSlot` over the merged\n // `manifestRules` vocabulary) — a blocks-prop region (e.g. a Section's `body`)\n // receives the paste exactly like the insert dialog and a DnD `into`.\n const manifest = getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n if (childSlot) {\n return leashed(\n childSlot.name,\n childSlot,\n { parentId: node.id, slot: childSlot.name },\n `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`,\n )\n }\n\n const info = getParentInfo(root, node.id)\n if (info) {\n // A sibling paste is governed by the PARENT's slot leash (the drop rule's\n // before/after case). An unknown parent manifest imposes no allow-list;\n // validation owns that issue.\n const parentSlot = manifestRules(getManifest(info.parent.componentId))?.slots.find((s) => s.name === info.slot)\n return leashed(\n info.slot,\n parentSlot,\n { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n `${componentDisplayName(info.parent.componentId, getManifest(info.parent.componentId))} · ${info.slot}`,\n )\n }\n\n // Leaf root with no declared slots (degenerate graphs): append to `children`.\n // No slot rule exists, but the paste still lands at the root — the field leash\n // applies.\n return leashed('children', undefined, { parentId: root.id, slot: 'children' }, 'root')\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { localizedStringSchema, resolveField, resolveLabel, titleFieldOf, } from '@elytracms/core/cms-core';
|
|
2
|
+
import { canonicalDateValue, localizedStringSchema, resolveField, resolveLabel, titleFieldOf, } from '@elytracms/core/cms-core';
|
|
3
3
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
4
4
|
import { contentQueryErrorMessage } from '@elytracms/studio/lib/persona/labels';
|
|
5
|
-
import { FILTER_OPERATORS, compareListEntries,
|
|
5
|
+
import { FILTER_OPERATORS, compareListEntries, sortScalarOf, filterableFields, matchesFieldValue, validateWhere, } from '@elytracms/core/content';
|
|
6
6
|
/**
|
|
7
7
|
* The pure table-view model for the CMS collection table (EC-169).
|
|
8
8
|
*
|
|
@@ -280,10 +280,10 @@ export function scalarFromText(option, raw) {
|
|
|
280
280
|
}
|
|
281
281
|
if (type === 'boolean')
|
|
282
282
|
return raw === 'true';
|
|
283
|
-
//
|
|
284
|
-
//
|
|
283
|
+
// EC-586: a date operand goes through THE date canonicaliser — the same one every
|
|
284
|
+
// write and the comparator use — so `2026-09-18` means midnight UTC on that day.
|
|
285
285
|
if (type === 'date' && DATE_ONLY.test(raw))
|
|
286
|
-
return
|
|
286
|
+
return canonicalDateValue(raw) ?? raw;
|
|
287
287
|
return raw;
|
|
288
288
|
}
|
|
289
289
|
/** Parse a comma-separated list for the `in` operator. */
|
|
@@ -365,9 +365,11 @@ function scalarOf(collection, doc, field, localeConfig, locale, isPublished, met
|
|
|
365
365
|
return meta.publishedAt?.(doc) ?? null;
|
|
366
366
|
if (field === URL_FIELD)
|
|
367
367
|
return null;
|
|
368
|
-
|
|
368
|
+
const def = collection.fields.find((f) => f.name === field);
|
|
369
|
+
if (!def)
|
|
369
370
|
return null;
|
|
370
|
-
|
|
371
|
+
// EC-586: a `date` sorts as the instant it denotes (`sortScalarOf`), as delivery does.
|
|
372
|
+
return sortScalarOf(resolveField(collection, doc, field, locale, localeConfig).value, def);
|
|
371
373
|
}
|
|
372
374
|
/**
|
|
373
375
|
* Does a document satisfy one clause? Identical verdict to the content accessors'
|
|
@@ -378,7 +380,8 @@ function scalarOf(collection, doc, field, localeConfig, locale, isPublished, met
|
|
|
378
380
|
export function clauseMatches(collection, doc, localeConfig, locale, clause, isPublished = defaultIsPublished) {
|
|
379
381
|
const value = rawValueOf(collection, doc, clause.field, localeConfig, locale, isPublished);
|
|
380
382
|
const condition = { [clause.op]: clause.value };
|
|
381
|
-
|
|
383
|
+
// EC-586: the field def makes a `date` compare as an instant — delivery's exact rule.
|
|
384
|
+
return matchesFieldValue(value, condition, collection.fields.find((f) => f.name === clause.field));
|
|
382
385
|
}
|
|
383
386
|
/**
|
|
384
387
|
* Sort documents by one column in the canonical deterministic order
|