@elytracms/studio 0.0.93 → 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/auth.ts +5 -2
- package/convex/authority.ts +35 -11
- package/convex/contract-parity.test.ts +7 -0
- package/convex/delivery.ts +12 -9
- 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/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 +30 -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 +2 -2
- 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 +6 -6
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +1 -1
- package/dist/components/ai/translate-skipped.js +2 -2
- package/dist/components/ai/translate-skipped.js.map +1 -1
- 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 +2 -2
- 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-thumb.js +3 -3
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
- 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 +2 -2
- package/dist/components/assets/assets-store.js +6 -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.js +1 -1
- package/dist/components/assets/replace-file.js.map +1 -1
- 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 +1 -1
- package/dist/components/builder/blocks-model.js +3 -3
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +1 -1
- package/dist/components/builder/blocks-panel.js +4 -4
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.js +2 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-breakpoints.js +1 -1
- package/dist/components/builder/canvas-breakpoints.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +2 -2
- package/dist/components/builder/canvas-dnd.js +1 -1
- 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 +1 -1
- package/dist/components/builder/canvas-render-context.js +4 -4
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- 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 +3 -3
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +2 -2
- package/dist/components/builder/clipboard-actions.js +4 -4
- 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 +1 -1
- package/dist/components/builder/composition-field-editor.js +3 -3
- package/dist/components/builder/composition-field-editor.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 +1 -1
- package/dist/components/builder/insert-context.js +3 -3
- 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 +1 -1
- package/dist/components/builder/page-tree.js +5 -5
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/placement-refusal.js +3 -3
- package/dist/components/builder/placement-refusal.js.map +1 -1
- package/dist/components/builder/property-inspector.js +4 -4
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +4 -4
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.js +1 -1
- 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.js +1 -1
- package/dist/components/builder/run-inspector.js.map +1 -1
- 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.js +2 -2
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +2 -2
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +1 -1
- package/dist/components/cms/bulk-actions.js +2 -2
- 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 +2 -2
- package/dist/components/cms/cms-store.js +10 -10
- 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 -14
- 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.js +5 -5
- 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-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.js +1 -1
- 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.js +1 -1
- 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 +2 -2
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.js +2 -2
- 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.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/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 +1 -1
- package/dist/components/cms/use-publish-controller.js +1 -1
- 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 +3 -3
- 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 +2 -2
- 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 +3 -3
- package/dist/components/components/transfer-dialog.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/index.d.ts +1 -1
- package/dist/lib/ai/index.js +1 -1
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/node-handler.d.ts +1 -1
- package/dist/lib/ai/node-handler.js.map +1 -1
- package/dist/lib/ai/translate-document.js +1 -1
- package/dist/lib/ai/translate-document.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.js +2 -2
- package/dist/lib/clipboard/paste-target.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 +12 -12
- 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 +7 -2
- package/dist/lib/collection-view/collection-view-source.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/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 +4 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -0
- package/dist/lib/i18n/messages.en.js +8 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +9 -0
- package/dist/lib/operations/project-runtime-config.js +13 -0
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +9 -1
- package/dist/lib/operations/studio-operations.js +6 -1
- 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 +78 -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 +1 -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/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 +6 -0
- package/dist/lib/workspace/index.js +8 -0
- 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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio-project-screen.js","sourceRoot":"","sources":["../../src/components/studio-project-screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport {\n launcherVisitState,\n Link,\n NavigateOnce,\n STUDIO_PROJECT_ROUTE,\n useParams,\n useSearch,\n} from '@elytracms/studio/router'\nimport { matchStudioRoute } from '@elytracms/studio/router/match'\nimport type { StudioRouteMatch } from '@elytracms/studio/router/match'\nimport { studioSearch } from '@elytracms/studio/router/search'\nimport type { StudioSearch } from '@elytracms/studio/router/search'\nimport { Button, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useStudio } from './studio-context'\nimport { StudioShell } from './studio-shell'\nimport { AssetsProvider } from './assets/assets-store'\nimport { ProjectSessionProvider } from './project-session'\nimport { flatPath } from './app-views/module'\nimport { landingViewId, useStudioNav } from './app-views/sidebar'\n\n/**\n * EC-223 Stage 6 (catch-all routing): the studio's per-project route component.\n *\n * The consumer mounts ONE splat route (`/$projectId/$`) whose component is this.\n * Everything that was a per-project file route — the provider stack, the section\n * rail shell, and every section screen — now lives inside the package and is\n * resolved here from the URL. The consumer's route tree shrinks to a single\n * ~10-line mount, which is the whole point of Stage 6: the engine owns its\n * navigation, so an external repo consumes the studio without rebuilding a route\n * file per screen.\n *\n * `projectId` stays a real route param (the `$projectId` segment); the trailing\n * pathname (`_splat`) is matched against the engine route table\n * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)\n * resolves the screen. Every surface already takes its ids as props, so the\n * resolver just hands the module its parsed deep-link.\n */\nexport function StudioProjectScreen() {\n const params = useParams({ strict: false }) as { projectId?: string; _splat?: string }\n // The route's VALIDATED search (the splat's `validateSearch: studioSearch`).\n // Read it through `useSearch`, not `useLocation().search`: the validated search\n // is resolved at route-match time and stable from the component's first render,\n // whereas `location.search` can lag a tick during hydration — long enough for a\n // child `<Navigate>` (the builder redirect) to fire on a momentarily-empty\n // search and drop its `?page=` deep-link target.\n const rawSearch = useSearch({ strict: false }) as Record<string, unknown>\n const studio = useStudio()\n const projectId = params.projectId ?? ''\n const project = studio.getProject(projectId)\n\n // Record the active project once it resolves (preserves recents across sections).\n useEffect(() => {\n if (project) studio.activate(project.id)\n }, [project, studio])\n\n if (!project) return <ProjectNotFound />\n\n const match = matchStudioRoute(params._splat ?? '')\n const search = studioSearch(rawSearch)\n\n return (\n <StudioShell project={project}>\n {/* EC-177 gap 1: in Convex mode the session HYDRATES from persistence\n before any provider mounts — the builder boots from the latest\n persisted revision, publishing reflects the persisted state, and the\n preview content engine serves live documents. Playground renders\n synchronously from fixtures (session: null), exactly as before. */}\n <ProjectSessionProvider key={project.id} projectId={project.id}>\n <ProjectProviders project={project}>\n <StudioSectionResolver project={project} match={match} search={search} />\n </ProjectProviders>\n </ProjectSessionProvider>\n </StudioShell>\n )\n}\n\n/**\n * The per-project provider stack, mounted once the session source is known.\n *\n * EC-287: there is no project-level builder graph anymore — layouts are code and\n * the DB holds content only. The block-field canvas mounts its OWN scoped\n * `BuilderProvider` per document (`composition-field-editor`), so no graph is\n * threaded here. (The Phase-10 `CollabProvider` that used to sit here went in\n * EC-574; the canvas surfaces read `projectId` from the project session.)\n */\nfunction ProjectProviders({ project, children }: { project: Project; children: ReactNode }) {\n return (\n // Keyed by project id so per-project state resets when switching projects.\n // EC-195: project-level so the canvas resolves `asset`-typed Image props and\n // the inspector picks from the live library — not just the Assets section.\n // Asset METADATA only (small); blobs stay lazy. (The data-source store that\n // used to wrap this went with the data-binding subsystem, EC-574.)\n <AssetsProvider key={project.id} projectId={project.id}>\n {children}\n </AssetsProvider>\n )\n}\n\n/**\n * The URL→screen switch (EC-223 Stage 6), driven by the view registry DERIVED from\n * the section rail (EC-237/239). The bare project URL redirects to the first\n * navigable rail view; otherwise the matched view id is resolved from the registry\n * (rail views + always-on built-ins) and its `parsePath` interprets the trailing\n * segments into a deep-link the view renders. An unknown view id or an unparseable\n * path is a not-found; rail CONFIG problems (duplicate/invalid views) surface as\n * visible issues in the shell, never silently. Renders inside the shell + provider\n * stack, so every surface sees the same live CMS store and breadcrumb context.\n */\nfunction StudioSectionResolver({\n project,\n match,\n search,\n}: {\n project: Project\n match: StudioRouteMatch\n search: StudioSearch\n}) {\n const projectId = project.id\n const nav = useStudioNav(project)\n\n if (match.section === 'index') {\n // Bare `/$projectId` → the project's first navigable rail view.\n // EC-441: `NavigateOnce` — this resolver re-renders while the landing\n // view's transition is pending; a per-render `<Navigate>` re-fires each time.\n return (\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: landingViewId(nav, project) }}\n replace\n />\n )\n }\n\n const view = nav.registry.get(match.viewId)\n if (!view) return <StudioRouteNotFound />\n\n // A flat view (no `parsePath`) accepts only the bare path; a nested one\n // (CMS/components) interprets its segments. `null` → an over-deep path → not-found.\n const deepLink = (view.parsePath ?? flatPath)(match.segments)\n if (deepLink === null) return <StudioRouteNotFound />\n\n return <>{view.render({ project, projectId, deepLink, search })}</>\n}\n\nfunction ProjectNotFound() {\n return (\n <div className=\"grid min-h-dvh place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading size=\"display\">{translate('shell.projectNotFound.title')}</Heading>\n <Text tone=\"muted\">{translate('shell.projectNotFound.body')}</Text>\n <Button asChild>\n {/* EC-401: an explicit \"back to projects\" navigation — marked so the\n launcher's single-project auto-forward never bounces it back. */}\n <Link to=\"/\" state={launcherVisitState}>\n {translate('shell.backToProjects')}\n </Link>\n </Button>\n </Stack>\n </div>\n )\n}\n\nfunction StudioRouteNotFound() {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"2\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n {translate('shell.routeNotFound.title')}\n </Heading>\n <Text tone=\"muted\">{translate('shell.routeNotFound.body')}</Text>\n </Stack>\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"studio-project-screen.js","sourceRoot":"","sources":["../../src/components/studio-project-screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport {\n launcherVisitState,\n Link,\n NavigateOnce,\n STUDIO_PROJECT_ROUTE,\n useParams,\n useSearch,\n} from '@elytracms/studio/router'\nimport { matchStudioRoute } from '@elytracms/studio/router/match'\nimport type { StudioRouteMatch } from '@elytracms/studio/router/match'\nimport { studioSearch } from '@elytracms/studio/router/search'\nimport type { StudioSearch } from '@elytracms/studio/router/search'\nimport { Button, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '../lib/i18n'\nimport { useStudio } from './studio-context'\nimport { StudioShell } from './studio-shell'\nimport { AssetsProvider } from './assets/assets-store'\nimport { ProjectSessionProvider } from './project-session'\nimport { flatPath } from './app-views/module'\nimport { landingViewId, useStudioNav } from './app-views/sidebar'\nimport { NavIssuesProvider } from './nav-issues-registry'\nimport { useAuth, useProjectRole } from './auth-context'\nimport { getStudioOperations } from '../lib/operations'\nimport { useOperationCache, useOperationQuery } from '../lib/query'\nimport { useStudioLocale } from '../lib/locale'\nimport type { AppViewQueryHooks } from './app-views/module'\n\n/**\n * EC-223 Stage 6 (catch-all routing): the studio's per-project route component.\n *\n * The consumer mounts ONE splat route (`/$projectId/$`) whose component is this.\n * Everything that was a per-project file route — the provider stack, the section\n * rail shell, and every section screen — now lives inside the package and is\n * resolved here from the URL. The consumer's route tree shrinks to a single\n * ~10-line mount, which is the whole point of Stage 6: the engine owns its\n * navigation, so an external repo consumes the studio without rebuilding a route\n * file per screen.\n *\n * `projectId` stays a real route param (the `$projectId` segment); the trailing\n * pathname (`_splat`) is matched against the engine route table\n * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)\n * resolves the screen. Every surface already takes its ids as props, so the\n * resolver just hands the module its parsed deep-link.\n */\nexport function StudioProjectScreen() {\n const params = useParams({ strict: false }) as { projectId?: string; _splat?: string }\n // The route's VALIDATED search (the splat's `validateSearch: studioSearch`).\n // Read it through `useSearch`, not `useLocation().search`: the validated search\n // is resolved at route-match time and stable from the component's first render,\n // whereas `location.search` can lag a tick during hydration — long enough for a\n // child `<Navigate>` (the builder redirect) to fire on a momentarily-empty\n // search and drop its `?page=` deep-link target.\n const rawSearch = useSearch({ strict: false }) as Record<string, unknown>\n const studio = useStudio()\n const projectId = params.projectId ?? ''\n const project = studio.getProject(projectId)\n\n // Record the active project once it resolves (preserves recents across sections).\n useEffect(() => {\n if (project) studio.activate(project.id)\n }, [project, studio])\n\n if (!project) return <ProjectNotFound />\n\n const match = matchStudioRoute(params._splat ?? '')\n const search = studioSearch(rawSearch)\n\n return (\n // EC-616: the rail foot's contribution seam spans the shell (rail + content).\n <NavIssuesProvider>\n <StudioShell project={project}>\n {/* EC-177 gap 1: in Convex mode the session HYDRATES from persistence\n before any provider mounts — the builder boots from the latest\n persisted revision, publishing reflects the persisted state, and the\n preview content engine serves live documents. Playground renders\n synchronously from fixtures (session: null), exactly as before. */}\n <ProjectSessionProvider key={project.id} projectId={project.id}>\n <ProjectProviders project={project}>\n <StudioSectionResolver project={project} match={match} search={search} />\n </ProjectProviders>\n </ProjectSessionProvider>\n </StudioShell>\n </NavIssuesProvider>\n )\n}\n\n/**\n * The per-project provider stack, mounted once the session source is known.\n *\n * EC-287: there is no project-level builder graph anymore — layouts are code and\n * the DB holds content only. The block-field canvas mounts its OWN scoped\n * `BuilderProvider` per document (`composition-field-editor`), so no graph is\n * threaded here. (The Phase-10 `CollabProvider` that used to sit here went in\n * EC-574; the canvas surfaces read `projectId` from the project session.)\n */\nfunction ProjectProviders({ project, children }: { project: Project; children: ReactNode }) {\n return (\n // Keyed by project id so per-project state resets when switching projects.\n // EC-195: project-level so the canvas resolves `asset`-typed Image props and\n // the inspector picks from the live library — not just the Assets section.\n // Asset METADATA only (small); blobs stay lazy. (The data-source store that\n // used to wrap this went with the data-binding subsystem, EC-574.)\n <AssetsProvider key={project.id} projectId={project.id}>\n {children}\n </AssetsProvider>\n )\n}\n\n/**\n * The URL→screen switch (EC-223 Stage 6), driven by the view registry DERIVED from\n * the section rail (EC-237/239). The bare project URL redirects to the first\n * navigable rail view; otherwise the matched view id is resolved from the registry\n * (rail views + always-on built-ins) and its `parsePath` interprets the trailing\n * segments into a deep-link the view renders. An unknown view id or an unparseable\n * path is a not-found; rail CONFIG problems (duplicate/invalid views) surface as\n * visible issues in the shell, never silently. Renders inside the shell + provider\n * stack, so every surface sees the same live CMS store and breadcrumb context.\n */\nfunction StudioSectionResolver({\n project,\n match,\n search,\n}: {\n project: Project\n match: StudioRouteMatch\n search: StudioSearch\n}) {\n const projectId = project.id\n const nav = useStudioNav(project)\n const viewServices = useViewServices(projectId)\n\n if (match.section === 'index') {\n // Bare `/$projectId` → the project's first navigable rail view.\n // EC-441: `NavigateOnce` — this resolver re-renders while the landing\n // view's transition is pending; a per-render `<Navigate>` re-fires each time.\n return (\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: landingViewId(nav, project) }}\n replace\n />\n )\n }\n\n const view = nav.registry.get(match.viewId)\n if (!view) return <StudioRouteNotFound />\n\n // A flat view (no `parsePath`) accepts only the bare path; a nested one\n // (CMS/components) interprets its segments. `null` → an over-deep path → not-found.\n const deepLink = (view.parsePath ?? flatPath)(match.segments)\n if (deepLink === null) return <StudioRouteNotFound />\n\n return <>{view.render({ project, projectId, deepLink, search, ...viewServices })}</>\n}\n\n/** The query hooks every view receives (stable: the hooks themselves). */\nconst VIEW_QUERY_HOOKS: AppViewQueryHooks = { useOperationQuery, useOperationCache }\n\n/** EC-616: the operations client, as a view reaches it (the one chokepoint). */\nconst viewOperations = async () => (await getStudioOperations()).operations\n\n/**\n * EC-616 — what `AppViewContext` grows by: the operations client, the query hooks, the\n * ambient content locale, the signed-in user and their role. Read here, in the resolver\n * (a component), so a view'''s plain `render` function receives them as values.\n */\nfunction useViewServices(projectId: string) {\n const { session } = useAuth()\n const role = useProjectRole(projectId)\n const { locale } = useStudioLocale()\n return {\n operations: viewOperations,\n query: VIEW_QUERY_HOOKS,\n locale,\n user: session?.user ?? null,\n role,\n }\n}\n\nfunction ProjectNotFound() {\n return (\n <div className=\"grid min-h-dvh place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading size=\"display\">{translate('shell.projectNotFound.title')}</Heading>\n <Text tone=\"muted\">{translate('shell.projectNotFound.body')}</Text>\n <Button asChild>\n {/* EC-401: an explicit \"back to projects\" navigation — marked so the\n launcher's single-project auto-forward never bounces it back. */}\n <Link to=\"/\" state={launcherVisitState}>\n {translate('shell.backToProjects')}\n </Link>\n </Button>\n </Stack>\n </div>\n )\n}\n\nfunction StudioRouteNotFound() {\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"2\" align=\"center\" className=\"max-w-sm text-center\">\n <Heading level={2} size=\"h2\">\n {translate('shell.routeNotFound.title')}\n </Heading>\n <Text tone=\"muted\">{translate('shell.routeNotFound.body')}</Text>\n </Stack>\n </div>\n )\n}\n"]}
|
|
@@ -3,12 +3,13 @@ import { launcherVisitState, Link, STUDIO_PROJECT_ROUTE, useLocation } from '@el
|
|
|
3
3
|
import { ChevronRight } from 'lucide-react';
|
|
4
4
|
import { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import { Row, Spacer, Toaster } from '@elytracms/studio/ui';
|
|
6
|
-
import { readRailCollapsed, storeRailCollapsed } from '
|
|
7
|
-
import { composeTabTitle, studioBaseTitle, tabTitleParts } from '
|
|
8
|
-
import { isSingleProjectWorkspace } from '
|
|
9
|
-
import { translate } from '
|
|
6
|
+
import { readRailCollapsed, storeRailCollapsed } from '../lib/rail.js';
|
|
7
|
+
import { composeTabTitle, studioBaseTitle, tabTitleParts } from '../lib/tab-title.js';
|
|
8
|
+
import { isSingleProjectWorkspace } from '../lib/workspace/index.js';
|
|
9
|
+
import { translate } from '../lib/i18n/index.js';
|
|
10
10
|
import { CurrentUserMenu } from './current-user-menu.js';
|
|
11
11
|
import { DeliveryHealthBanner } from './delivery-health-banner.js';
|
|
12
|
+
import { RolePolicyDriftIssues } from './users/role-policy-drift-issues.js';
|
|
12
13
|
import { PlaygroundBadge } from './playground-badge.js';
|
|
13
14
|
import { DevLoginBadge } from './dev-login-badge.js';
|
|
14
15
|
import { AiPanelToggle } from './ai/ai-panel-toggle.js';
|
|
@@ -45,7 +46,7 @@ export function StudioShell({ project, children }) {
|
|
|
45
46
|
return next;
|
|
46
47
|
});
|
|
47
48
|
}, []);
|
|
48
|
-
return (_jsxs(BreadcrumbProvider, { children: [_jsx(StudioTabTitle, { project: project, activeViewId: activeViewId }), _jsxs("div", { className: "flex h-dvh flex-col bg-background", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border px-3", children: [_jsx(Link, { to: "/", state: launcherVisitState, className: "grid size-6 shrink-0 place-items-center", "aria-label": translate('shell.allProjects'), children: _jsx("span", { className: "size-5 rounded-[5px] bg-primary shadow-sm" }) }), singleProject ? (_jsx(ActiveProjectName, { project: project })) : (_jsx(ProjectSwitcher, { activeProject: project })), _jsx(HeaderBreadcrumb, { project: project, activeViewId: activeViewId }), _jsx(Spacer, {}), _jsx(PlaygroundBadge, {}), _jsx(DevLoginBadge, {}), _jsx(AiPanelToggle, {}), _jsx(CurrentUserMenu, { projectId: project.id })] }), _jsx(DeliveryHealthBanner, { projectId: project.id }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx(SectionNav, { project: project, collapsed: railCollapsed, onToggleCollapsed: toggleRail }), _jsx("main", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: children }) })] })] }), _jsx(Toaster, {})] }));
|
|
49
|
+
return (_jsxs(BreadcrumbProvider, { children: [_jsx(StudioTabTitle, { project: project, activeViewId: activeViewId }), _jsxs("div", { className: "flex h-dvh flex-col bg-background", children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border px-3", children: [_jsx(Link, { to: "/", state: launcherVisitState, className: "grid size-6 shrink-0 place-items-center", "aria-label": translate('shell.allProjects'), children: _jsx("span", { className: "size-5 rounded-[5px] bg-primary shadow-sm" }) }), singleProject ? (_jsx(ActiveProjectName, { project: project })) : (_jsx(ProjectSwitcher, { activeProject: project })), _jsx(HeaderBreadcrumb, { project: project, activeViewId: activeViewId }), _jsx(Spacer, {}), _jsx(PlaygroundBadge, {}), _jsx(DevLoginBadge, {}), _jsx(AiPanelToggle, {}), _jsx(CurrentUserMenu, { projectId: project.id })] }), _jsx(DeliveryHealthBanner, { projectId: project.id }), _jsx(RolePolicyDriftIssues, { projectId: project.id }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx(SectionNav, { project: project, collapsed: railCollapsed, onToggleCollapsed: toggleRail }), _jsx("main", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: children }) })] })] }), _jsx(Toaster, {})] }));
|
|
49
50
|
}
|
|
50
51
|
/**
|
|
51
52
|
* Per-view browser tab title (EC-402). The studio is ONE catch-all splat route,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio-shell.js","sourceRoot":"","sources":["../../src/components/studio-shell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { launcherVisitState, Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { ChevronRight } from 'lucide-react'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project, ProjectId } from '@elytracms/core/studio-core'\nimport { Row, Spacer, Toaster } from '@elytracms/studio/ui'\nimport { readRailCollapsed, storeRailCollapsed } from '@elytracms/studio/lib/rail'\nimport { composeTabTitle, studioBaseTitle, tabTitleParts } from '@elytracms/studio/lib/tab-title'\nimport { isSingleProjectWorkspace } from '@elytracms/studio/lib/workspace'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { CurrentUserMenu } from './current-user-menu'\nimport { DeliveryHealthBanner } from './delivery-health-banner'\nimport { PlaygroundBadge } from './playground-badge'\nimport { DevLoginBadge } from './dev-login-badge'\nimport { AiPanelToggle } from './ai/ai-panel-toggle'\nimport { ActiveProjectName, ProjectSwitcher } from './project-switcher'\nimport { SectionNav } from './section-nav'\nimport { useSwitcherEntries } from './workspace-projects'\nimport { activeViewIdFromPath, useStudioNav } from './app-views/sidebar'\nimport { BreadcrumbProvider, useBreadcrumbTrail } from './breadcrumb-context'\n\nexport interface StudioShellProps {\n project: Project\n children: ReactNode\n}\n\n/**\n * The Studio shell (brief §4.3, §8.1): a persistent top bar + section rail that\n * preserve the active project while the user moves between surfaces. The top bar\n * carries the single global breadcrumb reaching the active record (EC-214) — no\n * secondary band, no per-view title repeating the section. Responsive and\n * overflow-safe.\n */\nexport function StudioShell({ project, children }: StudioShellProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n\n // EC-401: the shared single-project predicate — the same one that drives the\n // launcher's auto-forward — decides whether the header slot carries the\n // switcher dropdown or just the project's name.\n const { entries } = useSwitcherEntries()\n const singleProject = isSingleProjectWorkspace(entries)\n\n // User-toggled, persisted rail collapse (EC-214). Defaults to expanded; the\n // stored preference is read after mount (no flash script needed for a rail).\n const [railCollapsed, setRailCollapsed] = useState(false)\n useEffect(() => {\n setRailCollapsed(readRailCollapsed())\n }, [])\n const toggleRail = useCallback(() => {\n setRailCollapsed((prev) => {\n const next = !prev\n storeRailCollapsed(next)\n return next\n })\n }, [])\n\n return (\n <BreadcrumbProvider>\n {/* EC-402: per-view tab title, fed by the same breadcrumb/nav data as the header. */}\n <StudioTabTitle project={project} activeViewId={activeViewId} />\n <div className=\"flex h-dvh flex-col bg-background\">\n {/* Top bar — global controls + the one breadcrumb to the active record. */}\n <header className=\"flex h-12 shrink-0 items-center gap-2 border-b border-border px-3\">\n {/* EC-401: an explicit \"all projects\" navigation — marked so the\n launcher's single-project auto-forward never bounces it back. */}\n <Link\n to=\"/\"\n state={launcherVisitState}\n className=\"grid size-6 shrink-0 place-items-center\"\n aria-label={translate('shell.allProjects')}\n >\n <span className=\"size-5 rounded-[5px] bg-primary shadow-sm\" />\n </Link>\n {singleProject ? (\n <ActiveProjectName project={project} />\n ) : (\n <ProjectSwitcher activeProject={project} />\n )}\n <HeaderBreadcrumb project={project} activeViewId={activeViewId} />\n <Spacer />\n {/* EC-153: explicit playground-mode marker (renders nothing on Convex). */}\n <PlaygroundBadge />\n {/* Dev-only unattended-auth marker (renders nothing in prod / when unset). */}\n <DevLoginBadge />\n {/* EC-199: AI assistant toggle — renders nothing unless `defineStudio({ ai })`. */}\n <AiPanelToggle />\n {/* EC-150: signed-in user + their role on this project + sign-out. */}\n <CurrentUserMenu projectId={project.id} />\n </header>\n\n {/* EC-268: warns when instant-publish env is incomplete (connected mode only). */}\n <DeliveryHealthBanner projectId={project.id} />\n\n <div className=\"flex min-h-0 flex-1\">\n <SectionNav\n project={project}\n collapsed={railCollapsed}\n onToggleCollapsed={toggleRail}\n />\n\n <main className=\"flex min-w-0 flex-1 flex-col overflow-hidden\">\n <div className=\"min-h-0 flex-1 overflow-auto\">{children}</div>\n </main>\n </div>\n </div>\n {/* EC-268: transient action feedback (e.g. a publish that fails at transport). */}\n <Toaster />\n </BreadcrumbProvider>\n )\n}\n\n/**\n * Per-view browser tab title (EC-402). The studio is ONE catch-all splat route,\n * so per-view titles cannot come from per-route `head()` — this effect composes\n * `deepest crumb · view label · project name · base title` from the exact\n * sources the header breadcrumb renders (no new data flow, AD-17): the\n * contributed trail, the nav registry's localized view label, `project.name`,\n * and the `createStudioRootRoute` base title. Assignment is effect-only\n * (SSR-safe); unmounting (launcher, sign-in, project switch) restores the base\n * title, so non-project surfaces keep the consumer's branding untouched.\n */\nfunction StudioTabTitle({\n project,\n activeViewId,\n}: {\n project: Project\n activeViewId: string | null\n}) {\n const nav = useStudioNav(project)\n const trail = useBreadcrumbTrail()\n const view = activeViewId ? nav.registry.get(activeViewId) : undefined\n const title = composeTabTitle(\n tabTitleParts({\n breadcrumb: trail,\n viewLabel: view?.label,\n projectName: project.name,\n baseTitle: studioBaseTitle(),\n }),\n )\n\n useEffect(() => {\n document.title = title\n }, [title])\n\n // Leaving the project shell restores the base title (launcher/sign-in keep it).\n useEffect(\n () => () => {\n document.title = studioBaseTitle()\n },\n [],\n )\n\n return null\n}\n\nconst CRUMB_LINK =\n 'max-w-[12rem] truncate text-muted-foreground transition-colors hover:text-foreground'\nconst CRUMB_CURRENT = 'max-w-[16rem] truncate font-medium text-foreground'\n\n/**\n * The single global breadcrumb (EC-214): project (the switcher) › section › …the\n * active record. The section crumb is the active VIEW's label (EC-237/239, resolved\n * from the rail-derived registry); deeper crumbs are contributed by the active\n * surface via {@link useBreadcrumbTrail}. The last crumb is the current page (plain\n * text); earlier crumbs link back.\n */\nfunction HeaderBreadcrumb({\n project,\n activeViewId,\n}: {\n project: Project\n activeViewId: string | null\n}) {\n const projectId: ProjectId = project.id\n const nav = useStudioNav(project)\n const extra = useBreadcrumbTrail()\n const view = activeViewId ? nav.registry.get(activeViewId) : undefined\n if (!view) return null\n const sectionIsLast = extra.length === 0\n\n return (\n <Row gap=\"1\" className=\"min-w-0 text-sm\" aria-label={translate('shell.breadcrumb.label')}>\n <ChevronRight className=\"size-3.5 shrink-0 text-muted-foreground/50\" aria-hidden />\n {sectionIsLast ? (\n <span className={CRUMB_CURRENT}>{view.label}</span>\n ) : (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.id }}\n className={CRUMB_LINK}\n >\n {view.label}\n </Link>\n )}\n {extra.map((item, index) => {\n const isLast = index === extra.length - 1\n return (\n <Row gap=\"1\" key={`${item.label}-${index}`} className=\"min-w-0\">\n <ChevronRight className=\"size-3.5 shrink-0 text-muted-foreground/50\" aria-hidden />\n {!isLast && item.to ? (\n // `to` and `params` are always contributed as a unit (the type\n // pairs them); the assertion satisfies the route's required params.\n <Link to={item.to} params={item.params!} className={CRUMB_LINK}>\n {item.label}\n </Link>\n ) : (\n <span className={isLast ? CRUMB_CURRENT : CRUMB_LINK}>{item.label}</span>\n )}\n </Row>\n )\n })}\n </Row>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"studio-shell.js","sourceRoot":"","sources":["../../src/components/studio-shell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { launcherVisitState, Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { ChevronRight } from 'lucide-react'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { Project, ProjectId } from '@elytracms/core/studio-core'\nimport { Row, Spacer, Toaster } from '@elytracms/studio/ui'\nimport { readRailCollapsed, storeRailCollapsed } from '../lib/rail'\nimport { composeTabTitle, studioBaseTitle, tabTitleParts } from '../lib/tab-title'\nimport { isSingleProjectWorkspace } from '../lib/workspace'\nimport { translate } from '../lib/i18n'\nimport { CurrentUserMenu } from './current-user-menu'\nimport { DeliveryHealthBanner } from './delivery-health-banner'\nimport { RolePolicyDriftIssues } from './users/role-policy-drift-issues'\nimport { PlaygroundBadge } from './playground-badge'\nimport { DevLoginBadge } from './dev-login-badge'\nimport { AiPanelToggle } from './ai/ai-panel-toggle'\nimport { ActiveProjectName, ProjectSwitcher } from './project-switcher'\nimport { SectionNav } from './section-nav'\nimport { useSwitcherEntries } from './workspace-projects'\nimport { activeViewIdFromPath, useStudioNav } from './app-views/sidebar'\nimport { BreadcrumbProvider, useBreadcrumbTrail } from './breadcrumb-context'\n\nexport interface StudioShellProps {\n project: Project\n children: ReactNode\n}\n\n/**\n * The Studio shell (brief §4.3, §8.1): a persistent top bar + section rail that\n * preserve the active project while the user moves between surfaces. The top bar\n * carries the single global breadcrumb reaching the active record (EC-214) — no\n * secondary band, no per-view title repeating the section. Responsive and\n * overflow-safe.\n */\nexport function StudioShell({ project, children }: StudioShellProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n\n // EC-401: the shared single-project predicate — the same one that drives the\n // launcher's auto-forward — decides whether the header slot carries the\n // switcher dropdown or just the project's name.\n const { entries } = useSwitcherEntries()\n const singleProject = isSingleProjectWorkspace(entries)\n\n // User-toggled, persisted rail collapse (EC-214). Defaults to expanded; the\n // stored preference is read after mount (no flash script needed for a rail).\n const [railCollapsed, setRailCollapsed] = useState(false)\n useEffect(() => {\n setRailCollapsed(readRailCollapsed())\n }, [])\n const toggleRail = useCallback(() => {\n setRailCollapsed((prev) => {\n const next = !prev\n storeRailCollapsed(next)\n return next\n })\n }, [])\n\n return (\n <BreadcrumbProvider>\n {/* EC-402: per-view tab title, fed by the same breadcrumb/nav data as the header. */}\n <StudioTabTitle project={project} activeViewId={activeViewId} />\n <div className=\"flex h-dvh flex-col bg-background\">\n {/* Top bar — global controls + the one breadcrumb to the active record. */}\n <header className=\"flex h-12 shrink-0 items-center gap-2 border-b border-border px-3\">\n {/* EC-401: an explicit \"all projects\" navigation — marked so the\n launcher's single-project auto-forward never bounces it back. */}\n <Link\n to=\"/\"\n state={launcherVisitState}\n className=\"grid size-6 shrink-0 place-items-center\"\n aria-label={translate('shell.allProjects')}\n >\n <span className=\"size-5 rounded-[5px] bg-primary shadow-sm\" />\n </Link>\n {singleProject ? (\n <ActiveProjectName project={project} />\n ) : (\n <ProjectSwitcher activeProject={project} />\n )}\n <HeaderBreadcrumb project={project} activeViewId={activeViewId} />\n <Spacer />\n {/* EC-153: explicit playground-mode marker (renders nothing on Convex). */}\n <PlaygroundBadge />\n {/* Dev-only unattended-auth marker (renders nothing in prod / when unset). */}\n <DevLoginBadge />\n {/* EC-199: AI assistant toggle — renders nothing unless `defineStudio({ ai })`. */}\n <AiPanelToggle />\n {/* EC-150: signed-in user + their role on this project + sign-out. */}\n <CurrentUserMenu projectId={project.id} />\n </header>\n\n {/* EC-268: warns when instant-publish env is incomplete (connected mode only). */}\n <DeliveryHealthBanner projectId={project.id} />\n {/* EC-605: a role-policy drift, published to the rail's issues foot (renders nothing). */}\n <RolePolicyDriftIssues projectId={project.id} />\n\n <div className=\"flex min-h-0 flex-1\">\n <SectionNav\n project={project}\n collapsed={railCollapsed}\n onToggleCollapsed={toggleRail}\n />\n\n <main className=\"flex min-w-0 flex-1 flex-col overflow-hidden\">\n <div className=\"min-h-0 flex-1 overflow-auto\">{children}</div>\n </main>\n </div>\n </div>\n {/* EC-268: transient action feedback (e.g. a publish that fails at transport). */}\n <Toaster />\n </BreadcrumbProvider>\n )\n}\n\n/**\n * Per-view browser tab title (EC-402). The studio is ONE catch-all splat route,\n * so per-view titles cannot come from per-route `head()` — this effect composes\n * `deepest crumb · view label · project name · base title` from the exact\n * sources the header breadcrumb renders (no new data flow, AD-17): the\n * contributed trail, the nav registry's localized view label, `project.name`,\n * and the `createStudioRootRoute` base title. Assignment is effect-only\n * (SSR-safe); unmounting (launcher, sign-in, project switch) restores the base\n * title, so non-project surfaces keep the consumer's branding untouched.\n */\nfunction StudioTabTitle({\n project,\n activeViewId,\n}: {\n project: Project\n activeViewId: string | null\n}) {\n const nav = useStudioNav(project)\n const trail = useBreadcrumbTrail()\n const view = activeViewId ? nav.registry.get(activeViewId) : undefined\n const title = composeTabTitle(\n tabTitleParts({\n breadcrumb: trail,\n viewLabel: view?.label,\n projectName: project.name,\n baseTitle: studioBaseTitle(),\n }),\n )\n\n useEffect(() => {\n document.title = title\n }, [title])\n\n // Leaving the project shell restores the base title (launcher/sign-in keep it).\n useEffect(\n () => () => {\n document.title = studioBaseTitle()\n },\n [],\n )\n\n return null\n}\n\nconst CRUMB_LINK =\n 'max-w-[12rem] truncate text-muted-foreground transition-colors hover:text-foreground'\nconst CRUMB_CURRENT = 'max-w-[16rem] truncate font-medium text-foreground'\n\n/**\n * The single global breadcrumb (EC-214): project (the switcher) › section › …the\n * active record. The section crumb is the active VIEW's label (EC-237/239, resolved\n * from the rail-derived registry); deeper crumbs are contributed by the active\n * surface via {@link useBreadcrumbTrail}. The last crumb is the current page (plain\n * text); earlier crumbs link back.\n */\nfunction HeaderBreadcrumb({\n project,\n activeViewId,\n}: {\n project: Project\n activeViewId: string | null\n}) {\n const projectId: ProjectId = project.id\n const nav = useStudioNav(project)\n const extra = useBreadcrumbTrail()\n const view = activeViewId ? nav.registry.get(activeViewId) : undefined\n if (!view) return null\n const sectionIsLast = extra.length === 0\n\n return (\n <Row gap=\"1\" className=\"min-w-0 text-sm\" aria-label={translate('shell.breadcrumb.label')}>\n <ChevronRight className=\"size-3.5 shrink-0 text-muted-foreground/50\" aria-hidden />\n {sectionIsLast ? (\n <span className={CRUMB_CURRENT}>{view.label}</span>\n ) : (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.id }}\n className={CRUMB_LINK}\n >\n {view.label}\n </Link>\n )}\n {extra.map((item, index) => {\n const isLast = index === extra.length - 1\n return (\n <Row gap=\"1\" key={`${item.label}-${index}`} className=\"min-w-0\">\n <ChevronRight className=\"size-3.5 shrink-0 text-muted-foreground/50\" aria-hidden />\n {!isLast && item.to ? (\n // `to` and `params` are always contributed as a unit (the type\n // pairs them); the assertion satisfies the route's required params.\n <Link to={item.to} params={item.params!} className={CRUMB_LINK}>\n {item.label}\n </Link>\n ) : (\n <span className={isLast ? CRUMB_CURRENT : CRUMB_LINK}>{item.label}</span>\n )}\n </Row>\n )\n })}\n </Row>\n )\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { Theme } from '
|
|
2
|
+
import type { Theme } from '../lib/theme.js';
|
|
3
3
|
/**
|
|
4
4
|
* Theme store: owns the active light/dark theme, persists the choice, and keeps
|
|
5
5
|
* the `dark` class on the document element in sync. The pre-hydration script
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { DEFAULT_THEME, applyTheme, readStoredTheme, storeTheme } from '
|
|
4
|
-
import { createRequiredContext } from '
|
|
3
|
+
import { DEFAULT_THEME, applyTheme, readStoredTheme, storeTheme } from '../lib/theme.js';
|
|
4
|
+
import { createRequiredContext } from '../lib/required-context.js';
|
|
5
5
|
const { Context: ThemeContext, useRequired: useTheme } = createRequiredContext('Theme');
|
|
6
6
|
export function ThemeProvider({ children }) {
|
|
7
7
|
const [theme, setThemeState] = useState(DEFAULT_THEME);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-context.js","sourceRoot":"","sources":["../../src/components/theme-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEjE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"theme-context.js","sourceRoot":"","sources":["../../src/components/theme-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEjE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAErF,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAc/D,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CAAa,OAAO,CAAC,CAAA;AAEnG,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAA2B;IACjE,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAQ,aAAa,CAAC,CAAA;IAE7D,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAChC,aAAa,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IACnG,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,IAAW,EAAE,EAAE;QAC3C,aAAa,CAAC,IAAI,CAAC,CAAA;QACnB,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAA;IAC5C,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EACxF,CAAC,KAAK,EAAE,QAAQ,CAAC,CAClB,CAAA;IAED,OAAO,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAyB,CAAA;AAChF,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { DEFAULT_THEME, applyTheme, readStoredTheme, storeTheme } from '../lib/theme'\nimport type { Theme } from '../lib/theme'\nimport { createRequiredContext } from '../lib/required-context'\n\n/**\n * Theme store: owns the active light/dark theme, persists the choice, and keeps\n * the `dark` class on the document element in sync. The pre-hydration script\n * (THEME_INIT_SCRIPT) sets the initial class; this provider syncs React state to\n * it on mount so toggling is reactive.\n */\nexport interface ThemeStore {\n theme: Theme\n setTheme: (theme: Theme) => void\n toggle: () => void\n}\n\nconst { Context: ThemeContext, useRequired: useTheme } = createRequiredContext<ThemeStore>('Theme')\n\nexport function ThemeProvider({ children }: { children: ReactNode }) {\n const [theme, setThemeState] = useState<Theme>(DEFAULT_THEME)\n\n // Sync state to whatever the pre-hydration script already applied.\n useEffect(() => {\n const stored = readStoredTheme()\n setThemeState(stored ?? (document.documentElement.classList.contains('dark') ? 'dark' : 'light'))\n }, [])\n\n const setTheme = useCallback((next: Theme) => {\n setThemeState(next)\n storeTheme(next)\n applyTheme(next, document.documentElement)\n }, [])\n\n const store = useMemo<ThemeStore>(\n () => ({ theme, setTheme, toggle: () => setTheme(theme === 'dark' ? 'light' : 'dark') }),\n [theme, setTheme],\n )\n\n return <ThemeContext.Provider value={store}>{children}</ThemeContext.Provider>\n}\n\nexport { useTheme }\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { OperationError, OperationResult } from '
|
|
2
|
+
import type { OperationError, OperationResult } from '../lib/operations/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* The shared hydration state machine (EC-329) behind both
|
|
5
5
|
* `ProjectSessionProvider` and `CmsSessionGate`: playground short-circuit,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Button, Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
-
import { translate } from '
|
|
4
|
+
import { translate } from '../lib/i18n/index.js';
|
|
5
5
|
import { OperationErrorBanner } from './operation-error.js';
|
|
6
6
|
/**
|
|
7
7
|
* `loader`'s IDENTITY is the refetch trigger — memoize it (`useCallback`) over
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-hydration-phase.js","sourceRoot":"","sources":["../../src/components/use-hydration-phase.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"use-hydration-phase.js","sourceRoot":"","sources":["../../src/components/use-hydration-phase.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '../lib/i18n'\nimport type { OperationError, OperationResult } from '../lib/operations'\nimport { OperationErrorBanner } from './operation-error'\n\n/**\n * The shared hydration state machine (EC-329) behind both\n * `ProjectSessionProvider` and `CmsSessionGate`: playground short-circuit,\n * phase union, attempt-driven retry, cancelled-flag cleanup — a specific\n * multi-part shape the two providers used to hand-roll identically. Real\n * asymmetries between them stay explicit HOOK OPTIONS rather than being\n * flattened away:\n *\n * - `ProjectSessionProvider`'s playground branch calls its `onResolved` side\n * effect (`configureContentSession(null)` — the reset; EC-361's separate\n * playground effect then hydrates the content session from the in-memory\n * adapter without gating); `CmsSessionGate` passes no `onResolved` at all\n * (it owns no such side effect — `loadCms`'s own body already calls it).\n * `enabled` toggles whether `onResolved` fires in the playground fast-path —\n * the ONE point of divergence between the two.\n * - `onResolved` runs synchronously BEFORE the phase flips to `'ready'` (both\n * the playground fast-path and the real load), so children reading a\n * module global (e.g. `configureContentSession`) see it updated on the\n * SAME render they first see `'ready'` — a bare `loader().then(setPhase)`\n * cannot guarantee that ordering.\n */\nexport type HydrationPhase<T> =\n | { kind: 'loading' }\n | { kind: 'ready'; value: T }\n | { kind: 'error'; error: OperationError }\n\nexport interface UseHydrationPhaseOptions<T> {\n /** Playground (fixture) sessions resolve synchronously to `playgroundValue` — no loader call. */\n playground: boolean\n /** The value `phase` resolves to in playground mode. */\n playgroundValue: T\n /** Call `onResolved` for the playground fast-path too (default `false`). */\n enabled?: boolean\n /**\n * Run synchronously before the phase flips to `'ready'` — in BOTH the\n * playground fast-path (when `enabled`) and the real load. Read via a ref\n * internally, so an inline (non-memoized) callback never re-triggers a fetch.\n */\n onResolved?: (value: T) => void\n}\n\nexport interface UseHydrationPhaseResult<T> {\n phase: HydrationPhase<T>\n /** Re-run the loader (the retry button's handler). */\n retry: () => void\n}\n\n/**\n * `loader`'s IDENTITY is the refetch trigger — memoize it (`useCallback`) over\n * whatever inputs should re-hydrate (e.g. `projectId`), exactly like the\n * original two effects keyed their dependency arrays.\n */\nexport function useHydrationPhase<T>(\n loader: () => Promise<OperationResult<T>>,\n options: UseHydrationPhaseOptions<T>,\n): UseHydrationPhaseResult<T> {\n const { playground, playgroundValue, enabled = false } = options\n const onResolvedRef = useRef(options.onResolved)\n onResolvedRef.current = options.onResolved\n\n const [phase, setPhase] = useState<HydrationPhase<T>>(() =>\n playground ? { kind: 'ready', value: playgroundValue } : { kind: 'loading' },\n )\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) {\n if (enabled) onResolvedRef.current?.(playgroundValue)\n return\n }\n let cancelled = false\n setPhase({ kind: 'loading' })\n void loader().then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setPhase({ kind: 'error', error: result.error })\n return\n }\n onResolvedRef.current?.(result.value)\n setPhase({ kind: 'ready', value: result.value })\n })\n return () => {\n cancelled = true\n }\n // `onResolved` is read via the ref above (see the doc comment) — its\n // identity intentionally does not retrigger this effect.\n }, [loader, playground, attempt, enabled, playgroundValue])\n\n const retry = useCallback(() => setAttempt((n) => n + 1), [])\n return { phase, retry }\n}\n\n/**\n * Renders `phase`'s loading/error frames, or `children(value)` once ready —\n * the identical 3-way branch both `ProjectSessionProvider` and\n * `CmsSessionGate` used to repeat verbatim.\n */\nexport function HydrationBoundary<T>({\n phase,\n retry,\n children,\n}: {\n phase: HydrationPhase<T>\n retry: () => void\n children: (value: T) => ReactNode\n}): ReactNode {\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') return <SessionHydrationError error={phase.error} onRetry={retry} />\n return <>{children(phase.value)}</>\n}\n\n/** Deterministic loading frame while persisted state is fetched (mirrors DeploymentSetupGate). */\nfunction SessionHydrating() {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\" data-testid=\"session-hydrating\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.session.loading')}\n </Text>\n </div>\n )\n}\n\n/** A failed hydration is a visible, retryable state — never a fixture fallback. */\nfunction SessionHydrationError({\n error,\n onRetry,\n}: {\n error: OperationError\n onRetry: () => void\n}) {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\">\n <Stack gap=\"3\" className=\"w-full max-w-xl\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('shell.session.error.title')}\n </Text>\n <OperationErrorBanner error={error} />\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.session.error.body')}\n </Text>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('common.retry')}\n </Button>\n </Stack>\n </div>\n )\n}\n"]}
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
import type { RolePolicy } from '@elytracms/core/persistence';
|
|
2
|
+
/**
|
|
3
|
+
* Invite-based onboarding UI (EC-324) — the admin "Invite User" action that
|
|
4
|
+
* REPLACES open sign-up, plus the list of pending invites. An admin invites an
|
|
5
|
+
* email + role; the invite is consumed when that email first creates their
|
|
6
|
+
* account. All three operations (`invites.create`/`list`/`revoke`) are admin-gated
|
|
7
|
+
* at the chokepoint AND server-side, so this UI is only mounted for admins (the
|
|
8
|
+
* Users module already gates non-admins).
|
|
9
|
+
*
|
|
10
|
+
* The invite form is a small ACTION dialog (email + role) — not a record property
|
|
11
|
+
* form — so a dialog is appropriate here (the house "no modal property forms" rule
|
|
12
|
+
* governs editing a record's fields, not a create action).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* EC-605 — what the invite picker offers for a project's role policy: its DECLARED
|
|
16
|
+
* roles with their labels, strongest first (`roleOptions`), and the preselected one —
|
|
17
|
+
* `editor` when the project declares it (the historical default), else its WEAKEST
|
|
18
|
+
* role, so a slip of the submit button never hands out more than the least authority.
|
|
19
|
+
* Pure (the policy in, the options out).
|
|
20
|
+
*/
|
|
21
|
+
export declare function inviteRoleChoices(policy: RolePolicy): {
|
|
22
|
+
options: {
|
|
23
|
+
value: string;
|
|
24
|
+
label: string;
|
|
25
|
+
}[];
|
|
26
|
+
defaultRole: string;
|
|
27
|
+
};
|
|
1
28
|
export declare function InviteUserBar({ projectId }: {
|
|
2
29
|
projectId: string;
|
|
3
30
|
}): import("react").JSX.Element;
|
|
@@ -3,9 +3,10 @@ import { useState } from 'react';
|
|
|
3
3
|
import { Mail, Plus, X } from 'lucide-react';
|
|
4
4
|
import { roleOptions } from '@elytracms/core/persistence';
|
|
5
5
|
import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Field, Input, Row, Stack, Text, toast, } from '@elytracms/studio/ui';
|
|
6
|
-
import { getStudioOperations } from '
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { getStudioOperations } from '../../lib/operations/index.js';
|
|
7
|
+
import { projectRolePolicyFor } from '../../lib/operations/project-runtime-config.js';
|
|
8
|
+
import { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query/index.js';
|
|
9
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
9
10
|
/**
|
|
10
11
|
* Invite-based onboarding UI (EC-324) — the admin "Invite User" action that
|
|
11
12
|
* REPLACES open sign-up, plus the list of pending invites. An admin invites an
|
|
@@ -18,8 +19,18 @@ import { translate } from '@elytracms/studio/lib/i18n';
|
|
|
18
19
|
* form — so a dialog is appropriate here (the house "no modal property forms" rule
|
|
19
20
|
* governs editing a record's fields, not a create action).
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* EC-605 — what the invite picker offers for a project's role policy: its DECLARED
|
|
24
|
+
* roles with their labels, strongest first (`roleOptions`), and the preselected one —
|
|
25
|
+
* `editor` when the project declares it (the historical default), else its WEAKEST
|
|
26
|
+
* role, so a slip of the submit button never hands out more than the least authority.
|
|
27
|
+
* Pure (the policy in, the options out).
|
|
28
|
+
*/
|
|
29
|
+
export function inviteRoleChoices(policy) {
|
|
30
|
+
const options = roleOptions(policy);
|
|
31
|
+
const defaultRole = options.find((option) => option.value === 'editor')?.value ?? options[options.length - 1]?.value ?? '';
|
|
32
|
+
return { options, defaultRole };
|
|
33
|
+
}
|
|
23
34
|
function invitesQueryKey(projectId) {
|
|
24
35
|
return ['invites', projectId];
|
|
25
36
|
}
|
|
@@ -36,6 +47,9 @@ export function InviteUserBar({ projectId }) {
|
|
|
36
47
|
});
|
|
37
48
|
const { status } = listStoreState(query);
|
|
38
49
|
const invites = query.data ?? [];
|
|
50
|
+
// EC-605: a pending invite names its role by the project's declared label.
|
|
51
|
+
const { options: roleChoices } = inviteRoleChoices(projectRolePolicyFor(projectId));
|
|
52
|
+
const roleLabel = (role) => roleChoices.find((option) => option.value === role)?.label ?? role;
|
|
39
53
|
const [open, setOpen] = useState(false);
|
|
40
54
|
async function revoke(email) {
|
|
41
55
|
const { operations } = await getStudioOperations();
|
|
@@ -47,7 +61,7 @@ export function InviteUserBar({ projectId }) {
|
|
|
47
61
|
cache.patch(invitesQueryKey(projectId), (prev) => (prev ?? []).filter((invite) => invite.email !== email));
|
|
48
62
|
toast.success(translate('invites.revoked', { email }));
|
|
49
63
|
}
|
|
50
|
-
return (_jsxs(Row, { gap: "3", align: "center", className: "shrink-0 flex-wrap border-b border-border px-4 py-2 sm:px-6", children: [_jsxs(Button, { size: "sm", onClick: () => setOpen(true), "data-testid": "invite-user-button", children: [_jsx(Plus, {}), translate('invites.invite')] }), status === 'ready' && invites.length > 0 ? (_jsxs(Row, { gap: "2", align: "center", className: "min-w-0 flex-wrap", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('invites.pending') }), invites.map((invite) => (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Mail, { className: "size-3" }), _jsx("span", { className: "truncate", children: invite.email }), _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", invite.role] }), _jsx("button", { type: "button", "aria-label": translate('invites.revokeAria', { email: invite.email }), onClick: () => void revoke(invite.email), className: "ml-0.5 rounded-sm text-muted-foreground hover:text-foreground", children: _jsx(X, { className: "size-3" }) })] }, invite.email)))] })) : null, _jsx(InviteDialog, { projectId: projectId, open: open, onOpenChange: setOpen, onInvited: (invite) => {
|
|
64
|
+
return (_jsxs(Row, { gap: "3", align: "center", className: "shrink-0 flex-wrap border-b border-border px-4 py-2 sm:px-6", children: [_jsxs(Button, { size: "sm", onClick: () => setOpen(true), "data-testid": "invite-user-button", children: [_jsx(Plus, {}), translate('invites.invite')] }), status === 'ready' && invites.length > 0 ? (_jsxs(Row, { gap: "2", align: "center", className: "min-w-0 flex-wrap", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('invites.pending') }), invites.map((invite) => (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Mail, { className: "size-3" }), _jsx("span", { className: "truncate", children: invite.email }), _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", roleLabel(invite.role)] }), _jsx("button", { type: "button", "aria-label": translate('invites.revokeAria', { email: invite.email }), onClick: () => void revoke(invite.email), className: "ml-0.5 rounded-sm text-muted-foreground hover:text-foreground", children: _jsx(X, { className: "size-3" }) })] }, invite.email)))] })) : null, _jsx(InviteDialog, { projectId: projectId, open: open, onOpenChange: setOpen, onInvited: (invite) => {
|
|
51
65
|
cache.patch(invitesQueryKey(projectId), (prev) => {
|
|
52
66
|
const rest = (prev ?? []).filter((i) => i.email !== invite.email);
|
|
53
67
|
return [...rest, invite].sort((a, b) => (a.email < b.email ? -1 : a.email > b.email ? 1 : 0));
|
|
@@ -55,13 +69,15 @@ export function InviteUserBar({ projectId }) {
|
|
|
55
69
|
} })] }));
|
|
56
70
|
}
|
|
57
71
|
function InviteDialog({ projectId, open, onOpenChange, onInvited, }) {
|
|
72
|
+
// EC-605: the project's declared roles (the default three when it declares none).
|
|
73
|
+
const { options: roleOptionsForProject, defaultRole } = inviteRoleChoices(projectRolePolicyFor(projectId));
|
|
58
74
|
const [email, setEmail] = useState('');
|
|
59
|
-
const [role, setRole] = useState(
|
|
75
|
+
const [role, setRole] = useState(defaultRole);
|
|
60
76
|
const [phase, setPhase] = useState('idle');
|
|
61
77
|
const [error, setError] = useState(null);
|
|
62
78
|
function reset() {
|
|
63
79
|
setEmail('');
|
|
64
|
-
setRole(
|
|
80
|
+
setRole(defaultRole);
|
|
65
81
|
setError(null);
|
|
66
82
|
setPhase('idle');
|
|
67
83
|
}
|
|
@@ -86,6 +102,6 @@ function InviteDialog({ projectId, open, onOpenChange, onInvited, }) {
|
|
|
86
102
|
if (!next)
|
|
87
103
|
reset();
|
|
88
104
|
onOpenChange(next);
|
|
89
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('invites.dialogTitle') }), _jsx(DialogDescription, { children: translate('invites.dialogBody') })] }), _jsx("form", { onSubmit: handleSubmit, noValidate: true, children: _jsxs(Stack, { gap: "4", children: [_jsx(Field, { label: translate('invites.emailLabel'), htmlFor: "invite-email", required: true, error: error, children: _jsx(Input, { id: "invite-email", type: "email", autoComplete: "off", placeholder: "teammate@example.com", value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }) }), _jsx(Field, { label: translate('invites.roleLabel'), htmlFor: "invite-role", children: _jsx("select", { id: "invite-role", value: role, onChange: (e) => setRole(e.target.value), disabled: submitting, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", children:
|
|
105
|
+
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('invites.dialogTitle') }), _jsx(DialogDescription, { children: translate('invites.dialogBody') })] }), _jsx("form", { onSubmit: handleSubmit, noValidate: true, children: _jsxs(Stack, { gap: "4", children: [_jsx(Field, { label: translate('invites.emailLabel'), htmlFor: "invite-email", required: true, error: error, children: _jsx(Input, { id: "invite-email", type: "email", autoComplete: "off", placeholder: "teammate@example.com", value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }) }), _jsx(Field, { label: translate('invites.roleLabel'), htmlFor: "invite-role", children: _jsx("select", { id: "invite-role", value: role, onChange: (e) => setRole(e.target.value), disabled: submitting, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", children: roleOptionsForProject.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), disabled: submitting, children: translate('common.cancel') }), _jsx(Button, { type: "submit", disabled: submitting || email.trim().length === 0, children: submitting ? translate('invites.sending') : translate('invites.send') })] })] }) })] }) }));
|
|
90
106
|
}
|
|
91
107
|
//# sourceMappingURL=invite-user-bar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite-user-bar.js","sourceRoot":"","sources":["../../../src/components/users/invite-user-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { FormEvent } from 'react'\nimport { Mail, Plus, X } from 'lucide-react'\nimport type { InviteRecord } from '@elytracms/core/persistence'\nimport { roleOptions } from '@elytracms/core/persistence'\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Row,\n Stack,\n Text,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Invite-based onboarding UI (EC-324) — the admin \"Invite User\" action that\n * REPLACES open sign-up, plus the list of pending invites. An admin invites an\n * email + role; the invite is consumed when that email first creates their\n * account. All three operations (`invites.create`/`list`/`revoke`) are admin-gated\n * at the chokepoint AND server-side, so this UI is only mounted for admins (the\n * Users module already gates non-admins).\n *\n * The invite form is a small ACTION dialog (email + role) — not a record property\n * form — so a dialog is appropriate here (the house \"no modal property forms\" rule\n * governs editing a record's fields, not a create action).\n */\nconst ROLE_OPTIONS = roleOptions()\nconst DEFAULT_INVITE_ROLE = ROLE_OPTIONS.find((r) => r.value === 'editor')?.value ?? ROLE_OPTIONS[0]?.value ?? 'viewer'\n\nfunction invitesQueryKey(projectId: string): readonly [string, string] {\n return ['invites', projectId]\n}\n\nexport function InviteUserBar({ projectId }: { projectId: string }) {\n const cache = useOperationCache()\n const query = useOperationQuery<InviteRecord[]>({\n queryKey: invitesQueryKey(projectId),\n loader: async (operations) => {\n const result = await operations.invites.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n },\n })\n const { status } = listStoreState(query)\n const invites = query.data ?? []\n\n const [open, setOpen] = useState(false)\n\n async function revoke(email: string) {\n const { operations } = await getStudioOperations()\n const result = await operations.invites.revoke({ projectId, email })\n if (!result.ok) {\n toast.error(result.error.message)\n return\n }\n cache.patch<InviteRecord[]>(invitesQueryKey(projectId), (prev) =>\n (prev ?? []).filter((invite) => invite.email !== email),\n )\n toast.success(translate('invites.revoked', { email }))\n }\n\n return (\n <Row\n gap=\"3\"\n align=\"center\"\n className=\"shrink-0 flex-wrap border-b border-border px-4 py-2 sm:px-6\"\n >\n <Button size=\"sm\" onClick={() => setOpen(true)} data-testid=\"invite-user-button\">\n <Plus />\n {translate('invites.invite')}\n </Button>\n\n {status === 'ready' && invites.length > 0 ? (\n <Row gap=\"2\" align=\"center\" className=\"min-w-0 flex-wrap\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('invites.pending')}\n </Text>\n {invites.map((invite) => (\n <Badge key={invite.email} variant=\"outline\" className=\"gap-1 font-normal\">\n <Mail className=\"size-3\" />\n <span className=\"truncate\">{invite.email}</span>\n <span className=\"text-muted-foreground\">· {invite.role}</span>\n <button\n type=\"button\"\n aria-label={translate('invites.revokeAria', { email: invite.email })}\n onClick={() => void revoke(invite.email)}\n className=\"ml-0.5 rounded-sm text-muted-foreground hover:text-foreground\"\n >\n <X className=\"size-3\" />\n </button>\n </Badge>\n ))}\n </Row>\n ) : null}\n\n <InviteDialog\n projectId={projectId}\n open={open}\n onOpenChange={setOpen}\n onInvited={(invite) => {\n cache.patch<InviteRecord[]>(invitesQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((i) => i.email !== invite.email)\n return [...rest, invite].sort((a, b) => (a.email < b.email ? -1 : a.email > b.email ? 1 : 0))\n })\n }}\n />\n </Row>\n )\n}\n\nfunction InviteDialog({\n projectId,\n open,\n onOpenChange,\n onInvited,\n}: {\n projectId: string\n open: boolean\n onOpenChange: (open: boolean) => void\n onInvited: (invite: InviteRecord) => void\n}) {\n const [email, setEmail] = useState('')\n const [role, setRole] = useState<string>(DEFAULT_INVITE_ROLE)\n const [phase, setPhase] = useState<'idle' | 'submitting'>('idle')\n const [error, setError] = useState<string | null>(null)\n\n function reset() {\n setEmail('')\n setRole(DEFAULT_INVITE_ROLE)\n setError(null)\n setPhase('idle')\n }\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault()\n setError(null)\n setPhase('submitting')\n const { operations } = await getStudioOperations()\n const result = await operations.invites.create({ projectId, email, role })\n setPhase('idle')\n if (!result.ok) {\n setError(result.error.message)\n return\n }\n onInvited(result.value)\n toast.success(translate('invites.sent', { email: result.value.email }))\n reset()\n onOpenChange(false)\n }\n\n const submitting = phase === 'submitting'\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset()\n onOpenChange(next)\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('invites.dialogTitle')}</DialogTitle>\n <DialogDescription>{translate('invites.dialogBody')}</DialogDescription>\n </DialogHeader>\n <form onSubmit={handleSubmit} noValidate>\n <Stack gap=\"4\">\n <Field label={translate('invites.emailLabel')} htmlFor=\"invite-email\" required error={error}>\n <Input\n id=\"invite-email\"\n type=\"email\"\n autoComplete=\"off\"\n placeholder=\"teammate@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={submitting}\n />\n </Field>\n <Field label={translate('invites.roleLabel')} htmlFor=\"invite-role\">\n <select\n id=\"invite-role\"\n value={role}\n onChange={(e) => setRole(e.target.value)}\n disabled={submitting}\n className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n >\n {ROLE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </Field>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={() => onOpenChange(false)} disabled={submitting}>\n {translate('common.cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting || email.trim().length === 0}>\n {submitting ? translate('invites.sending') : translate('invites.send')}\n </Button>\n </DialogFooter>\n </Stack>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"invite-user-bar.js","sourceRoot":"","sources":["../../../src/components/users/invite-user-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { FormEvent } from 'react'\nimport { Mail, Plus, X } from 'lucide-react'\nimport type { InviteRecord, RolePolicy } from '@elytracms/core/persistence'\nimport { roleOptions } from '@elytracms/core/persistence'\nimport {\n Badge,\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Row,\n Stack,\n Text,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioOperations } from '../../lib/operations'\nimport { projectRolePolicyFor } from '../../lib/operations/project-runtime-config'\nimport { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query'\nimport { translate } from '../../lib/i18n'\n\n/**\n * Invite-based onboarding UI (EC-324) — the admin \"Invite User\" action that\n * REPLACES open sign-up, plus the list of pending invites. An admin invites an\n * email + role; the invite is consumed when that email first creates their\n * account. All three operations (`invites.create`/`list`/`revoke`) are admin-gated\n * at the chokepoint AND server-side, so this UI is only mounted for admins (the\n * Users module already gates non-admins).\n *\n * The invite form is a small ACTION dialog (email + role) — not a record property\n * form — so a dialog is appropriate here (the house \"no modal property forms\" rule\n * governs editing a record's fields, not a create action).\n */\n/**\n * EC-605 — what the invite picker offers for a project's role policy: its DECLARED\n * roles with their labels, strongest first (`roleOptions`), and the preselected one —\n * `editor` when the project declares it (the historical default), else its WEAKEST\n * role, so a slip of the submit button never hands out more than the least authority.\n * Pure (the policy in, the options out).\n */\nexport function inviteRoleChoices(policy: RolePolicy): {\n options: { value: string; label: string }[]\n defaultRole: string\n} {\n const options = roleOptions(policy)\n const defaultRole =\n options.find((option) => option.value === 'editor')?.value ?? options[options.length - 1]?.value ?? ''\n return { options, defaultRole }\n}\n\nfunction invitesQueryKey(projectId: string): readonly [string, string] {\n return ['invites', projectId]\n}\n\nexport function InviteUserBar({ projectId }: { projectId: string }) {\n const cache = useOperationCache()\n const query = useOperationQuery<InviteRecord[]>({\n queryKey: invitesQueryKey(projectId),\n loader: async (operations) => {\n const result = await operations.invites.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n },\n })\n const { status } = listStoreState(query)\n const invites = query.data ?? []\n // EC-605: a pending invite names its role by the project's declared label.\n const { options: roleChoices } = inviteRoleChoices(projectRolePolicyFor(projectId))\n const roleLabel = (role: string) => roleChoices.find((option) => option.value === role)?.label ?? role\n\n const [open, setOpen] = useState(false)\n\n async function revoke(email: string) {\n const { operations } = await getStudioOperations()\n const result = await operations.invites.revoke({ projectId, email })\n if (!result.ok) {\n toast.error(result.error.message)\n return\n }\n cache.patch<InviteRecord[]>(invitesQueryKey(projectId), (prev) =>\n (prev ?? []).filter((invite) => invite.email !== email),\n )\n toast.success(translate('invites.revoked', { email }))\n }\n\n return (\n <Row\n gap=\"3\"\n align=\"center\"\n className=\"shrink-0 flex-wrap border-b border-border px-4 py-2 sm:px-6\"\n >\n <Button size=\"sm\" onClick={() => setOpen(true)} data-testid=\"invite-user-button\">\n <Plus />\n {translate('invites.invite')}\n </Button>\n\n {status === 'ready' && invites.length > 0 ? (\n <Row gap=\"2\" align=\"center\" className=\"min-w-0 flex-wrap\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('invites.pending')}\n </Text>\n {invites.map((invite) => (\n <Badge key={invite.email} variant=\"outline\" className=\"gap-1 font-normal\">\n <Mail className=\"size-3\" />\n <span className=\"truncate\">{invite.email}</span>\n <span className=\"text-muted-foreground\">· {roleLabel(invite.role)}</span>\n <button\n type=\"button\"\n aria-label={translate('invites.revokeAria', { email: invite.email })}\n onClick={() => void revoke(invite.email)}\n className=\"ml-0.5 rounded-sm text-muted-foreground hover:text-foreground\"\n >\n <X className=\"size-3\" />\n </button>\n </Badge>\n ))}\n </Row>\n ) : null}\n\n <InviteDialog\n projectId={projectId}\n open={open}\n onOpenChange={setOpen}\n onInvited={(invite) => {\n cache.patch<InviteRecord[]>(invitesQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((i) => i.email !== invite.email)\n return [...rest, invite].sort((a, b) => (a.email < b.email ? -1 : a.email > b.email ? 1 : 0))\n })\n }}\n />\n </Row>\n )\n}\n\nfunction InviteDialog({\n projectId,\n open,\n onOpenChange,\n onInvited,\n}: {\n projectId: string\n open: boolean\n onOpenChange: (open: boolean) => void\n onInvited: (invite: InviteRecord) => void\n}) {\n // EC-605: the project's declared roles (the default three when it declares none).\n const { options: roleOptionsForProject, defaultRole } = inviteRoleChoices(projectRolePolicyFor(projectId))\n const [email, setEmail] = useState('')\n const [role, setRole] = useState<string>(defaultRole)\n const [phase, setPhase] = useState<'idle' | 'submitting'>('idle')\n const [error, setError] = useState<string | null>(null)\n\n function reset() {\n setEmail('')\n setRole(defaultRole)\n setError(null)\n setPhase('idle')\n }\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault()\n setError(null)\n setPhase('submitting')\n const { operations } = await getStudioOperations()\n const result = await operations.invites.create({ projectId, email, role })\n setPhase('idle')\n if (!result.ok) {\n setError(result.error.message)\n return\n }\n onInvited(result.value)\n toast.success(translate('invites.sent', { email: result.value.email }))\n reset()\n onOpenChange(false)\n }\n\n const submitting = phase === 'submitting'\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset()\n onOpenChange(next)\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('invites.dialogTitle')}</DialogTitle>\n <DialogDescription>{translate('invites.dialogBody')}</DialogDescription>\n </DialogHeader>\n <form onSubmit={handleSubmit} noValidate>\n <Stack gap=\"4\">\n <Field label={translate('invites.emailLabel')} htmlFor=\"invite-email\" required error={error}>\n <Input\n id=\"invite-email\"\n type=\"email\"\n autoComplete=\"off\"\n placeholder=\"teammate@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={submitting}\n />\n </Field>\n <Field label={translate('invites.roleLabel')} htmlFor=\"invite-role\">\n <select\n id=\"invite-role\"\n value={role}\n onChange={(e) => setRole(e.target.value)}\n disabled={submitting}\n className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n >\n {roleOptionsForProject.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </Field>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={() => onOpenChange(false)} disabled={submitting}>\n {translate('common.cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting || email.trim().length === 0}>\n {submitting ? translate('invites.sending') : translate('invites.send')}\n </Button>\n </DialogFooter>\n </Stack>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RolePolicy } from '@elytracms/core/persistence';
|
|
2
|
+
import type { DeployedRolePolicyProbe, RolePolicyDriftVerdict } from '@elytracms/core/export-sync/deployment-ready';
|
|
3
|
+
import type { NavIssue } from '../nav-issues-registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* EC-605 — the studio's half of the role-policy drift check: which verdict, if any, the
|
|
6
|
+
* signed-in user is shown. Pure. The decision itself is core's ONE verdict
|
|
7
|
+
* (`rolePolicyDriftVerdict`, the same `elytra doctor` prints as its `role-policy` row);
|
|
8
|
+
* the studio surfaces only the `fail`s — a deployment enforcing another policy than the
|
|
9
|
+
* config declares (or an invalid one, or none it can show while the config declares
|
|
10
|
+
* roles). A `warn` (functions without `roles:fingerprint`, no roles declared) is a
|
|
11
|
+
* developer's upgrade step the doctor names; an `unknown` is transient. `null` → nothing
|
|
12
|
+
* to show.
|
|
13
|
+
*/
|
|
14
|
+
export declare function studioRolePolicyDrift({ policy, declared, probe, }: {
|
|
15
|
+
readonly policy: RolePolicy;
|
|
16
|
+
readonly declared: boolean;
|
|
17
|
+
readonly probe: DeployedRolePolicyProbe;
|
|
18
|
+
}): RolePolicyDriftVerdict | null;
|
|
19
|
+
/** The rail-foot finding for a verdict (pure): the localized headline, then the verdict's own detail and fix. */
|
|
20
|
+
export declare function rolePolicyDriftNavIssues(verdict: RolePolicyDriftVerdict | null): readonly NavIssue[];
|
|
21
|
+
/**
|
|
22
|
+
* EC-605 — a visible issue, from boot, for every signed-in user when the connected
|
|
23
|
+
* deployment enforces a different role policy than this project's `elytra.config.ts`
|
|
24
|
+
* declares. While the two copies differ the deployment enforces ITS copy: a role only the
|
|
25
|
+
* config declares is refused everywhere, and a role whose grants the config reduced keeps
|
|
26
|
+
* the deployed grants — so a drift is never left silent. Published to the rail's issues
|
|
27
|
+
* foot (EC-616 `useNavIssues`, on every studio surface) for as long as the shell is
|
|
28
|
+
* mounted; renders nothing itself.
|
|
29
|
+
*
|
|
30
|
+
* It reads the deployed fingerprint through the public `roles:fingerprint` query with the
|
|
31
|
+
* same probe the doctor runs (`probeDeployedRolePolicy`), cached in the one query cache
|
|
32
|
+
* like every studio read (AD-17). Playground has no deployed copy (the operations layer is
|
|
33
|
+
* the only reader), so it never publishes there. A read that fails publishes nothing —
|
|
34
|
+
* only a CONFIRMED drift does.
|
|
35
|
+
*/
|
|
36
|
+
export declare function RolePolicyDriftIssues({ projectId }: {
|
|
37
|
+
projectId: string;
|
|
38
|
+
}): null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { rolePolicyFingerprint } from '@elytracms/core/persistence';
|
|
3
|
+
import { probeDeployedRolePolicy, rolePolicyDriftVerdict } from '@elytracms/core/export-sync/deployment-ready';
|
|
4
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
5
|
+
import { projectRolePolicyFor } from '../../lib/operations/project-runtime-config.js';
|
|
6
|
+
import { resolveBackendConfig } from '../../lib/persistence/backend-mode.js';
|
|
7
|
+
import { configProjectFor, deploymentUrlForSlug } from '../../lib/workspace/index.js';
|
|
8
|
+
import { useOperationQuery } from '../../lib/query/index.js';
|
|
9
|
+
import { useNavIssues } from '../nav-issues-registry.js';
|
|
10
|
+
/**
|
|
11
|
+
* EC-605 — the studio's half of the role-policy drift check: which verdict, if any, the
|
|
12
|
+
* signed-in user is shown. Pure. The decision itself is core's ONE verdict
|
|
13
|
+
* (`rolePolicyDriftVerdict`, the same `elytra doctor` prints as its `role-policy` row);
|
|
14
|
+
* the studio surfaces only the `fail`s — a deployment enforcing another policy than the
|
|
15
|
+
* config declares (or an invalid one, or none it can show while the config declares
|
|
16
|
+
* roles). A `warn` (functions without `roles:fingerprint`, no roles declared) is a
|
|
17
|
+
* developer's upgrade step the doctor names; an `unknown` is transient. `null` → nothing
|
|
18
|
+
* to show.
|
|
19
|
+
*/
|
|
20
|
+
export function studioRolePolicyDrift({ policy, declared, probe, }) {
|
|
21
|
+
const verdict = rolePolicyDriftVerdict({ configFingerprint: rolePolicyFingerprint(policy), configDeclared: declared, probe });
|
|
22
|
+
return verdict.status === 'fail' ? verdict : null;
|
|
23
|
+
}
|
|
24
|
+
/** The rail-foot finding for a verdict (pure): the localized headline, then the verdict's own detail and fix. */
|
|
25
|
+
export function rolePolicyDriftNavIssues(verdict) {
|
|
26
|
+
if (!verdict)
|
|
27
|
+
return [];
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
key: 'drift',
|
|
31
|
+
severity: 'error',
|
|
32
|
+
message: `${translate('shell.rolePolicyDrift.title')} ${verdict.detail}${verdict.fix ? ` ${verdict.fix}` : ''}`,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
const NO_ISSUES = [];
|
|
37
|
+
/**
|
|
38
|
+
* EC-605 — a visible issue, from boot, for every signed-in user when the connected
|
|
39
|
+
* deployment enforces a different role policy than this project's `elytra.config.ts`
|
|
40
|
+
* declares. While the two copies differ the deployment enforces ITS copy: a role only the
|
|
41
|
+
* config declares is refused everywhere, and a role whose grants the config reduced keeps
|
|
42
|
+
* the deployed grants — so a drift is never left silent. Published to the rail's issues
|
|
43
|
+
* foot (EC-616 `useNavIssues`, on every studio surface) for as long as the shell is
|
|
44
|
+
* mounted; renders nothing itself.
|
|
45
|
+
*
|
|
46
|
+
* It reads the deployed fingerprint through the public `roles:fingerprint` query with the
|
|
47
|
+
* same probe the doctor runs (`probeDeployedRolePolicy`), cached in the one query cache
|
|
48
|
+
* like every studio read (AD-17). Playground has no deployed copy (the operations layer is
|
|
49
|
+
* the only reader), so it never publishes there. A read that fails publishes nothing —
|
|
50
|
+
* only a CONFIRMED drift does.
|
|
51
|
+
*/
|
|
52
|
+
export function RolePolicyDriftIssues({ projectId }) {
|
|
53
|
+
const backend = resolveBackendConfig();
|
|
54
|
+
const url = backend.kind === 'convex' ? deploymentUrlForSlug(projectId, backend.url) : undefined;
|
|
55
|
+
const query = useOperationQuery({
|
|
56
|
+
queryKey: ['roles', 'deployed-policy', projectId],
|
|
57
|
+
loader: () => probeDeployedRolePolicy(url),
|
|
58
|
+
enabled: url !== undefined,
|
|
59
|
+
});
|
|
60
|
+
const probe = query.data;
|
|
61
|
+
const issues = useMemo(() => probe
|
|
62
|
+
? rolePolicyDriftNavIssues(studioRolePolicyDrift({
|
|
63
|
+
policy: projectRolePolicyFor(projectId),
|
|
64
|
+
declared: configProjectFor(projectId)?.roles !== undefined,
|
|
65
|
+
probe,
|
|
66
|
+
}))
|
|
67
|
+
: NO_ISSUES, [probe, projectId]);
|
|
68
|
+
useNavIssues('roles:policy', issues);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=role-policy-drift-issues.js.map
|