@elytracms/studio 0.0.91 → 0.0.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/asset-alt-migration.test.ts +127 -0
- package/convex/asset-replace.test.ts +168 -0
- package/convex/asset-revalidate.test.ts +6 -6
- package/convex/assets.ts +41 -5
- package/convex/cms.ts +537 -177
- package/convex/contract-parity.test.ts +173 -19
- package/convex/delivery.ts +26 -13
- package/convex/document-revalidate.test.ts +17 -16
- package/convex/orphan-hydration.test.ts +116 -0
- package/convex/presence-cost.test.ts +89 -0
- package/convex/rail-write-parity.test.ts +17 -14
- package/convex/referenceMaintenance.ts +15 -15
- package/convex/schema.ts +70 -5
- package/convex/triggers.ts +21 -8
- package/convex/write-limits.ts +1 -1
- package/dist/components/ai/ai-chat-panel.js +33 -22
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/proposal-card.js +2 -1
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/translate-document-action.d.ts +8 -2
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.d.ts +1 -1
- package/dist/components/ai/translate-document-dialog.js +14 -8
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +19 -0
- package/dist/components/ai/translate-skipped.js +46 -0
- package/dist/components/ai/translate-skipped.js.map +1 -0
- package/dist/components/app-views/default-modules.d.ts +23 -6
- package/dist/components/app-views/default-modules.js +33 -14
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-detail.js +18 -8
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-metadata.d.ts +15 -4
- package/dist/components/assets/asset-metadata.js +15 -2
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-search.js +4 -1
- package/dist/components/assets/asset-search.js.map +1 -1
- package/dist/components/assets/asset-selection.d.ts +0 -8
- package/dist/components/assets/asset-selection.js +4 -23
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +3 -2
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +3 -3
- package/dist/components/assets/assets-collection-view-source.js +9 -5
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +14 -0
- package/dist/components/assets/assets-store.js +23 -1
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/replace-file.d.ts +22 -0
- package/dist/components/assets/replace-file.js +59 -0
- package/dist/components/assets/replace-file.js.map +1 -0
- package/dist/components/builder/blocks-model.d.ts +14 -26
- package/dist/components/builder/blocks-model.js +35 -41
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +7 -3
- package/dist/components/builder/blocks-panel.js +6 -5
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +126 -30
- package/dist/components/builder/builder-store.js +169 -67
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-body.d.ts +30 -0
- package/dist/components/builder/canvas-body.js +38 -0
- package/dist/components/builder/canvas-body.js.map +1 -0
- package/dist/components/builder/canvas-chrome-css.js +7 -0
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +7 -7
- package/dist/components/builder/canvas-dnd.js +5 -5
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +30 -22
- package/dist/components/builder/canvas-render-context.js +44 -44
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas-runs.d.ts +45 -0
- package/dist/components/builder/canvas-runs.js +51 -0
- package/dist/components/builder/canvas-runs.js.map +1 -0
- package/dist/components/builder/canvas.d.ts +5 -4
- package/dist/components/builder/canvas.js +34 -6
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +15 -0
- package/dist/components/builder/clipboard-actions.js +50 -13
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +10 -16
- package/dist/components/builder/composition-field-editor.js +30 -40
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/composition-patches.d.ts +7 -0
- package/dist/components/builder/composition-patches.js +256 -0
- package/dist/components/builder/composition-patches.js.map +1 -0
- package/dist/components/builder/delete-key.d.ts +5 -0
- package/dist/components/builder/delete-key.js +4 -0
- package/dist/components/builder/delete-key.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +17 -25
- package/dist/components/builder/insert-context.js +45 -92
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +3 -3
- package/dist/components/builder/page-tree.js +3 -3
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/placement-refusal.d.ts +18 -0
- package/dist/components/builder/placement-refusal.js +46 -0
- package/dist/components/builder/placement-refusal.js.map +1 -0
- package/dist/components/builder/property-inspector.js +4 -0
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +65 -26
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +79 -8
- package/dist/components/builder/reuse.js +71 -23
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/run-inspector.d.ts +11 -0
- package/dist/components/builder/run-inspector.js +33 -0
- package/dist/components/builder/run-inspector.js.map +1 -0
- package/dist/components/cms/asset-field.d.ts +38 -1
- package/dist/components/cms/asset-field.js +182 -58
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +16 -0
- package/dist/components/cms/asset-picker-core.js +20 -0
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/asset-picker.js +12 -18
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +4 -20
- package/dist/components/cms/bulk-actions.js +3 -24
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +5 -5
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +87 -45
- package/dist/components/cms/cms-store.js +347 -203
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +7 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/commit-policy.d.ts +24 -0
- package/dist/components/cms/commit-policy.js +37 -0
- package/dist/components/cms/commit-policy.js.map +1 -0
- package/dist/components/cms/detail-editor.js +83 -137
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-diff-view.d.ts +3 -1
- package/dist/components/cms/document-diff-view.js +12 -7
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/draft-commit.d.ts +70 -0
- package/dist/components/cms/draft-commit.js +82 -0
- package/dist/components/cms/draft-commit.js.map +1 -0
- package/dist/components/cms/duplicate-document.d.ts +11 -13
- package/dist/components/cms/duplicate-document.js +26 -34
- package/dist/components/cms/duplicate-document.js.map +1 -1
- package/dist/components/cms/field-anchors.d.ts +7 -4
- package/dist/components/cms/field-anchors.js +9 -7
- package/dist/components/cms/field-anchors.js.map +1 -1
- package/dist/components/cms/field-collaboration.d.ts +47 -0
- package/dist/components/cms/field-collaboration.js +87 -0
- package/dist/components/cms/field-collaboration.js.map +1 -0
- package/dist/components/cms/field-control-lib.d.ts +22 -1
- package/dist/components/cms/field-control-lib.js +33 -4
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +17 -14
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +21 -30
- package/dist/components/cms/gate-validation.js +11 -22
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/object-array-field.js +4 -4
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.d.ts +7 -2
- package/dist/components/cms/prop-fields.js +13 -6
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/reference-confirm.js +10 -4
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/insert.d.ts +20 -9
- package/dist/components/cms/rich-text/insert.js +25 -17
- package/dist/components/cms/rich-text/insert.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +96 -0
- package/dist/components/cms/use-document-draft.js +283 -0
- package/dist/components/cms/use-document-draft.js.map +1 -0
- package/dist/components/cms/use-document-versions.js +2 -2
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +6 -12
- package/dist/components/cms/use-live-url.js +16 -15
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.d.ts +13 -18
- package/dist/components/cms/use-publish-controller.js +24 -41
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +2 -3
- package/dist/components/cms/use-published-perspective.js +4 -3
- package/dist/components/cms/use-published-perspective.js.map +1 -1
- package/dist/components/cms/versions-pane.js +8 -1
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +24 -7
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +28 -23
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +31 -21
- package/dist/components/components/transfer.js +49 -35
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +6 -3
- package/dist/components/project-session.js +14 -8
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-view-source.js +2 -1
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/section-nav.js +1 -1
- package/dist/components/section-nav.js.map +1 -1
- package/dist/lib/ai/apply-content.js +56 -116
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +8 -0
- package/dist/lib/ai/chat-context.js +37 -6
- package/dist/lib/ai/chat-context.js.map +1 -1
- package/dist/lib/ai/chat-state.d.ts +14 -1
- package/dist/lib/ai/chat-state.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +20 -5
- package/dist/lib/ai/constraints.js +125 -33
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/fake-provider.d.ts +16 -3
- package/dist/lib/ai/fake-provider.js +22 -6
- package/dist/lib/ai/fake-provider.js.map +1 -1
- 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/leash.d.ts +34 -0
- package/dist/lib/ai/leash.js +28 -0
- package/dist/lib/ai/leash.js.map +1 -0
- package/dist/lib/ai/provenance.d.ts +1 -1
- package/dist/lib/ai/provenance.js.map +1 -1
- package/dist/lib/ai/rich-text-input.d.ts +8 -8
- package/dist/lib/ai/rich-text-input.js +2 -1
- package/dist/lib/ai/rich-text-input.js.map +1 -1
- package/dist/lib/ai/rich-text-translate.d.ts +27 -4
- package/dist/lib/ai/rich-text-translate.js +28 -10
- package/dist/lib/ai/rich-text-translate.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +13 -13
- package/dist/lib/ai/shape.js +69 -93
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +36 -41
- package/dist/lib/ai/tools.js +85 -87
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +39 -5
- package/dist/lib/ai/translate-document.js +160 -51
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +34 -23
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +21 -12
- package/dist/lib/ai/update-fields.js +118 -177
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/ai/writer-form.d.ts +116 -0
- package/dist/lib/ai/writer-form.js +429 -0
- package/dist/lib/ai/writer-form.js.map +1 -0
- package/dist/lib/ai-current-document.d.ts +11 -11
- package/dist/lib/ai-current-document.js +4 -4
- package/dist/lib/ai-current-document.js.map +1 -1
- package/dist/lib/assets/upload.d.ts +31 -2
- package/dist/lib/assets/upload.js +61 -6
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
- package/dist/lib/clipboard/os-clipboard.js +8 -4
- package/dist/lib/clipboard/os-clipboard.js.map +1 -1
- package/dist/lib/clipboard/paste-target.d.ts +18 -9
- package/dist/lib/clipboard/paste-target.js +8 -7
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/field-patch.d.ts +15 -0
- package/dist/lib/cms/field-patch.js +25 -0
- package/dist/lib/cms/field-patch.js.map +1 -0
- package/dist/lib/cms/published-documents.d.ts +1 -1
- package/dist/lib/cms/published-documents.js +1 -1
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +3 -3
- package/dist/lib/collection-view/__fixtures__/document-writes.d.ts +6 -0
- package/dist/lib/collection-view/__fixtures__/document-writes.js +23 -0
- package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -0
- package/dist/lib/collection-view/collection-view-source.d.ts +69 -6
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +4 -13
- package/dist/lib/data/collection-content.js +11 -5
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +25 -31
- package/dist/lib/dnd/drop-rules.js +53 -59
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +72 -15
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +61 -12
- package/dist/lib/i18n/messages.en.js +76 -16
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/__fixtures__/write-document.d.ts +30 -0
- package/dist/lib/operations/__fixtures__/write-document.js +16 -0
- package/dist/lib/operations/__fixtures__/write-document.js.map +1 -0
- package/dist/lib/operations/cms-persistence.d.ts +124 -31
- package/dist/lib/operations/cms-persistence.js +447 -68
- package/dist/lib/operations/cms-persistence.js.map +1 -1
- package/dist/lib/operations/config-manifest-lookup.d.ts +35 -0
- package/dist/lib/operations/config-manifest-lookup.js +56 -0
- package/dist/lib/operations/config-manifest-lookup.js.map +1 -0
- package/dist/lib/operations/project-runtime-config.d.ts +39 -0
- package/dist/lib/operations/project-runtime-config.js +54 -0
- package/dist/lib/operations/project-runtime-config.js.map +1 -0
- package/dist/lib/operations/publish-with-revision.d.ts +64 -0
- package/dist/lib/operations/publish-with-revision.js +66 -0
- package/dist/lib/operations/publish-with-revision.js.map +1 -0
- package/dist/lib/operations/runtime-validator.d.ts +32 -0
- package/dist/lib/operations/runtime-validator.js +18 -0
- package/dist/lib/operations/runtime-validator.js.map +1 -0
- package/dist/lib/operations/session-hydration.d.ts +31 -14
- package/dist/lib/operations/session-hydration.js +12 -16
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +59 -2
- package/dist/lib/operations/studio-operations.js +76 -14
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +13 -5
- package/dist/lib/persistence/convex-adapter.js +189 -74
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/convex-client.d.ts +65 -5
- package/dist/lib/persistence/convex-client.js +106 -3
- package/dist/lib/persistence/convex-client.js.map +1 -1
- package/dist/lib/persistence/index.js +17 -18
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.js +4 -1
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +1 -1
- package/dist/lib/query/operations-query.js +1 -1
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/references/impact.d.ts +21 -15
- package/dist/lib/references/impact.js +13 -26
- package/dist/lib/references/impact.js.map +1 -1
- package/dist/lib/references/references-store.d.ts +9 -0
- package/dist/lib/references/references-store.js +24 -4
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/routes/route-issues.d.ts +6 -6
- package/dist/lib/routes/route-issues.js +26 -80
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/workspace/config-issues.js +2 -1
- package/dist/lib/workspace/config-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +31 -1
- package/dist/lib/workspace/index.js +64 -17
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/switcher-source.d.ts +3 -3
- package/dist/lib/workspace/switcher-source.js.map +1 -1
- package/dist/lib/workspace/workspace-source.d.ts +2 -2
- package/dist/lib/workspace/workspace-source.js +2 -1
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/package.json +3 -3
- package/dist/components/cms/cms-composition-validators.d.ts +0 -16
- package/dist/components/cms/cms-composition-validators.js +0 -133
- package/dist/components/cms/cms-composition-validators.js.map +0 -1
- package/dist/components/cms/publish-clean.d.ts +0 -28
- package/dist/components/cms/publish-clean.js +0 -38
- package/dist/components/cms/publish-clean.js.map +0 -1
- package/dist/components/cms/use-save-mode.d.ts +0 -35
- package/dist/components/cms/use-save-mode.js +0 -54
- package/dist/components/cms/use-save-mode.js.map +0 -1
- package/dist/lib/assets/asset-placements.d.ts +0 -28
- package/dist/lib/assets/asset-placements.js +0 -83
- package/dist/lib/assets/asset-placements.js.map +0 -1
- package/dist/lib/cms/document-gate.d.ts +0 -47
- package/dist/lib/cms/document-gate.js +0 -61
- package/dist/lib/cms/document-gate.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal-card.js","sourceRoot":"","sources":["../../../src/components/ai/proposal-card.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { ArrowUpRight, Check, Maximize2, Sparkles, X } from 'lucide-react'\nimport { diffDocuments } from '@elytracms/core/content'\nimport type { DocumentDiff } from '@elytracms/core/content'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core'\nimport { Badge, Button, ExpandableSurface, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { proposalDocuments } from '@elytracms/studio/lib/ai'\nimport type { ProposalOrigin, ProposalReview } from '@elytracms/studio/lib/ai'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useCmsCollectionViewSource } from '../collection-view/cms-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { DocumentDiffView } from '../cms/document-diff-view'\nimport { ProposalIssues } from './proposal-issues'\n\n/**\n * The AI proposal review (EC-199, extended by EC-332/EC-378): a valid,\n * leash-compliant proposal the editor reviews as a DIFF (the shared EC-317\n * {@link DocumentDiffView} — never a second renderer) before applying it as a\n * draft or discarding it. Create proposals diff `empty-before` vs\n * `proposed-after`; translate/update proposals diff the EXISTING document\n * (old → new). Apply/discard are handled by the panel (the operations write +\n * provenance snapshot happen there); this card is the surface.\n *\n * {@link ProposalCard} is the CMS-wired shell; {@link ProposalCardView} is the\n * presentational card over an injected collection + view source (node-smoked\n * with `renderToStaticMarkup`, house pattern).\n *\n * ## EC-496 — the proposal's own findings\n *\n * The diff answers *what changes*; {@link ProposalIssues} answers *what the engine\n * flagged about it*, immediately above the Apply/Discard row so it is read while the\n * decision is being made. Two calls made here, both deliberate:\n *\n * **An error on the card does NOT block apply.** Grounded before deciding\n * (`file:line`, read 2026-08-01): every producer already filters this list to\n * non-error findings (`lib/ai/apply-content.ts:259`, `lib/ai/update-fields.ts:442`,\n * `lib/ai/tools.ts:764`), so a card-blocking gate would be dead code today. More\n * importantly the real gate already exists one layer up and is strictly better\n * placed: EC-473 made apply RE-VALIDATE against the session as it is NOW\n * (`ai-chat-panel.tsx:371`) and refuse with `ai.error.applyInvalid` when the write\n * would INTRODUCE an error. A build-time snapshot cannot see an edit the editor made\n * since, and gating on pre-existing brokenness is exactly what EC-473 argued makes an\n * assistant unusable on real content. So this surface REPORTS truthfully — errors\n * render as errors and sort first ({@link orderedProposalIssues}) — and the gate\n * stays where it can see the truth.\n *\n * **`attempts` is not rendered** (the EC-496 scope-2 owner call). The repair-loop\n * count is provenance about the MODEL, not about the content: a proposal repaired on\n * the second attempt passed the identical validation gate as a first-shot one, so the\n * number changes nothing an editor can act on — and spending sidebar space plus a new\n * catalog string on it would trade a real signal (the findings) for a trivia line. The\n * field stays on `ChatProposal` (it is deterministic, cheap, and the natural input for\n * a future AI-diagnostics surface); it simply has no place on the editor's card.\n */\n\n/** The card's origin-dependent captions (title + diff column headers). */\nexport function proposalCaptions(\n origin: ProposalOrigin,\n collectionLabel: string,\n): { title: string; unified: string; before: string; after: string } {\n if (origin === 'translate') {\n return {\n title: translate('ai.translate.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.translate.diffBefore'),\n after: translate('ai.translate.diffAfter'),\n }\n }\n if (origin === 'update') {\n return {\n title: translate('ai.update.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.update.diffBefore'),\n after: translate('ai.update.diffAfter'),\n }\n }\n return {\n title: translate('ai.proposal.title', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.proposal.diffBefore'),\n after: translate('ai.proposal.diffAfter'),\n }\n}\n\nexport function ProposalCard({\n proposal,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const cms = useCms()\n const collection = cms.getCollection(proposal.collection)\n const source = useCmsCollectionViewSource(proposal.collection)\n return (\n <ProposalCardView\n proposal={proposal}\n collection={collection}\n source={source}\n onApply={onApply}\n onDiscard={onDiscard}\n onOpen={onOpen}\n />\n )\n}\n\n/** The presentational proposal card over an injected collection + view source. */\nexport function ProposalCardView({\n proposal,\n collection,\n source,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n collection: CollectionDef | undefined\n source: CollectionViewSource | null\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const diff = useMemo(() => {\n if (!collection) return null\n const { before, after } = proposalDocuments(proposal.patch, proposal.before)\n return diffDocuments(collection, before, after)\n }, [collection, proposal.patch, proposal.before])\n\n const collectionLabel =\n (collection?.form?.labelSingular && resolveLabel(collection.form.labelSingular)) ??\n (collection?.form?.label && resolveLabel(collection.form.label)) ??\n proposal.collection\n\n const captions = proposalCaptions(proposal.origin, collectionLabel)\n const applied = proposal.status === 'applied'\n const rejected = proposal.status === 'rejected'\n const columnHeaders = { unified: captions.unified, before: captions.before, after: captions.after }\n\n // EC-382 — the expandable review surface: the sidebar card stays stacked; a\n // roomy full-height sheet is offered for a large proposal (never a modal form).\n const [reviewOpen, setReviewOpen] = useState(false)\n const reviewable = diff !== null && source !== null\n\n return (\n <Stack\n gap=\"2\"\n className=\"overflow-hidden rounded-xl border border-border bg-card\"\n >\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-3 py-2\">\n <Sparkles className=\"size-4 shrink-0 text-primary\" />\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {captions.title}\n </Text>\n <Spacer />\n {applied ? <Badge variant=\"success\">{translate('ai.proposal.applied')}</Badge> : null}\n {rejected ? <Badge variant=\"outline\">{translate('ai.proposal.discarded')}</Badge> : null}\n {reviewable ? (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setReviewOpen(true)}\n title={translate('ai.proposal.expandReview')}\n aria-label={translate('ai.proposal.expandReview')}\n >\n <Maximize2 />\n </Button>\n ) : null}\n </Row>\n\n <div className=\"px-3\">\n {reviewable ? (\n <CollectionViewProvider source={source!}>\n <DocumentDiffView\n diff={diff!}\n collection={collection!}\n columnHeaders={columnHeaders}\n layout=\"stacked\"\n />\n </CollectionViewProvider>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"py-2\">\n {translate('ai.proposal.unavailable')}\n </Text>\n )}\n </div>\n\n {/* EC-496 — the engine's surviving findings, last thing before the actions. */}\n <ProposalIssues issues={proposal.issues} className=\"px-3\" />\n\n <Row gap=\"2\" align=\"center\" className=\"border-t border-border/60 px-3 py-2\">\n {proposal.status === 'pending' ? (\n <>\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button size=\"sm\" variant=\"ghost\" onClick={onDiscard}>\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button size=\"sm\" onClick={onApply}>\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </>\n ) : applied ? (\n <>\n <Spacer />\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen}>\n <ArrowUpRight /> {translate('ai.proposal.open')}\n </Button>\n </>\n ) : (\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.proposal.discardedNote')}\n </Text>\n )}\n </Row>\n\n {reviewable && reviewOpen ? (\n <ProposalReviewSheet\n title={captions.title}\n diff={diff!}\n collection={collection!}\n source={source!}\n columnHeaders={columnHeaders}\n issues={proposal.issues}\n status={proposal.status}\n onApply={onApply}\n onDiscard={onDiscard}\n onClose={() => setReviewOpen(false)}\n />\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The expandable review surface (EC-382): the proposal's diff promoted into a\n * roomy, full-height right-anchored sheet where the wide `grid` layout is\n * comfortable — for a large proposal the 320px sidebar can only cramp. It is a\n * REVIEW surface, not a form, so it stays lightly dismissible (backdrop click /\n * Escape / close button) rather than a trapping modal — the forms-never-modal\n * spirit, applied. Presentation only: Apply/Discard call the same handlers the\n * card does; it changes no proposal data. Since EC-392 it renders through the\n * shared {@link ExpandableSurface} primitive (portal-to-body, backdrop, header\n * close, layer-stacked Escape) at the shipped 720px width, so it escapes the\n * narrow AI panel; the card default keeps it closed, so the SSR smoke path\n * never mounts the portal.\n */\nfunction ProposalReviewSheet({\n title,\n diff,\n collection,\n source,\n columnHeaders,\n issues,\n status,\n onApply,\n onDiscard,\n onClose,\n}: {\n title: string\n diff: DocumentDiff\n collection: CollectionDef\n source: CollectionViewSource\n columnHeaders: { unified: string; before: string; after: string }\n /** EC-496 — the same findings the card shows; the roomy surface must not hide them. */\n issues: readonly CmsValidationIssue[]\n status: ProposalReview['status']\n onApply: () => void\n onDiscard: () => void\n onClose: () => void\n}) {\n return (\n <ExpandableSurface\n title={title}\n closeLabel={translate('ai.proposal.collapseReview')}\n width=\"min(720px, 92vw)\"\n onDismiss={onClose}\n headerLeading={<Sparkles className=\"size-4 shrink-0 text-primary\" />}\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-3xl\">\n <CollectionViewProvider source={source}>\n <DocumentDiffView diff={diff} collection={collection} columnHeaders={columnHeaders} />\n </CollectionViewProvider>\n <ProposalIssues issues={issues} className=\"mt-4\" />\n </div>\n </div>\n\n {status === 'pending' ? (\n <Row gap=\"2\" align=\"center\" className=\"shrink-0 border-t border-border px-4 py-3\">\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n onDiscard()\n onClose()\n }}\n >\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button\n size=\"sm\"\n onClick={() => {\n onApply()\n onClose()\n }}\n >\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </Row>\n ) : null}\n </ExpandableSurface>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"proposal-card.js","sourceRoot":"","sources":["../../../src/components/ai/proposal-card.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { ArrowUpRight, Check, Maximize2, Sparkles, X } from 'lucide-react'\nimport { diffDocuments } from '@elytracms/core/content'\nimport type { DocumentDiff } from '@elytracms/core/content'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core'\nimport { Badge, Button, ExpandableSurface, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { proposalDocuments } from '@elytracms/studio/lib/ai'\nimport type { ProposalOrigin, ProposalReview } from '@elytracms/studio/lib/ai'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useCmsCollectionViewSource } from '../collection-view/cms-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { DocumentDiffView } from '../cms/document-diff-view'\nimport { ProposalIssues } from './proposal-issues'\nimport { TranslateSkipped } from './translate-skipped'\n\n/**\n * The AI proposal review (EC-199, extended by EC-332/EC-378): a valid,\n * leash-compliant proposal the editor reviews as a DIFF (the shared EC-317\n * {@link DocumentDiffView} — never a second renderer) before applying it as a\n * draft or discarding it. Create proposals diff `empty-before` vs\n * `proposed-after`; translate/update proposals diff the EXISTING document\n * (old → new). Apply/discard are handled by the panel (the operations write +\n * provenance snapshot happen there); this card is the surface.\n *\n * {@link ProposalCard} is the CMS-wired shell; {@link ProposalCardView} is the\n * presentational card over an injected collection + view source (node-smoked\n * with `renderToStaticMarkup`, house pattern).\n *\n * ## EC-496 — the proposal's own findings\n *\n * The diff answers *what changes*; {@link ProposalIssues} answers *what the engine\n * flagged about it*, immediately above the Apply/Discard row so it is read while the\n * decision is being made. Two calls made here, both deliberate:\n *\n * **An error on the card does NOT block apply.** Grounded before deciding\n * (`file:line`, read 2026-08-01): every producer already filters this list to\n * non-error findings (`lib/ai/apply-content.ts:259`, `lib/ai/update-fields.ts:442`,\n * `lib/ai/tools.ts:764`), so a card-blocking gate would be dead code today. More\n * importantly the real gate already exists one layer up and is strictly better\n * placed: EC-473 made apply RE-VALIDATE against the session as it is NOW\n * (`ai-chat-panel.tsx:371`) and refuse with `ai.error.applyInvalid` when the write\n * would INTRODUCE an error. A build-time snapshot cannot see an edit the editor made\n * since, and gating on pre-existing brokenness is exactly what EC-473 argued makes an\n * assistant unusable on real content. So this surface REPORTS truthfully — errors\n * render as errors and sort first ({@link orderedProposalIssues}) — and the gate\n * stays where it can see the truth.\n *\n * **`attempts` is not rendered** (the EC-496 scope-2 owner call). The repair-loop\n * count is provenance about the MODEL, not about the content: a proposal repaired on\n * the second attempt passed the identical validation gate as a first-shot one, so the\n * number changes nothing an editor can act on — and spending sidebar space plus a new\n * catalog string on it would trade a real signal (the findings) for a trivia line. The\n * field stays on `ChatProposal` (it is deterministic, cheap, and the natural input for\n * a future AI-diagnostics surface); it simply has no place on the editor's card.\n */\n\n/** The card's origin-dependent captions (title + diff column headers). */\nexport function proposalCaptions(\n origin: ProposalOrigin,\n collectionLabel: string,\n): { title: string; unified: string; before: string; after: string } {\n if (origin === 'translate') {\n return {\n title: translate('ai.translate.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.translate.diffBefore'),\n after: translate('ai.translate.diffAfter'),\n }\n }\n if (origin === 'update') {\n return {\n title: translate('ai.update.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.update.diffBefore'),\n after: translate('ai.update.diffAfter'),\n }\n }\n return {\n title: translate('ai.proposal.title', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.proposal.diffBefore'),\n after: translate('ai.proposal.diffAfter'),\n }\n}\n\nexport function ProposalCard({\n proposal,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const cms = useCms()\n const collection = cms.getCollection(proposal.collection)\n const source = useCmsCollectionViewSource(proposal.collection)\n return (\n <ProposalCardView\n proposal={proposal}\n collection={collection}\n source={source}\n onApply={onApply}\n onDiscard={onDiscard}\n onOpen={onOpen}\n />\n )\n}\n\n/** The presentational proposal card over an injected collection + view source. */\nexport function ProposalCardView({\n proposal,\n collection,\n source,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n collection: CollectionDef | undefined\n source: CollectionViewSource | null\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const diff = useMemo(() => {\n if (!collection) return null\n const { before, after } = proposalDocuments(proposal.patch, proposal.before)\n return diffDocuments(collection, before, after)\n }, [collection, proposal.patch, proposal.before])\n\n const collectionLabel =\n (collection?.form?.labelSingular && resolveLabel(collection.form.labelSingular)) ??\n (collection?.form?.label && resolveLabel(collection.form.label)) ??\n proposal.collection\n\n const captions = proposalCaptions(proposal.origin, collectionLabel)\n const applied = proposal.status === 'applied'\n const rejected = proposal.status === 'rejected'\n const columnHeaders = { unified: captions.unified, before: captions.before, after: captions.after }\n\n // EC-382 — the expandable review surface: the sidebar card stays stacked; a\n // roomy full-height sheet is offered for a large proposal (never a modal form).\n const [reviewOpen, setReviewOpen] = useState(false)\n const reviewable = diff !== null && source !== null\n\n return (\n <Stack\n gap=\"2\"\n className=\"overflow-hidden rounded-xl border border-border bg-card\"\n >\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-3 py-2\">\n <Sparkles className=\"size-4 shrink-0 text-primary\" />\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {captions.title}\n </Text>\n <Spacer />\n {applied ? <Badge variant=\"success\">{translate('ai.proposal.applied')}</Badge> : null}\n {rejected ? <Badge variant=\"outline\">{translate('ai.proposal.discarded')}</Badge> : null}\n {reviewable ? (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setReviewOpen(true)}\n title={translate('ai.proposal.expandReview')}\n aria-label={translate('ai.proposal.expandReview')}\n >\n <Maximize2 />\n </Button>\n ) : null}\n </Row>\n\n <div className=\"px-3\">\n {reviewable ? (\n <CollectionViewProvider source={source!}>\n <DocumentDiffView\n diff={diff!}\n collection={collection!}\n columnHeaders={columnHeaders}\n layout=\"stacked\"\n />\n </CollectionViewProvider>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"py-2\">\n {translate('ai.proposal.unavailable')}\n </Text>\n )}\n </div>\n\n {/* EC-496 — the engine's surviving findings, last thing before the actions. */}\n <ProposalIssues issues={proposal.issues} className=\"px-3\" />\n {/* EC-593 — what a translation could not translate, listed (never skipped silently). */}\n {proposal.skipped && proposal.skipped.length > 0 ? (\n <div className=\"px-3\">\n <TranslateSkipped skipped={proposal.skipped} collection={collection} />\n </div>\n ) : null}\n\n <Row gap=\"2\" align=\"center\" className=\"border-t border-border/60 px-3 py-2\">\n {proposal.status === 'pending' ? (\n <>\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button size=\"sm\" variant=\"ghost\" onClick={onDiscard}>\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button size=\"sm\" onClick={onApply}>\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </>\n ) : applied ? (\n <>\n <Spacer />\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen}>\n <ArrowUpRight /> {translate('ai.proposal.open')}\n </Button>\n </>\n ) : (\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.proposal.discardedNote')}\n </Text>\n )}\n </Row>\n\n {reviewable && reviewOpen ? (\n <ProposalReviewSheet\n title={captions.title}\n diff={diff!}\n collection={collection!}\n source={source!}\n columnHeaders={columnHeaders}\n issues={proposal.issues}\n status={proposal.status}\n onApply={onApply}\n onDiscard={onDiscard}\n onClose={() => setReviewOpen(false)}\n />\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The expandable review surface (EC-382): the proposal's diff promoted into a\n * roomy, full-height right-anchored sheet where the wide `grid` layout is\n * comfortable — for a large proposal the 320px sidebar can only cramp. It is a\n * REVIEW surface, not a form, so it stays lightly dismissible (backdrop click /\n * Escape / close button) rather than a trapping modal — the forms-never-modal\n * spirit, applied. Presentation only: Apply/Discard call the same handlers the\n * card does; it changes no proposal data. Since EC-392 it renders through the\n * shared {@link ExpandableSurface} primitive (portal-to-body, backdrop, header\n * close, layer-stacked Escape) at the shipped 720px width, so it escapes the\n * narrow AI panel; the card default keeps it closed, so the SSR smoke path\n * never mounts the portal.\n */\nfunction ProposalReviewSheet({\n title,\n diff,\n collection,\n source,\n columnHeaders,\n issues,\n status,\n onApply,\n onDiscard,\n onClose,\n}: {\n title: string\n diff: DocumentDiff\n collection: CollectionDef\n source: CollectionViewSource\n columnHeaders: { unified: string; before: string; after: string }\n /** EC-496 — the same findings the card shows; the roomy surface must not hide them. */\n issues: readonly CmsValidationIssue[]\n status: ProposalReview['status']\n onApply: () => void\n onDiscard: () => void\n onClose: () => void\n}) {\n return (\n <ExpandableSurface\n title={title}\n closeLabel={translate('ai.proposal.collapseReview')}\n width=\"min(720px, 92vw)\"\n onDismiss={onClose}\n headerLeading={<Sparkles className=\"size-4 shrink-0 text-primary\" />}\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-3xl\">\n <CollectionViewProvider source={source}>\n <DocumentDiffView diff={diff} collection={collection} columnHeaders={columnHeaders} />\n </CollectionViewProvider>\n <ProposalIssues issues={issues} className=\"mt-4\" />\n </div>\n </div>\n\n {status === 'pending' ? (\n <Row gap=\"2\" align=\"center\" className=\"shrink-0 border-t border-border px-4 py-3\">\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n onDiscard()\n onClose()\n }}\n >\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button\n size=\"sm\"\n onClick={() => {\n onApply()\n onClose()\n }}\n >\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </Row>\n ) : null}\n </ExpandableSurface>\n )\n}\n"]}
|
|
@@ -8,7 +8,13 @@ export interface TranslateDocumentActionProps {
|
|
|
8
8
|
localeConfig: LocaleConfig;
|
|
9
9
|
/** Whether the collection view exposes per-locale editing (`source.capabilities.locales`). */
|
|
10
10
|
localesEnabled: boolean;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* EC-595 A2 — the FORCED FLUSH before the AI reads the draft (the ruling: "a forced
|
|
13
|
+
* flush before … an AI proposal"): the editor commits its uncommitted fields and the
|
|
14
|
+
* persister lands them, so the translation's patches are made on the committed draft.
|
|
15
|
+
* The translated buckets reach the open editor through the session (it rebases), so
|
|
16
|
+
* there is no draft to hand back.
|
|
17
|
+
*/
|
|
18
|
+
flushDraft: () => Promise<void>;
|
|
13
19
|
}
|
|
14
20
|
export declare function TranslateDocumentAction(props: TranslateDocumentActionProps): import("react").JSX.Element | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate-document-action.js","sourceRoot":"","sources":["../../../src/components/ai/translate-document-action.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAE3D,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAErE;;;;;;;;;;GAUG;AACH,MAAM,uBAAuB,GAAG,IAAI,CAAC,GAAG,EAAE,CACxC,MAAM,CAAC,6BAA6B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAC5F,CAAA;
|
|
1
|
+
{"version":3,"file":"translate-document-action.js","sourceRoot":"","sources":["../../../src/components/ai/translate-document-action.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAE3D,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAErE;;;;;;;;;;GAUG;AACH,MAAM,uBAAuB,GAAG,IAAI,CAAC,GAAG,EAAE,CACxC,MAAM,CAAC,6BAA6B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAC5F,CAAA;AAqBD,MAAM,UAAU,uBAAuB,CAAC,KAAmC;IACzE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IAErE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO,KAAK,CAAA;QAC7C,IAAI,CAAC,cAAc,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QAC5D,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAA;QACjC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO,KAAK,CAAA;QAC/D,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5E,CAAC,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC,CAAA;IAEzD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1B,OAAO,CACL,8BACE,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,SAAS,EACjB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,iBAChB,eAAe,EAC3B,KAAK,EAAE,SAAS,CAAC,sBAAsB,CAAC,aAExC,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,qBAAqB,CAAC,IACxC,EACR,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,QAAQ,IAAC,QAAQ,EAAE,IAAI,YACtB,KAAC,uBAAuB,OAAK,KAAK,EAAE,IAAI,QAAC,YAAY,EAAE,OAAO,GAAI,GACzD,CACZ,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy, useMemo, useState } from 'react'\nimport { Languages } from 'lucide-react'\nimport { isFieldLocalized } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, LocaleConfig } from '@elytracms/core/cms-core'\nimport { Button } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\n\n/**\n * The \"Translate document\" header action (EC-332), gate side.\n *\n * Rendered ALWAYS in the detail-editor header but returns null unless the studio is\n * `ai`-configured (EC-331 presence-enable), the collection is localized with a real\n * localized field, and there is a target locale to fill. Like the AI panel toggle, the\n * gate reads only the config object + pure core helpers — it never statically imports\n * the AI engine (`@elytracms/studio/lib/ai`) or the AI SDK. The heavy dialog (which runs\n * the translation engine + operations write) loads ONLY when the editor opens it, via a\n * dynamic `import()`, so an unconfigured studio pulls none of it onto the critical path.\n */\nconst TranslateDocumentDialog = lazy(() =>\n import('./translate-document-dialog').then((m) => ({ default: m.TranslateDocumentDialog })),\n)\n\nexport interface TranslateDocumentActionProps {\n projectId: string | undefined\n collection: CollectionDef\n docRef: DocumentRef\n /** The live editor draft — the translation source + the target for the fills. */\n document: CmsDocument\n localeConfig: LocaleConfig\n /** Whether the collection view exposes per-locale editing (`source.capabilities.locales`). */\n localesEnabled: boolean\n /**\n * EC-595 A2 — the FORCED FLUSH before the AI reads the draft (the ruling: \"a forced\n * flush before … an AI proposal\"): the editor commits its uncommitted fields and the\n * persister lands them, so the translation's patches are made on the committed draft.\n * The translated buckets reach the open editor through the session (it rebases), so\n * there is no draft to hand back.\n */\n flushDraft: () => Promise<void>\n}\n\nexport function TranslateDocumentAction(props: TranslateDocumentActionProps) {\n const [open, setOpen] = useState(false)\n const { collection, localeConfig, localesEnabled, projectId } = props\n\n const eligible = useMemo(() => {\n const ai = resolveDeploymentAi()\n if (!ai || ai.enabled === false) return false\n if (!localesEnabled || projectId === undefined) return false\n const base = localeConfig.default\n if (!localeConfig.locales.some((l) => l !== base)) return false\n return collection.fields.some((f) => isFieldLocalized(collection, f.name))\n }, [collection, localeConfig, localesEnabled, projectId])\n\n if (!eligible) return null\n\n return (\n <>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setOpen(true)}\n data-testid=\"doc-translate\"\n title={translate('ai.translate.tooltip')}\n >\n <Languages /> {translate('ai.translate.action')}\n </Button>\n {open ? (\n <Suspense fallback={null}>\n <TranslateDocumentDialog {...props} open onOpenChange={setOpen} />\n </Suspense>\n ) : null}\n </>\n )\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TranslateDocumentActionProps } from './translate-document-action.js';
|
|
2
|
-
export declare function TranslateDocumentDialog({ projectId, collection, docRef, document, localeConfig,
|
|
2
|
+
export declare function TranslateDocumentDialog({ projectId, collection, docRef, document, localeConfig, flushDraft, open, onOpenChange, }: TranslateDocumentActionProps & {
|
|
3
3
|
open: boolean;
|
|
4
4
|
onOpenChange: (open: boolean) => void;
|
|
5
5
|
}): import("react").JSX.Element;
|
|
@@ -10,7 +10,8 @@ import { resolveDeploymentAi } from '@elytracms/studio/lib/workspace';
|
|
|
10
10
|
import { resolveAiConfig, resolveKnowledge, translateDocument } from '@elytracms/studio/lib/ai';
|
|
11
11
|
import { cmsValidationIssueMessage } from '@elytracms/studio/lib/persona/labels';
|
|
12
12
|
import { useCms } from '../cms/cms-store.js';
|
|
13
|
-
|
|
13
|
+
import { TranslateSkipped } from './translate-skipped.js';
|
|
14
|
+
export function TranslateDocumentDialog({ projectId, collection, docRef, document, localeConfig, flushDraft, open, onOpenChange, }) {
|
|
14
15
|
const [phase, setPhase] = useState('confirm');
|
|
15
16
|
const [progress, setProgress] = useState(null);
|
|
16
17
|
const [summary, setSummary] = useState(null);
|
|
@@ -46,6 +47,7 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
46
47
|
abortRef.current = controller;
|
|
47
48
|
setRefusal(null);
|
|
48
49
|
setPhase('running');
|
|
50
|
+
await flushDraft();
|
|
49
51
|
setProgress({ completed: 0, total: 0 });
|
|
50
52
|
try {
|
|
51
53
|
const knowledge = await resolveKnowledge(resolved.resolved?.knowledge ?? []);
|
|
@@ -77,12 +79,16 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
81
|
if (result.translated > 0 || result.slugsDerived > 0) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// EC-595 A2: the translation persists as the patches of the buckets it filled
|
|
83
|
+
// (never the whole document) — through the session's queue, at the revision
|
|
84
|
+
// the editor's view is at; a bucket a colleague changed meanwhile is refused on
|
|
85
|
+
// that field and shown there.
|
|
86
|
+
const outcome = await cms.commitPatches(docRef, result.patches);
|
|
87
|
+
if (outcome?.status === 'failed') {
|
|
83
88
|
setPhase('error');
|
|
84
89
|
return;
|
|
85
90
|
}
|
|
91
|
+
const { operations } = await getStudioOperations();
|
|
86
92
|
// Provenance: an AI-tagged version snapshot (best-effort — the draft already landed).
|
|
87
93
|
try {
|
|
88
94
|
await operations.documents.snapshot({
|
|
@@ -95,14 +101,14 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
95
101
|
catch {
|
|
96
102
|
// ignore — provenance is additive
|
|
97
103
|
}
|
|
98
|
-
onApplied(result.document);
|
|
99
104
|
}
|
|
100
105
|
setSummary({
|
|
101
106
|
translated: result.translated,
|
|
102
107
|
slugs: result.slugsDerived,
|
|
103
108
|
locales: Object.keys(result.perLocale).length,
|
|
104
109
|
cancelled: result.cancelled,
|
|
105
|
-
nothing: result.total === 0,
|
|
110
|
+
nothing: result.total === 0 && result.skipped.length === 0,
|
|
111
|
+
skipped: result.skipped,
|
|
106
112
|
});
|
|
107
113
|
setPhase('done');
|
|
108
114
|
}
|
|
@@ -112,7 +118,7 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
112
118
|
finally {
|
|
113
119
|
abortRef.current = null;
|
|
114
120
|
}
|
|
115
|
-
}, [projectId, collection, document, localeConfig, docRef,
|
|
121
|
+
}, [projectId, collection, document, localeConfig, docRef, flushDraft, registry, cms]);
|
|
116
122
|
const close = () => {
|
|
117
123
|
if (phase === 'running')
|
|
118
124
|
return; // never close mid-run — Cancel aborts instead
|
|
@@ -127,6 +133,6 @@ export function TranslateDocumentDialog({ projectId, collection, docRef, documen
|
|
|
127
133
|
};
|
|
128
134
|
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
|
|
129
135
|
? translate('ai.translate.progress', { completed: progress.completed, total: progress.total })
|
|
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') })) })] }) }));
|
|
136
|
+
: 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) }), _jsx(TranslateSkipped, { skipped: summary.skipped, collection: collection })] })) : 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') })) })] }) }));
|
|
131
137
|
}
|
|
132
138
|
//# 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, 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
|
+
{"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, 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, TranslateSkip } 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'\nimport { TranslateSkipped } from './translate-skipped'\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 /** EC-593: every text position the run could not translate — listed, never silent. */\n skipped: readonly TranslateSkip[]\n}\n\nexport function TranslateDocumentDialog({\n projectId,\n collection,\n docRef,\n document,\n localeConfig,\n flushDraft,\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 await flushDraft()\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 // EC-595 A2: the translation persists as the patches of the buckets it filled\n // (never the whole document) — through the session's queue, at the revision\n // the editor's view is at; a bucket a colleague changed meanwhile is refused on\n // that field and shown there.\n const outcome = await cms.commitPatches(docRef, result.patches)\n if (outcome?.status === 'failed') {\n setPhase('error')\n return\n }\n const { operations } = await getStudioOperations()\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 }\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 && result.skipped.length === 0,\n skipped: result.skipped,\n })\n setPhase('done')\n } catch {\n setPhase('error')\n } finally {\n abortRef.current = null\n }\n }, [projectId, collection, document, localeConfig, docRef, flushDraft, registry, cms])\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 <TranslateSkipped skipped={summary.skipped} collection={collection} />\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"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CollectionDef } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { TranslateSkip } from '@elytracms/studio/lib/ai';
|
|
3
|
+
/** One skip as the editor reads it: where (field label › component › position), which language, why. */
|
|
4
|
+
export interface TranslateSkipLine {
|
|
5
|
+
where: string;
|
|
6
|
+
locale: string;
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The where-label of a skipped position: the field's label, then — inside a component —
|
|
11
|
+
* the component and its prop path, else the position below the field (an item counted
|
|
12
|
+
* from 1). Index-bearing segments the editor does not see (`content`, `props`) are left out.
|
|
13
|
+
*/
|
|
14
|
+
export declare function translateSkipLines(skipped: readonly TranslateSkip[], collection: CollectionDef | undefined): TranslateSkipLine[];
|
|
15
|
+
/** The list; renders nothing when everything was translated. */
|
|
16
|
+
export declare function TranslateSkipped({ skipped, collection, }: {
|
|
17
|
+
skipped: readonly TranslateSkip[];
|
|
18
|
+
collection: CollectionDef | undefined;
|
|
19
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { resolveLabel } from '@elytracms/core/cms-core';
|
|
3
|
+
import { AlertTriangle } from 'lucide-react';
|
|
4
|
+
import { Row, Stack, Text } from '@elytracms/studio/ui';
|
|
5
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
6
|
+
import { localeChipLabel } from '@elytracms/studio/lib/locale';
|
|
7
|
+
/**
|
|
8
|
+
* EC-593 (ratified 4e) — what a translation run could NOT translate, shown where the
|
|
9
|
+
* editor reads the outcome: the "Translate document" dialog and the chat's translation
|
|
10
|
+
* proposal card. Before, a localized object or composition was skipped with no word,
|
|
11
|
+
* and a garbled rich-text block silently kept its original.
|
|
12
|
+
*/
|
|
13
|
+
const REASON_KEY = {
|
|
14
|
+
unreadable: 'ai.translate.skipped.unreadable',
|
|
15
|
+
'not-returned': 'ai.translate.skipped.notReturned',
|
|
16
|
+
'structure-changed': 'ai.translate.skipped.structureChanged',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The where-label of a skipped position: the field's label, then — inside a component —
|
|
20
|
+
* the component and its prop path, else the position below the field (an item counted
|
|
21
|
+
* from 1). Index-bearing segments the editor does not see (`content`, `props`) are left out.
|
|
22
|
+
*/
|
|
23
|
+
export function translateSkipLines(skipped, collection) {
|
|
24
|
+
return skipped.map((skip) => {
|
|
25
|
+
const field = collection?.fields.find((f) => f.name === skip.field);
|
|
26
|
+
const label = resolveLabel(field?.form?.label) ?? skip.field;
|
|
27
|
+
const below = skip.component
|
|
28
|
+
? [skip.component.componentId, ...skip.at.slice(skip.at.lastIndexOf('props') + 1)]
|
|
29
|
+
: skip.at;
|
|
30
|
+
const segments = below
|
|
31
|
+
.filter((segment) => segment !== 'content' && segment !== 'props')
|
|
32
|
+
.map((segment) => (typeof segment === 'number' ? `#${segment + 1}` : segment));
|
|
33
|
+
return {
|
|
34
|
+
where: [label, ...segments].join(' › '),
|
|
35
|
+
locale: localeChipLabel(skip.locale),
|
|
36
|
+
reason: translate(REASON_KEY[skip.reason]),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** The list; renders nothing when everything was translated. */
|
|
41
|
+
export function TranslateSkipped({ skipped, collection, }) {
|
|
42
|
+
if (skipped.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return (_jsxs(Stack, { gap: "1", "data-testid": "translate-skipped", children: [_jsxs(Row, { gap: "1", align: "center", children: [_jsx(AlertTriangle, { className: "size-3.5 text-warning" }), _jsx(Text, { size: "xs", weight: "medium", children: translate('ai.translate.skippedTitle', { count: skipped.length }) })] }), translateSkipLines(skipped, collection).map((line, index) => (_jsxs(Text, { size: "xs", tone: "muted", "data-translate-skip": true, children: [line.where, " (", line.locale, ") \u2014 ", line.reason] }, index)))] }));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=translate-skipped.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate-skipped.js","sourceRoot":"","sources":["../../../src/components/ai/translate-skipped.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAG9D;;;;;GAKG;AAEH,MAAM,UAAU,GAAwC;IACtD,UAAU,EAAE,iCAAiC;IAC7C,cAAc,EAAE,kCAAkC;IAClD,mBAAmB,EAAE,uCAAuC;CAC7D,CAAA;AASD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAiC,EACjC,UAAqC;IAErC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,CAAA;QACnE,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAA;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS;YAC1B,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAClF,CAAC,CAAC,IAAI,CAAC,EAAE,CAAA;QACX,MAAM,QAAQ,GAAG,KAAK;aACnB,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,OAAO,CAAC;aACjE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;QAChF,OAAO;YACL,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YACvC,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC;YACpC,MAAM,EAAE,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SAC3C,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,gBAAgB,CAAC,EAC/B,OAAO,EACP,UAAU,GAIX;IACC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,iBAAa,mBAAmB,aAC5C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,aAAa,IAAC,SAAS,EAAC,uBAAuB,GAAG,EACnD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,YAC5B,SAAS,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,GAC7D,IACH,EACL,kBAAkB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC5D,MAAC,IAAI,IAAa,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,0CACrC,IAAI,CAAC,KAAK,QAAI,IAAI,CAAC,MAAM,eAAM,IAAI,CAAC,MAAM,KADlC,KAAK,CAET,CACR,CAAC,IACI,CACT,CAAA;AACH,CAAC","sourcesContent":["import { resolveLabel } from '@elytracms/core/cms-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { AlertTriangle } from 'lucide-react'\nimport { Row, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { localeChipLabel } from '@elytracms/studio/lib/locale'\nimport type { TranslateSkip, TranslateSkipReason } from '@elytracms/studio/lib/ai'\n\n/**\n * EC-593 (ratified 4e) — what a translation run could NOT translate, shown where the\n * editor reads the outcome: the \"Translate document\" dialog and the chat's translation\n * proposal card. Before, a localized object or composition was skipped with no word,\n * and a garbled rich-text block silently kept its original.\n */\n\nconst REASON_KEY: Record<TranslateSkipReason, string> = {\n unreadable: 'ai.translate.skipped.unreadable',\n 'not-returned': 'ai.translate.skipped.notReturned',\n 'structure-changed': 'ai.translate.skipped.structureChanged',\n}\n\n/** One skip as the editor reads it: where (field label › component › position), which language, why. */\nexport interface TranslateSkipLine {\n where: string\n locale: string\n reason: string\n}\n\n/**\n * The where-label of a skipped position: the field's label, then — inside a component —\n * the component and its prop path, else the position below the field (an item counted\n * from 1). Index-bearing segments the editor does not see (`content`, `props`) are left out.\n */\nexport function translateSkipLines(\n skipped: readonly TranslateSkip[],\n collection: CollectionDef | undefined,\n): TranslateSkipLine[] {\n return skipped.map((skip) => {\n const field = collection?.fields.find((f) => f.name === skip.field)\n const label = resolveLabel(field?.form?.label) ?? skip.field\n const below = skip.component\n ? [skip.component.componentId, ...skip.at.slice(skip.at.lastIndexOf('props') + 1)]\n : skip.at\n const segments = below\n .filter((segment) => segment !== 'content' && segment !== 'props')\n .map((segment) => (typeof segment === 'number' ? `#${segment + 1}` : segment))\n return {\n where: [label, ...segments].join(' › '),\n locale: localeChipLabel(skip.locale),\n reason: translate(REASON_KEY[skip.reason]),\n }\n })\n}\n\n/** The list; renders nothing when everything was translated. */\nexport function TranslateSkipped({\n skipped,\n collection,\n}: {\n skipped: readonly TranslateSkip[]\n collection: CollectionDef | undefined\n}) {\n if (skipped.length === 0) return null\n return (\n <Stack gap=\"1\" data-testid=\"translate-skipped\">\n <Row gap=\"1\" align=\"center\">\n <AlertTriangle className=\"size-3.5 text-warning\" />\n <Text size=\"xs\" weight=\"medium\">\n {translate('ai.translate.skippedTitle', { count: skipped.length })}\n </Text>\n </Row>\n {translateSkipLines(skipped, collection).map((line, index) => (\n <Text key={index} size=\"xs\" tone=\"muted\" data-translate-skip>\n {line.where} ({line.locale}) — {line.reason}\n </Text>\n ))}\n </Stack>\n )\n}\n"]}
|
|
@@ -163,11 +163,17 @@ export declare function redirectsView(config?: RedirectsViewConfig): AppViewModu
|
|
|
163
163
|
* Resolve the assets rail's EFFECTIVE schema (EC-319/EC-311): the deployment's
|
|
164
164
|
* OWN `kind:'asset'` collection — hydrated session → playground config,
|
|
165
165
|
* the exact same precedence {@link resolveCmsInitialCollections} gives every
|
|
166
|
-
* other CMS collection — falling back to the
|
|
167
|
-
*
|
|
166
|
+
* other CMS collection — falling back to the ENGINE DEFAULT asset collection
|
|
167
|
+
* (`defaultAssetCollection`, the same declaration the resolver injects for a project
|
|
168
|
+
* that uses asset fields, EC-257) only when nothing resolves. A mount's
|
|
168
169
|
* `nav.assets({ form, fields })` then merges over THAT base (EC-311, additive
|
|
169
170
|
* fields allowed — a project's asset record already carries an open `meta` bag,
|
|
170
171
|
* EC-301). Pure — no React — so it's unit-testable without `CmsSessionGate`.
|
|
172
|
+
*
|
|
173
|
+
* EC-583 (MODE #23): the fallback used to be the engine's TEST fixture
|
|
174
|
+
* (`assetCollection` — alt localized, no copyright/credit), a second "default asset
|
|
175
|
+
* collection" beside the resolver's. Engine fixtures never stand in for project
|
|
176
|
+
* structure; there is one default.
|
|
171
177
|
*/
|
|
172
178
|
export declare function resolveAssetsSchema(collections: readonly CollectionDef[] | undefined, config: AssetsViewConfig): CollectionDef;
|
|
173
179
|
/**
|
|
@@ -189,6 +195,8 @@ export declare function assetsView(config?: AssetsViewConfig): AppViewModule;
|
|
|
189
195
|
export declare function usersView(config?: BuiltinViewConfig): AppViewModule;
|
|
190
196
|
/** The project settings view. */
|
|
191
197
|
export declare function settingsView(config?: BuiltinViewConfig): AppViewModule;
|
|
198
|
+
/** Where the legacy `/builder?page=` lands (pure): the page collection's document or list. */
|
|
199
|
+
export declare function legacyBuilderTarget(pageCollection: string | undefined, page: string | undefined): string;
|
|
192
200
|
/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */
|
|
193
201
|
export declare function builderView(): AppViewModule;
|
|
194
202
|
/**
|
|
@@ -281,9 +289,18 @@ export declare const nav: {
|
|
|
281
289
|
readonly separator: () => import("./module.js").SidebarSeparatorNode;
|
|
282
290
|
};
|
|
283
291
|
/**
|
|
284
|
-
* The CMS schema source for a project mount:
|
|
285
|
-
*
|
|
286
|
-
*
|
|
292
|
+
* The CMS schema source for a project mount: the active project's config-declared
|
|
293
|
+
* collections (AD-11, `resolveDeploymentCollections`) — in EVERY mode, and nothing else.
|
|
294
|
+
*
|
|
295
|
+
* EC-559 / EC-583 — this was a ladder whose first rung was the HYDRATED session's
|
|
296
|
+
* schema (`cms?.collections ?? …`). In the playground that schema was the in-memory
|
|
297
|
+
* adapter's record, a JSON projection with every config-as-code function stripped,
|
|
298
|
+
* and it won over the config — so `validate`, `form.hidden` predicates and function
|
|
299
|
+
* `titleField`s were undefined in the playground (connected mode's adapter served the
|
|
300
|
+
* config, so it had them): one studio, two schema sources, and every playground check
|
|
301
|
+
* of those features a false pass. The store holds no schema now and hydration carries
|
|
302
|
+
* documents only, so the ladder collapses to its last rung. `cms` stays a parameter
|
|
303
|
+
* purely for signature parity with its twins (`resolveCmsInitialDocuments`).
|
|
287
304
|
*
|
|
288
305
|
* EC-479 — every rung is the PROJECT's own schema and there is no rung after
|
|
289
306
|
* them. The config rung used to be gated on playground mode, because outside it
|
|
@@ -297,7 +314,7 @@ export declare const nav: {
|
|
|
297
314
|
* section rail reads `resolveDeploymentCollections()` directly, and now so does
|
|
298
315
|
* the store's last rung, so the two can no longer disagree.
|
|
299
316
|
*/
|
|
300
|
-
export declare function resolveCmsInitialCollections(
|
|
317
|
+
export declare function resolveCmsInitialCollections(_cms: HydratedCms | null): CollectionDef[];
|
|
301
318
|
/**
|
|
302
319
|
* The CMS documents source for a project mount — the document-side twin of
|
|
303
320
|
* {@link resolveCmsInitialCollections}: a hydrated session wins (Convex), then
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import { Blocks, Database, FileCog, Image, LayoutGrid, Settings as SettingsIcon, Signpost, Users, } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { defaultAssetCollection, extendCollectionDef, isSingleton, parseLocaleConfig, resolveSingletonDocument, } from '@elytracms/core/cms-core';
|
|
5
5
|
import { NavigateOnce, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
|
|
6
6
|
import { Text } from '@elytracms/studio/ui';
|
|
7
7
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
8
|
-
import { resolveDeploymentCollections, resolveDeploymentLocaleConfig } from '@elytracms/studio/lib/workspace';
|
|
8
|
+
import { resolveDeploymentCollections, resolveDeploymentLocaleConfig, resolveDeploymentPageCollections, } from '@elytracms/studio/lib/workspace';
|
|
9
9
|
import { cmsDeepLink, componentsDeepLink, usersDeepLink, redirectsDeepLink, assetsDeepLink, groupNode, separatorNode, viewNode, } from './module.js';
|
|
10
10
|
import { SectionSurface } from '../section-surface.js';
|
|
11
11
|
import { SectionStateView } from '../section-state.js';
|
|
@@ -210,14 +210,20 @@ export function redirectsView(config = {}) {
|
|
|
210
210
|
* Resolve the assets rail's EFFECTIVE schema (EC-319/EC-311): the deployment's
|
|
211
211
|
* OWN `kind:'asset'` collection — hydrated session → playground config,
|
|
212
212
|
* the exact same precedence {@link resolveCmsInitialCollections} gives every
|
|
213
|
-
* other CMS collection — falling back to the
|
|
214
|
-
*
|
|
213
|
+
* other CMS collection — falling back to the ENGINE DEFAULT asset collection
|
|
214
|
+
* (`defaultAssetCollection`, the same declaration the resolver injects for a project
|
|
215
|
+
* that uses asset fields, EC-257) only when nothing resolves. A mount's
|
|
215
216
|
* `nav.assets({ form, fields })` then merges over THAT base (EC-311, additive
|
|
216
217
|
* fields allowed — a project's asset record already carries an open `meta` bag,
|
|
217
218
|
* EC-301). Pure — no React — so it's unit-testable without `CmsSessionGate`.
|
|
219
|
+
*
|
|
220
|
+
* EC-583 (MODE #23): the fallback used to be the engine's TEST fixture
|
|
221
|
+
* (`assetCollection` — alt localized, no copyright/credit), a second "default asset
|
|
222
|
+
* collection" beside the resolver's. Engine fixtures never stand in for project
|
|
223
|
+
* structure; there is one default.
|
|
218
224
|
*/
|
|
219
225
|
export function resolveAssetsSchema(collections, config) {
|
|
220
|
-
const base = collections?.find((c) => c.kind === 'asset') ??
|
|
226
|
+
const base = collections?.find((c) => c.kind === 'asset') ?? defaultAssetCollection();
|
|
221
227
|
return extendCollectionDef(base, config, { additiveFields: 'allow' });
|
|
222
228
|
}
|
|
223
229
|
/**
|
|
@@ -285,6 +291,12 @@ export function settingsView(config = {}) {
|
|
|
285
291
|
render: (ctx) => (_jsx(SectionSurface, { project: ctx.project, section: "settings", forcedState: ctx.search.state, children: _jsx(SettingsForm, { project: ctx.project }) })),
|
|
286
292
|
};
|
|
287
293
|
}
|
|
294
|
+
/** Where the legacy `/builder?page=` lands (pure): the page collection's document or list. */
|
|
295
|
+
export function legacyBuilderTarget(pageCollection, page) {
|
|
296
|
+
if (pageCollection === undefined)
|
|
297
|
+
return '';
|
|
298
|
+
return page ? studioPath.cmsDocument(pageCollection, page) : studioPath.cmsCollection(pageCollection);
|
|
299
|
+
}
|
|
288
300
|
/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */
|
|
289
301
|
export function builderView() {
|
|
290
302
|
return {
|
|
@@ -295,9 +307,7 @@ export function builderView() {
|
|
|
295
307
|
hidden: true,
|
|
296
308
|
render: (ctx) => (_jsx(NavigateOnce, { to: STUDIO_PROJECT_ROUTE, params: {
|
|
297
309
|
projectId: ctx.projectId,
|
|
298
|
-
_splat: ctx.search.page
|
|
299
|
-
? studioPath.cmsDocument('page', ctx.search.page)
|
|
300
|
-
: studioPath.cmsCollection('page'),
|
|
310
|
+
_splat: legacyBuilderTarget(resolveDeploymentPageCollections()[0], ctx.search.page),
|
|
301
311
|
}, replace: true })),
|
|
302
312
|
};
|
|
303
313
|
}
|
|
@@ -508,9 +518,18 @@ function ComponentsScreen({ projectId, componentId, state, }) {
|
|
|
508
518
|
return (_jsxs("div", { className: "flex h-full min-h-0", children: [_jsx("aside", { className: "hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block", children: _jsx(RegistryBrowser, { projectId: projectId, activeComponentId: componentId ?? undefined }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: componentId === null ? (_jsx(ComponentsIndex, { projectId: projectId })) : (_jsx(ComponentDetail, { projectId: projectId, componentId: componentId })) })] }));
|
|
509
519
|
}
|
|
510
520
|
/**
|
|
511
|
-
* The CMS schema source for a project mount:
|
|
512
|
-
*
|
|
513
|
-
*
|
|
521
|
+
* The CMS schema source for a project mount: the active project's config-declared
|
|
522
|
+
* collections (AD-11, `resolveDeploymentCollections`) — in EVERY mode, and nothing else.
|
|
523
|
+
*
|
|
524
|
+
* EC-559 / EC-583 — this was a ladder whose first rung was the HYDRATED session's
|
|
525
|
+
* schema (`cms?.collections ?? …`). In the playground that schema was the in-memory
|
|
526
|
+
* adapter's record, a JSON projection with every config-as-code function stripped,
|
|
527
|
+
* and it won over the config — so `validate`, `form.hidden` predicates and function
|
|
528
|
+
* `titleField`s were undefined in the playground (connected mode's adapter served the
|
|
529
|
+
* config, so it had them): one studio, two schema sources, and every playground check
|
|
530
|
+
* of those features a false pass. The store holds no schema now and hydration carries
|
|
531
|
+
* documents only, so the ladder collapses to its last rung. `cms` stays a parameter
|
|
532
|
+
* purely for signature parity with its twins (`resolveCmsInitialDocuments`).
|
|
514
533
|
*
|
|
515
534
|
* EC-479 — every rung is the PROJECT's own schema and there is no rung after
|
|
516
535
|
* them. The config rung used to be gated on playground mode, because outside it
|
|
@@ -524,8 +543,8 @@ function ComponentsScreen({ projectId, componentId, state, }) {
|
|
|
524
543
|
* section rail reads `resolveDeploymentCollections()` directly, and now so does
|
|
525
544
|
* the store's last rung, so the two can no longer disagree.
|
|
526
545
|
*/
|
|
527
|
-
export function resolveCmsInitialCollections(
|
|
528
|
-
return
|
|
546
|
+
export function resolveCmsInitialCollections(_cms) {
|
|
547
|
+
return resolveDeploymentCollections();
|
|
529
548
|
}
|
|
530
549
|
/**
|
|
531
550
|
* The CMS documents source for a project mount — the document-side twin of
|
|
@@ -596,7 +615,7 @@ function CmsScreen({ project, collectionId, documentId, state, field, focusColle
|
|
|
596
615
|
return _jsx(SectionStateView, { state: state, title: translate('nav.cms.label') });
|
|
597
616
|
}
|
|
598
617
|
return (_jsx(CmsSessionGate, { children: (cms) => {
|
|
599
|
-
return (_jsx(CmsProvider, { projectId: projectId, initialCollections: resolveCmsInitialCollections(cms), initialDocuments: resolveCmsInitialDocuments(cms), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, project),
|
|
618
|
+
return (_jsx(CmsProvider, { projectId: projectId, initialCollections: resolveCmsInitialCollections(cms), initialDocuments: resolveCmsInitialDocuments(cms), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialRevisions: cms?.revisions, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, project),
|
|
600
619
|
// EC-461: a hydrated mount (cms non-null — both modes now) seeds the
|
|
601
620
|
// CURRENT persisted state, so staged docs are superseded by it; only
|
|
602
621
|
// a static kit/fixture seed lets staged entries win.
|