@elytracms/studio 0.0.92 → 0.0.94
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/analytics-cost.test.ts +817 -0
- 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/auth.ts +5 -2
- package/convex/authority.ts +35 -11
- package/convex/cms.ts +25 -11
- package/convex/contract-parity.test.ts +25 -3
- package/convex/delivery.ts +12 -9
- package/convex/document-revalidate.test.ts +10 -12
- package/convex/guard.ts +9 -7
- package/convex/http-routes.test.ts +18 -8
- package/convex/http.ts +23 -17
- package/convex/ingest.test.ts +326 -0
- package/convex/ingest.ts +281 -0
- package/convex/invites.ts +13 -5
- package/convex/members.ts +10 -4
- package/convex/orphan-hydration.test.ts +116 -0
- package/convex/rail-write-parity.test.ts +11 -8
- package/convex/record-functions.ts +325 -0
- package/convex/records.test.ts +156 -0
- package/convex/redirect-migration.test.ts +205 -0
- package/convex/redirect-revalidate.test.ts +5 -1
- package/convex/redirects.ts +120 -79
- package/convex/role-policy.test.ts +315 -0
- package/convex/roles.ts +83 -0
- package/convex/schema.ts +36 -0
- package/convex/triggers.ts +21 -1
- package/dist/addon.d.ts +71 -0
- package/dist/addon.js +67 -0
- package/dist/addon.js.map +1 -0
- package/dist/components/account/account-settings.js +2 -2
- package/dist/components/account/account-settings.js.map +1 -1
- package/dist/components/account/credential-panel.js +2 -2
- package/dist/components/account/credential-panel.js.map +1 -1
- package/dist/components/account/self-account-schema.d.ts +6 -15
- package/dist/components/account/self-account-schema.js +52 -34
- package/dist/components/account/self-account-schema.js.map +1 -1
- package/dist/components/account/self-account-view-source.d.ts +1 -1
- package/dist/components/account/self-account-view-source.js +6 -4
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.js +6 -6
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/ai-cms-panel.js +2 -2
- package/dist/components/ai/ai-cms-panel.js.map +1 -1
- package/dist/components/ai/ai-panel-toggle.js +4 -4
- package/dist/components/ai/ai-panel-toggle.js.map +1 -1
- package/dist/components/ai/chat-ui.js +2 -2
- package/dist/components/ai/chat-ui.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +2 -2
- package/dist/components/ai/proposal-card.js +4 -3
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/proposal-issues.js +2 -2
- package/dist/components/ai/proposal-issues.js.map +1 -1
- package/dist/components/ai/translate-document-action.js +2 -2
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.js +10 -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 +50 -2
- package/dist/components/app-views/default-modules.js +65 -20
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +53 -3
- package/dist/components/app-views/module.js +14 -7
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.js +1 -1
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +11 -2
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-detail.js +20 -10
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-grid.js +2 -2
- package/dist/components/assets/asset-grid.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 +6 -5
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +5 -5
- 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-module.js +1 -1
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +16 -2
- package/dist/components/assets/assets-store.js +28 -6
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.js +1 -1
- package/dist/components/assets/assets-view-preference.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/auth-context.d.ts +1 -1
- package/dist/components/auth-context.js +6 -6
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +15 -27
- package/dist/components/builder/blocks-model.js +38 -44
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +8 -4
- package/dist/components/builder/blocks-panel.js +10 -9
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +104 -15
- package/dist/components/builder/builder-store.js +157 -32
- 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-breakpoints.js +1 -1
- package/dist/components/builder/canvas-breakpoints.js.map +1 -1
- 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 +9 -9
- package/dist/components/builder/canvas-dnd.js +6 -6
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
- package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +28 -20
- package/dist/components/builder/canvas-render-context.js +46 -47
- 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-theme-notice.js +2 -2
- package/dist/components/builder/canvas-theme-notice.js.map +1 -1
- package/dist/components/builder/canvas.js +35 -8
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +16 -1
- package/dist/components/builder/clipboard-actions.js +48 -11
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +4 -16
- package/dist/components/builder/composition-field-editor.js +32 -43
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- 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/drag-session.d.ts +1 -1
- package/dist/components/builder/drag-session.js.map +1 -1
- package/dist/components/builder/fixture-thumbnail.js +1 -1
- package/dist/components/builder/fixture-thumbnail.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +18 -26
- package/dist/components/builder/insert-context.js +47 -94
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/layout-inspector.js +1 -1
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/node-title.js +1 -1
- package/dist/components/builder/node-title.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +4 -4
- package/dist/components/builder/page-tree.js +8 -8
- 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 +8 -4
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +68 -30
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +79 -1
- package/dist/components/builder/reuse.js +70 -3
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/rich-text-toolbar.js +1 -1
- package/dist/components/builder/rich-text-toolbar.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/builder/selection-overlay.js +3 -3
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +2 -2
- package/dist/components/builder/spacing-handles.js +3 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +38 -1
- package/dist/components/cms/asset-field.js +184 -60
- 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 +14 -20
- 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 +5 -26
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +2 -2
- package/dist/components/cms/bulk-bar.js +2 -2
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +1 -1
- package/dist/components/cms/cell-edit.js +1 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cell.d.ts +1 -1
- package/dist/components/cms/cell.js +3 -3
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-index.js +2 -2
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-labels.js +2 -2
- package/dist/components/cms/cms-labels.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +7 -6
- package/dist/components/cms/cms-store.js +23 -43
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +7 -7
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.d.ts +5 -2
- package/dist/components/cms/detail-editor.js +20 -15
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.js +1 -1
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/doc-state.js +1 -1
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/document-diff-view.d.ts +3 -1
- package/dist/components/cms/document-diff-view.js +17 -12
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/document-editor-context.js +1 -1
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-staging.js +1 -1
- package/dist/components/cms/document-staging.js.map +1 -1
- package/dist/components/cms/document-url-path.js +2 -2
- package/dist/components/cms/document-url-path.js.map +1 -1
- package/dist/components/cms/duplicate-document.d.ts +1 -1
- package/dist/components/cms/duplicate-document.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -3
- package/dist/components/cms/editable-cell.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 +1 -1
- package/dist/components/cms/field-collaboration.js +1 -1
- package/dist/components/cms/field-collaboration.js.map +1 -1
- package/dist/components/cms/field-connection.d.ts +1 -1
- package/dist/components/cms/field-connection.js +2 -2
- package/dist/components/cms/field-connection.js.map +1 -1
- package/dist/components/cms/field-control-lib.d.ts +15 -1
- package/dist/components/cms/field-control-lib.js +23 -4
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +4 -4
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.d.ts +1 -1
- package/dist/components/cms/field-renderer.js +2 -2
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +1 -1
- package/dist/components/cms/filter-builder.js +3 -3
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +21 -30
- package/dist/components/cms/gate-validation.js +11 -32
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/import-export.js +2 -2
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issue-copy.js +1 -1
- package/dist/components/cms/issue-copy.js.map +1 -1
- package/dist/components/cms/issues-pane.js +3 -3
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/object-array-field.js +6 -6
- 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 +14 -7
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/reference-confirm.d.ts +1 -1
- package/dist/components/cms/reference-confirm.js +2 -2
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +2 -2
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/rich-text/block-style.js +1 -1
- package/dist/components/cms/rich-text/block-style.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.js +4 -4
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +3 -3
- package/dist/components/cms/rich-text/editor.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 +3 -3
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.js +3 -3
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.js +1 -1
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
- package/dist/components/cms/rich-text/font-size-control.js +2 -2
- package/dist/components/cms/rich-text/font-size-control.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/rich-text/lazy.js +1 -1
- package/dist/components/cms/rich-text/lazy.js.map +1 -1
- package/dist/components/cms/rich-text/link-control.js +2 -2
- package/dist/components/cms/rich-text/link-control.js.map +1 -1
- package/dist/components/cms/rich-text/slash-command.js +3 -3
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +1 -1
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/rich-text/unknown-node.js +2 -2
- package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
- package/dist/components/cms/rich-text-preview.js +2 -2
- package/dist/components/cms/rich-text-preview.js.map +1 -1
- package/dist/components/cms/row-actions.d.ts +1 -1
- package/dist/components/cms/row-actions.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.d.ts +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -2
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -1
- package/dist/components/cms/use-discard-controller.js +1 -1
- package/dist/components/cms/use-discard-controller.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +7 -1
- package/dist/components/cms/use-document-draft.js +16 -2
- package/dist/components/cms/use-document-draft.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -1
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/use-field-focus.d.ts +1 -1
- package/dist/components/cms/use-field-focus.js +2 -2
- package/dist/components/cms/use-field-focus.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +1 -1
- package/dist/components/cms/use-live-url.js +3 -3
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.d.ts +2 -3
- package/dist/components/cms/use-publish-controller.js +8 -20
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +1 -1
- package/dist/components/cms/use-published-perspective.js.map +1 -1
- package/dist/components/cms/use-restore-version.d.ts +1 -1
- package/dist/components/cms/use-restore-version.js +1 -1
- package/dist/components/cms/use-restore-version.js.map +1 -1
- package/dist/components/cms/version-compare-lazy.js +1 -1
- package/dist/components/cms/version-compare-lazy.js.map +1 -1
- package/dist/components/cms/version-compare.js +1 -1
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +11 -4
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.js +3 -3
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +5 -3
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +1 -1
- package/dist/components/collection-view/collection-view-context.js +1 -1
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
- package/dist/components/collection-view/collection-view-surface.js +18 -0
- package/dist/components/collection-view/collection-view-surface.js.map +1 -0
- package/dist/components/components/component-detail.js +10 -10
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.d.ts +2 -2
- package/dist/components/components/component-preview.js +5 -5
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/component-status.d.ts +1 -1
- package/dist/components/components/component-status.js +1 -1
- package/dist/components/components/component-status.js.map +1 -1
- package/dist/components/components/components-index.js +2 -2
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +1 -1
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/registry-browser.js +3 -3
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +1 -1
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +4 -3
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -10
- package/dist/components/components/transfer.js +13 -12
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/current-user-menu.js +1 -1
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/delivery-health-banner.js +2 -2
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/deployment-setup-gate.js +3 -3
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/dev-login-badge.js +2 -2
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/guards.js +1 -1
- package/dist/components/guards.js.map +1 -1
- package/dist/components/inspector/document-panes.d.ts +54 -0
- package/dist/components/inspector/document-panes.js +41 -0
- package/dist/components/inspector/document-panes.js.map +1 -0
- package/dist/components/inspector/inspector-sidebar.js +1 -1
- package/dist/components/inspector/inspector-sidebar.js.map +1 -1
- package/dist/components/nav-issues-registry.d.ts +53 -0
- package/dist/components/nav-issues-registry.js +69 -0
- package/dist/components/nav-issues-registry.js.map +1 -0
- package/dist/components/nav-issues.js +4 -4
- package/dist/components/nav-issues.js.map +1 -1
- package/dist/components/operation-error.d.ts +1 -1
- package/dist/components/operation-error.js +4 -4
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/playground-badge.js +2 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +3 -3
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +2 -2
- package/dist/components/project-session.js +5 -5
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-store-status.js +1 -1
- package/dist/components/rail-store-status.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +6 -5
- package/dist/components/rail-view-source.js +4 -3
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/records/record-lens.d.ts +51 -0
- package/dist/components/records/record-lens.js +156 -0
- package/dist/components/records/record-lens.js.map +1 -0
- package/dist/components/records/record-rail.d.ts +86 -0
- package/dist/components/records/record-rail.js +241 -0
- package/dist/components/records/record-rail.js.map +1 -0
- package/dist/components/redirects/redirects-schema.js +1 -1
- package/dist/components/redirects/redirects-schema.js.map +1 -1
- package/dist/components/references/references-panel.js +2 -2
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +27 -11
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.js +2 -2
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/section-surface.js +2 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/session-expired-overlay.js +3 -3
- package/dist/components/session-expired-overlay.js.map +1 -1
- package/dist/components/settings-form.js +1 -1
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/sign-in-needed-badge.js +1 -1
- package/dist/components/sign-in-needed-badge.js.map +1 -1
- package/dist/components/sign-in-view.d.ts +2 -2
- package/dist/components/sign-in-view.js +5 -5
- package/dist/components/sign-in-view.js.map +1 -1
- package/dist/components/studio-context.js +3 -3
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +30 -3
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.js +6 -5
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +1 -1
- package/dist/components/theme-context.js +2 -2
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +1 -1
- package/dist/components/use-hydration-phase.js +1 -1
- package/dist/components/use-hydration-phase.js.map +1 -1
- package/dist/components/users/invite-user-bar.d.ts +27 -0
- package/dist/components/users/invite-user-bar.js +25 -9
- package/dist/components/users/invite-user-bar.js.map +1 -1
- package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
- package/dist/components/users/role-policy-drift-issues.js +71 -0
- package/dist/components/users/role-policy-drift-issues.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +1 -1
- package/dist/components/users/users-collection-view-source.js +4 -2
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/components/users/users-module.js +1 -1
- package/dist/components/users/users-module.js.map +1 -1
- package/dist/components/users/users-schema.d.ts +10 -1
- package/dist/components/users/users-schema.js +45 -25
- package/dist/components/users/users-schema.js.map +1 -1
- package/dist/components/users/users-store.js +3 -3
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +1 -1
- package/dist/components/workspace-projects.js +1 -1
- package/dist/components/workspace-projects.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 +6 -0
- 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 +3 -1
- package/dist/lib/ai/index.js +3 -1
- 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/node-handler.d.ts +1 -1
- package/dist/lib/ai/node-handler.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 +83 -87
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +32 -4
- package/dist/lib/ai/translate-document.js +145 -47
- 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 +15 -12
- package/dist/lib/ai/update-fields.js +112 -188
- 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/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-issues.js +1 -1
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.d.ts +18 -9
- package/dist/lib/clipboard/paste-target.js +10 -9
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/field-patch.d.ts +0 -10
- package/dist/lib/cms/field-patch.js +4 -30
- package/dist/lib/cms/field-patch.js.map +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 +9 -9
- package/dist/lib/cms-views/view-model.js +2 -2
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +14 -5
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.js +3 -2
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/data/content-session-refresh.d.ts +1 -1
- package/dist/lib/data/content-session-refresh.js.map +1 -1
- package/dist/lib/data/playground-unload-guard.d.ts +1 -1
- package/dist/lib/data/playground-unload-guard.js +1 -1
- package/dist/lib/data/playground-unload-guard.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/i18n.js +1 -1
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +44 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +38 -2
- package/dist/lib/i18n/messages.en.js +51 -3
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +34 -6
- package/dist/lib/operations/project-runtime-config.js +50 -10
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/publish-with-revision.d.ts +33 -1
- package/dist/lib/operations/publish-with-revision.js +32 -0
- package/dist/lib/operations/publish-with-revision.js.map +1 -1
- package/dist/lib/operations/runtime-validator.d.ts +19 -68
- package/dist/lib/operations/runtime-validator.js +12 -138
- package/dist/lib/operations/runtime-validator.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +17 -2
- package/dist/lib/operations/studio-operations.js +18 -11
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +16 -3
- package/dist/lib/persistence/convex-adapter.js +83 -47
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/index.js +14 -1
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.js +4 -2
- 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/reference-model.js +1 -1
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +1 -1
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +14 -11
- package/dist/lib/workspace/index.js +20 -24
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/mount.d.ts +28 -0
- package/dist/mount.js +27 -0
- package/dist/mount.js.map +1 -0
- package/dist/router/root.js +10 -10
- package/dist/router/root.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +2 -2
- package/package.json +15 -11
- 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/redirects/redirects-collection-view-source.d.ts +0 -53
- package/dist/components/redirects/redirects-collection-view-source.js +0 -146
- package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
- package/dist/components/redirects/redirects-module.d.ts +0 -21
- package/dist/components/redirects/redirects-module.js +0 -40
- package/dist/components/redirects/redirects-module.js.map +0 -1
- package/dist/components/redirects/redirects-store.d.ts +0 -68
- package/dist/components/redirects/redirects-store.js +0 -132
- package/dist/components/redirects/redirects-store.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
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react';
|
|
2
|
-
import { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core';
|
|
3
|
-
import { studioPath } from '@elytracms/studio/router';
|
|
4
|
-
import { useRedirects } from './redirects-store.js';
|
|
5
|
-
import { redirectsCollectionDef } from './redirects-schema.js';
|
|
6
|
-
import { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source.js';
|
|
7
|
-
import { parseLocaleConfig } from '@elytracms/core/cms-core';
|
|
8
|
-
/** The redirects rail is not localized — one implicit locale keeps the lens honest. */
|
|
9
|
-
const REDIRECTS_LOCALE = 'en';
|
|
10
|
-
const REDIRECTS_LOCALE_CONFIG = parseLocaleConfig({
|
|
11
|
-
default: REDIRECTS_LOCALE,
|
|
12
|
-
locales: [REDIRECTS_LOCALE],
|
|
13
|
-
});
|
|
14
|
-
/**
|
|
15
|
-
* The capability profile of the redirects rail (EC-303): content-gated, create
|
|
16
|
-
* + remove, no publish/versions/locales — redirects are CONTENT (an editor
|
|
17
|
-
* owns them), so unlike the users rail this source allows `create`, is NOT
|
|
18
|
-
* admin-only, and needs no principal field-access policy. Hoisted to a
|
|
19
|
-
* module-level constant (EC-327), mirroring `ASSET_COLLECTION_CAPABILITIES`.
|
|
20
|
-
*/
|
|
21
|
-
export const REDIRECT_COLLECTION_CAPABILITIES = {
|
|
22
|
-
publish: false,
|
|
23
|
-
versions: false,
|
|
24
|
-
locales: false,
|
|
25
|
-
inlineEdit: false,
|
|
26
|
-
create: true, // editors create redirects
|
|
27
|
-
duplicate: false, // EC-442 owner call: no rail Duplicate for now (redirects deferred)
|
|
28
|
-
remove: true,
|
|
29
|
-
bulk: true, // EC-433: bulk delete = the same per-id remove, honestly reported
|
|
30
|
-
liveUrl: false,
|
|
31
|
-
hierarchy: false,
|
|
32
|
-
importExport: false,
|
|
33
|
-
adminOnly: false, // content, not administration
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* The canonical redirect fields — the storage/delivery contract of the primitive
|
|
37
|
-
* rail. Anything a mount adds via *additive* `nav.redirects({ fields })` (EC-311
|
|
38
|
-
* Tier 2) is by definition NOT one of these, so its value round-trips through the
|
|
39
|
-
* record's open `meta` bag. Derived from the built-in def so the two never drift.
|
|
40
|
-
*/
|
|
41
|
-
const CANONICAL_REDIRECT_FIELDS = new Set(redirectsCollectionDef.fields.map((f) => f.name));
|
|
42
|
-
/**
|
|
43
|
-
* Project a stored redirect into the lens's `values` shape. EC-311 Tier 2:
|
|
44
|
-
* additive schema fields round-trip through the open meta bag, spread FIRST so a
|
|
45
|
-
* canonical column always wins over a same-named meta key (RESIDUE-3c, EC-577):
|
|
46
|
-
* spread last, a stored `meta.to` displayed as the redirect's target and a
|
|
47
|
-
* re-save wrote it into the canonical `to`. The record schema now refuses such a
|
|
48
|
-
* key on write; this order keeps a row stored before that harmless.
|
|
49
|
-
*/
|
|
50
|
-
export function redirectToDocument(entry) {
|
|
51
|
-
return {
|
|
52
|
-
collection: redirectsCollectionDef.id,
|
|
53
|
-
id: entry.id,
|
|
54
|
-
values: {
|
|
55
|
-
...(entry.meta ?? {}),
|
|
56
|
-
from: entry.from,
|
|
57
|
-
to: entry.to,
|
|
58
|
-
permanent: entry.permanent,
|
|
59
|
-
enabled: entry.enabled,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Map an edited lens document back to the redirect write (EC-577). A column the
|
|
65
|
-
* document does not carry is left out of the write, so the stored value is kept:
|
|
66
|
-
* `permanent`/`enabled` get their defaults only when the save CREATES the
|
|
67
|
-
* redirect, applied once inside the adapter's transaction — this lens no longer
|
|
68
|
-
* restates them (`?? true` here was the third copy of the default, RESIDUE-3b).
|
|
69
|
-
* `from`/`to` are always sent: the editor owns them, and an empty one is refused
|
|
70
|
-
* visibly by the write's validation rather than kept silently.
|
|
71
|
-
*/
|
|
72
|
-
export function documentToSaveRedirect(doc) {
|
|
73
|
-
const values = doc.values ?? {};
|
|
74
|
-
// EC-311 Tier 2: any value keyed outside the canonical set comes from a mount's
|
|
75
|
-
// additive `nav.redirects({ fields })` override — persist it in the meta bag. The
|
|
76
|
-
// editor holds the complete additive set, so this is the whole bag (replace, not
|
|
77
|
-
// merge). Omit `meta` entirely for the default rail so its saves are unchanged.
|
|
78
|
-
const meta = metaBagProjection(values, CANONICAL_REDIRECT_FIELDS);
|
|
79
|
-
return {
|
|
80
|
-
id: doc.id,
|
|
81
|
-
from: typeof values.from === 'string' ? values.from : '',
|
|
82
|
-
to: typeof values.to === 'string' ? values.to : '',
|
|
83
|
-
...(typeof values.permanent === 'boolean' ? { permanent: values.permanent } : {}),
|
|
84
|
-
...(typeof values.enabled === 'boolean' ? { enabled: values.enabled } : {}),
|
|
85
|
-
...(Object.keys(meta).length > 0 ? { meta } : {}),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —
|
|
90
|
-
* the portable list/edit surfaces over the editorial redirects store instead of
|
|
91
|
-
* the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users
|
|
92
|
-
* rail this source allows `create`, is NOT admin-only, and needs no principal
|
|
93
|
-
* field-access policy. No publish/versions/locales/inline/import — a redirect
|
|
94
|
-
* is a small record saved explicitly (a saved+enabled one is live); bulk
|
|
95
|
-
* delete rides the shared rail runner (EC-433).
|
|
96
|
-
*
|
|
97
|
-
* Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).
|
|
98
|
-
*
|
|
99
|
-
* `schema` defaults to the engine-owned {@link redirectsCollectionDef}; a mount may
|
|
100
|
-
* pass a customized def (EC-311 — built-in fields overridden by equal name via
|
|
101
|
-
* `nav.redirects({ fields })`). The canonical `from`/`to`/`permanent`/`enabled`
|
|
102
|
-
* names+types are contract-locked, so the projection helpers still read the same
|
|
103
|
-
* canonical values; a mount's *additive* fields (Tier 2) round-trip through the
|
|
104
|
-
* record's open `meta` bag ({@link documentToSaveRedirect}/{@link redirectToDocument}).
|
|
105
|
-
*
|
|
106
|
-
* The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`
|
|
107
|
-
* plumbing and the assembled source; this hook supplies only the redirects-store
|
|
108
|
-
* reads/writes, the capability profile, and the registry-driven `validate`.
|
|
109
|
-
*/
|
|
110
|
-
export function useRedirectsCollectionViewSource(projectId, schema = redirectsCollectionDef) {
|
|
111
|
-
const store = useRedirects();
|
|
112
|
-
// EC-311: run the (possibly customized) schema's validation — required/pattern and
|
|
113
|
-
// custom `validate` fns on an overridden field must actually fire in the editor.
|
|
114
|
-
const registry = useMemo(() => createCollectionRegistry([schema]), [schema]);
|
|
115
|
-
const documents = useCallback(() => store.redirects.map(redirectToDocument), [store.redirects]);
|
|
116
|
-
const get = useCallback((ref) => {
|
|
117
|
-
const record = store.get(ref.id);
|
|
118
|
-
return record ? redirectToDocument(record) : undefined;
|
|
119
|
-
}, [store]);
|
|
120
|
-
const titleOf = useCallback((ref) => store.get(ref.id)?.from || '(new redirect)', [store]);
|
|
121
|
-
const validate = useCallback((doc) => validateDocument(registry, doc, { localeConfig: REDIRECTS_LOCALE_CONFIG }), [registry]);
|
|
122
|
-
const create = useCallback(() => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }), [store]);
|
|
123
|
-
const save = useCallback((doc) => store.save(documentToSaveRedirect(doc)), [store]);
|
|
124
|
-
const remove = useCallback((ref) => store.remove(ref.id), [store]);
|
|
125
|
-
return useRailViewSource({
|
|
126
|
-
projectId,
|
|
127
|
-
schema,
|
|
128
|
-
capabilities: REDIRECT_COLLECTION_CAPABILITIES,
|
|
129
|
-
localeConfig: REDIRECTS_LOCALE_CONFIG,
|
|
130
|
-
documents,
|
|
131
|
-
get,
|
|
132
|
-
titleOf,
|
|
133
|
-
// No `referenceCandidates`/`collections` override: redirects carry no
|
|
134
|
-
// relation/asset fields, so nothing cross-collection to resolve (the
|
|
135
|
-
// factory's `() => []` / `[schema]` defaults apply).
|
|
136
|
-
detailPath: (id) => studioPath.redirect(id),
|
|
137
|
-
listPath: studioPath.redirects(),
|
|
138
|
-
validate,
|
|
139
|
-
create,
|
|
140
|
-
save,
|
|
141
|
-
remove,
|
|
142
|
-
normalizeSaveResult: normalizeOkResult,
|
|
143
|
-
normalizeRemoveResult: normalizeOkResult,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
//# sourceMappingURL=redirects-collection-view-source.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useRedirects } from './redirects-store'\nimport { redirectsCollectionDef } from './redirects-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { parseLocaleConfig } from '@elytracms/core/cms-core'\n\n/** The redirects rail is not localized — one implicit locale keeps the lens honest. */\nconst REDIRECTS_LOCALE: Locale = 'en'\nconst REDIRECTS_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({\n default: REDIRECTS_LOCALE,\n locales: [REDIRECTS_LOCALE],\n})\n\n/**\n * The capability profile of the redirects rail (EC-303): content-gated, create\n * + remove, no publish/versions/locales — redirects are CONTENT (an editor\n * owns them), so unlike the users rail this source allows `create`, is NOT\n * admin-only, and needs no principal field-access policy. Hoisted to a\n * module-level constant (EC-327), mirroring `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const REDIRECT_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true, // editors create redirects\n duplicate: false, // EC-442 owner call: no rail Duplicate for now (redirects deferred)\n remove: true,\n bulk: true, // EC-433: bulk delete = the same per-id remove, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false, // content, not administration\n}\n\n/**\n * The canonical redirect fields — the storage/delivery contract of the primitive\n * rail. Anything a mount adds via *additive* `nav.redirects({ fields })` (EC-311\n * Tier 2) is by definition NOT one of these, so its value round-trips through the\n * record's open `meta` bag. Derived from the built-in def so the two never drift.\n */\nconst CANONICAL_REDIRECT_FIELDS = new Set(redirectsCollectionDef.fields.map((f) => f.name))\n\n/**\n * Project a stored redirect into the lens's `values` shape. EC-311 Tier 2:\n * additive schema fields round-trip through the open meta bag, spread FIRST so a\n * canonical column always wins over a same-named meta key (RESIDUE-3c, EC-577):\n * spread last, a stored `meta.to` displayed as the redirect's target and a\n * re-save wrote it into the canonical `to`. The record schema now refuses such a\n * key on write; this order keeps a row stored before that harmless.\n */\nexport function redirectToDocument(entry: RedirectEntry): CmsDocument {\n return {\n collection: redirectsCollectionDef.id,\n id: entry.id,\n values: {\n ...(entry.meta ?? {}),\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n enabled: entry.enabled,\n },\n }\n}\n\n/**\n * Map an edited lens document back to the redirect write (EC-577). A column the\n * document does not carry is left out of the write, so the stored value is kept:\n * `permanent`/`enabled` get their defaults only when the save CREATES the\n * redirect, applied once inside the adapter's transaction — this lens no longer\n * restates them (`?? true` here was the third copy of the default, RESIDUE-3b).\n * `from`/`to` are always sent: the editor owns them, and an empty one is refused\n * visibly by the write's validation rather than kept silently.\n */\nexport function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput {\n const values = doc.values ?? {}\n // EC-311 Tier 2: any value keyed outside the canonical set comes from a mount's\n // additive `nav.redirects({ fields })` override — persist it in the meta bag. The\n // editor holds the complete additive set, so this is the whole bag (replace, not\n // merge). Omit `meta` entirely for the default rail so its saves are unchanged.\n const meta = metaBagProjection(values, CANONICAL_REDIRECT_FIELDS)\n return {\n id: doc.id,\n from: typeof values.from === 'string' ? values.from : '',\n to: typeof values.to === 'string' ? values.to : '',\n ...(typeof values.permanent === 'boolean' ? { permanent: values.permanent } : {}),\n ...(typeof values.enabled === 'boolean' ? { enabled: values.enabled } : {}),\n ...(Object.keys(meta).length > 0 ? { meta } : {}),\n }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —\n * the portable list/edit surfaces over the editorial redirects store instead of\n * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users\n * rail this source allows `create`, is NOT admin-only, and needs no principal\n * field-access policy. No publish/versions/locales/inline/import — a redirect\n * is a small record saved explicitly (a saved+enabled one is live); bulk\n * delete rides the shared rail runner (EC-433).\n *\n * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).\n *\n * `schema` defaults to the engine-owned {@link redirectsCollectionDef}; a mount may\n * pass a customized def (EC-311 — built-in fields overridden by equal name via\n * `nav.redirects({ fields })`). The canonical `from`/`to`/`permanent`/`enabled`\n * names+types are contract-locked, so the projection helpers still read the same\n * canonical values; a mount's *additive* fields (Tier 2) round-trip through the\n * record's open `meta` bag ({@link documentToSaveRedirect}/{@link redirectToDocument}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the redirects-store\n * reads/writes, the capability profile, and the registry-driven `validate`.\n */\nexport function useRedirectsCollectionViewSource(\n projectId: string,\n schema: CollectionDef = redirectsCollectionDef,\n): CollectionViewSource {\n const store = useRedirects()\n // EC-311: run the (possibly customized) schema's validation — required/pattern and\n // custom `validate` fns on an overridden field must actually fire in the editor.\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(() => store.redirects.map(redirectToDocument), [store.redirects])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? redirectToDocument(record) : undefined\n },\n [store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => store.get(ref.id)?.from || '(new redirect)',\n [store],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: REDIRECTS_LOCALE_CONFIG }),\n [registry],\n )\n const create = useCallback(\n (): DocumentRef => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }),\n [store],\n )\n const save = useCallback((doc: CmsDocument) => store.save(documentToSaveRedirect(doc)), [store])\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: REDIRECT_COLLECTION_CAPABILITIES,\n localeConfig: REDIRECTS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: redirects carry no\n // relation/asset fields, so nothing cross-collection to resolve (the\n // factory's `() => []` / `[schema]` defaults apply).\n detailPath: (id: string) => studioPath.redirect(id),\n listPath: studioPath.redirects(),\n validate,\n create,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { CollectionDef } from '@elytracms/core/cms-core';
|
|
2
|
-
/**
|
|
3
|
-
* The studio Redirects module (EC-303). Editorial redirects as data — the reuse
|
|
4
|
-
* proof (with the users rail) that the portable interaction surfaces serve any
|
|
5
|
-
* rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME
|
|
6
|
-
* `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a
|
|
7
|
-
* redirects {@link useRedirectsCollectionViewSource}. The redirects capability
|
|
8
|
-
* profile (content-gated, create + remove, no publish/versions/locales) drives
|
|
9
|
-
* which regions appear; the edit shell's explicit-save path (no publish) makes a
|
|
10
|
-
* saved+enabled redirect live.
|
|
11
|
-
*
|
|
12
|
-
* `redirectId === null` is the list; a `redirectId` opens that redirect's detail
|
|
13
|
-
* (routed via `studioPath.redirect`). No admin gate — redirects are content, so
|
|
14
|
-
* viewers read and editors write (enforced at the chokepoint + Convex boundary).
|
|
15
|
-
*/
|
|
16
|
-
export declare function RedirectsModule({ projectId, redirectId, schema, }: {
|
|
17
|
-
projectId: string;
|
|
18
|
-
redirectId?: string | null;
|
|
19
|
-
/** EC-311: a mount-customized redirects def (built-in fields overridden by name). */
|
|
20
|
-
schema?: CollectionDef;
|
|
21
|
-
}): import("react").JSX.Element;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { translate } from '@elytracms/studio/lib/i18n';
|
|
3
|
-
import { RedirectsProvider, useRedirects } from './redirects-store.js';
|
|
4
|
-
import { useRedirectsCollectionViewSource } from './redirects-collection-view-source.js';
|
|
5
|
-
import { CollectionViewProvider } from '../collection-view/collection-view-context.js';
|
|
6
|
-
import { RailStoreStatus } from '../rail-store-status.js';
|
|
7
|
-
import { LoadedCollectionTable } from '../cms/collection-table.js';
|
|
8
|
-
import { PortableDetailEditor } from '../cms/detail-editor.js';
|
|
9
|
-
/**
|
|
10
|
-
* The studio Redirects module (EC-303). Editorial redirects as data — the reuse
|
|
11
|
-
* proof (with the users rail) that the portable interaction surfaces serve any
|
|
12
|
-
* rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME
|
|
13
|
-
* `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a
|
|
14
|
-
* redirects {@link useRedirectsCollectionViewSource}. The redirects capability
|
|
15
|
-
* profile (content-gated, create + remove, no publish/versions/locales) drives
|
|
16
|
-
* which regions appear; the edit shell's explicit-save path (no publish) makes a
|
|
17
|
-
* saved+enabled redirect live.
|
|
18
|
-
*
|
|
19
|
-
* `redirectId === null` is the list; a `redirectId` opens that redirect's detail
|
|
20
|
-
* (routed via `studioPath.redirect`). No admin gate — redirects are content, so
|
|
21
|
-
* viewers read and editors write (enforced at the chokepoint + Convex boundary).
|
|
22
|
-
*/
|
|
23
|
-
export function RedirectsModule({ projectId, redirectId = null, schema, }) {
|
|
24
|
-
return (_jsx(RedirectsProvider, { projectId: projectId, children: _jsx(RedirectsSurface, { projectId: projectId, redirectId: redirectId, schema: schema }) }));
|
|
25
|
-
}
|
|
26
|
-
/** The loaded surface: the portable list (or one redirect's detail) over the source. */
|
|
27
|
-
function RedirectsSurface({ projectId, redirectId, schema, }) {
|
|
28
|
-
const store = useRedirects();
|
|
29
|
-
const source = useRedirectsCollectionViewSource(projectId, schema);
|
|
30
|
-
if (store.status !== 'ready') {
|
|
31
|
-
return (_jsx(RailStoreStatus, { store: store, errorLabel: translate('redirects.loadFailed'), loadingLabel: translate('redirects.loading'), errorTestId: "redirects-error" }));
|
|
32
|
-
}
|
|
33
|
-
// One redirect's detail opens in the portable edit shell (AD-19); the list otherwise.
|
|
34
|
-
// The shell mounts its own CollectionViewProvider, so only the list wraps here.
|
|
35
|
-
if (redirectId !== null) {
|
|
36
|
-
return _jsx(PortableDetailEditor, { source: source, projectId: projectId, documentId: redirectId });
|
|
37
|
-
}
|
|
38
|
-
return (_jsx(CollectionViewProvider, { source: source, children: _jsx(LoadedCollectionTable, { projectId: projectId }) }));
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=redirects-module.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-module.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-module.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAA;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAA;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,SAAS,EACT,UAAU,GAAG,IAAI,EACjB,MAAM,GAMP;IACC,OAAO,CACL,KAAC,iBAAiB,IAAC,SAAS,EAAE,SAAS,YACrC,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAI,GAChE,CACrB,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,UAAU,EACV,MAAM,GAKP;IACC,MAAM,KAAK,GAAG,YAAY,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,gCAAgC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAElE,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO,CACL,KAAC,eAAe,IACd,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAC7C,YAAY,EAAE,SAAS,CAAC,mBAAmB,CAAC,EAC5C,WAAW,EAAC,iBAAiB,GAC7B,CACH,CAAA;IACH,CAAC;IAED,sFAAsF;IACtF,gFAAgF;IAChF,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,KAAC,oBAAoB,IAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,GAAI,CAAA;IAC/F,CAAC;IAED,OAAO,CACL,KAAC,sBAAsB,IAAC,MAAM,EAAE,MAAM,YACpC,KAAC,qBAAqB,IAAC,SAAS,EAAE,SAAS,GAAI,GACxB,CAC1B,CAAA;AACH,CAAC","sourcesContent":["import type { CollectionDef } from '@elytracms/core/cms-core'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RedirectsProvider, useRedirects } from './redirects-store'\nimport { useRedirectsCollectionViewSource } from './redirects-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { PortableDetailEditor } from '../cms/detail-editor'\n\n/**\n * The studio Redirects module (EC-303). Editorial redirects as data — the reuse\n * proof (with the users rail) that the portable interaction surfaces serve any\n * rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME\n * `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a\n * redirects {@link useRedirectsCollectionViewSource}. The redirects capability\n * profile (content-gated, create + remove, no publish/versions/locales) drives\n * which regions appear; the edit shell's explicit-save path (no publish) makes a\n * saved+enabled redirect live.\n *\n * `redirectId === null` is the list; a `redirectId` opens that redirect's detail\n * (routed via `studioPath.redirect`). No admin gate — redirects are content, so\n * viewers read and editors write (enforced at the chokepoint + Convex boundary).\n */\nexport function RedirectsModule({\n projectId,\n redirectId = null,\n schema,\n}: {\n projectId: string\n redirectId?: string | null\n /** EC-311: a mount-customized redirects def (built-in fields overridden by name). */\n schema?: CollectionDef\n}) {\n return (\n <RedirectsProvider projectId={projectId}>\n <RedirectsSurface projectId={projectId} redirectId={redirectId} schema={schema} />\n </RedirectsProvider>\n )\n}\n\n/** The loaded surface: the portable list (or one redirect's detail) over the source. */\nfunction RedirectsSurface({\n projectId,\n redirectId,\n schema,\n}: {\n projectId: string\n redirectId: string | null\n schema?: CollectionDef\n}) {\n const store = useRedirects()\n const source = useRedirectsCollectionViewSource(projectId, schema)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={translate('redirects.loadFailed')}\n loadingLabel={translate('redirects.loading')}\n errorTestId=\"redirects-error\"\n />\n )\n }\n\n // One redirect's detail opens in the portable edit shell (AD-19); the list otherwise.\n // The shell mounts its own CollectionViewProvider, so only the list wraps here.\n if (redirectId !== null) {\n return <PortableDetailEditor source={source} projectId={projectId} documentId={redirectId} />\n }\n\n return (\n <CollectionViewProvider source={source}>\n <LoadedCollectionTable projectId={projectId} />\n </CollectionViewProvider>\n )\n}\n"]}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence';
|
|
3
|
-
/**
|
|
4
|
-
* Editorial redirects store (EC-303). The redirects rail — data-backed 301/302
|
|
5
|
-
* forwards — read + written through the shared operations client (`redirects.*`),
|
|
6
|
-
* the same seam the users/assets stores use. Content-gated (a viewer reads, an
|
|
7
|
-
* editor writes); no draft/published split — a saved+enabled redirect is live.
|
|
8
|
-
*
|
|
9
|
-
* The "New" flow keeps an unsaved redirect as a LOCAL draft (memory only) until
|
|
10
|
-
* the first explicit save persists it — so an abandoned "New" never writes a stub
|
|
11
|
-
* to the backend and never shows in the list. Drafts are reachable via {@link
|
|
12
|
-
* RedirectsStore.get} (the detail editor loads one), never listed.
|
|
13
|
-
*/
|
|
14
|
-
export interface RedirectsStore {
|
|
15
|
-
projectId: string;
|
|
16
|
-
status: 'loading' | 'ready' | 'error';
|
|
17
|
-
errorMessage?: string;
|
|
18
|
-
/** The persisted redirects, ordered by source path. Unsaved drafts are excluded. */
|
|
19
|
-
redirects: readonly RedirectEntry[];
|
|
20
|
-
/** One redirect: a local unsaved draft if present, else a persisted record. */
|
|
21
|
-
get(id: string): RedirectEntry | undefined;
|
|
22
|
-
/** Mint a blank in-memory draft and return its id (the "New" navigation target). */
|
|
23
|
-
createDraft(): string;
|
|
24
|
-
/** Persist a create/modify; drops the matching draft on success. */
|
|
25
|
-
save(input: SaveRedirectInput): Promise<{
|
|
26
|
-
ok: true;
|
|
27
|
-
redirect: RedirectEntry;
|
|
28
|
-
} | {
|
|
29
|
-
ok: false;
|
|
30
|
-
message: string;
|
|
31
|
-
}>;
|
|
32
|
-
/** Delete a persisted redirect, or silently discard an unsaved draft. */
|
|
33
|
-
remove(id: string): Promise<{
|
|
34
|
-
ok: true;
|
|
35
|
-
} | {
|
|
36
|
-
ok: false;
|
|
37
|
-
message: string;
|
|
38
|
-
}>;
|
|
39
|
-
/** Re-list from the backend. */
|
|
40
|
-
refresh(): Promise<void>;
|
|
41
|
-
}
|
|
42
|
-
declare const useRedirects: () => RedirectsStore;
|
|
43
|
-
/**
|
|
44
|
-
* The project's redirects, read through the shared operations cache — the ONE query
|
|
45
|
-
* this rail uses, without needing {@link RedirectsProvider}.
|
|
46
|
-
*
|
|
47
|
-
* Extracted (EC-469) so the section rail's URL-map validation can read redirects from
|
|
48
|
-
* outside the redirects module while sharing this exact query key. One key, one cache
|
|
49
|
-
* entry (AD-17): the rail's foot and the rail itself are the same query, so opening
|
|
50
|
-
* Redirects hits a warm cache, and `save`/`remove`'s `cache.patch` below updates both
|
|
51
|
-
* at once — a fixed loop stops being reported the moment it is fixed.
|
|
52
|
-
*/
|
|
53
|
-
export declare function useRedirectList(projectId: string): import("@tanstack/react-query").UseQueryResult<{
|
|
54
|
-
createdAt: string;
|
|
55
|
-
updatedAt: string;
|
|
56
|
-
id: string;
|
|
57
|
-
projectId: string;
|
|
58
|
-
from: string;
|
|
59
|
-
to: string;
|
|
60
|
-
permanent: boolean;
|
|
61
|
-
enabled: boolean;
|
|
62
|
-
meta?: Record<string, unknown> | undefined;
|
|
63
|
-
}[]>;
|
|
64
|
-
export declare function RedirectsProvider({ projectId, children, }: {
|
|
65
|
-
projectId: string;
|
|
66
|
-
children: ReactNode;
|
|
67
|
-
}): import("react").JSX.Element;
|
|
68
|
-
export { useRedirects };
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
3
|
-
import { getStudioOperations } from '@elytracms/studio/lib/operations';
|
|
4
|
-
import { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
|
|
5
|
-
import { createRequiredContext } from '@elytracms/studio/lib/required-context';
|
|
6
|
-
const { Context: RedirectsContext, useRequired: useRedirects } = createRequiredContext('Redirects');
|
|
7
|
-
const EMPTY_REDIRECTS = [];
|
|
8
|
-
let draftCounter = 0;
|
|
9
|
-
/** A collision-resistant client id for a new redirect (used as its real id on save). */
|
|
10
|
-
function newDraftId() {
|
|
11
|
-
draftCounter += 1;
|
|
12
|
-
return `redirect_${Date.now().toString(36)}_${draftCounter}`;
|
|
13
|
-
}
|
|
14
|
-
function redirectsQueryKey(projectId) {
|
|
15
|
-
return ['redirects', projectId];
|
|
16
|
-
}
|
|
17
|
-
async function loadRedirects(operations, projectId) {
|
|
18
|
-
const result = await operations.redirects.list({ projectId });
|
|
19
|
-
if (!result.ok)
|
|
20
|
-
throw new Error(result.error.message);
|
|
21
|
-
return [...result.value];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* The project's redirects, read through the shared operations cache — the ONE query
|
|
25
|
-
* this rail uses, without needing {@link RedirectsProvider}.
|
|
26
|
-
*
|
|
27
|
-
* Extracted (EC-469) so the section rail's URL-map validation can read redirects from
|
|
28
|
-
* outside the redirects module while sharing this exact query key. One key, one cache
|
|
29
|
-
* entry (AD-17): the rail's foot and the rail itself are the same query, so opening
|
|
30
|
-
* Redirects hits a warm cache, and `save`/`remove`'s `cache.patch` below updates both
|
|
31
|
-
* at once — a fixed loop stops being reported the moment it is fixed.
|
|
32
|
-
*/
|
|
33
|
-
export function useRedirectList(projectId) {
|
|
34
|
-
return useOperationQuery({
|
|
35
|
-
queryKey: redirectsQueryKey(projectId),
|
|
36
|
-
loader: (operations) => loadRedirects(operations, projectId),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function byFrom(a, b) {
|
|
40
|
-
return a.from < b.from ? -1 : a.from > b.from ? 1 : 0;
|
|
41
|
-
}
|
|
42
|
-
export function RedirectsProvider({ projectId, children, }) {
|
|
43
|
-
const query = useRedirectList(projectId);
|
|
44
|
-
const cache = useOperationCache();
|
|
45
|
-
const refetch = query.refetch;
|
|
46
|
-
const persisted = query.data ?? EMPTY_REDIRECTS;
|
|
47
|
-
const { status, errorMessage } = listStoreState(query);
|
|
48
|
-
// In-memory unsaved "New" drafts, keyed by id. Not listed; only get()-able.
|
|
49
|
-
const [drafts, setDrafts] = useState({});
|
|
50
|
-
const refresh = useCallback(async () => {
|
|
51
|
-
await refetch();
|
|
52
|
-
}, [refetch]);
|
|
53
|
-
const get = useCallback((id) => drafts[id] ?? persisted.find((r) => r.id === id), [drafts, persisted]);
|
|
54
|
-
const createDraft = useCallback(() => {
|
|
55
|
-
const id = newDraftId();
|
|
56
|
-
const now = new Date().toISOString();
|
|
57
|
-
const draft = {
|
|
58
|
-
id,
|
|
59
|
-
projectId,
|
|
60
|
-
from: '',
|
|
61
|
-
to: '',
|
|
62
|
-
permanent: true,
|
|
63
|
-
// New redirects default to enabled — the reason you create one is for it to
|
|
64
|
-
// work; `enabled` is really the switch for *pausing* an existing redirect. A
|
|
65
|
-
// half-filled draft can't go live anyway (save requires from/to).
|
|
66
|
-
enabled: true,
|
|
67
|
-
createdAt: now,
|
|
68
|
-
updatedAt: now,
|
|
69
|
-
};
|
|
70
|
-
setDrafts((prev) => ({ ...prev, [id]: draft }));
|
|
71
|
-
return id;
|
|
72
|
-
}, [projectId]);
|
|
73
|
-
const save = useCallback(async (input) => {
|
|
74
|
-
const { operations } = await getStudioOperations();
|
|
75
|
-
const result = await operations.redirects.save({
|
|
76
|
-
projectId,
|
|
77
|
-
...(input.id !== undefined ? { id: input.id } : {}),
|
|
78
|
-
from: input.from,
|
|
79
|
-
to: input.to,
|
|
80
|
-
...(input.permanent !== undefined ? { permanent: input.permanent } : {}),
|
|
81
|
-
...(input.enabled !== undefined ? { enabled: input.enabled } : {}),
|
|
82
|
-
...(input.meta !== undefined ? { meta: input.meta } : {}),
|
|
83
|
-
});
|
|
84
|
-
if (!result.ok)
|
|
85
|
-
return { ok: false, message: result.error.message };
|
|
86
|
-
const saved = result.value;
|
|
87
|
-
cache.patch(redirectsQueryKey(projectId), (prev) => {
|
|
88
|
-
const rest = (prev ?? []).filter((r) => r.id !== saved.id);
|
|
89
|
-
return [...rest, saved].sort(byFrom);
|
|
90
|
-
});
|
|
91
|
-
// The record is now persisted — drop any matching draft.
|
|
92
|
-
setDrafts((prev) => {
|
|
93
|
-
if (!(saved.id in prev))
|
|
94
|
-
return prev;
|
|
95
|
-
const next = { ...prev };
|
|
96
|
-
delete next[saved.id];
|
|
97
|
-
return next;
|
|
98
|
-
});
|
|
99
|
-
return { ok: true, redirect: saved };
|
|
100
|
-
}, [projectId, cache]);
|
|
101
|
-
const remove = useCallback(async (id) => {
|
|
102
|
-
// An unsaved draft was never persisted — discard it locally, no backend call.
|
|
103
|
-
if (drafts[id]) {
|
|
104
|
-
setDrafts((prev) => {
|
|
105
|
-
const next = { ...prev };
|
|
106
|
-
delete next[id];
|
|
107
|
-
return next;
|
|
108
|
-
});
|
|
109
|
-
return { ok: true };
|
|
110
|
-
}
|
|
111
|
-
const { operations } = await getStudioOperations();
|
|
112
|
-
const result = await operations.redirects.remove({ projectId, id });
|
|
113
|
-
if (!result.ok)
|
|
114
|
-
return { ok: false, message: result.error.message };
|
|
115
|
-
cache.patch(redirectsQueryKey(projectId), (prev) => (prev ?? []).filter((r) => r.id !== id));
|
|
116
|
-
return { ok: true };
|
|
117
|
-
}, [projectId, cache, drafts]);
|
|
118
|
-
const store = useMemo(() => ({
|
|
119
|
-
projectId,
|
|
120
|
-
status,
|
|
121
|
-
...(errorMessage !== undefined ? { errorMessage } : {}),
|
|
122
|
-
redirects: persisted,
|
|
123
|
-
get,
|
|
124
|
-
createDraft,
|
|
125
|
-
save,
|
|
126
|
-
remove,
|
|
127
|
-
refresh,
|
|
128
|
-
}), [projectId, status, errorMessage, persisted, get, createDraft, save, remove, refresh]);
|
|
129
|
-
return _jsx(RedirectsContext.Provider, { value: store, children: children });
|
|
130
|
-
}
|
|
131
|
-
export { useRedirects };
|
|
132
|
-
//# sourceMappingURL=redirects-store.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-store.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport type { OperationsClient } from '@elytracms/studio/lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * Editorial redirects store (EC-303). The redirects rail — data-backed 301/302\n * forwards — read + written through the shared operations client (`redirects.*`),\n * the same seam the users/assets stores use. Content-gated (a viewer reads, an\n * editor writes); no draft/published split — a saved+enabled redirect is live.\n *\n * The \"New\" flow keeps an unsaved redirect as a LOCAL draft (memory only) until\n * the first explicit save persists it — so an abandoned \"New\" never writes a stub\n * to the backend and never shows in the list. Drafts are reachable via {@link\n * RedirectsStore.get} (the detail editor loads one), never listed.\n */\n\nexport interface RedirectsStore {\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n /** The persisted redirects, ordered by source path. Unsaved drafts are excluded. */\n redirects: readonly RedirectEntry[]\n /** One redirect: a local unsaved draft if present, else a persisted record. */\n get(id: string): RedirectEntry | undefined\n /** Mint a blank in-memory draft and return its id (the \"New\" navigation target). */\n createDraft(): string\n /** Persist a create/modify; drops the matching draft on success. */\n save(\n input: SaveRedirectInput,\n ): Promise<{ ok: true; redirect: RedirectEntry } | { ok: false; message: string }>\n /** Delete a persisted redirect, or silently discard an unsaved draft. */\n remove(id: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend. */\n refresh(): Promise<void>\n}\n\nconst { Context: RedirectsContext, useRequired: useRedirects } =\n createRequiredContext<RedirectsStore>('Redirects')\n\nconst EMPTY_REDIRECTS: readonly RedirectEntry[] = []\n\nlet draftCounter = 0\n/** A collision-resistant client id for a new redirect (used as its real id on save). */\nfunction newDraftId(): string {\n draftCounter += 1\n return `redirect_${Date.now().toString(36)}_${draftCounter}`\n}\n\nfunction redirectsQueryKey(projectId: string): readonly [string, string] {\n return ['redirects', projectId]\n}\n\nasync function loadRedirects(\n operations: OperationsClient,\n projectId: string,\n): Promise<RedirectEntry[]> {\n const result = await operations.redirects.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\n/**\n * The project's redirects, read through the shared operations cache — the ONE query\n * this rail uses, without needing {@link RedirectsProvider}.\n *\n * Extracted (EC-469) so the section rail's URL-map validation can read redirects from\n * outside the redirects module while sharing this exact query key. One key, one cache\n * entry (AD-17): the rail's foot and the rail itself are the same query, so opening\n * Redirects hits a warm cache, and `save`/`remove`'s `cache.patch` below updates both\n * at once — a fixed loop stops being reported the moment it is fixed.\n */\nexport function useRedirectList(projectId: string) {\n return useOperationQuery<RedirectEntry[]>({\n queryKey: redirectsQueryKey(projectId),\n loader: (operations) => loadRedirects(operations, projectId),\n })\n}\n\nfunction byFrom(a: RedirectEntry, b: RedirectEntry): number {\n return a.from < b.from ? -1 : a.from > b.from ? 1 : 0\n}\n\nexport function RedirectsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const query = useRedirectList(projectId)\n const cache = useOperationCache()\n const refetch = query.refetch\n const persisted = query.data ?? EMPTY_REDIRECTS\n const { status, errorMessage } = listStoreState(query)\n\n // In-memory unsaved \"New\" drafts, keyed by id. Not listed; only get()-able.\n const [drafts, setDrafts] = useState<Record<string, RedirectEntry>>({})\n\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const get = useCallback<RedirectsStore['get']>(\n (id) => drafts[id] ?? persisted.find((r) => r.id === id),\n [drafts, persisted],\n )\n\n const createDraft = useCallback<RedirectsStore['createDraft']>(() => {\n const id = newDraftId()\n const now = new Date().toISOString()\n const draft: RedirectEntry = {\n id,\n projectId,\n from: '',\n to: '',\n permanent: true,\n // New redirects default to enabled — the reason you create one is for it to\n // work; `enabled` is really the switch for *pausing* an existing redirect. A\n // half-filled draft can't go live anyway (save requires from/to).\n enabled: true,\n createdAt: now,\n updatedAt: now,\n }\n setDrafts((prev) => ({ ...prev, [id]: draft }))\n return id\n }, [projectId])\n\n const save = useCallback<RedirectsStore['save']>(\n async (input) => {\n const { operations } = await getStudioOperations()\n const result = await operations.redirects.save({\n projectId,\n ...(input.id !== undefined ? { id: input.id } : {}),\n from: input.from,\n to: input.to,\n ...(input.permanent !== undefined ? { permanent: input.permanent } : {}),\n ...(input.enabled !== undefined ? { enabled: input.enabled } : {}),\n ...(input.meta !== undefined ? { meta: input.meta } : {}),\n })\n if (!result.ok) return { ok: false, message: result.error.message }\n const saved = result.value\n cache.patch<RedirectEntry[]>(redirectsQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((r) => r.id !== saved.id)\n return [...rest, saved].sort(byFrom)\n })\n // The record is now persisted — drop any matching draft.\n setDrafts((prev) => {\n if (!(saved.id in prev)) return prev\n const next = { ...prev }\n delete next[saved.id]\n return next\n })\n return { ok: true, redirect: saved }\n },\n [projectId, cache],\n )\n\n const remove = useCallback<RedirectsStore['remove']>(\n async (id) => {\n // An unsaved draft was never persisted — discard it locally, no backend call.\n if (drafts[id]) {\n setDrafts((prev) => {\n const next = { ...prev }\n delete next[id]\n return next\n })\n return { ok: true }\n }\n const { operations } = await getStudioOperations()\n const result = await operations.redirects.remove({ projectId, id })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<RedirectEntry[]>(redirectsQueryKey(projectId), (prev) =>\n (prev ?? []).filter((r) => r.id !== id),\n )\n return { ok: true }\n },\n [projectId, cache, drafts],\n )\n\n const store = useMemo<RedirectsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n redirects: persisted,\n get,\n createDraft,\n save,\n remove,\n refresh,\n }),\n [projectId, status, errorMessage, persisted, get, createDraft, save, remove, refresh],\n )\n\n return <RedirectsContext.Provider value={store}>{children}</RedirectsContext.Provider>\n}\n\nexport { useRedirects }\n"]}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/** The ids the field should hold next. The shape follows the field's cardinality. */
|
|
2
|
-
export type AssetPlacementTarget = {
|
|
3
|
-
multiple: false;
|
|
4
|
-
id: string | null;
|
|
5
|
-
} | {
|
|
6
|
-
multiple: true;
|
|
7
|
-
ids: readonly string[];
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Return the field's next value with every placement's per-use sub-fields kept.
|
|
11
|
-
*
|
|
12
|
-
* - **Single field.** The placement's sub-fields carry over to whatever id now fills
|
|
13
|
-
* it, so swapping the image keeps the caption. Re-picking the same id is a no-op,
|
|
14
|
-
* and `null` clears the field (`undefined`).
|
|
15
|
-
* - **Multiple field.** An id the value already held keeps its entry verbatim, with
|
|
16
|
-
* its sub-fields and its form (a bare id stays bare). Entries are matched in order,
|
|
17
|
-
* so an image placed twice keeps both placements. An id the value did not hold
|
|
18
|
-
* becomes a new envelope. A held entry whose id is not in `ids` is removed, and an
|
|
19
|
-
* emptied list is ABSENT (`undefined`, EC-586), never `[]`.
|
|
20
|
-
*
|
|
21
|
-
* The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`
|
|
22
|
-
* reads. That includes keys the config no longer declares (AD-14: the schema is a
|
|
23
|
-
* lens, so a writer never deletes what the lens does not show).
|
|
24
|
-
*
|
|
25
|
-
* When nothing changes it returns `current` itself (the same reference), so a caller
|
|
26
|
-
* can skip the write.
|
|
27
|
-
*/
|
|
28
|
-
export declare function carryAssetPlacements(current: unknown, next: AssetPlacementTarget): unknown;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The per-use sub-field carry rule for an asset field's value (EC-558).
|
|
3
|
-
*
|
|
4
|
-
* An asset field that declares per-use sub-fields (EC-301) stores envelopes such as
|
|
5
|
-
* `{ type: 'asset', id, caption }`. Those sub-fields are PLACEMENT data: they
|
|
6
|
-
* describe this use of the image, not the image itself (owner ruling 2026-09-26;
|
|
7
|
-
* data that describes the image belongs on the asset record's `meta`). A writer
|
|
8
|
-
* that changes which image a placement shows must therefore keep the placement's
|
|
9
|
-
* sub-fields. Before EC-558 only the detail editor did that. The table cell and AI
|
|
10
|
-
* `updateFields` rebuilt the value from bare ids and lost them.
|
|
11
|
-
*
|
|
12
|
-
* {@link carryAssetPlacements} is the one declaration of that rule. Two operations
|
|
13
|
-
* go through it:
|
|
14
|
-
* - **append / pick**: the table cell and the detail editor
|
|
15
|
-
* (`applyAssetSelection` / `appendAssetSelections` in
|
|
16
|
-
* `components/assets/asset-selection.ts`). The next list is the held list plus
|
|
17
|
-
* the picked ids, and a single field swaps its one placement's image.
|
|
18
|
-
* - **replace-with-list**: AI `updateFields` (`lib/ai/update-fields.ts`). The next
|
|
19
|
-
* list is the list the model sent.
|
|
20
|
-
*
|
|
21
|
-
* It lives in `lib/` because `lib/ai` may not import from `components/`, and both
|
|
22
|
-
* sides need it. Its only dependency is `@elytracms/core/cms-core`, so the AI host
|
|
23
|
-
* route pulls in nothing from the browser. Its natural home is next to `assetNode`
|
|
24
|
-
* in `core/cms-core/envelopes.ts`. Move it there if a writer outside the studio
|
|
25
|
-
* (import, CLI, MCP) appears.
|
|
26
|
-
*/
|
|
27
|
-
import { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core';
|
|
28
|
-
/**
|
|
29
|
-
* A BRAND-NEW placement, which has no sub-fields yet: always the canonical envelope
|
|
30
|
-
* (EC-586, ratified 2c — an asset value is `{ type: 'asset', id, … }`). There used to be
|
|
31
|
-
* a second, bare spelling (`'bare'`, a plain id string) for fields without sub-fields;
|
|
32
|
-
* the write lens wrapped it on save, so the in-session draft held a shape the stored one
|
|
33
|
-
* did not, and a def-less "has unpublished changes" verdict read the two as different.
|
|
34
|
-
*/
|
|
35
|
-
function newPlacement(id) {
|
|
36
|
-
return assetNode(id);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Return the field's next value with every placement's per-use sub-fields kept.
|
|
40
|
-
*
|
|
41
|
-
* - **Single field.** The placement's sub-fields carry over to whatever id now fills
|
|
42
|
-
* it, so swapping the image keeps the caption. Re-picking the same id is a no-op,
|
|
43
|
-
* and `null` clears the field (`undefined`).
|
|
44
|
-
* - **Multiple field.** An id the value already held keeps its entry verbatim, with
|
|
45
|
-
* its sub-fields and its form (a bare id stays bare). Entries are matched in order,
|
|
46
|
-
* so an image placed twice keeps both placements. An id the value did not hold
|
|
47
|
-
* becomes a new envelope. A held entry whose id is not in `ids` is removed, and an
|
|
48
|
-
* emptied list is ABSENT (`undefined`, EC-586), never `[]`.
|
|
49
|
-
*
|
|
50
|
-
* The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`
|
|
51
|
-
* reads. That includes keys the config no longer declares (AD-14: the schema is a
|
|
52
|
-
* lens, so a writer never deletes what the lens does not show).
|
|
53
|
-
*
|
|
54
|
-
* When nothing changes it returns `current` itself (the same reference), so a caller
|
|
55
|
-
* can skip the write.
|
|
56
|
-
*/
|
|
57
|
-
export function carryAssetPlacements(current, next) {
|
|
58
|
-
if (!next.multiple) {
|
|
59
|
-
if (next.id === null)
|
|
60
|
-
return undefined;
|
|
61
|
-
// `singleAssetIdOf` tolerates a legacy one-item array on a single field, so read
|
|
62
|
-
// its placement the same way.
|
|
63
|
-
const held = Array.isArray(current) ? current[0] : current;
|
|
64
|
-
if (readAssetId(held) === next.id)
|
|
65
|
-
return current;
|
|
66
|
-
const subfields = assetSubfieldsOf(held);
|
|
67
|
-
return Object.keys(subfields).length > 0 ? assetNode(next.id, subfields) : newPlacement(next.id);
|
|
68
|
-
}
|
|
69
|
-
const held = Array.isArray(current) ? current : [];
|
|
70
|
-
const used = new Set();
|
|
71
|
-
const out = next.ids.map((id) => {
|
|
72
|
-
const index = held.findIndex((entry, i) => !used.has(i) && readAssetId(entry) === id);
|
|
73
|
-
if (index < 0)
|
|
74
|
-
return newPlacement(id);
|
|
75
|
-
used.add(index);
|
|
76
|
-
return held[index];
|
|
77
|
-
});
|
|
78
|
-
const unchanged = out.length === held.length && out.every((entry, i) => entry === held[i]);
|
|
79
|
-
if (unchanged && Array.isArray(current))
|
|
80
|
-
return current;
|
|
81
|
-
return out.length === 0 ? undefined : out;
|
|
82
|
-
}
|
|
83
|
-
//# sourceMappingURL=asset-placements.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asset-placements.js","sourceRoot":"","sources":["../../../src/lib/assets/asset-placements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAOnF;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,SAAS,CAAC,EAAE,CAAC,CAAA;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB,EAAE,IAA0B;IAC/E,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,SAAS,CAAA;QACtC,iFAAiF;QACjF,8BAA8B;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QAC1D,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE;YAAE,OAAO,OAAO,CAAA;QACjD,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClG,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,OAAqB,CAAC,CAAC,CAAC,EAAE,CAAA;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACrF,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,YAAY,CAAC,EAAE,CAAC,CAAA;QACtC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC,CAAC,CAAA;IACF,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1F,IAAI,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IACvD,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA;AAC3C,CAAC","sourcesContent":["/**\n * The per-use sub-field carry rule for an asset field's value (EC-558).\n *\n * An asset field that declares per-use sub-fields (EC-301) stores envelopes such as\n * `{ type: 'asset', id, caption }`. Those sub-fields are PLACEMENT data: they\n * describe this use of the image, not the image itself (owner ruling 2026-09-26;\n * data that describes the image belongs on the asset record's `meta`). A writer\n * that changes which image a placement shows must therefore keep the placement's\n * sub-fields. Before EC-558 only the detail editor did that. The table cell and AI\n * `updateFields` rebuilt the value from bare ids and lost them.\n *\n * {@link carryAssetPlacements} is the one declaration of that rule. Two operations\n * go through it:\n * - **append / pick**: the table cell and the detail editor\n * (`applyAssetSelection` / `appendAssetSelections` in\n * `components/assets/asset-selection.ts`). The next list is the held list plus\n * the picked ids, and a single field swaps its one placement's image.\n * - **replace-with-list**: AI `updateFields` (`lib/ai/update-fields.ts`). The next\n * list is the list the model sent.\n *\n * It lives in `lib/` because `lib/ai` may not import from `components/`, and both\n * sides need it. Its only dependency is `@elytracms/core/cms-core`, so the AI host\n * route pulls in nothing from the browser. Its natural home is next to `assetNode`\n * in `core/cms-core/envelopes.ts`. Move it there if a writer outside the studio\n * (import, CLI, MCP) appears.\n */\nimport { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core'\n\n/** The ids the field should hold next. The shape follows the field's cardinality. */\nexport type AssetPlacementTarget =\n | { multiple: false; id: string | null }\n | { multiple: true; ids: readonly string[] }\n\n/**\n * A BRAND-NEW placement, which has no sub-fields yet: always the canonical envelope\n * (EC-586, ratified 2c — an asset value is `{ type: 'asset', id, … }`). There used to be\n * a second, bare spelling (`'bare'`, a plain id string) for fields without sub-fields;\n * the write lens wrapped it on save, so the in-session draft held a shape the stored one\n * did not, and a def-less \"has unpublished changes\" verdict read the two as different.\n */\nfunction newPlacement(id: string): unknown {\n return assetNode(id)\n}\n\n/**\n * Return the field's next value with every placement's per-use sub-fields kept.\n *\n * - **Single field.** The placement's sub-fields carry over to whatever id now fills\n * it, so swapping the image keeps the caption. Re-picking the same id is a no-op,\n * and `null` clears the field (`undefined`).\n * - **Multiple field.** An id the value already held keeps its entry verbatim, with\n * its sub-fields and its form (a bare id stays bare). Entries are matched in order,\n * so an image placed twice keeps both placements. An id the value did not hold\n * becomes a new envelope. A held entry whose id is not in `ids` is removed, and an\n * emptied list is ABSENT (`undefined`, EC-586), never `[]`.\n *\n * The rule carries every key except `type`/`id`, the same set that `assetSubfieldsOf`\n * reads. That includes keys the config no longer declares (AD-14: the schema is a\n * lens, so a writer never deletes what the lens does not show).\n *\n * When nothing changes it returns `current` itself (the same reference), so a caller\n * can skip the write.\n */\nexport function carryAssetPlacements(current: unknown, next: AssetPlacementTarget): unknown {\n if (!next.multiple) {\n if (next.id === null) return undefined\n // `singleAssetIdOf` tolerates a legacy one-item array on a single field, so read\n // its placement the same way.\n const held = Array.isArray(current) ? current[0] : current\n if (readAssetId(held) === next.id) return current\n const subfields = assetSubfieldsOf(held)\n return Object.keys(subfields).length > 0 ? assetNode(next.id, subfields) : newPlacement(next.id)\n }\n\n const held = Array.isArray(current) ? (current as unknown[]) : []\n const used = new Set<number>()\n const out = next.ids.map((id) => {\n const index = held.findIndex((entry, i) => !used.has(i) && readAssetId(entry) === id)\n if (index < 0) return newPlacement(id)\n used.add(index)\n return held[index]\n })\n const unchanged = out.length === held.length && out.every((entry, i) => entry === held[i])\n if (unchanged && Array.isArray(current)) return current\n return out.length === 0 ? undefined : out\n}\n"]}
|