@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { createCollectionRegistry } from '@elytracms/core/cms-core';
|
|
3
4
|
import { Check, Languages, Loader2, X } from 'lucide-react';
|
|
4
5
|
import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Row, Stack, Text, } from '@elytracms/studio/ui';
|
|
5
6
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
@@ -7,11 +8,26 @@ import { localeChipLabel } from '@elytracms/studio/lib/locale';
|
|
|
7
8
|
import { getStudioOperations } from '@elytracms/studio/lib/operations';
|
|
8
9
|
import { resolveDeploymentAi } from '@elytracms/studio/lib/workspace';
|
|
9
10
|
import { resolveAiConfig, resolveKnowledge, translateDocument } from '@elytracms/studio/lib/ai';
|
|
11
|
+
import { cmsValidationIssueMessage } from '@elytracms/studio/lib/persona/labels';
|
|
12
|
+
import { useCms } from '../cms/cms-store.js';
|
|
10
13
|
export function TranslateDocumentDialog({ projectId, collection, docRef, document, localeConfig, onApplied, open, onOpenChange, }) {
|
|
11
14
|
const [phase, setPhase] = useState('confirm');
|
|
12
15
|
const [progress, setProgress] = useState(null);
|
|
13
16
|
const [summary, setSummary] = useState(null);
|
|
17
|
+
/**
|
|
18
|
+
* EC-497 — why the refusal was blocked, when the gate refused. Reuses the
|
|
19
|
+
* EC-473 `ai.error.applyInvalid` copy ("the proposal no longer fits the current
|
|
20
|
+
* document: …"), which is exactly this situation: no new catalog string, and the
|
|
21
|
+
* editor is told WHICH rules the translation would have broken rather than a bare
|
|
22
|
+
* "failed".
|
|
23
|
+
*/
|
|
24
|
+
const [refusal, setRefusal] = useState(null);
|
|
14
25
|
const abortRef = useRef(null);
|
|
26
|
+
const cms = useCms();
|
|
27
|
+
// The session registry the produced draft is judged against. Built here rather
|
|
28
|
+
// than added to the CMS store's public surface for one consumer; the memo means
|
|
29
|
+
// once per open, not once per render.
|
|
30
|
+
const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections]);
|
|
15
31
|
useEffect(() => () => abortRef.current?.abort(), []);
|
|
16
32
|
const base = localeConfig.default;
|
|
17
33
|
const targets = localeConfig.locales.filter((l) => l !== base);
|
|
@@ -28,6 +44,7 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
28
44
|
}
|
|
29
45
|
const controller = new AbortController();
|
|
30
46
|
abortRef.current = controller;
|
|
47
|
+
setRefusal(null);
|
|
31
48
|
setPhase('running');
|
|
32
49
|
setProgress({ completed: 0, total: 0 });
|
|
33
50
|
try {
|
|
@@ -40,7 +57,25 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
40
57
|
knowledge,
|
|
41
58
|
signal: controller.signal,
|
|
42
59
|
onProgress: setProgress,
|
|
60
|
+
// EC-497 — this action used to hand `translateDocument`'s output STRAIGHT to
|
|
61
|
+
// `documents.upsert` with nothing in between: no gate, no proposal card, no
|
|
62
|
+
// re-validation, so model-generated content reached the document completely
|
|
63
|
+
// unvalidated while the chat path validated twice. It now validates with the
|
|
64
|
+
// session's canonical bag (`cms.validateOptions`) — the editor's own rules —
|
|
65
|
+
// and refuses below on what the translation INTRODUCED.
|
|
66
|
+
registry,
|
|
67
|
+
validateOptions: cms.validateOptions,
|
|
43
68
|
});
|
|
69
|
+
if (result.introducedErrors.length > 0) {
|
|
70
|
+
setRefusal(translate('ai.error.applyInvalid', {
|
|
71
|
+
issues: result.introducedErrors
|
|
72
|
+
.slice(0, 3)
|
|
73
|
+
.map((issue) => cmsValidationIssueMessage(issue))
|
|
74
|
+
.join('; '),
|
|
75
|
+
}));
|
|
76
|
+
setPhase('error');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
44
79
|
if (result.translated > 0 || result.slugsDerived > 0) {
|
|
45
80
|
const { operations } = await getStudioOperations();
|
|
46
81
|
const upserted = await operations.documents.upsert({ projectId, document: result.document });
|
|
@@ -77,7 +112,7 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
77
112
|
finally {
|
|
78
113
|
abortRef.current = null;
|
|
79
114
|
}
|
|
80
|
-
}, [projectId, collection, document, localeConfig, docRef, onApplied]);
|
|
115
|
+
}, [projectId, collection, document, localeConfig, docRef, onApplied, registry, cms.validateOptions]);
|
|
81
116
|
const close = () => {
|
|
82
117
|
if (phase === 'running')
|
|
83
118
|
return; // never close mid-run — Cancel aborts instead
|
|
@@ -92,6 +127,6 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
92
127
|
};
|
|
93
128
|
return (_jsx(Dialog, { open: open, onOpenChange: (next) => (!next ? close() : undefined), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: _jsxs(Row, { gap: "2", align: "center", children: [_jsx(Languages, { className: "size-4" }), " ", translate('ai.translate.dialogTitle')] }) }), _jsx(DialogDescription, { children: translate('ai.translate.dialogDescription', { base: localeChipLabel(base) }) })] }), phase === 'confirm' ? (_jsxs(Stack, { gap: "4", className: "py-2", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", weight: "medium", tone: "muted", children: translate('ai.translate.targetsLabel') }), _jsx(Row, { gap: "1", wrap: true, children: targets.map((l) => (_jsx(Badge, { variant: "outline", children: localeChipLabel(l) }, l))) })] }), _jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", weight: "medium", tone: "muted", children: translate('ai.translate.scopeLabel') }), _jsxs(Row, { gap: "2", align: "center", children: [_jsx(Badge, { variant: "default", children: translate('ai.translate.fillMissing') }), _jsx(Button, { size: "sm", variant: "ghost", disabled: true, title: translate('ai.translate.retranslateSoon'), children: translate('ai.translate.retranslateAll') }), _jsx(Text, { size: "xs", tone: "muted", children: translate('ai.translate.retranslateSoon') })] })] })] })) : null, phase === 'running' ? (_jsxs(Stack, { gap: "3", align: "center", className: "py-6", children: [_jsx(Loader2, { className: "size-6 animate-spin text-primary" }), _jsx(Text, { size: "sm", tone: "muted", "data-testid": "translate-progress", children: progress && progress.total > 0
|
|
94
129
|
? translate('ai.translate.progress', { completed: progress.completed, total: progress.total })
|
|
95
|
-
: translate('ai.translate.preparing') }), progress && progress.total > 0 ? (_jsx("div", { className: "h-1.5 w-56 overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all", style: { width: `${Math.round((progress.completed / progress.total) * 100)}%` } }) })) : null] })) : null, phase === 'done' && summary ? (_jsxs(Stack, { gap: "2", align: "center", className: "py-6", children: [_jsx(Check, { className: "size-6 text-success" }), _jsx(Text, { size: "sm", tone: "muted", className: "text-center", "data-testid": "translate-summary", children: doneMessage(summary) })] })) : null, phase === 'error' ? (_jsxs(Stack, { gap: "2", align: "center", className: "py-6", children: [_jsx(X, { className: "size-6 text-destructive" }), _jsx(Text, { size: "sm", tone: "muted", children: translate('ai.translate.failed') })] })) : null, _jsx(DialogFooter, { className: "mt-2", children: phase === 'confirm' ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: close, children: translate('common.cancel') }), _jsxs(Button, { size: "sm", onClick: () => void run(), "data-testid": "translate-confirm", children: [_jsx(Languages, {}), " ", translate('ai.translate.confirm')] })] })) : phase === 'running' ? (_jsx(Button, { variant: "outline", size: "sm", onClick: () => abortRef.current?.abort(), "data-testid": "translate-cancel", children: translate('common.cancel') })) : (_jsx(Button, { size: "sm", onClick: close, "data-testid": "translate-close", children: translate('ai.translate.close') })) })] }) }));
|
|
130
|
+
: translate('ai.translate.preparing') }), progress && progress.total > 0 ? (_jsx("div", { className: "h-1.5 w-56 overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all", style: { width: `${Math.round((progress.completed / progress.total) * 100)}%` } }) })) : null] })) : null, phase === 'done' && summary ? (_jsxs(Stack, { gap: "2", align: "center", className: "py-6", children: [_jsx(Check, { className: "size-6 text-success" }), _jsx(Text, { size: "sm", tone: "muted", className: "text-center", "data-testid": "translate-summary", children: doneMessage(summary) })] })) : null, phase === 'error' ? (_jsxs(Stack, { gap: "2", align: "center", className: "py-6", children: [_jsx(X, { className: "size-6 text-destructive" }), _jsx(Text, { size: "sm", tone: "muted", className: "text-center", "data-testid": "translate-error", children: refusal ?? translate('ai.translate.failed') })] })) : null, _jsx(DialogFooter, { className: "mt-2", children: phase === 'confirm' ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: close, children: translate('common.cancel') }), _jsxs(Button, { size: "sm", onClick: () => void run(), "data-testid": "translate-confirm", children: [_jsx(Languages, {}), " ", translate('ai.translate.confirm')] })] })) : phase === 'running' ? (_jsx(Button, { variant: "outline", size: "sm", onClick: () => abortRef.current?.abort(), "data-testid": "translate-cancel", children: translate('common.cancel') })) : (_jsx(Button, { size: "sm", onClick: close, "data-testid": "translate-close", children: translate('ai.translate.close') })) })] }) }));
|
|
96
131
|
}
|
|
97
132
|
//# sourceMappingURL=translate-document-dialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate-document-dialog.js","sourceRoot":"","sources":["../../../src/components/ai/translate-document-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { Check, Languages, Loader2, X } from 'lucide-react'\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Row,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { localeChipLabel } from '@elytracms/studio/lib/locale'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { resolveAiConfig, resolveKnowledge, translateDocument } from '@elytracms/studio/lib/ai'\nimport type { TranslateProgress } from '@elytracms/studio/lib/ai'\nimport type { TranslateDocumentActionProps } from './translate-document-action'\n\n/**\n * The \"Translate document\" confirm + progress dialog (EC-332), heavy side — loaded lazily\n * by the gate so the AI engine stays off the critical path (EC-331 bundle discipline). A\n * modal is allowed here (a picker/confirmation, per the house rule — property forms are\n * not). It resolves the EC-331 provider + knowledge, runs the ONE `translateDocument`\n * engine over the live draft (fill-empty-only on EC-318 connection state), streams\n * progress, then writes the result to the DRAFT via the operations layer with a\n * provenance-tagged snapshot (mirroring the EC-199 apply) and reflects it into the open\n * editor. Cancel aborts cleanly — any already-translated units stay on the draft, visible\n * in Compare (EC-317) and rejectable via Discard (EC-316).\n */\ntype Phase = 'confirm' | 'running' | 'done' | 'error'\n\ninterface RunSummary {\n translated: number\n slugs: number\n locales: number\n cancelled: boolean\n nothing: boolean\n}\n\nexport function TranslateDocumentDialog({\n projectId,\n collection,\n docRef,\n document,\n localeConfig,\n onApplied,\n open,\n onOpenChange,\n}: TranslateDocumentActionProps & { open: boolean; onOpenChange: (open: boolean) => void }) {\n const [phase, setPhase] = useState<Phase>('confirm')\n const [progress, setProgress] = useState<TranslateProgress | null>(null)\n const [summary, setSummary] = useState<RunSummary | null>(null)\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => abortRef.current?.abort(), [])\n\n const base = localeConfig.default\n const targets = localeConfig.locales.filter((l) => l !== base)\n\n const run = useCallback(async () => {\n if (projectId === undefined) {\n setPhase('error')\n return\n }\n const resolved = resolveAiConfig(resolveDeploymentAi())\n const provider = resolved.resolved?.provider\n if (!provider || !resolved.ok) {\n setPhase('error')\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n setPhase('running')\n setProgress({ completed: 0, total: 0 })\n try {\n const knowledge = await resolveKnowledge(resolved.resolved?.knowledge ?? [])\n const result = await translateDocument({\n provider,\n collection,\n document,\n localeConfig,\n knowledge,\n signal: controller.signal,\n onProgress: setProgress,\n })\n if (result.translated > 0 || result.slugsDerived > 0) {\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: result.document })\n if (!upserted.ok) {\n setPhase('error')\n return\n }\n // Provenance: an AI-tagged version snapshot (best-effort — the draft already landed).\n try {\n await operations.documents.snapshot({\n projectId,\n ref: docRef,\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n onApplied(result.document)\n }\n setSummary({\n translated: result.translated,\n slugs: result.slugsDerived,\n locales: Object.keys(result.perLocale).length,\n cancelled: result.cancelled,\n nothing: result.total === 0,\n })\n setPhase('done')\n } catch {\n setPhase('error')\n } finally {\n abortRef.current = null\n }\n }, [projectId, collection, document, localeConfig, docRef, onApplied])\n\n const close = () => {\n if (phase === 'running') return // never close mid-run — Cancel aborts instead\n onOpenChange(false)\n }\n\n const doneMessage = (s: RunSummary): string => {\n if (s.nothing) return translate('ai.translate.nothing')\n if (s.cancelled) return translate('ai.translate.cancelled')\n return translate('ai.translate.applied', { count: s.translated, locales: s.locales })\n }\n\n return (\n <Dialog open={open} onOpenChange={(next) => (!next ? close() : undefined)}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n <Row gap=\"2\" align=\"center\">\n <Languages className=\"size-4\" /> {translate('ai.translate.dialogTitle')}\n </Row>\n </DialogTitle>\n <DialogDescription>\n {translate('ai.translate.dialogDescription', { base: localeChipLabel(base) })}\n </DialogDescription>\n </DialogHeader>\n\n {phase === 'confirm' ? (\n <Stack gap=\"4\" className=\"py-2\">\n <Stack gap=\"1\">\n <Text size=\"xs\" weight=\"medium\" tone=\"muted\">\n {translate('ai.translate.targetsLabel')}\n </Text>\n <Row gap=\"1\" wrap>\n {targets.map((l) => (\n <Badge key={l} variant=\"outline\">\n {localeChipLabel(l)}\n </Badge>\n ))}\n </Row>\n </Stack>\n <Stack gap=\"1\">\n <Text size=\"xs\" weight=\"medium\" tone=\"muted\">\n {translate('ai.translate.scopeLabel')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Badge variant=\"default\">{translate('ai.translate.fillMissing')}</Badge>\n <Button size=\"sm\" variant=\"ghost\" disabled title={translate('ai.translate.retranslateSoon')}>\n {translate('ai.translate.retranslateAll')}\n </Button>\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.translate.retranslateSoon')}\n </Text>\n </Row>\n </Stack>\n </Stack>\n ) : null}\n\n {phase === 'running' ? (\n <Stack gap=\"3\" align=\"center\" className=\"py-6\">\n <Loader2 className=\"size-6 animate-spin text-primary\" />\n <Text size=\"sm\" tone=\"muted\" data-testid=\"translate-progress\">\n {progress && progress.total > 0\n ? translate('ai.translate.progress', { completed: progress.completed, total: progress.total })\n : translate('ai.translate.preparing')}\n </Text>\n {progress && progress.total > 0 ? (\n <div className=\"h-1.5 w-56 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-primary transition-all\"\n style={{ width: `${Math.round((progress.completed / progress.total) * 100)}%` }}\n />\n </div>\n ) : null}\n </Stack>\n ) : null}\n\n {phase === 'done' && summary ? (\n <Stack gap=\"2\" align=\"center\" className=\"py-6\">\n <Check className=\"size-6 text-success\" />\n <Text size=\"sm\" tone=\"muted\" className=\"text-center\" data-testid=\"translate-summary\">\n {doneMessage(summary)}\n </Text>\n </Stack>\n ) : null}\n\n {phase === 'error' ? (\n <Stack gap=\"2\" align=\"center\" className=\"py-6\">\n <X className=\"size-6 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('ai.translate.failed')}\n </Text>\n </Stack>\n ) : null}\n\n <DialogFooter className=\"mt-2\">\n {phase === 'confirm' ? (\n <>\n <Button variant=\"ghost\" size=\"sm\" onClick={close}>\n {translate('common.cancel')}\n </Button>\n <Button size=\"sm\" onClick={() => void run()} data-testid=\"translate-confirm\">\n <Languages /> {translate('ai.translate.confirm')}\n </Button>\n </>\n ) : phase === 'running' ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => abortRef.current?.abort()} data-testid=\"translate-cancel\">\n {translate('common.cancel')}\n </Button>\n ) : (\n <Button size=\"sm\" onClick={close} data-testid=\"translate-close\">\n {translate('ai.translate.close')}\n </Button>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"translate-document-dialog.js","sourceRoot":"","sources":["../../../src/components/ai/translate-document-dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACnE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EACL,KAAK,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,GAAG,EACH,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE/F,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAwBzC,MAAM,UAAU,uBAAuB,CAAC,EACtC,SAAS,EACT,UAAU,EACV,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,YAAY,GAC4E;IACxF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAQ,SAAS,CAAC,CAAA;IACpD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAA2B,IAAI,CAAC,CAAA;IACxE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAA;IAC/D;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC3D,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAA;IACrD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,+EAA+E;IAC/E,gFAAgF;IAChF,sCAAsC;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAA;IAE5F,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IAEpD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAA;IACjC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;IAE9D,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACjC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,eAAe,CAAC,mBAAmB,EAAE,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;QAC5C,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAC9B,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAA;QAC7B,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,SAAS,CAAC,CAAA;QACnB,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QACvC,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC,CAAA;YAC5E,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;gBACrC,QAAQ;gBACR,UAAU;gBACV,QAAQ;gBACR,YAAY;gBACZ,SAAS;gBACT,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,UAAU,EAAE,WAAW;gBACvB,6EAA6E;gBAC7E,4EAA4E;gBAC5E,4EAA4E;gBAC5E,6EAA6E;gBAC7E,6EAA6E;gBAC7E,wDAAwD;gBACxD,QAAQ;gBACR,eAAe,EAAE,GAAG,CAAC,eAAe;aACrC,CAAC,CAAA;YACF,IAAI,MAAM,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvC,UAAU,CACR,SAAS,CAAC,uBAAuB,EAAE;oBACjC,MAAM,EAAE,MAAM,CAAC,gBAAgB;yBAC5B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;yBACX,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;yBAChD,IAAI,CAAC,IAAI,CAAC;iBACd,CAAC,CACH,CAAA;gBACD,QAAQ,CAAC,OAAO,CAAC,CAAA;gBACjB,OAAM;YACR,CAAC;YACD,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,IAAI,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAA;gBAClD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;gBAC5F,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,QAAQ,CAAC,OAAO,CAAC,CAAA;oBACjB,OAAM;gBACR,CAAC;gBACD,sFAAsF;gBACtF,IAAI,CAAC;oBACH,MAAM,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC;wBAClC,SAAS;wBACT,GAAG,EAAE,MAAM;wBACX,KAAK,EAAE,SAAS,CAAC,6BAA6B,CAAC;wBAC/C,MAAM,EAAE,WAAW;qBACpB,CAAC,CAAA;gBACJ,CAAC;gBAAC,MAAM,CAAC;oBACP,kCAAkC;gBACpC,CAAC;gBACD,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAC5B,CAAC;YACD,UAAU,CAAC;gBACT,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,KAAK,EAAE,MAAM,CAAC,YAAY;gBAC1B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM;gBAC7C,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,OAAO,EAAE,MAAM,CAAC,KAAK,KAAK,CAAC;aAC5B,CAAC,CAAA;YACF,QAAQ,CAAC,MAAM,CAAC,CAAA;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,OAAO,CAAC,CAAA;QACnB,CAAC;gBAAS,CAAC;YACT,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAA;QACzB,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAA;IAErG,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,KAAK,KAAK,SAAS;YAAE,OAAM,CAAC,8CAA8C;QAC9E,YAAY,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,CAAa,EAAU,EAAE;QAC5C,IAAI,CAAC,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC,sBAAsB,CAAC,CAAA;QACvD,IAAI,CAAC,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC,wBAAwB,CAAC,CAAA;QAC3D,OAAO,SAAS,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IACvF,CAAC,CAAA;IAED,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,YACvE,MAAC,aAAa,eACZ,MAAC,YAAY,eACX,KAAC,WAAW,cACV,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,SAAS,CAAC,0BAA0B,CAAC,IACnE,GACM,EACd,KAAC,iBAAiB,cACf,SAAS,CAAC,gCAAgC,EAAE,EAAE,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,GAC3D,IACP,EAEd,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,MAAM,aAC7B,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAC,OAAO,YACzC,SAAS,CAAC,2BAA2B,CAAC,GAClC,EACP,KAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,kBACd,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClB,KAAC,KAAK,IAAS,OAAO,EAAC,SAAS,YAC7B,eAAe,CAAC,CAAC,CAAC,IADT,CAAC,CAEL,CACT,CAAC,GACE,IACA,EACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAC,OAAO,YACzC,SAAS,CAAC,yBAAyB,CAAC,GAChC,EACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,SAAS,CAAC,0BAA0B,CAAC,GAAS,EACxE,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,QAAC,KAAK,EAAE,SAAS,CAAC,8BAA8B,CAAC,YACxF,SAAS,CAAC,6BAA6B,CAAC,GAClC,EACT,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,8BAA8B,CAAC,GACrC,IACH,IACA,IACF,CACT,CAAC,CAAC,CAAC,IAAI,EAEP,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,MAAM,aAC5C,KAAC,OAAO,IAAC,SAAS,EAAC,kCAAkC,GAAG,EACxD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,iBAAa,oBAAoB,YAC1D,QAAQ,IAAI,QAAQ,CAAC,KAAK,GAAG,CAAC;gCAC7B,CAAC,CAAC,SAAS,CAAC,uBAAuB,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;gCAC9F,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,GAClC,EACN,QAAQ,IAAI,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAChC,cAAK,SAAS,EAAC,kDAAkD,YAC/D,cACE,SAAS,EAAC,+CAA+C,EACzD,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,GAC/E,GACE,CACP,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAC,CAAC,CAAC,IAAI,EAEP,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAC7B,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,MAAM,aAC5C,KAAC,KAAK,IAAC,SAAS,EAAC,qBAAqB,GAAG,EACzC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,aAAa,iBAAa,mBAAmB,YACjF,WAAW,CAAC,OAAO,CAAC,GAChB,IACD,CACT,CAAC,CAAC,CAAC,IAAI,EAEP,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,CACnB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,MAAM,aAC5C,KAAC,CAAC,IAAC,SAAS,EAAC,yBAAyB,GAAG,EACzC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,aAAa,iBAAa,iBAAiB,YAC/E,OAAO,IAAI,SAAS,CAAC,qBAAqB,CAAC,GACvC,IACD,CACT,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,YAAY,IAAC,SAAS,EAAC,MAAM,YAC3B,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,8BACE,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,YAC7C,SAAS,CAAC,eAAe,CAAC,GACpB,EACT,MAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,iBAAc,mBAAmB,aAC1E,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,sBAAsB,CAAC,IACzC,IACR,CACJ,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACxB,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,iBAAc,kBAAkB,YACzG,SAAS,CAAC,eAAe,CAAC,GACpB,CACV,CAAC,CAAC,CAAC,CACF,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,iBAAc,iBAAiB,YAC5D,SAAS,CAAC,oBAAoB,CAAC,GACzB,CACV,GACY,IACD,GACT,CACV,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createCollectionRegistry } from '@elytracms/core/cms-core'\nimport { Check, Languages, Loader2, X } from 'lucide-react'\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Row,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { localeChipLabel } from '@elytracms/studio/lib/locale'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { resolveAiConfig, resolveKnowledge, translateDocument } from '@elytracms/studio/lib/ai'\nimport type { TranslateProgress } from '@elytracms/studio/lib/ai'\nimport { cmsValidationIssueMessage } from '@elytracms/studio/lib/persona/labels'\nimport { useCms } from '../cms/cms-store'\nimport type { TranslateDocumentActionProps } from './translate-document-action'\n\n/**\n * The \"Translate document\" confirm + progress dialog (EC-332), heavy side — loaded lazily\n * by the gate so the AI engine stays off the critical path (EC-331 bundle discipline). A\n * modal is allowed here (a picker/confirmation, per the house rule — property forms are\n * not). It resolves the EC-331 provider + knowledge, runs the ONE `translateDocument`\n * engine over the live draft (fill-empty-only on EC-318 connection state), streams\n * progress, then writes the result to the DRAFT via the operations layer with a\n * provenance-tagged snapshot (mirroring the EC-199 apply) and reflects it into the open\n * editor. Cancel aborts cleanly — any already-translated units stay on the draft, visible\n * in Compare (EC-317) and rejectable via Discard (EC-316).\n */\ntype Phase = 'confirm' | 'running' | 'done' | 'error'\n\ninterface RunSummary {\n translated: number\n slugs: number\n locales: number\n cancelled: boolean\n nothing: boolean\n}\n\nexport function TranslateDocumentDialog({\n projectId,\n collection,\n docRef,\n document,\n localeConfig,\n onApplied,\n open,\n onOpenChange,\n}: TranslateDocumentActionProps & { open: boolean; onOpenChange: (open: boolean) => void }) {\n const [phase, setPhase] = useState<Phase>('confirm')\n const [progress, setProgress] = useState<TranslateProgress | null>(null)\n const [summary, setSummary] = useState<RunSummary | null>(null)\n /**\n * EC-497 — why the refusal was blocked, when the gate refused. Reuses the\n * EC-473 `ai.error.applyInvalid` copy (\"the proposal no longer fits the current\n * document: …\"), which is exactly this situation: no new catalog string, and the\n * editor is told WHICH rules the translation would have broken rather than a bare\n * \"failed\".\n */\n const [refusal, setRefusal] = useState<string | null>(null)\n const abortRef = useRef<AbortController | null>(null)\n const cms = useCms()\n // The session registry the produced draft is judged against. Built here rather\n // than added to the CMS store's public surface for one consumer; the memo means\n // once per open, not once per render.\n const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections])\n\n useEffect(() => () => abortRef.current?.abort(), [])\n\n const base = localeConfig.default\n const targets = localeConfig.locales.filter((l) => l !== base)\n\n const run = useCallback(async () => {\n if (projectId === undefined) {\n setPhase('error')\n return\n }\n const resolved = resolveAiConfig(resolveDeploymentAi())\n const provider = resolved.resolved?.provider\n if (!provider || !resolved.ok) {\n setPhase('error')\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n setRefusal(null)\n setPhase('running')\n setProgress({ completed: 0, total: 0 })\n try {\n const knowledge = await resolveKnowledge(resolved.resolved?.knowledge ?? [])\n const result = await translateDocument({\n provider,\n collection,\n document,\n localeConfig,\n knowledge,\n signal: controller.signal,\n onProgress: setProgress,\n // EC-497 — this action used to hand `translateDocument`'s output STRAIGHT to\n // `documents.upsert` with nothing in between: no gate, no proposal card, no\n // re-validation, so model-generated content reached the document completely\n // unvalidated while the chat path validated twice. It now validates with the\n // session's canonical bag (`cms.validateOptions`) — the editor's own rules —\n // and refuses below on what the translation INTRODUCED.\n registry,\n validateOptions: cms.validateOptions,\n })\n if (result.introducedErrors.length > 0) {\n setRefusal(\n translate('ai.error.applyInvalid', {\n issues: result.introducedErrors\n .slice(0, 3)\n .map((issue) => cmsValidationIssueMessage(issue))\n .join('; '),\n }),\n )\n setPhase('error')\n return\n }\n if (result.translated > 0 || result.slugsDerived > 0) {\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: result.document })\n if (!upserted.ok) {\n setPhase('error')\n return\n }\n // Provenance: an AI-tagged version snapshot (best-effort — the draft already landed).\n try {\n await operations.documents.snapshot({\n projectId,\n ref: docRef,\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n onApplied(result.document)\n }\n setSummary({\n translated: result.translated,\n slugs: result.slugsDerived,\n locales: Object.keys(result.perLocale).length,\n cancelled: result.cancelled,\n nothing: result.total === 0,\n })\n setPhase('done')\n } catch {\n setPhase('error')\n } finally {\n abortRef.current = null\n }\n }, [projectId, collection, document, localeConfig, docRef, onApplied, registry, cms.validateOptions])\n\n const close = () => {\n if (phase === 'running') return // never close mid-run — Cancel aborts instead\n onOpenChange(false)\n }\n\n const doneMessage = (s: RunSummary): string => {\n if (s.nothing) return translate('ai.translate.nothing')\n if (s.cancelled) return translate('ai.translate.cancelled')\n return translate('ai.translate.applied', { count: s.translated, locales: s.locales })\n }\n\n return (\n <Dialog open={open} onOpenChange={(next) => (!next ? close() : undefined)}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n <Row gap=\"2\" align=\"center\">\n <Languages className=\"size-4\" /> {translate('ai.translate.dialogTitle')}\n </Row>\n </DialogTitle>\n <DialogDescription>\n {translate('ai.translate.dialogDescription', { base: localeChipLabel(base) })}\n </DialogDescription>\n </DialogHeader>\n\n {phase === 'confirm' ? (\n <Stack gap=\"4\" className=\"py-2\">\n <Stack gap=\"1\">\n <Text size=\"xs\" weight=\"medium\" tone=\"muted\">\n {translate('ai.translate.targetsLabel')}\n </Text>\n <Row gap=\"1\" wrap>\n {targets.map((l) => (\n <Badge key={l} variant=\"outline\">\n {localeChipLabel(l)}\n </Badge>\n ))}\n </Row>\n </Stack>\n <Stack gap=\"1\">\n <Text size=\"xs\" weight=\"medium\" tone=\"muted\">\n {translate('ai.translate.scopeLabel')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Badge variant=\"default\">{translate('ai.translate.fillMissing')}</Badge>\n <Button size=\"sm\" variant=\"ghost\" disabled title={translate('ai.translate.retranslateSoon')}>\n {translate('ai.translate.retranslateAll')}\n </Button>\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.translate.retranslateSoon')}\n </Text>\n </Row>\n </Stack>\n </Stack>\n ) : null}\n\n {phase === 'running' ? (\n <Stack gap=\"3\" align=\"center\" className=\"py-6\">\n <Loader2 className=\"size-6 animate-spin text-primary\" />\n <Text size=\"sm\" tone=\"muted\" data-testid=\"translate-progress\">\n {progress && progress.total > 0\n ? translate('ai.translate.progress', { completed: progress.completed, total: progress.total })\n : translate('ai.translate.preparing')}\n </Text>\n {progress && progress.total > 0 ? (\n <div className=\"h-1.5 w-56 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-primary transition-all\"\n style={{ width: `${Math.round((progress.completed / progress.total) * 100)}%` }}\n />\n </div>\n ) : null}\n </Stack>\n ) : null}\n\n {phase === 'done' && summary ? (\n <Stack gap=\"2\" align=\"center\" className=\"py-6\">\n <Check className=\"size-6 text-success\" />\n <Text size=\"sm\" tone=\"muted\" className=\"text-center\" data-testid=\"translate-summary\">\n {doneMessage(summary)}\n </Text>\n </Stack>\n ) : null}\n\n {phase === 'error' ? (\n <Stack gap=\"2\" align=\"center\" className=\"py-6\">\n <X className=\"size-6 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\" className=\"text-center\" data-testid=\"translate-error\">\n {refusal ?? translate('ai.translate.failed')}\n </Text>\n </Stack>\n ) : null}\n\n <DialogFooter className=\"mt-2\">\n {phase === 'confirm' ? (\n <>\n <Button variant=\"ghost\" size=\"sm\" onClick={close}>\n {translate('common.cancel')}\n </Button>\n <Button size=\"sm\" onClick={() => void run()} data-testid=\"translate-confirm\">\n <Languages /> {translate('ai.translate.confirm')}\n </Button>\n </>\n ) : phase === 'running' ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => abortRef.current?.abort()} data-testid=\"translate-cancel\">\n {translate('common.cancel')}\n </Button>\n ) : (\n <Button size=\"sm\" onClick={close} data-testid=\"translate-close\">\n {translate('ai.translate.close')}\n </Button>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { LucideIcon } from 'lucide-react';
|
|
3
3
|
import type { CmsDocument, CollectionDef, CollectionFieldOverride, CollectionFormMeta, LocaleConfig } from '@elytracms/core/cms-core';
|
|
4
|
-
import type { Project, SectionId } from '@elytracms/core/studio-core';
|
|
4
|
+
import type { CustomizationIssue, Project, SectionId } from '@elytracms/core/studio-core';
|
|
5
5
|
import type { InstantiatedKit } from '@elytracms/core/starter-kits';
|
|
6
6
|
import type { HydratedCms } from '@elytracms/studio/lib/operations';
|
|
7
7
|
import { type AppViewContext, type AppViewDeepLink, type AppViewModule, type NavPermission, type SidebarViewNode } from './module.js';
|
|
@@ -28,6 +28,11 @@ export interface CmsViewConfig {
|
|
|
28
28
|
* Scope the rail auto-expansion (EC-293) to these collection ids, in order. The
|
|
29
29
|
* parent expands into one focused-lens child per id; the parent index shows the
|
|
30
30
|
* same subset. Omitted → every collection. Ignored for a single-collection lens.
|
|
31
|
+
*
|
|
32
|
+
* A ONE-ITEM array is legal and degrades to a working (if redundant) parent+child
|
|
33
|
+
* pair — the parent takes the `cms-group:<id>` id so it cannot collide with its own
|
|
34
|
+
* child (EC-468). For a single collection with no group heading, prefer the
|
|
35
|
+
* {@link CmsViewConfig.collection} leaf form.
|
|
31
36
|
*/
|
|
32
37
|
collections?: readonly string[];
|
|
33
38
|
/** Single-collection lens: a LEAF rail item that opens straight into this collection. */
|
|
@@ -103,12 +108,46 @@ export interface CustomViewConfig {
|
|
|
103
108
|
/** OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18). See {@link BuiltinViewConfig.visible}. */
|
|
104
109
|
visible?: NavPermission;
|
|
105
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Derive a stable, deterministic id from the CMS focus (overridable via `config.id`).
|
|
113
|
+
*
|
|
114
|
+
* The id is the route segment, the rail's active-state key AND the override key
|
|
115
|
+
* (EC-311), so it must be injective: two focuses deriving one id means
|
|
116
|
+
* `resolveOverrides` drops the later view. The derived shapes:
|
|
117
|
+
*
|
|
118
|
+
* | focus | id |
|
|
119
|
+
* | ---------------------------- | ------------------------- |
|
|
120
|
+
* | none | `cms` |
|
|
121
|
+
* | `collection: 'page'` (leaf) | `cms:page` |
|
|
122
|
+
* | `collections: ['a','b']` | `cms:a-b` |
|
|
123
|
+
* | `collections: ['page']` | `cms-group:page` |
|
|
124
|
+
*
|
|
125
|
+
* EC-468 — why the one-item group is its own namespace: `cms:${join(L)}` of a
|
|
126
|
+
* single-element list IS that element, so the auto-generated PARENT took the very id
|
|
127
|
+
* its own auto-expanded child derives (`cmsCollectionLeaf` → `cmsView({ collection })`).
|
|
128
|
+
* Both landed in the one flat override list `resolveSidebar` feeds `resolveOverrides`,
|
|
129
|
+
* so the child was dropped with a `duplicate-override-id` error and the parent rendered
|
|
130
|
+
* childless — a forced collision no config could avoid. Nothing else changes: the leaf
|
|
131
|
+
* and the multi-collection join keep their exact ids (consumer rails route on them).
|
|
132
|
+
*
|
|
133
|
+
* EC-477 — `-` is a SEPARATOR here, and a collection id may legally contain one, so a
|
|
134
|
+
* collection literally named `a-b` derives the same `cms:a-b` as a `['a','b']` group.
|
|
135
|
+
* That ambiguity is NOT closed by constraining collection ids (a permanent, repo-wide
|
|
136
|
+
* naming ban to fix a two-config coincidence, when hyphenated ids are ordinary — see
|
|
137
|
+
* the task's Done); it is DETECTED where both facts are known, by
|
|
138
|
+
* {@link checkSidebarIdKeyspace}, which raises a `derived-id-collision` the moment a
|
|
139
|
+
* group's join equals a real collection id. The escape hatch is unchanged: an explicit
|
|
140
|
+
* `nav.cms({ id })` always wins, and the derivation below is untouched — consumer rails
|
|
141
|
+
* route on these ids.
|
|
142
|
+
*/
|
|
143
|
+
export declare function cmsViewId(config: CmsViewConfig): string;
|
|
106
144
|
/**
|
|
107
145
|
* Build a CMS view — the default `cms` view, or a focused instance. Every instance
|
|
108
146
|
* shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index
|
|
109
147
|
* to a subset, `collection` is a single-collection lens that opens straight into
|
|
110
148
|
* that collection's list. The id is derived from the focus (deterministic, stable
|
|
111
|
-
* across rail reorders) unless `id` is given —
|
|
149
|
+
* across rail reorders) unless `id` is given — see {@link cmsViewId} for the derived
|
|
150
|
+
* shapes and the one keyspace ambiguity that survives.
|
|
112
151
|
*/
|
|
113
152
|
export declare function cmsView(config?: CmsViewConfig): AppViewModule;
|
|
114
153
|
/** The components registry browser view. */
|
|
@@ -155,7 +194,15 @@ export declare function extensionsView(config?: BuiltinViewConfig): AppViewModul
|
|
|
155
194
|
export declare function settingsView(config?: BuiltinViewConfig): AppViewModule;
|
|
156
195
|
/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */
|
|
157
196
|
export declare function builderView(): AppViewModule;
|
|
158
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* Build a fully custom view (the `nav.view(...)` escape hatch).
|
|
199
|
+
*
|
|
200
|
+
* With no `id` the id is a SLUG of the label, and that slug carries a
|
|
201
|
+
* {@link DerivedViewId} marker (EC-476): a label is a display string a developer picks
|
|
202
|
+
* freely, so `label: 'CMS'` deriving `cms` is a coincidence, not a request to replace
|
|
203
|
+
* the built-in CMS surface. {@link checkSidebarIdKeyspace} refuses the takeover and
|
|
204
|
+
* says so; an explicit `id` carries no marker and keeps the documented replacement.
|
|
205
|
+
*/
|
|
159
206
|
export declare function customView(config: CustomViewConfig): AppViewModule;
|
|
160
207
|
/**
|
|
161
208
|
* The studio's built-in views in canonical rail order (components · CMS · routes ·
|
|
@@ -165,11 +212,57 @@ export declare function customView(config: CustomViewConfig): AppViewModule;
|
|
|
165
212
|
* when a consumer's rail omits one.
|
|
166
213
|
*/
|
|
167
214
|
export declare const DEFAULT_VIEWS: readonly AppViewModule[];
|
|
215
|
+
/**
|
|
216
|
+
* Every id the engine already owns — the replacement targets of the sidebar seam,
|
|
217
|
+
* INCLUDING the hidden `builder` redirect (it is in the keyspace even though the stock
|
|
218
|
+
* rail does not list it, so a derived id can swallow it just the same).
|
|
219
|
+
*/
|
|
220
|
+
export declare const BUILTIN_VIEW_IDS: ReadonlySet<string>;
|
|
221
|
+
/** The outcome of the keyspace check: the entries that may still act as overrides, plus the findings. */
|
|
222
|
+
export interface SidebarIdKeyspace {
|
|
223
|
+
/**
|
|
224
|
+
* The views to resolve as overrides — an ACCIDENTAL built-in takeover removed, so the
|
|
225
|
+
* engine keeps its own block (the EC-236 contract for a rejected entry). Everything
|
|
226
|
+
* else passes through untouched, including the merely-ambiguous EC-477 case.
|
|
227
|
+
*/
|
|
228
|
+
views: AppViewModule[];
|
|
229
|
+
/** What was found — visible at the rail foot, never silent. */
|
|
230
|
+
issues: CustomizationIssue[];
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* EC-476/477 — guard the sidebar id keyspace against ids that were DERIVED rather than
|
|
234
|
+
* declared, and landed somewhere that already means something.
|
|
235
|
+
*
|
|
236
|
+
* The seam's replacement rule is id equality (`resolveOverrides`: an entry whose id is a
|
|
237
|
+
* default's id replaces it). That is exactly right for an id the developer TYPED, and
|
|
238
|
+
* exactly wrong for one a builder computed — and the generic core cannot tell them
|
|
239
|
+
* apart, because by the time an entry reaches it, an id is just a string. So the rule
|
|
240
|
+
* lives HERE, next to the derivations that produce it ({@link slugify},
|
|
241
|
+
* {@link cmsViewId}) and next to {@link DEFAULT_VIEWS}, the set it has to protect.
|
|
242
|
+
*
|
|
243
|
+
* Two derived shapes, two different remedies — because the cost of being wrong differs:
|
|
244
|
+
*
|
|
245
|
+
* - **A slugged LABEL landing on a built-in id (EC-476) is REJECTED** (`error`). Losing
|
|
246
|
+
* a whole built-in surface — the CMS, Assets, the users roster — to a coincidence of
|
|
247
|
+
* spelling is far worse than one custom view not appearing, and the developer's fix is
|
|
248
|
+
* a one-line explicit `id`. The built-in is kept, so nothing disappears.
|
|
249
|
+
* - **A collection JOIN equal to a real collection id (EC-477) is KEPT** (`warning`).
|
|
250
|
+
* Nothing here is a takeover: the group's id is legitimate, it is merely no longer
|
|
251
|
+
* unique to it, so dropping the view would destroy a rail item the developer
|
|
252
|
+
* explicitly declared in order to report a naming clash. Reporting is the whole fix.
|
|
253
|
+
*
|
|
254
|
+
* Pure and deterministic — the house "extract the core, test the core" shape.
|
|
255
|
+
* `builtinIds` and `collections` are parameters so the rule is testable against a
|
|
256
|
+
* synthetic keyspace, not just the live one.
|
|
257
|
+
*/
|
|
258
|
+
export declare function checkSidebarIdKeyspace(views: readonly AppViewModule[], collections?: readonly CollectionDef[], builtinIds?: ReadonlySet<string>): SidebarIdKeyspace;
|
|
168
259
|
export declare const nav: {
|
|
169
260
|
/**
|
|
170
261
|
* The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its
|
|
171
262
|
* collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`
|
|
172
263
|
* is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.
|
|
264
|
+
* A one-item `collections` group is a parent whose id is `cms-group:<id>`, distinct
|
|
265
|
+
* from its single child by construction (EC-468 — see {@link cmsViewId}).
|
|
173
266
|
*/
|
|
174
267
|
readonly cms: (config?: CmsViewConfig) => SidebarViewNode;
|
|
175
268
|
/** The component registry browser. */
|
|
@@ -193,46 +286,44 @@ export declare const nav: {
|
|
|
193
286
|
readonly separator: () => import("./module.js").SidebarSeparatorNode;
|
|
194
287
|
};
|
|
195
288
|
/**
|
|
196
|
-
* The CMS schema source for a project mount
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
*
|
|
289
|
+
* The CMS schema source for a project mount: a hydrated session wins (Convex),
|
|
290
|
+
* then a kit's imported schema (EC-119), then the active project's
|
|
291
|
+
* config-declared collections (AD-11, `resolveDeploymentCollections`).
|
|
292
|
+
*
|
|
293
|
+
* EC-479 — every rung is the PROJECT's own schema and there is no rung after
|
|
294
|
+
* them. The config rung used to be gated on playground mode, because outside it
|
|
295
|
+
* the `undefined` return let `CmsProvider` fall through to `cms-core`'s
|
|
296
|
+
* `sampleFilterableCollections` fixture — the engine's demo schema, shown as the
|
|
297
|
+
* project's, offering `filterable` fields the project's real backend would
|
|
298
|
+
* reject. With that fallback retired the gate has nothing left to protect: the
|
|
299
|
+
* Convex adapter is itself constructed with `resolveDeploymentCollections()`
|
|
300
|
+
* (`lib/persistence/index.ts`), so config collections are what connected mode
|
|
301
|
+
* serves too. Dropping it also DISSOLVES the EC-467 two-source divergence — the
|
|
302
|
+
* section rail reads `resolveDeploymentCollections()` directly, and now so does
|
|
303
|
+
* the store's last rung, so the two can no longer disagree.
|
|
304
|
+
*/
|
|
305
|
+
export declare function resolveCmsInitialCollections(cms: HydratedCms | null, kit: InstantiatedKit | undefined): CollectionDef[] | undefined;
|
|
306
|
+
/**
|
|
307
|
+
* The CMS documents source for a project mount — the document-side twin of
|
|
308
|
+
* {@link resolveCmsInitialCollections}: a hydrated session wins (Convex), then a
|
|
309
|
+
* kit's imported documents, then **nothing**.
|
|
215
310
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* did not
|
|
223
|
-
*
|
|
224
|
-
* project never actually wrote.
|
|
311
|
+
* EC-479 — the third rung used to be the engine's own `sampleDocuments` fixture,
|
|
312
|
+
* reconciled against the project's schema (EC-424). Reconciling it was already an
|
|
313
|
+
* admission that the content was foreign: EC-424 existed only because the demo
|
|
314
|
+
* declares `post.slug` localized while the shared fixture stores it plain, so the
|
|
315
|
+
* seeded posts carried standing validation noise no project had written. The
|
|
316
|
+
* honest fix is not a better reconciliation but no foreign content at all — a
|
|
317
|
+
* studio whose config did not arrive now shows an empty CMS and a visible issue
|
|
318
|
+
* at the rail, not six documents about Ada Lovelace wearing the project's schema.
|
|
225
319
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* resolution `resolveCmsInitialCollections` returns, so the two stay in lockstep
|
|
232
|
-
* by construction; only reconciles when that resolved to a non-empty config
|
|
233
|
-
* schema (env-only playground, with nothing config-declared, is unaffected).
|
|
320
|
+
* The playground keeps its world through the ordinary path, not through here: its
|
|
321
|
+
* seeded in-memory adapter IS a persisted backend behind the operations seam
|
|
322
|
+
* (AD-17), so `CmsSessionGate` hydrates it and the first rung answers. What that
|
|
323
|
+
* adapter seeds is `persistence/seed.ts`'s business — EC-475 (project-defined seed
|
|
324
|
+
* content) owns it.
|
|
234
325
|
*/
|
|
235
|
-
export declare function resolveCmsInitialDocuments(cms: HydratedCms | null, kit: InstantiatedKit | undefined
|
|
326
|
+
export declare function resolveCmsInitialDocuments(cms: HydratedCms | null, kit: InstantiatedKit | undefined): CmsDocument[] | undefined;
|
|
236
327
|
/**
|
|
237
328
|
* The CMS locale config for a project mount (EC-357 finding #3). Locales are
|
|
238
329
|
* structure and structure is code: when the active `elytra.config.ts` project
|
|
@@ -240,10 +331,16 @@ export declare function resolveCmsInitialDocuments(cms: HydratedCms | null, kit:
|
|
|
240
331
|
* wiring only applied the project's locale settings on a HYDRATED session, so a
|
|
241
332
|
* playground boot — or a connected FIRST RUN with nothing persisted yet — fell
|
|
242
333
|
* back to a kit's locales or the core's sample `en/de` fixture and the switcher
|
|
243
|
-
* showed `EN·/DE` against a `locales: ['de']` config.
|
|
244
|
-
*
|
|
245
|
-
*
|
|
334
|
+
* showed `EN·/DE` against a `locales: ['de']` config.
|
|
335
|
+
*
|
|
336
|
+
* EC-479/EC-483 — the ladder is now TOTAL, and every rung is the project's own:
|
|
337
|
+
* config-declared locales, then (hydrated) its persisted settings, then a kit's,
|
|
338
|
+
* then its settings regardless. A `Project` only ever exists for a config-declared
|
|
339
|
+
* project (`studio-context.tsx`: `configProjects()`), so `project.settings` is
|
|
340
|
+
* config-derived too — which is what lets the CMS session require a locale instead
|
|
341
|
+
* of borrowing the engine's `en/de` fixture. The default locale is the literal
|
|
342
|
+
* storage key for every localized value, so a guessed one is a data migration.
|
|
246
343
|
* `configLocales` is a parameter purely so the ladder is unit-testable (same seam
|
|
247
344
|
* pattern as {@link resolveCmsInitialCollections}).
|
|
248
345
|
*/
|
|
249
|
-
export declare function resolveCmsInitialLocaleConfig(cms: HydratedCms | null, kit: InstantiatedKit | undefined, project: Project, configLocales?: LocaleConfig | undefined): LocaleConfig
|
|
346
|
+
export declare function resolveCmsInitialLocaleConfig(cms: HydratedCms | null, kit: InstantiatedKit | undefined, project: Project, configLocales?: LocaleConfig | undefined): LocaleConfig;
|