@elytracms/studio 0.0.80 → 0.0.82
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/contract-parity.test.ts +1 -1
- package/convex/functions.ts +13 -3
- package/convex/http-routes.test.ts +143 -0
- package/convex/http.ts +19 -9
- package/convex/redirect-revalidate.test.ts +130 -0
- package/convex/revalidate.ts +9 -7
- package/convex/schema.ts +0 -19
- package/convex/triggers.ts +33 -3
- package/convex/users.ts +1 -1
- package/dist/components/ai/ai-chat-panel.js +26 -4
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +27 -0
- package/dist/components/ai/proposal-card.js +31 -3
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/proposal-issues.d.ts +55 -0
- package/dist/components/ai/proposal-issues.js +69 -0
- package/dist/components/ai/proposal-issues.js.map +1 -0
- package/dist/components/ai/translate-document-dialog.js +38 -3
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +141 -44
- package/dist/components/app-views/default-modules.js +229 -65
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +38 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.d.ts +5 -2
- package/dist/components/app-views/sidebar.js +16 -5
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +28 -1
- package/dist/components/app-views/studio-config.js +1 -1
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/canvas-render-context.js +10 -2
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +5 -3
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/reuse-context.js +7 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +94 -16
- package/dist/components/builder/reuse.js +201 -19
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/bulk-actions.js +10 -5
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +18 -3
- package/dist/components/cms/cell-edit.js +29 -7
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +54 -14
- package/dist/components/cms/cms-store.js +142 -45
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +2 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +15 -14
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +12 -1
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/field-control.js +22 -8
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.js +8 -1
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +36 -0
- package/dist/components/cms/gate-validation.js +17 -0
- package/dist/components/cms/gate-validation.js.map +1 -0
- package/dist/components/cms/import-export.js +2 -1
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/inline-field-errors.d.ts +8 -0
- package/dist/components/cms/inline-field-errors.js +52 -0
- package/dist/components/cms/inline-field-errors.js.map +1 -0
- package/dist/components/cms/issues-pane.d.ts +4 -2
- package/dist/components/cms/issues-pane.js +13 -7
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/slug-collision-report.d.ts +41 -0
- package/dist/components/cms/slug-collision-report.js +52 -0
- package/dist/components/cms/slug-collision-report.js.map +1 -0
- package/dist/components/components/transfer-dialog.js +10 -12
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/deployment-setup-gate.d.ts +24 -0
- package/dist/components/deployment-setup-gate.js +29 -4
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/issue-severity.d.ts +73 -0
- package/dist/components/issue-severity.js +56 -0
- package/dist/components/issue-severity.js.map +1 -0
- package/dist/components/nav-issues.d.ts +68 -0
- package/dist/components/nav-issues.js +78 -0
- package/dist/components/nav-issues.js.map +1 -0
- package/dist/components/operation-error.js +3 -3
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/project-list.d.ts +9 -3
- package/dist/components/project-list.js +11 -5
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-switcher.d.ts +3 -1
- package/dist/components/project-switcher.js +4 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/redirects/redirects-store.d.ts +21 -0
- package/dist/components/redirects/redirects-store.js +17 -4
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/section-nav.js +75 -10
- package/dist/components/section-nav.js.map +1 -1
- package/dist/customization.d.ts +7 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/ai/apply-content.js +6 -7
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +32 -26
- package/dist/lib/ai/chat-context.js +81 -9
- package/dist/lib/ai/chat-context.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +42 -0
- package/dist/lib/ai/constraints.js +259 -0
- package/dist/lib/ai/constraints.js.map +1 -0
- package/dist/lib/ai/index.d.ts +2 -0
- package/dist/lib/ai/index.js +2 -0
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/proposal-validation.d.ts +26 -0
- package/dist/lib/ai/proposal-validation.js +17 -0
- package/dist/lib/ai/proposal-validation.js.map +1 -0
- package/dist/lib/ai/shape.js +60 -13
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +8 -4
- package/dist/lib/ai/tools.js +43 -14
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +35 -1
- package/dist/lib/ai/translate-document.js +16 -1
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +11 -6
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +8 -6
- package/dist/lib/ai/update-fields.js +9 -22
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.d.ts +5 -0
- package/dist/lib/clipboard/paste-issues.js +16 -6
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/cms/document-gate.d.ts +37 -0
- package/dist/lib/cms/document-gate.js +40 -0
- package/dist/lib/cms/document-gate.js.map +1 -0
- package/dist/lib/cms-views/view-model.d.ts +5 -0
- package/dist/lib/cms-views/view-model.js +10 -4
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +6 -1
- package/dist/lib/data/collection-content.js +51 -15
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/i18n/i18n.d.ts +51 -16
- package/dist/lib/i18n/i18n.js +119 -43
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/index.d.ts +8 -1
- package/dist/lib/i18n/index.js +1 -1
- package/dist/lib/i18n/index.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +200 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +162 -0
- package/dist/lib/i18n/messages.en.js +226 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/i18n/types.d.ts +15 -9
- package/dist/lib/i18n/types.js.map +1 -1
- package/dist/lib/locale/binding-fallback.d.ts +8 -1
- package/dist/lib/locale/binding-fallback.js +8 -1
- package/dist/lib/locale/binding-fallback.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +41 -9
- package/dist/lib/locale/locale-store.js +34 -15
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/locale/locale-switcher.d.ts +3 -1
- package/dist/lib/locale/locale-switcher.js +4 -2
- package/dist/lib/locale/locale-switcher.js.map +1 -1
- package/dist/lib/locale/use-studio-locale.d.ts +9 -4
- package/dist/lib/locale/use-studio-locale.js.map +1 -1
- package/dist/lib/operations/studio-operations.js +19 -2
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +1 -3
- package/dist/lib/persistence/convex-adapter.js +0 -55
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/deployment-readiness.js +10 -0
- package/dist/lib/persistence/deployment-readiness.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +85 -9
- package/dist/lib/persona/labels.js +198 -58
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/routes/route-issues.d.ts +9 -0
- package/dist/lib/routes/route-issues.js +117 -0
- package/dist/lib/routes/route-issues.js.map +1 -0
- package/dist/lib/webhooks/dispatcher.d.ts +5 -2
- package/dist/lib/webhooks/dispatcher.js.map +1 -1
- package/dist/lib/webhooks/tags.js +150 -17
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/config-issues.d.ts +56 -0
- package/dist/lib/workspace/config-issues.js +72 -0
- package/dist/lib/workspace/config-issues.js.map +1 -0
- package/dist/lib/workspace/index.d.ts +24 -8
- package/dist/lib/workspace/index.js +28 -18
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/switcher-source.d.ts +20 -3
- package/dist/lib/workspace/switcher-source.js.map +1 -1
- package/dist/lib/workspace/workspace-source.js +27 -0
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/dist/previews/manifest-format.d.ts +13 -6
- package/dist/previews/manifest-format.js +9 -2
- package/dist/previews/manifest-format.js.map +1 -1
- package/dist/router/root.d.ts +36 -1
- package/dist/router/root.js +54 -7
- package/dist/router/root.js.map +1 -1
- package/package.json +2 -2
- package/convex/cliTokens.ts +0 -63
- package/convex/sync.ts +0 -297
|
@@ -7,6 +7,7 @@ import { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
|
7
7
|
import { Button, Input, Row, RowItem, Select, Stack, Text, } from '@elytracms/studio/ui';
|
|
8
8
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
9
9
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
10
|
+
import { customizationIssueMessage } from '@elytracms/studio/lib/persona/labels';
|
|
10
11
|
import { activeLocaleValues } from '@elytracms/studio/lib/locale';
|
|
11
12
|
import { useCollectionView } from '../collection-view/collection-view-context.js';
|
|
12
13
|
import { fieldLabel } from './cms-labels.js';
|
|
@@ -20,6 +21,7 @@ import { datePickerBounds } from './date-input-core.js';
|
|
|
20
21
|
import { AssetField } from './asset-field.js';
|
|
21
22
|
import { SegmentedOptions } from './field-control-lib.js';
|
|
22
23
|
import { slugAutoDeriveDecision } from './slug-auto-derive.js';
|
|
24
|
+
import { engineOwnsUniqueness } from './slug-collision-report.js';
|
|
23
25
|
import { ObjectField } from './object-array-field.js';
|
|
24
26
|
import { FieldRendererBoundary, FieldRendererIssue, useFieldRenderers, } from './field-renderer.js';
|
|
25
27
|
/**
|
|
@@ -49,7 +51,7 @@ export function FieldControl(props) {
|
|
|
49
51
|
if (control) {
|
|
50
52
|
const rejected = renderers.issues.find((issue) => issue.id === control);
|
|
51
53
|
if (rejected) {
|
|
52
|
-
return (_jsxs(Stack, { gap: "1", children: [_jsx(FieldRendererIssue, { message: rejected
|
|
54
|
+
return (_jsxs(Stack, { gap: "1", children: [_jsx(FieldRendererIssue, { message: customizationIssueMessage(rejected) }), builtIn] }));
|
|
53
55
|
}
|
|
54
56
|
// The field asks for a control with no registered renderer and no built-in
|
|
55
57
|
// name — surface it (a typo'd `control` would otherwise read as a plain input).
|
|
@@ -254,13 +256,25 @@ function SlugField({ field, value, onChange, readOnly, id, }) {
|
|
|
254
256
|
cancelled = true;
|
|
255
257
|
};
|
|
256
258
|
}, [current, slugConfig, draft, locale, source]);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
// EC-511 — one collision, one message. The engine's `validation.unique` finding
|
|
260
|
+
// OWNS this field's uniqueness whenever it speaks: it is collection-wide,
|
|
261
|
+
// localized, and it blocks publish, and it already renders inline in this very
|
|
262
|
+
// column (the FieldRow `error` slot) plus the Issues pane. The control's hint then
|
|
263
|
+
// fills only the gap the engine leaves — a collision that exists once the active
|
|
264
|
+
// locale falls back, which `validation.unique` deliberately does not see, and any
|
|
265
|
+
// field that declares no `validation.unique` at all. The two rules stay different
|
|
266
|
+
// on purpose (AD-497b); only the double presentation goes. Full argument, and why
|
|
267
|
+
// this matches on field+locale rather than on the colliding document's id, in
|
|
268
|
+
// `slug-collision-report.ts`.
|
|
269
|
+
const collisionMessage = engineOwnsUniqueness(editor?.issues ?? [], field.name, locale)
|
|
270
|
+
? undefined
|
|
271
|
+
: slugConfig?.unique
|
|
272
|
+
? asyncTaken
|
|
273
|
+
? translate('cms.field.slug.takenGeneric')
|
|
274
|
+
: undefined
|
|
275
|
+
: defaultCollisionTitle
|
|
276
|
+
? translate('cms.field.slug.taken', { title: defaultCollisionTitle })
|
|
277
|
+
: undefined;
|
|
264
278
|
function handleInput(next) {
|
|
265
279
|
onChange(next || undefined);
|
|
266
280
|
// Clearing re-enables auto-derive; any non-empty entry is a manual override.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Link2, Pencil, Plus, RotateCw, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport type {\n DocumentRef,\n FieldDef,\n RelationFieldDef,\n UrlSegment,\n} from '@elytracms/core/cms-core'\nimport {\n BUILTIN_FIELD_CONTROLS,\n applySlugify,\n descendantIds,\n documentDefaultLocale,\n findSlugCollision,\n isFieldLocalized,\n parentRelationField,\n resolveSlugPath,\n resolveUrlSegments,\n slugSourceValue,\n} from '@elytracms/core/cms-core'\nimport type { SlugFieldConfig, SlugPathParts } from '@elytracms/core/cms-core'\nimport { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport {\n Button,\n Input,\n Row,\n RowItem,\n Select,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { activeLocaleValues } from '@elytracms/studio/lib/locale'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { fieldLabel } from './cms-labels'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { RichTextEditor } from './rich-text'\nimport { DocumentUrlSegment } from './document-url-path'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { AssetField } from './asset-field'\nimport { SegmentedOptions } from './field-control-lib'\nimport { slugAutoDeriveDecision } from './slug-auto-derive'\nimport { ObjectField } from './object-array-field'\nimport {\n FieldRendererBoundary,\n FieldRendererIssue,\n useFieldRenderers,\n} from './field-renderer'\n\ninterface FieldControlProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\n\n/**\n * The document-editor field control. Resolves a project's custom renderer by the\n * field's `form.control` (EC-238, customization seam EC-236) and falls back to the\n * built-in control for the field's datatype: a registered renderer renders inside\n * an error boundary (a throw → the built-in + a visible issue); a rejected\n * registration (non-component) shows the built-in with the issue above it; a field\n * naming a control with NO renderer and NO built-in name (a typo) shows the\n * built-in with a loud issue — never a silent fall-through; otherwise the built-in\n * renders alone.\n *\n * EC-330 split: the asset-field family lives in `asset-field.tsx`, the object /\n * repeater editors in `object-array-field.tsx`, and their pure companions\n * (reorder/summary/depth/table decisions, the `options` segmented control) in\n * `field-control-lib.tsx` — this file keeps the dispatcher plus the slug and\n * relation controls.\n */\nexport function FieldControl(props: FieldControlProps) {\n const renderers = useFieldRenderers()\n const builtIn = <BuiltInFieldControl {...props} />\n const control = props.field.form?.control\n const Custom = control ? renderers.get(control) : undefined\n if (Custom) {\n return (\n <FieldRendererBoundary\n fallback={builtIn}\n issue={<FieldRendererIssue message={translate('cms.field.rendererCrashed')} />}\n >\n <Custom {...props} />\n </FieldRendererBoundary>\n )\n }\n if (control) {\n const rejected = renderers.issues.find((issue) => issue.id === control)\n if (rejected) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={rejected.message} />\n {builtIn}\n </Stack>\n )\n }\n // The field asks for a control with no registered renderer and no built-in\n // name — surface it (a typo'd `control` would otherwise read as a plain input).\n if (!(BUILTIN_FIELD_CONTROLS as readonly string[]).includes(control)) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={translate('cms.field.noRenderer', { control })} />\n {builtIn}\n </Stack>\n )\n }\n }\n return builtIn\n}\n\n/** The conventional slug field name (mirrors `url-for-document`'s `SLUG_FIELD`). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into the slug control (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/**\n * EC-315: a `text` field is rendered by the smart slug control when it opts in via\n * `form.control: 'slug'`, OR (the default auto-upgrade) when it is simply named\n * `slug` — the long-standing convention `url-for-document`/hierarchy already key off.\n */\nfunction isSlugControl(field: FieldDef): boolean {\n return field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n}\n\nfunction BuiltInFieldControl({ field, value, onChange, readOnly, id }: FieldControlProps) {\n // EC-315: the smart slug control (composed path, auto-slug, per-locale segments)\n // subsumes the old inline `slug` input + PathPreview. It degrades to a plain input\n // when the collection isn't routable (name-based auto-upgrade only) or no document\n // editor context is present (e.g. a table-cell picker).\n if (isSlugControl(field)) {\n return <SlugField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n }\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {translate(value === true ? 'common.true' : 'common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n // EC-463: never write a mid-entry segment state into the document — see\n // `date-input.tsx` (this control silently stored `0002-09-18` for an\n // editor typing 2026-09-18).\n return (\n <DateInput\n id={id}\n value={value}\n onChange={onChange}\n disabled={readOnly}\n testId={`field-date-${field.name}`}\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n }\n // EC-295: the `options` control renders a small fixed enumeration as inline\n // segmented buttons instead of the dropdown (single-select only).\n if (field.form?.control === 'options') {\n return (\n <SegmentedOptions field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <RichTextEditor\n value={value}\n onChange={onChange}\n readOnly={readOnly}\n id={id}\n allow={field.allow}\n // EC-436: the SAME resolved localized label the field row renders (the\n // EC-241 `fieldLabel` path) — titles the expand stage \"<label> — Rich text\".\n label={fieldLabel(field)}\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\n/**\n * EC-315 — the smart slug / URL-segment control. A `text` field named `slug` (or any\n * `text` field with `form.control: 'slug'`) renders here instead of a bare input:\n *\n * - **Composed path**: the route-derived URL, decomposed by {@link resolveSlugPath}\n * into read-only parent/context segments + the editable slug segment inline + the\n * locale prefix (non-default locales). Reuses the same `urlForDocument` the live\n * site uses, so the preview matches what visitors get. Draft-derived (updates as\n * you type); the live \"View live\" button in the header stays the published open.\n * - **Auto-slug**: while the slug is empty or was last produced by auto-derivation,\n * it live-derives from the source field (default: the collection's `titleField`).\n * A manual edit stops the auto-fill; the ↻ button always re-derives.\n * - **Per-locale**: reads/writes through the shared `value`/`onChange` seam, so a\n * `localized` slug edits the active locale's segment with no bespoke locale code.\n * - **Uniqueness**: an inline warning when the candidate collides with a sibling in\n * the same scope (same parent when hierarchical, same locale). A custom\n * `slug.unique` hook replaces the default sibling scan.\n *\n * Degrades to a plain input when there is no document editor context (a table-cell\n * picker) or when a name-based auto-upgrade lands on a non-routable collection.\n */\nfunction SlugField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const slugConfig = field.slug as SlugFieldConfig | undefined\n const explicit = field.form?.control === SLUG_CONTROL\n const current = typeof value === 'string' ? value : ''\n const locale = source.activeLocale\n const localeConfig = source.localeConfig\n\n // 0.0.39 regression fix: resolve the deployment routes on EVERY render (a cheap\n // synchronous accessor), never freeze them at first paint. The earlier\n // `useMemo(() => resolveDeploymentRoutes(), [])` captured the EMPTY route set from\n // before the workspace/config finished loading — so the composed path never\n // appeared once routes arrived, and a stale set could paint a misleading prefix.\n const routes = resolveDeploymentRoutes()\n const routable = routes.some((r) => r.document?.collection === source.collectionId)\n\n const draft = editor?.draft\n // EC-463: a slug field that is NOT localized holds ONE value for every locale,\n // so its auto-slug must read ONE locale's source fields — the document's base\n // locale. Deriving from the ACTIVE locale made switching to the DE tab show a\n // German-derived slug preview for a field that is shared across locales (and,\n // with the EC-461 derive latch, a locale switch alone could then WRITE it).\n // A localized slug keeps per-locale derivation, which is the whole point of it.\n const deriveLocale =\n isFieldLocalized(source.schema, field.name) || !draft\n ? locale\n : documentDefaultLocale(draft, localeConfig)\n const values = useMemo<Record<string, unknown>>(\n () => (draft ? activeLocaleValues(draft, deriveLocale) : {}),\n [draft, deriveLocale],\n )\n const derived = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n\n // Track whether the slug was manually set — auto-derive stays active while the\n // slug is empty or was last produced by auto-derivation (never overwrites a manual\n // slug). Seeded true when the loaded document already carries a slug.\n const [manuallyEdited, setManuallyEdited] = useState(() => current.trim() !== '')\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // EC-461: opening a document is not an edit. Auto-derive fires only once the\n // derived value has moved away from its mount state (the editor actually\n // changed a source field this session) — the old on-mount fill made every\n // slug-less document dirty on open, and the 1s autosave then WROTE it (in the\n // playground: the stale re-seeded baseline over the adapter's newer record —\n // the destructive half of the EC-461 loss family). Decision extracted pure in\n // `slug-auto-derive.ts`; the latch keeps derivation live after the first\n // divergence so title edits that return to the initial value still land.\n const initialDerivedRef = useRef(derived)\n const deriveLatchRef = useRef(false)\n useEffect(() => {\n const decision = slugAutoDeriveDecision({\n derived,\n current,\n initialDerived: initialDerivedRef.current,\n latchOpen: deriveLatchRef.current,\n manuallyEdited,\n readOnly,\n })\n if (decision.openLatch) deriveLatchRef.current = true\n if (decision.write) onChangeRef.current(derived)\n }, [derived, manuallyEdited, readOnly, current])\n\n // The composed path parts (routable collections with a document editor only).\n const siblings = draft ? source.documents() : []\n // Draft-derived (routes + locale only, no `frontendUrl`) — the composed path is a\n // read-only preview that updates as you type, mirroring the detail editor's draft\n // URL. The header's published-gated \"View live\" stays the sole open-live action,\n // so the draft/live distinction is preserved (an unpublished slug never links out).\n const parts: SlugPathParts | undefined =\n draft && routable\n ? resolveSlugPath({ routes, localeConfig }, source.schema, draft, { siblings, locale })\n : undefined\n // The classified context segments (0.0.42, round 3) — the same `resolveUrlSegments`\n // the header URL row uses, so the slug prefix and the header path attribute each\n // segment identically. Everything before this document's own `current` slug is the\n // context prefix; its LAST segment is the immediate parent shown in the compact pill.\n const contextSegments: UrlSegment[] =\n draft && routable\n ? resolveUrlSegments({ routes, localeConfig }, source.schema, draft, { siblings, locale }).filter(\n (s) => s.kind !== 'current',\n )\n : []\n const lastContext = contextSegments[contextSegments.length - 1]\n\n // Default (sync) uniqueness — the first colliding sibling's title, or null.\n const defaultCollisionTitle = useMemo<string | null>(() => {\n if (slugConfig?.unique || !draft) return null\n const trimmed = current.trim()\n if (trimmed === '') return null\n const hit = findSlugCollision(trimmed, draft, source.documents(), {\n collection: source.schema,\n locale,\n localeConfig,\n })\n return hit ? source.titleOf({ collection: source.collectionId, id: hit.id }) : null\n }, [current, draft, slugConfig, locale, localeConfig, source])\n\n // Custom (possibly async) uniqueness hook — race-guarded.\n const [asyncTaken, setAsyncTaken] = useState(false)\n useEffect(() => {\n const unique = slugConfig?.unique\n if (!unique || !draft) {\n setAsyncTaken(false)\n return\n }\n const trimmed = current.trim()\n if (trimmed === '') {\n setAsyncTaken(false)\n return\n }\n let cancelled = false\n Promise.resolve(unique(trimmed, { document: draft, locale, siblings: source.documents() }))\n .then((ok) => {\n if (!cancelled) setAsyncTaken(ok === false)\n })\n .catch(() => {\n if (!cancelled) setAsyncTaken(false)\n })\n return () => {\n cancelled = true\n }\n }, [current, slugConfig, draft, locale, source])\n\n const collisionMessage = slugConfig?.unique\n ? asyncTaken\n ? translate('cms.field.slug.takenGeneric')\n : undefined\n : defaultCollisionTitle\n ? translate('cms.field.slug.taken', { title: defaultCollisionTitle })\n : undefined\n\n function handleInput(next: string) {\n onChange(next || undefined)\n // Clearing re-enables auto-derive; any non-empty entry is a manual override.\n setManuallyEdited(next.trim() !== '')\n }\n\n function regenerate() {\n setManuallyEdited(false)\n const next = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n onChange(next || undefined)\n }\n\n // No document editor (a picker), or a name-based auto-upgrade on a non-routable\n // collection: keep the historical plain input, no slug chrome (no regression).\n if (!editor || (!explicit && !routable)) {\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n\n const input = (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n placeholder={derived || undefined}\n onChange={(e) => handleInput(e.target.value)}\n className={cn(parts ? 'h-7 min-w-[10ch] flex-1 bg-background font-mono text-xs' : 'flex-1')}\n />\n )\n const regenerateButton = !readOnly ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={regenerate}\n aria-label={translate('cms.field.slug.regenerate')}\n title={translate('cms.field.slug.regenerate')}\n >\n <RotateCw />\n </Button>\n ) : null\n\n // The compact, ellipsized read-only prefix (0.0.39 \"hybrid split\", round-3 clickable):\n // only the LAST context segment (`…/produkte/`) so a deep path can't overflow the\n // narrow Details sidebar — but that shown segment is now CLICKABLE with the same\n // navigation semantics as the header URL row (an ancestor DOCUMENT links to its\n // editor; a static route literal stays plain). The full composed path is the tooltip.\n const prefixTitle =\n parts?.path ??\n (contextSegments.length > 0 ? `/${contextSegments.map((s) => s.text).join('/')}/` : '/')\n\n return (\n <Stack gap=\"2\">\n {parts ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-composed-path\">\n <span\n className=\"inline-flex min-w-0 shrink-0 items-center rounded bg-muted/40 px-1.5 py-1 font-mono text-xs text-muted-foreground ring-1 ring-inset ring-border\"\n title={prefixTitle}\n data-testid=\"slug-prefix\"\n >\n {contextSegments.length === 0 ? (\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n ) : (\n <>\n <span aria-hidden className=\"text-muted-foreground/50\">\n {contextSegments.length > 1 ? '…/' : '/'}\n </span>\n {lastContext ? (\n <DocumentUrlSegment\n segment={lastContext}\n projectId={editor?.projectId}\n collectionId={source.collectionId}\n />\n ) : null}\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n </>\n )}\n </span>\n {input}\n {regenerateButton}\n </Row>\n ) : (\n <Row gap=\"1\" align=\"center\">\n {input}\n {regenerateButton}\n </Row>\n )}\n\n {collisionMessage ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-collision\">\n <AlertTriangle className=\"size-3.5 shrink-0 text-warning\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {collisionMessage}\n </Text>\n </Row>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n const projectId = source.projectId\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, source.referenceCandidates(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, source])\n\n function apply(next: DocumentRef[]) {\n onChange(field.multiple ? next : (next[0] ?? undefined))\n }\n\n return (\n <Stack gap=\"2\">\n {refs.length > 0 ? (\n <Stack gap=\"1\">\n {refs.map((ref, i) => {\n // EC-296: the relation title navigates to the referenced document (autosave\n // keeps the current edit safe). Without a projectId (no persistence, e.g.\n // fixtures) it stays plain text. EC-246c: full-width row, not a pill —\n // long titles wrap awkwardly in a pill and a narrow field crushes it.\n const title = source.titleOf(ref)\n const label = (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <Link2 className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 truncate\">{title}</span>\n </span>\n )\n return (\n <RowItem\n key={`${ref.id}-${i}`}\n data-testid=\"relation-value\"\n actions={\n !readOnly ? (\n <>\n {field.multiple === false ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.relation.change')}\n title={translate('cms.relation.change')}\n onClick={() => setOpen(true)}\n >\n <Pencil />\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('common.remove')}\n title={translate('common.remove')}\n className=\"hover:text-destructive\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n >\n <X />\n </Button>\n </>\n ) : undefined\n }\n >\n {projectId ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(ref.collection, ref.id) }}\n title={translate('cms.relation.openTitle', { title })}\n className=\"text-sm font-medium hover:text-primary hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <Text as=\"span\" size=\"sm\" weight=\"medium\">\n {label}\n </Text>\n )}\n </RowItem>\n )\n })}\n </Stack>\n ) : null}\n {!readOnly && (refs.length === 0 || field.multiple) ? (\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Plus /> {refs.length === 0 ? translate('cms.relation.pick') : translate('common.add')}\n </Button>\n </Row>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Stack>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Link2, Pencil, Plus, RotateCw, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport type {\n DocumentRef,\n FieldDef,\n RelationFieldDef,\n UrlSegment,\n} from '@elytracms/core/cms-core'\nimport {\n BUILTIN_FIELD_CONTROLS,\n applySlugify,\n descendantIds,\n documentDefaultLocale,\n findSlugCollision,\n isFieldLocalized,\n parentRelationField,\n resolveSlugPath,\n resolveUrlSegments,\n slugSourceValue,\n} from '@elytracms/core/cms-core'\nimport type { SlugFieldConfig, SlugPathParts } from '@elytracms/core/cms-core'\nimport { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport {\n Button,\n Input,\n Row,\n RowItem,\n Select,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { customizationIssueMessage } from '@elytracms/studio/lib/persona/labels'\nimport { activeLocaleValues } from '@elytracms/studio/lib/locale'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { fieldLabel } from './cms-labels'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { RichTextEditor } from './rich-text'\nimport { DocumentUrlSegment } from './document-url-path'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\nimport { AssetField } from './asset-field'\nimport { SegmentedOptions } from './field-control-lib'\nimport { slugAutoDeriveDecision } from './slug-auto-derive'\nimport { engineOwnsUniqueness } from './slug-collision-report'\nimport { ObjectField } from './object-array-field'\nimport {\n FieldRendererBoundary,\n FieldRendererIssue,\n useFieldRenderers,\n} from './field-renderer'\n\ninterface FieldControlProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\n\n/**\n * The document-editor field control. Resolves a project's custom renderer by the\n * field's `form.control` (EC-238, customization seam EC-236) and falls back to the\n * built-in control for the field's datatype: a registered renderer renders inside\n * an error boundary (a throw → the built-in + a visible issue); a rejected\n * registration (non-component) shows the built-in with the issue above it; a field\n * naming a control with NO renderer and NO built-in name (a typo) shows the\n * built-in with a loud issue — never a silent fall-through; otherwise the built-in\n * renders alone.\n *\n * EC-330 split: the asset-field family lives in `asset-field.tsx`, the object /\n * repeater editors in `object-array-field.tsx`, and their pure companions\n * (reorder/summary/depth/table decisions, the `options` segmented control) in\n * `field-control-lib.tsx` — this file keeps the dispatcher plus the slug and\n * relation controls.\n */\nexport function FieldControl(props: FieldControlProps) {\n const renderers = useFieldRenderers()\n const builtIn = <BuiltInFieldControl {...props} />\n const control = props.field.form?.control\n const Custom = control ? renderers.get(control) : undefined\n if (Custom) {\n return (\n <FieldRendererBoundary\n fallback={builtIn}\n issue={<FieldRendererIssue message={translate('cms.field.rendererCrashed')} />}\n >\n <Custom {...props} />\n </FieldRendererBoundary>\n )\n }\n if (control) {\n const rejected = renderers.issues.find((issue) => issue.id === control)\n if (rejected) {\n return (\n <Stack gap=\"1\">\n {/* EC-478: the issue's catalog ref, resolved for the active locale. */}\n <FieldRendererIssue message={customizationIssueMessage(rejected)} />\n {builtIn}\n </Stack>\n )\n }\n // The field asks for a control with no registered renderer and no built-in\n // name — surface it (a typo'd `control` would otherwise read as a plain input).\n if (!(BUILTIN_FIELD_CONTROLS as readonly string[]).includes(control)) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={translate('cms.field.noRenderer', { control })} />\n {builtIn}\n </Stack>\n )\n }\n }\n return builtIn\n}\n\n/** The conventional slug field name (mirrors `url-for-document`'s `SLUG_FIELD`). */\nconst SLUG_FIELD_NAME = 'slug'\n/** The built-in control name that opts any `text` field into the slug control (EC-315). */\nconst SLUG_CONTROL = 'slug'\n\n/**\n * EC-315: a `text` field is rendered by the smart slug control when it opts in via\n * `form.control: 'slug'`, OR (the default auto-upgrade) when it is simply named\n * `slug` — the long-standing convention `url-for-document`/hierarchy already key off.\n */\nfunction isSlugControl(field: FieldDef): boolean {\n return field.type === 'text' && (field.form?.control === SLUG_CONTROL || field.name === SLUG_FIELD_NAME)\n}\n\nfunction BuiltInFieldControl({ field, value, onChange, readOnly, id }: FieldControlProps) {\n // EC-315: the smart slug control (composed path, auto-slug, per-locale segments)\n // subsumes the old inline `slug` input + PathPreview. It degrades to a plain input\n // when the collection isn't routable (name-based auto-upgrade only) or no document\n // editor context is present (e.g. a table-cell picker).\n if (isSlugControl(field)) {\n return <SlugField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n }\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {translate(value === true ? 'common.true' : 'common.false')}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n // EC-463: never write a mid-entry segment state into the document — see\n // `date-input.tsx` (this control silently stored `0002-09-18` for an\n // editor typing 2026-09-18).\n return (\n <DateInput\n id={id}\n value={value}\n onChange={onChange}\n disabled={readOnly}\n testId={`field-date-${field.name}`}\n {...datePickerBounds(field.validation)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {localizedLabel(opt.label, opt.value)}\n </label>\n )\n })}\n </Row>\n )\n }\n // EC-295: the `options` control renders a small fixed enumeration as inline\n // segmented buttons instead of the dropdown (single-select only).\n if (field.form?.control === 'options') {\n return (\n <SegmentedOptions field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <RichTextEditor\n value={value}\n onChange={onChange}\n readOnly={readOnly}\n id={id}\n allow={field.allow}\n // EC-436: the SAME resolved localized label the field row renders (the\n // EC-241 `fieldLabel` path) — titles the expand stage \"<label> — Rich text\".\n label={fieldLabel(field)}\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\n/**\n * EC-315 — the smart slug / URL-segment control. A `text` field named `slug` (or any\n * `text` field with `form.control: 'slug'`) renders here instead of a bare input:\n *\n * - **Composed path**: the route-derived URL, decomposed by {@link resolveSlugPath}\n * into read-only parent/context segments + the editable slug segment inline + the\n * locale prefix (non-default locales). Reuses the same `urlForDocument` the live\n * site uses, so the preview matches what visitors get. Draft-derived (updates as\n * you type); the live \"View live\" button in the header stays the published open.\n * - **Auto-slug**: while the slug is empty or was last produced by auto-derivation,\n * it live-derives from the source field (default: the collection's `titleField`).\n * A manual edit stops the auto-fill; the ↻ button always re-derives.\n * - **Per-locale**: reads/writes through the shared `value`/`onChange` seam, so a\n * `localized` slug edits the active locale's segment with no bespoke locale code.\n * - **Uniqueness**: an inline warning when the candidate collides with a sibling in\n * the same scope (same parent when hierarchical, same locale). A custom\n * `slug.unique` hook replaces the default sibling scan.\n *\n * Degrades to a plain input when there is no document editor context (a table-cell\n * picker) or when a name-based auto-upgrade lands on a non-routable collection.\n */\nfunction SlugField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const slugConfig = field.slug as SlugFieldConfig | undefined\n const explicit = field.form?.control === SLUG_CONTROL\n const current = typeof value === 'string' ? value : ''\n const locale = source.activeLocale\n const localeConfig = source.localeConfig\n\n // 0.0.39 regression fix: resolve the deployment routes on EVERY render (a cheap\n // synchronous accessor), never freeze them at first paint. The earlier\n // `useMemo(() => resolveDeploymentRoutes(), [])` captured the EMPTY route set from\n // before the workspace/config finished loading — so the composed path never\n // appeared once routes arrived, and a stale set could paint a misleading prefix.\n const routes = resolveDeploymentRoutes()\n const routable = routes.some((r) => r.document?.collection === source.collectionId)\n\n const draft = editor?.draft\n // EC-463: a slug field that is NOT localized holds ONE value for every locale,\n // so its auto-slug must read ONE locale's source fields — the document's base\n // locale. Deriving from the ACTIVE locale made switching to the DE tab show a\n // German-derived slug preview for a field that is shared across locales (and,\n // with the EC-461 derive latch, a locale switch alone could then WRITE it).\n // A localized slug keeps per-locale derivation, which is the whole point of it.\n const deriveLocale =\n isFieldLocalized(source.schema, field.name) || !draft\n ? locale\n : documentDefaultLocale(draft, localeConfig)\n const values = useMemo<Record<string, unknown>>(\n () => (draft ? activeLocaleValues(draft, deriveLocale) : {}),\n [draft, deriveLocale],\n )\n const derived = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n\n // Track whether the slug was manually set — auto-derive stays active while the\n // slug is empty or was last produced by auto-derivation (never overwrites a manual\n // slug). Seeded true when the loaded document already carries a slug.\n const [manuallyEdited, setManuallyEdited] = useState(() => current.trim() !== '')\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // EC-461: opening a document is not an edit. Auto-derive fires only once the\n // derived value has moved away from its mount state (the editor actually\n // changed a source field this session) — the old on-mount fill made every\n // slug-less document dirty on open, and the 1s autosave then WROTE it (in the\n // playground: the stale re-seeded baseline over the adapter's newer record —\n // the destructive half of the EC-461 loss family). Decision extracted pure in\n // `slug-auto-derive.ts`; the latch keeps derivation live after the first\n // divergence so title edits that return to the initial value still land.\n const initialDerivedRef = useRef(derived)\n const deriveLatchRef = useRef(false)\n useEffect(() => {\n const decision = slugAutoDeriveDecision({\n derived,\n current,\n initialDerived: initialDerivedRef.current,\n latchOpen: deriveLatchRef.current,\n manuallyEdited,\n readOnly,\n })\n if (decision.openLatch) deriveLatchRef.current = true\n if (decision.write) onChangeRef.current(derived)\n }, [derived, manuallyEdited, readOnly, current])\n\n // The composed path parts (routable collections with a document editor only).\n const siblings = draft ? source.documents() : []\n // Draft-derived (routes + locale only, no `frontendUrl`) — the composed path is a\n // read-only preview that updates as you type, mirroring the detail editor's draft\n // URL. The header's published-gated \"View live\" stays the sole open-live action,\n // so the draft/live distinction is preserved (an unpublished slug never links out).\n const parts: SlugPathParts | undefined =\n draft && routable\n ? resolveSlugPath({ routes, localeConfig }, source.schema, draft, { siblings, locale })\n : undefined\n // The classified context segments (0.0.42, round 3) — the same `resolveUrlSegments`\n // the header URL row uses, so the slug prefix and the header path attribute each\n // segment identically. Everything before this document's own `current` slug is the\n // context prefix; its LAST segment is the immediate parent shown in the compact pill.\n const contextSegments: UrlSegment[] =\n draft && routable\n ? resolveUrlSegments({ routes, localeConfig }, source.schema, draft, { siblings, locale }).filter(\n (s) => s.kind !== 'current',\n )\n : []\n const lastContext = contextSegments[contextSegments.length - 1]\n\n // Default (sync) uniqueness — the first colliding sibling's title, or null.\n const defaultCollisionTitle = useMemo<string | null>(() => {\n if (slugConfig?.unique || !draft) return null\n const trimmed = current.trim()\n if (trimmed === '') return null\n const hit = findSlugCollision(trimmed, draft, source.documents(), {\n collection: source.schema,\n locale,\n localeConfig,\n })\n return hit ? source.titleOf({ collection: source.collectionId, id: hit.id }) : null\n }, [current, draft, slugConfig, locale, localeConfig, source])\n\n // Custom (possibly async) uniqueness hook — race-guarded.\n const [asyncTaken, setAsyncTaken] = useState(false)\n useEffect(() => {\n const unique = slugConfig?.unique\n if (!unique || !draft) {\n setAsyncTaken(false)\n return\n }\n const trimmed = current.trim()\n if (trimmed === '') {\n setAsyncTaken(false)\n return\n }\n let cancelled = false\n Promise.resolve(unique(trimmed, { document: draft, locale, siblings: source.documents() }))\n .then((ok) => {\n if (!cancelled) setAsyncTaken(ok === false)\n })\n .catch(() => {\n if (!cancelled) setAsyncTaken(false)\n })\n return () => {\n cancelled = true\n }\n }, [current, slugConfig, draft, locale, source])\n\n // EC-511 — one collision, one message. The engine's `validation.unique` finding\n // OWNS this field's uniqueness whenever it speaks: it is collection-wide,\n // localized, and it blocks publish, and it already renders inline in this very\n // column (the FieldRow `error` slot) plus the Issues pane. The control's hint then\n // fills only the gap the engine leaves — a collision that exists once the active\n // locale falls back, which `validation.unique` deliberately does not see, and any\n // field that declares no `validation.unique` at all. The two rules stay different\n // on purpose (AD-497b); only the double presentation goes. Full argument, and why\n // this matches on field+locale rather than on the colliding document's id, in\n // `slug-collision-report.ts`.\n const collisionMessage = engineOwnsUniqueness(editor?.issues ?? [], field.name, locale)\n ? undefined\n : slugConfig?.unique\n ? asyncTaken\n ? translate('cms.field.slug.takenGeneric')\n : undefined\n : defaultCollisionTitle\n ? translate('cms.field.slug.taken', { title: defaultCollisionTitle })\n : undefined\n\n function handleInput(next: string) {\n onChange(next || undefined)\n // Clearing re-enables auto-derive; any non-empty entry is a manual override.\n setManuallyEdited(next.trim() !== '')\n }\n\n function regenerate() {\n setManuallyEdited(false)\n const next = applySlugify(slugConfig, slugSourceValue(slugConfig, source.schema, values))\n onChange(next || undefined)\n }\n\n // No document editor (a picker), or a name-based auto-upgrade on a non-routable\n // collection: keep the historical plain input, no slug chrome (no regression).\n if (!editor || (!explicit && !routable)) {\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n\n const input = (\n <Input\n id={id}\n disabled={readOnly}\n value={current}\n placeholder={derived || undefined}\n onChange={(e) => handleInput(e.target.value)}\n className={cn(parts ? 'h-7 min-w-[10ch] flex-1 bg-background font-mono text-xs' : 'flex-1')}\n />\n )\n const regenerateButton = !readOnly ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={regenerate}\n aria-label={translate('cms.field.slug.regenerate')}\n title={translate('cms.field.slug.regenerate')}\n >\n <RotateCw />\n </Button>\n ) : null\n\n // The compact, ellipsized read-only prefix (0.0.39 \"hybrid split\", round-3 clickable):\n // only the LAST context segment (`…/produkte/`) so a deep path can't overflow the\n // narrow Details sidebar — but that shown segment is now CLICKABLE with the same\n // navigation semantics as the header URL row (an ancestor DOCUMENT links to its\n // editor; a static route literal stays plain). The full composed path is the tooltip.\n const prefixTitle =\n parts?.path ??\n (contextSegments.length > 0 ? `/${contextSegments.map((s) => s.text).join('/')}/` : '/')\n\n return (\n <Stack gap=\"2\">\n {parts ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-composed-path\">\n <span\n className=\"inline-flex min-w-0 shrink-0 items-center rounded bg-muted/40 px-1.5 py-1 font-mono text-xs text-muted-foreground ring-1 ring-inset ring-border\"\n title={prefixTitle}\n data-testid=\"slug-prefix\"\n >\n {contextSegments.length === 0 ? (\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n ) : (\n <>\n <span aria-hidden className=\"text-muted-foreground/50\">\n {contextSegments.length > 1 ? '…/' : '/'}\n </span>\n {lastContext ? (\n <DocumentUrlSegment\n segment={lastContext}\n projectId={editor?.projectId}\n collectionId={source.collectionId}\n />\n ) : null}\n <span aria-hidden className=\"text-muted-foreground/50\">\n /\n </span>\n </>\n )}\n </span>\n {input}\n {regenerateButton}\n </Row>\n ) : (\n <Row gap=\"1\" align=\"center\">\n {input}\n {regenerateButton}\n </Row>\n )}\n\n {collisionMessage ? (\n <Row gap=\"1\" align=\"center\" data-testid=\"slug-collision\">\n <AlertTriangle className=\"size-3.5 shrink-0 text-warning\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {collisionMessage}\n </Text>\n </Row>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const source = useCollectionView()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n const projectId = source.projectId\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, source.referenceCandidates(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, source])\n\n function apply(next: DocumentRef[]) {\n onChange(field.multiple ? next : (next[0] ?? undefined))\n }\n\n return (\n <Stack gap=\"2\">\n {refs.length > 0 ? (\n <Stack gap=\"1\">\n {refs.map((ref, i) => {\n // EC-296: the relation title navigates to the referenced document (autosave\n // keeps the current edit safe). Without a projectId (no persistence, e.g.\n // fixtures) it stays plain text. EC-246c: full-width row, not a pill —\n // long titles wrap awkwardly in a pill and a narrow field crushes it.\n const title = source.titleOf(ref)\n const label = (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <Link2 className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 truncate\">{title}</span>\n </span>\n )\n return (\n <RowItem\n key={`${ref.id}-${i}`}\n data-testid=\"relation-value\"\n actions={\n !readOnly ? (\n <>\n {field.multiple === false ? (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.relation.change')}\n title={translate('cms.relation.change')}\n onClick={() => setOpen(true)}\n >\n <Pencil />\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('common.remove')}\n title={translate('common.remove')}\n className=\"hover:text-destructive\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n >\n <X />\n </Button>\n </>\n ) : undefined\n }\n >\n {projectId ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.cmsDocument(ref.collection, ref.id) }}\n title={translate('cms.relation.openTitle', { title })}\n className=\"text-sm font-medium hover:text-primary hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <Text as=\"span\" size=\"sm\" weight=\"medium\">\n {label}\n </Text>\n )}\n </RowItem>\n )\n })}\n </Stack>\n ) : null}\n {!readOnly && (refs.length === 0 || field.multiple) ? (\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Plus /> {refs.length === 0 ? translate('cms.relation.pick') : translate('common.add')}\n </Button>\n </Row>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n multiple={field.multiple}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Stack>\n )\n}\n"]}
|
|
@@ -20,7 +20,14 @@ export function resolveFieldRenderers(renderers) {
|
|
|
20
20
|
value: component,
|
|
21
21
|
})),
|
|
22
22
|
allowNew: true,
|
|
23
|
-
|
|
23
|
+
// EC-478: the rejection carries its catalog key as well as its English, so the
|
|
24
|
+
// `{reason}` half of `invalid-override` localizes with the sentence around it.
|
|
25
|
+
validate: (entry) => typeof entry.value !== 'function'
|
|
26
|
+
? {
|
|
27
|
+
message: 'the renderer is not a component',
|
|
28
|
+
key: 'customization.issue.reason.rendererNotAComponent',
|
|
29
|
+
}
|
|
30
|
+
: null,
|
|
24
31
|
});
|
|
25
32
|
return { get: (control) => resolved.get(control), issues: resolved.issues };
|
|
26
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-renderer.js","sourceRoot":"","sources":["../../../src/components/cms/field-renderer.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAE9D,OAAO,EAAE,+BAA+B,EAAE,MAAM,iCAAiC,CAAA;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AA8BvD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAA0D;IAE1D,MAAM,QAAQ,GAAG,gBAAgB,CAAsB;QACrD,KAAK,EAAE,gBAAgB;QACvB,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;YACxE,EAAE,EAAE,OAAO;YACX,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QACH,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"field-renderer.js","sourceRoot":"","sources":["../../../src/components/cms/field-renderer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, useMemo } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { resolveDeploymentFieldRenderers } from '@elytracms/studio/lib/workspace'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Input, Row, Text } from '@elytracms/studio/ui'\nimport type { CustomFieldRenderer } from '../app-views/studio-config'\n\n/**\n * EC-238 — custom field renderers, an instance of the EC-236 customization seam.\n *\n * A project registers a React component (via `defineStudio({ fieldRenderers })`)\n * keyed by CONTROL name to REPLACE the built-in document-editor control. A field\n * opts in PER-FIELD with `form: { control: '<name>' }` — so a renderer targets\n * exactly the fields that ask for it, not every field of a datatype. (`control` is\n * a presentation hint on the field; the field's `type` stays its honest datatype.\n * That split is why a colour is `type: 'text'` + `control: 'color'`, not a bogus\n * `plugin.color` type.) The contract is the exact {@link FieldControl} props seam,\n * so a project component drops in unchanged and reuses internal helpers\n * (relation/asset pickers, validation state) via the exported studio hooks\n * (`useCms`, `useOptionalDocumentEditor`). Resolution falls back to the built-in\n * control, and every failure mode — a non-component value, an unknown control on a\n * field, or a renderer that throws — surfaces a visible issue, never a silent drop\n * or a crash.\n */\n\nexport type { CustomFieldRenderer, CustomFieldRendererProps } from '../app-views/studio-config'\n\nexport interface ResolvedFieldRenderers {\n /** The custom renderer for a control name, or `undefined` → use the built-in. */\n get(control: string): CustomFieldRenderer | undefined\n /** Resolution issues (non-component); located by `id` = control name. */\n issues: CustomizationIssue[]\n}\n\n/**\n * Resolve the studio config's custom field renderers through the shared\n * customization core (EC-236): a registry keyed by control name, rejecting a\n * non-component with a visible issue and keeping the built-in. The map's keys are\n * unique by construction, so duplicates are structurally impossible.\n */\nexport function resolveFieldRenderers(\n renderers: Record<string, CustomFieldRenderer> | undefined,\n): ResolvedFieldRenderers {\n const resolved = resolveOverrides<CustomFieldRenderer>({\n point: 'fieldRenderers',\n defaults: [],\n overrides: Object.entries(renderers ?? {}).map(([control, component]) => ({\n id: control,\n value: component,\n })),\n allowNew: true,\n // EC-478: the rejection carries its catalog key as well as its English, so the\n // `{reason}` half of `invalid-override` localizes with the sentence around it.\n validate: (entry) =>\n typeof entry.value !== 'function'\n ? {\n message: 'the renderer is not a component',\n key: 'customization.issue.reason.rendererNotAComponent',\n }\n : null,\n })\n return { get: (control) => resolved.get(control), issues: resolved.issues }\n}\n\n/** The active studio's resolved field renderers (config is static per studio boot). */\nexport function useFieldRenderers(): ResolvedFieldRenderers {\n return useMemo(() => resolveFieldRenderers(resolveDeploymentFieldRenderers()), [])\n}\n\n/** A visible, inline field-renderer issue — the customization seam's \"never silent\". */\nexport function FieldRendererIssue({ message }: { message: string }) {\n return (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={message}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">{message}</Text>\n </Row>\n )\n}\n\n/**\n * Error boundary around a custom renderer: a renderer that throws falls back to\n * the built-in control (passed as `fallback`) with a visible `issue` above it,\n * so a broken third-party component degrades the one field, never the editor.\n */\nexport class FieldRendererBoundary extends Component<\n { fallback: ReactNode; issue: ReactNode; children: ReactNode },\n { failed: boolean }\n> {\n override state = { failed: false }\n static getDerivedStateFromError() {\n return { failed: true }\n }\n override render() {\n if (this.state.failed) {\n return (\n <>\n {this.props.issue}\n {this.props.fallback}\n </>\n )\n }\n return this.props.children\n }\n}\n\n/**\n * Example custom field renderer (EC-238 DoD): a colour control proving reuse of\n * the studio form primitives and a save round-trip. A consuming repo registers it\n * (or its own) with `defineStudio({ fieldRenderers: { color: colorFieldRenderer } })`\n * and opts a field in with `form: { control: 'color' }` — the field's datatype\n * stays `text` (a `#rrggbb` string).\n */\nexport const colorFieldRenderer: CustomFieldRenderer = ({ value, onChange, readOnly, id }) => {\n const hex = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000'\n return (\n <Row gap=\"2\" align=\"center\">\n <input\n id={id}\n type=\"color\"\n disabled={readOnly}\n value={hex}\n onChange={(event) => onChange(event.target.value)}\n aria-label={translate('cms.field.colour')}\n className=\"h-8 w-12 cursor-pointer rounded-md border border-input bg-background disabled:opacity-50\"\n />\n <Input\n value={typeof value === 'string' ? value : ''}\n disabled={readOnly}\n placeholder=\"#rrggbb\"\n onChange={(event) => onChange(event.target.value || undefined)}\n className=\"w-28 font-mono text-xs\"\n />\n </Row>\n )\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DocumentRef, DocumentStore, LocaleConfig, ValidateDocumentOptions } from '@elytracms/core/cms-core';
|
|
2
|
+
/**
|
|
3
|
+
* THE option bag (EC-497, AD-497a).
|
|
4
|
+
*
|
|
5
|
+
* Before this module there were seven `validateDocument` call sites, each
|
|
6
|
+
* hand-assembling its own subset of `ValidateDocumentOptions` — and the weakest of
|
|
7
|
+
* the seven was the publish gate, the last thing between a draft and the live
|
|
8
|
+
* site. One document therefore earned a different verdict depending on which
|
|
9
|
+
* surface asked, which is a validation system that cannot be reasoned about.
|
|
10
|
+
*
|
|
11
|
+
* The contract, in one line: **surfaces differ in what they DO with the issues,
|
|
12
|
+
* never in what they validated with.** Publish filters by severity, cell edit
|
|
13
|
+
* filters by field + locale, the AI paths take `introducedErrors` (after − before)
|
|
14
|
+
* — all three start from the identical list. There are deliberately NO named
|
|
15
|
+
* "weaker subsets": a subset is how this defect happened, and the moment one
|
|
16
|
+
* exists someone has to decide per call site which one applies.
|
|
17
|
+
*
|
|
18
|
+
* The one conditionality that remains is the engine's own and is documented on
|
|
19
|
+
* `ValidateDocumentOptions`: `store`-dependent rules (relation/asset existence,
|
|
20
|
+
* hierarchy cycles, `validation.unique`) fire exactly when a store is supplied.
|
|
21
|
+
*
|
|
22
|
+
* **Out of contract by declaration:** the assets and redirects rails. They
|
|
23
|
+
* validate synthetic, non-`cmsDocuments` collections against their own registries
|
|
24
|
+
* with no session store, and their writes go through `operations.assets.*` /
|
|
25
|
+
* `operations.redirects.*` — a different entity, not a weaker check of this one.
|
|
26
|
+
*/
|
|
27
|
+
export interface GateValidationInput {
|
|
28
|
+
/** The session document store, with the document under judgement swapped in. */
|
|
29
|
+
store: DocumentStore;
|
|
30
|
+
localeConfig: LocaleConfig;
|
|
31
|
+
/** Published-ness from the version POINTER (EC-224) — never a flat state flag. */
|
|
32
|
+
isPublished: (ref: DocumentRef) => boolean;
|
|
33
|
+
/** The real asset library oracle; absent where no library is mounted. */
|
|
34
|
+
assetExists?: (id: string) => boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function gateValidateOptions(input: GateValidationInput): ValidateDocumentOptions;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { validateComposition, validateRichTextEmbeds } from './cms-composition-validators.js';
|
|
2
|
+
export function gateValidateOptions(input) {
|
|
3
|
+
return {
|
|
4
|
+
store: input.store,
|
|
5
|
+
localeConfig: input.localeConfig,
|
|
6
|
+
// EC-188/EC-189: the composition + rich-text-embed leashes, injected so
|
|
7
|
+
// `cms-core` stays free of `project-graph` and `rich-text`. These are what the
|
|
8
|
+
// operations layer could never supply on its own — it has no registry — and
|
|
9
|
+
// the reason the canonical validator is injected downward rather than rebuilt
|
|
10
|
+
// there.
|
|
11
|
+
validateComposition,
|
|
12
|
+
validateRichTextEmbeds,
|
|
13
|
+
isPublished: input.isPublished,
|
|
14
|
+
...(input.assetExists ? { assetExists: input.assetExists } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=gate-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-validation.js","sourceRoot":"","sources":["../../../src/components/cms/gate-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AA2C1F,MAAM,UAAU,mBAAmB,CAAC,KAA0B;IAC5D,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,wEAAwE;QACxE,+EAA+E;QAC/E,4EAA4E;QAC5E,8EAA8E;QAC9E,SAAS;QACT,mBAAmB;QACnB,sBAAsB;QACtB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjE,CAAA;AACH,CAAC","sourcesContent":["import { validateComposition, validateRichTextEmbeds } from './cms-composition-validators'\nimport type {\n DocumentRef,\n DocumentStore,\n LocaleConfig,\n ValidateDocumentOptions,\n} from '@elytracms/core/cms-core'\n\n/**\n * THE option bag (EC-497, AD-497a).\n *\n * Before this module there were seven `validateDocument` call sites, each\n * hand-assembling its own subset of `ValidateDocumentOptions` — and the weakest of\n * the seven was the publish gate, the last thing between a draft and the live\n * site. One document therefore earned a different verdict depending on which\n * surface asked, which is a validation system that cannot be reasoned about.\n *\n * The contract, in one line: **surfaces differ in what they DO with the issues,\n * never in what they validated with.** Publish filters by severity, cell edit\n * filters by field + locale, the AI paths take `introducedErrors` (after − before)\n * — all three start from the identical list. There are deliberately NO named\n * \"weaker subsets\": a subset is how this defect happened, and the moment one\n * exists someone has to decide per call site which one applies.\n *\n * The one conditionality that remains is the engine's own and is documented on\n * `ValidateDocumentOptions`: `store`-dependent rules (relation/asset existence,\n * hierarchy cycles, `validation.unique`) fire exactly when a store is supplied.\n *\n * **Out of contract by declaration:** the assets and redirects rails. They\n * validate synthetic, non-`cmsDocuments` collections against their own registries\n * with no session store, and their writes go through `operations.assets.*` /\n * `operations.redirects.*` — a different entity, not a weaker check of this one.\n */\nexport interface GateValidationInput {\n /** The session document store, with the document under judgement swapped in. */\n store: DocumentStore\n localeConfig: LocaleConfig\n /** Published-ness from the version POINTER (EC-224) — never a flat state flag. */\n isPublished: (ref: DocumentRef) => boolean\n /** The real asset library oracle; absent where no library is mounted. */\n assetExists?: (id: string) => boolean\n}\n\nexport function gateValidateOptions(input: GateValidationInput): ValidateDocumentOptions {\n return {\n store: input.store,\n localeConfig: input.localeConfig,\n // EC-188/EC-189: the composition + rich-text-embed leashes, injected so\n // `cms-core` stays free of `project-graph` and `rich-text`. These are what the\n // operations layer could never supply on its own — it has no registry — and\n // the reason the canonical validator is injected downward rather than rebuilt\n // there.\n validateComposition,\n validateRichTextEmbeds,\n isPublished: input.isPublished,\n ...(input.assetExists ? { assetExists: input.assetExists } : {}),\n }\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Badge, Button, Code, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Stack, Text, } from '@elytracms/studio/ui';
|
|
4
4
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
5
|
+
import { cmsValidationIssueMessage } from '@elytracms/studio/lib/persona/labels';
|
|
5
6
|
import { useCollectionView } from '../collection-view/collection-view-context.js';
|
|
6
7
|
/** Import collection data from JSON with validation + error reporting (brief §8.4, EC-079). */
|
|
7
8
|
export function ImportDialog({ collectionId, open, onOpenChange, }) {
|
|
@@ -31,6 +32,6 @@ export function ImportDialog({ collectionId, open, onOpenChange, }) {
|
|
|
31
32
|
if (outcome)
|
|
32
33
|
setReport(outcome);
|
|
33
34
|
}
|
|
34
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.import.title') }), _jsxs(DialogDescription, { children: ["Paste a JSON array of documents for ", _jsx(Code, { tone: "muted", children: collectionId }), ". Each is validated against the collection schema before import."] })] }), _jsx("textarea", { value: text, onChange: (e) => setText(e.target.value), rows: 8, placeholder: '[{ "id": "post-x", "values": { "slug": "x" }, ... }]', className: "w-full rounded-md border border-input bg-background p-2.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none" }), parseError ? (_jsx(Text, { size: "sm", className: "text-destructive", children: parseError })) : null, report ? (_jsxs(Stack, { gap: "2", className: "rounded-md border border-border p-3", children: [_jsx(Badge, { variant: report.added > 0 ? 'success' : 'outline', children: translate('cms.import.imported', { count: report.added }) }), report.rejected.length > 0 ? (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('cms.import.rejected', { count: report.rejected.length }) }), report.rejected.map((r, i) => (_jsxs(Text, { size: "xs", className: "text-destructive", children: [_jsx(Code, { tone: "muted", children: r.id }), " \u2014
|
|
35
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.import.title') }), _jsxs(DialogDescription, { children: ["Paste a JSON array of documents for ", _jsx(Code, { tone: "muted", children: collectionId }), ". Each is validated against the collection schema before import."] })] }), _jsx("textarea", { value: text, onChange: (e) => setText(e.target.value), rows: 8, placeholder: '[{ "id": "post-x", "values": { "slug": "x" }, ... }]', className: "w-full rounded-md border border-input bg-background p-2.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none" }), parseError ? (_jsx(Text, { size: "sm", className: "text-destructive", children: parseError })) : null, report ? (_jsxs(Stack, { gap: "2", className: "rounded-md border border-border p-3", children: [_jsx(Badge, { variant: report.added > 0 ? 'success' : 'outline', children: translate('cms.import.imported', { count: report.added }) }), report.rejected.length > 0 ? (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('cms.import.rejected', { count: report.rejected.length }) }), report.rejected.map((r, i) => (_jsxs(Text, { size: "xs", className: "text-destructive", children: [_jsx(Code, { tone: "muted", children: r.id }), " \u2014", ' ', r.issues[0] ? cmsValidationIssueMessage(r.issues[0]) : undefined] }, `${r.id}-${i}`)))] })) : null] })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: translate('common.close') }), _jsx(Button, { onClick: runImport, disabled: !text.trim(), children: translate('cms.import.confirm') })] })] }) }));
|
|
35
36
|
}
|
|
36
37
|
//# sourceMappingURL=import-export.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-export.js","sourceRoot":"","sources":["../../../src/components/cms/import-export.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"import-export.js","sourceRoot":"","sources":["../../../src/components/cms/import-export.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from 'react'\nimport {\n Badge,\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cmsValidationIssueMessage } from '@elytracms/studio/lib/persona/labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport type { ImportReport } from './cms-store'\n\n/** Import collection data from JSON with validation + error reporting (brief §8.4, EC-079). */\nexport function ImportDialog({\n collectionId,\n open,\n onOpenChange,\n}: {\n collectionId: string\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const source = useCollectionView()\n const [text, setText] = useState('')\n const [report, setReport] = useState<ImportReport | null>(null)\n const [parseError, setParseError] = useState<string | null>(null)\n\n useEffect(() => {\n if (open) {\n setText('')\n setReport(null)\n setParseError(null)\n }\n }, [open])\n\n function runImport() {\n setParseError(null)\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch {\n setParseError(translate('cms.import.invalidJson'))\n return\n }\n const items = Array.isArray(parsed) ? parsed : [parsed]\n const outcome = source.imports?.importDocuments(items)\n if (outcome) setReport(outcome)\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{translate('cms.import.title')}</DialogTitle>\n <DialogDescription>\n Paste a JSON array of documents for <Code tone=\"muted\">{collectionId}</Code>. Each is\n validated against the collection schema before import.\n </DialogDescription>\n </DialogHeader>\n\n <textarea\n value={text}\n onChange={(e) => setText(e.target.value)}\n rows={8}\n placeholder='[{ \"id\": \"post-x\", \"values\": { \"slug\": \"x\" }, ... }]'\n className=\"w-full rounded-md border border-input bg-background p-2.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none\"\n />\n\n {parseError ? (\n <Text size=\"sm\" className=\"text-destructive\">\n {parseError}\n </Text>\n ) : null}\n\n {report ? (\n <Stack gap=\"2\" className=\"rounded-md border border-border p-3\">\n <Badge variant={report.added > 0 ? 'success' : 'outline'}>\n {translate('cms.import.imported', { count: report.added })}\n </Badge>\n {report.rejected.length > 0 ? (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('cms.import.rejected', { count: report.rejected.length })}\n </Text>\n {report.rejected.map((r, i) => (\n <Text key={`${r.id}-${i}`} size=\"xs\" className=\"text-destructive\">\n <Code tone=\"muted\">{r.id}</Code> —{' '}\n {r.issues[0] ? cmsValidationIssueMessage(r.issues[0]) : undefined}\n </Text>\n ))}\n </Stack>\n ) : null}\n </Stack>\n ) : null}\n\n <DialogFooter>\n <Button variant=\"ghost\" onClick={() => onOpenChange(false)}>\n {translate('common.close')}\n </Button>\n <Button onClick={runImport} disabled={!text.trim()}>\n {translate('cms.import.confirm')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core';
|
|
2
|
+
/**
|
|
3
|
+
* The document form's inline errors, keyed by the top-level field row that owns
|
|
4
|
+
* them. `messageOf` is injected so this stays catalog-free and pure (the studio
|
|
5
|
+
* passes `cmsValidationIssueMessage`); it is called only for the issue that
|
|
6
|
+
* actually wins a row.
|
|
7
|
+
*/
|
|
8
|
+
export declare function inlineFieldErrors(collection: CollectionDef, issues: readonly CmsValidationIssue[], messageOf: (issue: CmsValidationIssue) => string): ReadonlyMap<string, string>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { issueJumpTarget } from './issue-copy.js';
|
|
2
|
+
/**
|
|
3
|
+
* EC-498 — WHERE the document form puts an inline field error.
|
|
4
|
+
*
|
|
5
|
+
* The producer (`validateDocument`'s `validateObject`) sets `meta.field` to the
|
|
6
|
+
* LEAF subfield name (`caption`) while `path` carries the full document path
|
|
7
|
+
* (`values/gallery/2/caption`). The form used to key its inline errors on that
|
|
8
|
+
* leaf name and read them back with TOP-LEVEL field names, which broke three
|
|
9
|
+
* ways at once: a leaf matching no top-level field lost its error entirely, a
|
|
10
|
+
* leaf colliding with a top-level field name put the nested error on that
|
|
11
|
+
* unrelated row, and — first-wins — it could shadow that row's own real error.
|
|
12
|
+
*
|
|
13
|
+
* The anchor is therefore resolved from the issue's **path**, through the SAME
|
|
14
|
+
* grammar the Issues pane's jump links already use ({@link issueJumpTarget}) —
|
|
15
|
+
* deliberately not a fifth parallel path parser. That resolver already encodes
|
|
16
|
+
* the whole ladder: full path first, `meta.field` only as the fallback for bare
|
|
17
|
+
* paths (the EC-300 embed issues) and only when it names a real top-level field,
|
|
18
|
+
* `null` for document-scoped issues. Its first token IS the owning row.
|
|
19
|
+
*
|
|
20
|
+
* Note what is deliberately unchanged: `meta.field` keeps its leaf semantics
|
|
21
|
+
* (other readers depend on it — `cell-edit.ts`, `slug-collision-report.ts`), the
|
|
22
|
+
* form still shows at most ONE message per row (first issue wins, which after
|
|
23
|
+
* this change means the field's own issue before any of its items', since
|
|
24
|
+
* `validateFieldValue` runs the field's own checks before recursing), and the
|
|
25
|
+
* locale axis is still ignored — a `localized/<other>/title` issue anchors to
|
|
26
|
+
* `title` exactly as it did before.
|
|
27
|
+
*/
|
|
28
|
+
/** The top-level field row an issue belongs to, or `null` for a document-scoped one. */
|
|
29
|
+
function anchorFieldName(collection, issue) {
|
|
30
|
+
const target = issueJumpTarget(collection, issue);
|
|
31
|
+
if (target === null)
|
|
32
|
+
return null;
|
|
33
|
+
const [top] = target.path.split('/');
|
|
34
|
+
return top !== undefined && top !== '' ? top : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The document form's inline errors, keyed by the top-level field row that owns
|
|
38
|
+
* them. `messageOf` is injected so this stays catalog-free and pure (the studio
|
|
39
|
+
* passes `cmsValidationIssueMessage`); it is called only for the issue that
|
|
40
|
+
* actually wins a row.
|
|
41
|
+
*/
|
|
42
|
+
export function inlineFieldErrors(collection, issues, messageOf) {
|
|
43
|
+
const map = new Map();
|
|
44
|
+
for (const issue of issues) {
|
|
45
|
+
const field = anchorFieldName(collection, issue);
|
|
46
|
+
if (field === null || map.has(field))
|
|
47
|
+
continue;
|
|
48
|
+
map.set(field, messageOf(issue));
|
|
49
|
+
}
|
|
50
|
+
return map;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=inline-field-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-field-errors.js","sourceRoot":"","sources":["../../../src/components/cms/inline-field-errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,wFAAwF;AACxF,SAAS,eAAe,CAAC,UAAyB,EAAE,KAAyB;IAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;IACjD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAChC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACpC,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAyB,EACzB,MAAqC,EACrC,SAAgD;IAEhD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAA;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;QAChD,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAQ;QAC9C,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["import type { CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core'\nimport { issueJumpTarget } from './issue-copy'\n\n/**\n * EC-498 — WHERE the document form puts an inline field error.\n *\n * The producer (`validateDocument`'s `validateObject`) sets `meta.field` to the\n * LEAF subfield name (`caption`) while `path` carries the full document path\n * (`values/gallery/2/caption`). The form used to key its inline errors on that\n * leaf name and read them back with TOP-LEVEL field names, which broke three\n * ways at once: a leaf matching no top-level field lost its error entirely, a\n * leaf colliding with a top-level field name put the nested error on that\n * unrelated row, and — first-wins — it could shadow that row's own real error.\n *\n * The anchor is therefore resolved from the issue's **path**, through the SAME\n * grammar the Issues pane's jump links already use ({@link issueJumpTarget}) —\n * deliberately not a fifth parallel path parser. That resolver already encodes\n * the whole ladder: full path first, `meta.field` only as the fallback for bare\n * paths (the EC-300 embed issues) and only when it names a real top-level field,\n * `null` for document-scoped issues. Its first token IS the owning row.\n *\n * Note what is deliberately unchanged: `meta.field` keeps its leaf semantics\n * (other readers depend on it — `cell-edit.ts`, `slug-collision-report.ts`), the\n * form still shows at most ONE message per row (first issue wins, which after\n * this change means the field's own issue before any of its items', since\n * `validateFieldValue` runs the field's own checks before recursing), and the\n * locale axis is still ignored — a `localized/<other>/title` issue anchors to\n * `title` exactly as it did before.\n */\n\n/** The top-level field row an issue belongs to, or `null` for a document-scoped one. */\nfunction anchorFieldName(collection: CollectionDef, issue: CmsValidationIssue): string | null {\n const target = issueJumpTarget(collection, issue)\n if (target === null) return null\n const [top] = target.path.split('/')\n return top !== undefined && top !== '' ? top : null\n}\n\n/**\n * The document form's inline errors, keyed by the top-level field row that owns\n * them. `messageOf` is injected so this stays catalog-free and pure (the studio\n * passes `cmsValidationIssueMessage`); it is called only for the issue that\n * actually wins a row.\n */\nexport function inlineFieldErrors(\n collection: CollectionDef,\n issues: readonly CmsValidationIssue[],\n messageOf: (issue: CmsValidationIssue) => string,\n): ReadonlyMap<string, string> {\n const map = new Map<string, string>()\n for (const issue of issues) {\n const field = anchorFieldName(collection, issue)\n if (field === null || map.has(field)) continue\n map.set(field, messageOf(issue))\n }\n return map\n}\n"]}
|
|
@@ -14,8 +14,10 @@ import type { CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core
|
|
|
14
14
|
* names the deepest resolvable field's localized label, never a raw leaf token
|
|
15
15
|
* when a def resolves. Card copy is localized studio-side: the title via
|
|
16
16
|
* `cmsIssueLabel` (catalog-routed), the body via the `issueBodyDescriptor`
|
|
17
|
-
* template where one exists
|
|
18
|
-
*
|
|
17
|
+
* template where one exists — it is PERSONA copy, naming the field by its localized
|
|
18
|
+
* label rather than its raw name, so it keeps precedence — else the issue's own
|
|
19
|
+
* message, localized through `cmsValidationIssueMessage` (EC-487) and degrading to
|
|
20
|
+
* the producer's English only for an issue that carries no catalog key.
|
|
19
21
|
*
|
|
20
22
|
* Persona boundary (EC-192): rows show the plain-language `cmsIssueLabel`; the raw
|
|
21
23
|
* `code` rides along in `title=` for engineers, never as visible copy.
|