@elytracms/studio 0.0.103 → 0.0.105
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/ingest.test.ts +208 -13
- package/convex/ingest.ts +56 -35
- package/dist/components/app-views/module.d.ts +4 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/builder/canvas-theme-notice.js +4 -1
- package/dist/components/builder/canvas-theme-notice.js.map +1 -1
- package/dist/components/cms/document-diff-view.js +43 -7
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/components/components-index.d.ts +0 -7
- package/dist/components/components/components-index.js +9 -2
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/redirects/redirects-schema.d.ts +31 -0
- package/dist/components/redirects/redirects-schema.js +12 -6
- package/dist/components/redirects/redirects-schema.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +7 -3
- package/dist/lib/ai/translate-document.js +63 -48
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/writer-form.d.ts +48 -5
- package/dist/lib/ai/writer-form.js +118 -64
- package/dist/lib/ai/writer-form.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 -5
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +21 -1
- package/dist/lib/i18n/messages.en.js +29 -6
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/persona/labels.js +1 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +9 -1
- package/dist/lib/workspace/index.js +13 -2
- package/dist/lib/workspace/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react'\nimport { getStudioRegistry, registryHealth } from '../../lib/registry'\nimport { translate } from '../../lib/i18n'\nimport { RegistryBrowser } from './registry-browser'\nimport { deriveComponentStats } from './taxonomy'\nimport { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH\n * audiences: editors get a live census of what components exist (totals,\n * per-category breakdown, health) and a \"pick one\" nudge;\n * devs get pointers to the real in-repo docs for registering and extending\n * components — no code sample to drift, no invented docs.\n */\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const manifests = studio.registry.list()\n const stats = deriveComponentStats(manifests)\n const health = registryHealth(studio)\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard label={translate('components.index.stat.total')} value={stats.total} />\n <StatCard\n label={translate('components.index.stat.valid')}\n value={health.valid}\n tone=\"success\"\n />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n {/* By category — a cheap census derived from manifest `category`. */}\n {stats.categories.length > 0 ? (\n <Panel>\n <PanelHeader>\n <Layers className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.byCategory')}\n </Text>\n </PanelHeader>\n <Row gap=\"2\" wrap className=\"p-4\">\n {stats.categories.map((category) => (\n <Badge key={category.key || '__uncategorized__'} variant=\"outline\">\n <span className={category.key ? 'capitalize' : undefined}>\n {category.key || translate('components.taxonomy.uncategorized')}\n </span>\n <span className=\"font-mono text-muted-foreground\">{category.count}</span>\n </Badge>\n ))}\n </Row>\n </Panel>\n ) : null}\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n {/* Dev guidance — pointers to the REAL docs (no code sample to drift). They live in the\n engine repository's docs/, never in the consumer's repo (EC-723: the rail pointed at a\n HISTORICAL doc and a missing one). */}\n <Panel>\n <PanelHeader>\n <BookOpen className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.guide.intro')}\n </Text>\n <Stack gap=\"2\">\n <DocPointer\n path=\"docs/component-authoring.md\"\n description={translate('components.index.guide.doc.authoring')}\n />\n <DocPointer\n path=\"docs/guide/03-components.md\"\n description={translate('components.index.guide.doc.components')}\n />\n <DocPointer\n path=\"docs/guide/00-overview.md\"\n description={translate('components.index.guide.doc.guide')}\n />\n </Stack>\n <Text size=\"xs\" tone=\"subtle\">\n <Sparkles className=\"mr-1 inline size-3\" aria-hidden />\n {translate('components.index.guide.namespaceNote')}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction DocPointer({ path, description }: { path: string; description: string }) {\n return (\n <Row gap=\"3\" align=\"center\" className=\"rounded-md border border-border p-2.5\">\n <Code tone=\"primary\" className=\"shrink-0\">\n {path}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {description}\n </Text>\n </Row>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react'\nimport { elytraGuidePath } from '@elytracms/core/studio-core'\nimport { getStudioRegistry, registryHealth } from '../../lib/registry'\nimport { translate } from '../../lib/i18n'\nimport { RegistryBrowser } from './registry-browser'\nimport { deriveComponentStats } from './taxonomy'\nimport { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH\n * audiences: editors get a live census of what components exist (totals,\n * per-category breakdown, health) and a \"pick one\" nudge;\n * devs get pointers to the guide's chapters on registering and extending\n * components — no code sample to drift, no invented docs.\n */\n/** The guide chapters the developer panel points at (EC-765: file names in the shipped guide). */\nconst COMPONENTS_INDEX_CHAPTERS = {\n authoring: '12-component-authoring.md',\n components: '03-components.md',\n overview: '00-overview.md',\n} as const\n\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const manifests = studio.registry.list()\n const stats = deriveComponentStats(manifests)\n const health = registryHealth(studio)\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard label={translate('components.index.stat.total')} value={stats.total} />\n <StatCard\n label={translate('components.index.stat.valid')}\n value={health.valid}\n tone=\"success\"\n />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n {/* By category — a cheap census derived from manifest `category`. */}\n {stats.categories.length > 0 ? (\n <Panel>\n <PanelHeader>\n <Layers className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.byCategory')}\n </Text>\n </PanelHeader>\n <Row gap=\"2\" wrap className=\"p-4\">\n {stats.categories.map((category) => (\n <Badge key={category.key || '__uncategorized__'} variant=\"outline\">\n <span className={category.key ? 'capitalize' : undefined}>\n {category.key || translate('components.taxonomy.uncategorized')}\n </span>\n <span className=\"font-mono text-muted-foreground\">{category.count}</span>\n </Badge>\n ))}\n </Row>\n </Panel>\n ) : null}\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n {/* Dev guidance — pointers to the REAL docs (no code sample to drift; EC-723: the rail\n pointed at a HISTORICAL doc and a missing one). EC-765: the guide ships inside\n @elytracms/core, so each pointer is the chapter's path in the project's node_modules. */}\n <Panel>\n <PanelHeader>\n <BookOpen className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.guide.intro')}\n </Text>\n <Stack gap=\"2\">\n <DocPointer\n path={elytraGuidePath(COMPONENTS_INDEX_CHAPTERS.authoring)}\n description={translate('components.index.guide.doc.authoring')}\n />\n <DocPointer\n path={elytraGuidePath(COMPONENTS_INDEX_CHAPTERS.components)}\n description={translate('components.index.guide.doc.components')}\n />\n <DocPointer\n path={elytraGuidePath(COMPONENTS_INDEX_CHAPTERS.overview)}\n description={translate('components.index.guide.doc.guide')}\n />\n </Stack>\n <Text size=\"xs\" tone=\"subtle\">\n <Sparkles className=\"mr-1 inline size-3\" aria-hidden />\n {translate('components.index.guide.namespaceNote')}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction DocPointer({ path, description }: { path: string; description: string }) {\n return (\n <Row gap=\"3\" align=\"center\" className=\"rounded-md border border-border p-2.5\">\n <Code tone=\"primary\" className=\"shrink-0\">\n {path}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {description}\n </Text>\n </Row>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
|
|
@@ -6,5 +6,36 @@ import type { CollectionDef } from '@elytracms/core/cms-core';
|
|
|
6
6
|
* {@link CollectionViewSource}. The rail's semantics (content-gated, no
|
|
7
7
|
* publish/versions/locales, create + remove) come from the source's
|
|
8
8
|
* capabilities, never the schema `kind`. The title identity is the `from` path.
|
|
9
|
+
*
|
|
10
|
+
* The lens as AUTHORED — exported so its test runs it through the engine's unknown-key gate
|
|
11
|
+
* (`unknownCollectionKeyFindings`, EC-625), the one a project's own collections pass. The parse
|
|
12
|
+
* below strips a key the schema does not have: each field once declared `form.help` (the key is
|
|
13
|
+
* `description`), and the redirect form showed no help at all (wave-0106, measured connected).
|
|
9
14
|
*/
|
|
15
|
+
export declare const redirectsCollectionInput: {
|
|
16
|
+
id: string;
|
|
17
|
+
kind: string;
|
|
18
|
+
titleField: string;
|
|
19
|
+
form: {
|
|
20
|
+
label: string | Record<string, string>;
|
|
21
|
+
labelSingular: string | Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
fields: ({
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
filterable: boolean;
|
|
27
|
+
form: {
|
|
28
|
+
label: string | Record<string, string>;
|
|
29
|
+
description: string | Record<string, string>;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
name: string;
|
|
33
|
+
type: string;
|
|
34
|
+
form: {
|
|
35
|
+
label: string | Record<string, string>;
|
|
36
|
+
description: string | Record<string, string>;
|
|
37
|
+
};
|
|
38
|
+
filterable?: undefined;
|
|
39
|
+
})[];
|
|
40
|
+
};
|
|
10
41
|
export declare const redirectsCollectionDef: CollectionDef;
|
|
@@ -7,8 +7,13 @@ import { msgLabel } from '../../lib/i18n/index.js';
|
|
|
7
7
|
* {@link CollectionViewSource}. The rail's semantics (content-gated, no
|
|
8
8
|
* publish/versions/locales, create + remove) come from the source's
|
|
9
9
|
* capabilities, never the schema `kind`. The title identity is the `from` path.
|
|
10
|
+
*
|
|
11
|
+
* The lens as AUTHORED — exported so its test runs it through the engine's unknown-key gate
|
|
12
|
+
* (`unknownCollectionKeyFindings`, EC-625), the one a project's own collections pass. The parse
|
|
13
|
+
* below strips a key the schema does not have: each field once declared `form.help` (the key is
|
|
14
|
+
* `description`), and the redirect form showed no help at all (wave-0106, measured connected).
|
|
10
15
|
*/
|
|
11
|
-
export const
|
|
16
|
+
export const redirectsCollectionInput = {
|
|
12
17
|
id: 'redirect',
|
|
13
18
|
// Redirects aren't a CMS `kind`; 'document' just satisfies the lens schema.
|
|
14
19
|
kind: 'document',
|
|
@@ -23,25 +28,26 @@ export const redirectsCollectionDef = collectionDefSchema.parse({
|
|
|
23
28
|
name: 'from',
|
|
24
29
|
type: 'text',
|
|
25
30
|
filterable: true,
|
|
26
|
-
form: { label: msgLabel('redirects.field.from.label'),
|
|
31
|
+
form: { label: msgLabel('redirects.field.from.label'), description: msgLabel('redirects.field.from.description') },
|
|
27
32
|
},
|
|
28
33
|
{
|
|
29
34
|
name: 'to',
|
|
30
35
|
type: 'text',
|
|
31
36
|
filterable: true,
|
|
32
|
-
form: { label: msgLabel('redirects.field.to.label'),
|
|
37
|
+
form: { label: msgLabel('redirects.field.to.label'), description: msgLabel('redirects.field.to.description') },
|
|
33
38
|
},
|
|
34
39
|
{
|
|
35
40
|
name: 'permanent',
|
|
36
41
|
type: 'boolean',
|
|
37
|
-
form: { label: msgLabel('redirects.field.permanent.label'),
|
|
42
|
+
form: { label: msgLabel('redirects.field.permanent.label'), description: msgLabel('redirects.field.permanent.description') },
|
|
38
43
|
},
|
|
39
44
|
{
|
|
40
45
|
name: 'enabled',
|
|
41
46
|
type: 'boolean',
|
|
42
47
|
filterable: true,
|
|
43
|
-
form: { label: msgLabel('redirects.field.enabled.label'),
|
|
48
|
+
form: { label: msgLabel('redirects.field.enabled.label'), description: msgLabel('redirects.field.enabled.description') },
|
|
44
49
|
},
|
|
45
50
|
],
|
|
46
|
-
}
|
|
51
|
+
};
|
|
52
|
+
export const redirectsCollectionDef = collectionDefSchema.parse(redirectsCollectionInput);
|
|
47
53
|
//# sourceMappingURL=redirects-schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-schema.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC
|
|
1
|
+
{"version":3,"file":"redirects-schema.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,EAAE,EAAE,UAAU;IACd,4EAA4E;IAC5E,IAAI,EAAE,UAAU;IAChB,UAAU,EAAE,MAAM;IAClB,qFAAqF;IACrF,mFAAmF;IACnF,yEAAyE;IACzE,2DAA2D;IAC3D,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,8BAA8B,CAAC,EAAE;IAC1G,MAAM,EAAE;QACN;YACE,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,4BAA4B,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,kCAAkC,CAAC,EAAE;SACnH;QACD;YACE,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,0BAA0B,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,gCAAgC,CAAC,EAAE;SAC/G;QACD;YACE,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,iCAAiC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,uCAAuC,CAAC,EAAE;SAC7H;QACD;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,+BAA+B,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,qCAAqC,CAAC,EAAE;SACzH;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAkB,mBAAmB,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA","sourcesContent":["import { collectionDefSchema } from '@elytracms/core/cms-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { msgLabel } from '../../lib/i18n'\n\n/**\n * The built-in **redirects** field lens (EC-303). Redirects are a separate\n * editorial rail — NOT a `cmsDocuments` collection — so this `CollectionDef`\n * exists only to drive the portable list/edit surfaces over the redirects\n * {@link CollectionViewSource}. The rail's semantics (content-gated, no\n * publish/versions/locales, create + remove) come from the source's\n * capabilities, never the schema `kind`. The title identity is the `from` path.\n *\n * The lens as AUTHORED — exported so its test runs it through the engine's unknown-key gate\n * (`unknownCollectionKeyFindings`, EC-625), the one a project's own collections pass. The parse\n * below strips a key the schema does not have: each field once declared `form.help` (the key is\n * `description`), and the redirect form showed no help at all (wave-0106, measured connected).\n */\nexport const redirectsCollectionInput = {\n id: 'redirect',\n // Redirects aren't a CMS `kind`; 'document' just satisfies the lens schema.\n kind: 'document',\n titleField: 'from',\n // EC-241: the built-in rail's labels resolve through the message catalog (msgLabel),\n // so a consumer's translation catalog localizes them — no per-rail EC-311 override\n // needed just to translate. EC-311 field overrides still win for genuine\n // customization (renaming, readOnly, control, validation).\n form: { label: msgLabel('redirects.rail.label'), labelSingular: msgLabel('redirects.rail.labelSingular') },\n fields: [\n {\n name: 'from',\n type: 'text',\n filterable: true,\n form: { label: msgLabel('redirects.field.from.label'), description: msgLabel('redirects.field.from.description') },\n },\n {\n name: 'to',\n type: 'text',\n filterable: true,\n form: { label: msgLabel('redirects.field.to.label'), description: msgLabel('redirects.field.to.description') },\n },\n {\n name: 'permanent',\n type: 'boolean',\n form: { label: msgLabel('redirects.field.permanent.label'), description: msgLabel('redirects.field.permanent.description') },\n },\n {\n name: 'enabled',\n type: 'boolean',\n filterable: true,\n form: { label: msgLabel('redirects.field.enabled.label'), description: msgLabel('redirects.field.enabled.description') },\n },\n ],\n}\n\nexport const redirectsCollectionDef: CollectionDef = collectionDefSchema.parse(redirectsCollectionInput)\n"]}
|
|
@@ -63,7 +63,10 @@ export type TranslateSkipReason =
|
|
|
63
63
|
/**
|
|
64
64
|
* The stored value at the position is not readable as its type (e.g. a pre-convergence rich
|
|
65
65
|
* text) — or (EC-752) it is a value the traversal cannot read at all (`OpaquePosition`: a
|
|
66
|
-
* holder stored in a shape its declaration no longer reads) that may hold text
|
|
66
|
+
* holder stored in a shape its declaration no longer reads) that may hold text — or (EC-774)
|
|
67
|
+
* it sits in a component whose node id its sequence holds more than once: the position has
|
|
68
|
+
* no identity of its own (a write addresses a prop by its node id within one sequence, so it
|
|
69
|
+
* would land in the twin too), and both twins are reported.
|
|
67
70
|
*/
|
|
68
71
|
'unreadable'
|
|
69
72
|
/** The provider returned no (or empty) text for the unit. */
|
|
@@ -108,8 +111,9 @@ export interface TranslateBucket {
|
|
|
108
111
|
* What a planned bucket is (EC-752): `fill` — the target FOLLOWS the base (EC-318 connected)
|
|
109
112
|
* and gets its own translation; `replace` — the target holds its own value (an earlier
|
|
110
113
|
* translation, an editor's edit), which a re-translation rebuilds from the base — its text
|
|
111
|
-
* translated, every other value (a number, an image, a select
|
|
112
|
-
* positions kept (EC-738). `replace` exists only in
|
|
114
|
+
* translated, every other value (a number, an image, a select, a text the base leaves empty —
|
|
115
|
+
* EC-759) the base's, its locked positions kept (EC-738). `replace` exists only in
|
|
116
|
+
* `retranslate-all`.
|
|
113
117
|
*/
|
|
114
118
|
export type TranslateBucketKind = 'fill' | 'replace';
|
|
115
119
|
export interface TranslatePlanBucket extends TranslateBucket {
|
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* - `fill-empty`: a field/locale is translated only when it is CONNECTED (no per-locale
|
|
22
22
|
* key yet) and the base value is non-empty — so writing the translation DIVERGES it,
|
|
23
23
|
* which is exactly what makes a re-run an idempotent no-op. `retranslate-all`: also when
|
|
24
|
-
* it holds its own value — and then a field/locale is written only WHOLE, when every
|
|
25
|
-
* of it landed (a dropped unit, a rejected block, a cancel or an unreadable
|
|
26
|
-
* leaves the target's value as it was, reported); its locked content is kept
|
|
24
|
+
* it holds its own value — and then a field/locale is written only WHOLE, when every
|
|
25
|
+
* PLANNED unit of it landed (a dropped unit, a rejected block, a cancel or an unreadable
|
|
26
|
+
* position leaves the target's value as it was, reported); its locked content is kept
|
|
27
|
+
* (EC-738). A text position the base leaves empty is no unit and no part of that judgement
|
|
28
|
+
* (EC-759): the rebuilt value takes the base's state there, as it does for a number — and
|
|
29
|
+
* so does a text the engine reads as a URL (EC-766: `isUrlLike` — a URL is not prose);
|
|
27
30
|
* - EVERY text position of the base value is a unit (EC-593, ratified 4e): the walk is
|
|
28
31
|
* core's ONE value-position traversal, so a `text` position is one unit and a
|
|
29
32
|
* `richText` / `blocks` sequence one unit per translatable block (structure preserved
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
* translates with cross-block context. Bounded concurrency (default 6) keeps a realistic
|
|
51
54
|
* document well under the route's per-minute rate limit.
|
|
52
55
|
*/
|
|
53
|
-
import { autoDeriveSlug, childPositions, createCollectionRegistry, declarationMayHold, fieldValuePosition, isFieldLocalized, isOpaquePosition, isWriterLocked, resolveField, slugFieldOf, validateDocument, valuesInLocale, writeField, } from '@elytracms/core/cms-core';
|
|
56
|
+
import { autoDeriveSlug, childPositions, createCollectionRegistry, declarationMayHold, fieldValuePosition, isFieldLocalized, isOpaquePosition, isUrlLike, isWriterLocked, resolveField, slugFieldOf, validateDocument, valuesInLocale, writeField, } from '@elytracms/core/cms-core';
|
|
54
57
|
import { introducedErrors, warningsOf } from './proposal-validation.js';
|
|
55
58
|
import { fieldWritePatch } from '../cms/field-patch.js';
|
|
56
59
|
import { fieldConnection, hasLocaleVariant } from '../locale/index.js';
|
|
@@ -64,6 +67,9 @@ export const DEFAULT_TRANSLATE_BATCH_SIZE = 12;
|
|
|
64
67
|
export const DEFAULT_TRANSLATE_CONCURRENCY = 6;
|
|
65
68
|
/** The schema name the batch call carries — how a FakeProvider recognises a translate call. */
|
|
66
69
|
export const TRANSLATE_SCHEMA_NAME = 'translate_units';
|
|
70
|
+
function plannedAt(locale, field, key) {
|
|
71
|
+
return JSON.stringify([locale, field, key]);
|
|
72
|
+
}
|
|
67
73
|
// --- Field classification ---------------------------------------------------------
|
|
68
74
|
/**
|
|
69
75
|
* True when a field is the collection's SLUG ROLE (EC-590 — `slugField`, default a text
|
|
@@ -81,24 +87,6 @@ function slugSourceField(field, collection) {
|
|
|
81
87
|
return typeof collection.titleField === 'string' ? collection.titleField : 'title';
|
|
82
88
|
return null; // a function source — fall back to "any translation happened this run"
|
|
83
89
|
}
|
|
84
|
-
/**
|
|
85
|
-
* A position's stable key within its field value: a component prop (or anything below
|
|
86
|
-
* one) by its node id — ids survive a translation that moves an inline embed — and
|
|
87
|
-
* every other position by its location. `''` for the field itself.
|
|
88
|
-
*/
|
|
89
|
-
function positionKey(position) {
|
|
90
|
-
const { at, component } = position;
|
|
91
|
-
if (component) {
|
|
92
|
-
const below = at.slice(at.lastIndexOf('props') + 1);
|
|
93
|
-
return `${component.nodeId}/${below.join('/')}`;
|
|
94
|
-
}
|
|
95
|
-
return at.length === 0 ? '' : `/${at.join('/')}`;
|
|
96
|
-
}
|
|
97
|
-
/** `<locale>|<field>` plus the position key when it is below the field. */
|
|
98
|
-
function positionId(locale, field, position) {
|
|
99
|
-
const key = positionKey(position);
|
|
100
|
-
return key === '' ? `${locale}|${field}` : `${locale}|${field}|${key}`;
|
|
101
|
-
}
|
|
102
90
|
/** A declaration whose value is text a writer may translate (a unit, or a sequence of them). */
|
|
103
91
|
function isTranslatableDeclaration(field) {
|
|
104
92
|
return (field.type === 'text' || field.type === 'richText' || field.type === 'blocks') && !isWriterLocked(field);
|
|
@@ -213,6 +201,8 @@ async function translateBatch(provider, locale, units, knowledge) {
|
|
|
213
201
|
schemaName: TRANSLATE_SCHEMA_NAME,
|
|
214
202
|
schemaDescription: `Translate content units into ${localeName(locale)}.`,
|
|
215
203
|
});
|
|
204
|
+
// EC-774: a reply maps back through THIS batch's own ids — an id it was not sent (another
|
|
205
|
+
// batch's, a location, an invention) is ignored.
|
|
216
206
|
const out = new Map();
|
|
217
207
|
const known = new Set(units.map((u) => u.id));
|
|
218
208
|
const raw = result.object?.units;
|
|
@@ -301,16 +291,26 @@ function planRun(input) {
|
|
|
301
291
|
const bucketSkips = [];
|
|
302
292
|
const unreadable = (site) => bucketSkips.push(skipOf(field.name, locale, site, 'unreadable'));
|
|
303
293
|
// EC-593: every text position of the base value — the converter module's fold
|
|
304
|
-
// over core's ONE value-position traversal.
|
|
294
|
+
// over core's ONE value-position traversal, each with its position key (EC-774).
|
|
305
295
|
for (const position of textPositionsOf(root, positionCtx)) {
|
|
306
|
-
const { value } = position;
|
|
296
|
+
const { value, key } = position;
|
|
307
297
|
if (value === undefined || value === null)
|
|
308
298
|
continue;
|
|
309
299
|
if (position.field.type === 'text') {
|
|
310
300
|
if (typeof value !== 'string')
|
|
311
301
|
unreadable(position);
|
|
312
|
-
|
|
313
|
-
|
|
302
|
+
// EC-766: a URL is not prose. A value the engine's one URL-shape declaration reads
|
|
303
|
+
// as a URL (`isUrlLike` — the renderer's own predicate) is no unit, as an empty text
|
|
304
|
+
// is none (EC-759): the rebuild carries the base's value, and a bucket holding
|
|
305
|
+
// nothing else is not planned. `mailto:`, `tel:`, `#…` and bare domains are no URLs
|
|
306
|
+
// by that rule and are still sent.
|
|
307
|
+
else if (value.trim().length > 0 && !isUrlLike(value.trim())) {
|
|
308
|
+
// EC-774: text with no key of its own (under a node its sequence holds twice)
|
|
309
|
+
// could only be written into its twin too — refused, reported, never sent.
|
|
310
|
+
if (key === undefined)
|
|
311
|
+
unreadable(position);
|
|
312
|
+
else
|
|
313
|
+
bucketUnits.push({ kind: 'text', at: plannedAt(locale, field.name, key), locale, field: field.name, text: value });
|
|
314
314
|
}
|
|
315
315
|
continue;
|
|
316
316
|
}
|
|
@@ -321,18 +321,14 @@ function planRun(input) {
|
|
|
321
321
|
}
|
|
322
322
|
if (extracted.units.length === 0)
|
|
323
323
|
continue;
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
if (key === undefined) {
|
|
325
|
+
unreadable(position); // EC-774, as for a text position above
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const at = plannedAt(locale, field.name, key);
|
|
329
|
+
bucketPlans.set(at, extracted.plan);
|
|
326
330
|
for (const block of extracted.units) {
|
|
327
|
-
bucketUnits.push({
|
|
328
|
-
kind: 'richText',
|
|
329
|
-
id: `${planId}|${block.id}`,
|
|
330
|
-
locale,
|
|
331
|
-
field: field.name,
|
|
332
|
-
plan: planId,
|
|
333
|
-
blockId: block.id,
|
|
334
|
-
text: block.text,
|
|
335
|
-
});
|
|
331
|
+
bucketUnits.push({ kind: 'richText', locale, field: field.name, plan: at, blockId: block.id, text: block.text });
|
|
336
332
|
}
|
|
337
333
|
}
|
|
338
334
|
// EC-752 (8b): text inside a value the traversal cannot read is no unit — reported, not
|
|
@@ -349,9 +345,11 @@ function planRun(input) {
|
|
|
349
345
|
continue;
|
|
350
346
|
if (bucketUnits.length === 0)
|
|
351
347
|
continue;
|
|
352
|
-
|
|
353
|
-
for (const
|
|
354
|
-
|
|
348
|
+
// EC-774: each planned unit's wire id — the run's opaque index, in plan order.
|
|
349
|
+
for (const unit of bucketUnits)
|
|
350
|
+
units.push({ ...unit, id: `u${units.length + 1}` });
|
|
351
|
+
for (const [at, plan] of bucketPlans)
|
|
352
|
+
richPlans.set(at, plan);
|
|
355
353
|
pending.push({ field, locale, base: conn.baseValue, current, kind: conn.connected ? 'fill' : 'replace' });
|
|
356
354
|
}
|
|
357
355
|
}
|
|
@@ -437,10 +435,16 @@ export async function translateDocument(input) {
|
|
|
437
435
|
perLocale[locale] = (perLocale[locale] ?? 0) + count;
|
|
438
436
|
translated += count;
|
|
439
437
|
};
|
|
438
|
+
// The plan's units by the position they translate — the rebuild's ONE reader of what was
|
|
439
|
+
// planned (EC-759): a text unit by its position, a rich-text position's blocks by its plan.
|
|
440
|
+
// EC-774: the position is named by its key, the same one the plan walked by.
|
|
441
|
+
const textUnitAt = new Map();
|
|
440
442
|
const blocksOf = new Map();
|
|
441
443
|
for (const unit of units) {
|
|
442
|
-
if (unit.kind
|
|
444
|
+
if (unit.kind === 'text') {
|
|
445
|
+
textUnitAt.set(unit.at, unit);
|
|
443
446
|
continue;
|
|
447
|
+
}
|
|
444
448
|
const list = blocksOf.get(unit.plan) ?? new Map();
|
|
445
449
|
list.set(unit.blockId, unit);
|
|
446
450
|
blocksOf.set(unit.plan, list);
|
|
@@ -453,19 +457,30 @@ export async function translateDocument(input) {
|
|
|
453
457
|
];
|
|
454
458
|
for (const { field, locale, base: baseValue, current, kind } of pending) {
|
|
455
459
|
let applied = 0;
|
|
456
|
-
// EC-752 rule 2: whether every unit of the bucket landed — a unit never
|
|
457
|
-
// not returned, or a block whose tokens did not survive makes it
|
|
460
|
+
// EC-752 rule 2: whether every PLANNED unit of the bucket landed — a planned unit never
|
|
461
|
+
// sent (a cancel), not returned, or a block whose tokens did not survive makes it
|
|
462
|
+
// incomplete. A position the plan made no unit of is no part of it (EC-759).
|
|
458
463
|
let complete = true;
|
|
459
464
|
// EC-593: the base value with each translated text position replaced — the
|
|
460
465
|
// converter module's writing fold; every other value is carried from the base.
|
|
461
466
|
const value = rebuildTextPositions(fieldValuePosition(field, baseValue, { locale: base }), positionCtx, (position) => {
|
|
462
|
-
const
|
|
467
|
+
const at = plannedAt(locale, field.name, position.key);
|
|
463
468
|
if (position.field.type === 'text') {
|
|
464
|
-
|
|
469
|
+
// EC-759 (Codex r10 F48): the PLANNED unit at this position, read as the rich-text
|
|
470
|
+
// branch reads its plan. None — the base leaves the position empty (absent, `null`,
|
|
471
|
+
// `''`, whitespace), or its value is unreadable and reported by the plan — means
|
|
472
|
+
// the base's value is carried, as every non-text value is, and completeness is
|
|
473
|
+
// untouched. Judging "every text position" by the units whose batch ran read a
|
|
474
|
+
// position never planned as a cancel, and the whole-bucket rule then dropped a
|
|
475
|
+
// listed replacement with no skip.
|
|
476
|
+
const unit = textUnitAt.get(at);
|
|
477
|
+
if (!unit)
|
|
478
|
+
return position.value;
|
|
479
|
+
if (!attempted.has(unit.id)) {
|
|
465
480
|
complete = false;
|
|
466
481
|
return position.value;
|
|
467
482
|
}
|
|
468
|
-
const text = returned(id);
|
|
483
|
+
const text = returned(unit.id);
|
|
469
484
|
if (text === undefined) {
|
|
470
485
|
skip(field.name, locale, position, 'not-returned');
|
|
471
486
|
complete = false;
|
|
@@ -474,11 +489,11 @@ export async function translateDocument(input) {
|
|
|
474
489
|
applied += 1;
|
|
475
490
|
return text;
|
|
476
491
|
}
|
|
477
|
-
const plan = plans.get(
|
|
492
|
+
const plan = plans.get(at);
|
|
478
493
|
if (!plan)
|
|
479
494
|
return position.value;
|
|
480
495
|
const blockTranslations = new Map();
|
|
481
|
-
for (const [blockId, unit] of blocksOf.get(
|
|
496
|
+
for (const [blockId, unit] of blocksOf.get(at) ?? []) {
|
|
482
497
|
if (!attempted.has(unit.id)) {
|
|
483
498
|
complete = false;
|
|
484
499
|
continue;
|