@elytracms/studio 0.0.26 → 0.0.28

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.
Files changed (138) hide show
  1. package/convex/assets.ts +19 -10
  2. package/convex/authority.ts +60 -0
  3. package/convex/cliTokens.ts +4 -4
  4. package/convex/cms.ts +12 -12
  5. package/convex/content.ts +10 -3
  6. package/convex/delivery.ts +22 -4
  7. package/convex/guard.ts +28 -11
  8. package/convex/members.ts +40 -7
  9. package/convex/redirects.ts +67 -0
  10. package/convex/references.ts +11 -11
  11. package/convex/schema.ts +30 -1
  12. package/convex/sync.ts +17 -10
  13. package/convex/users.ts +63 -0
  14. package/dist/components/app-views/default-modules.d.ts +33 -14
  15. package/dist/components/app-views/default-modules.js +59 -20
  16. package/dist/components/app-views/default-modules.js.map +1 -1
  17. package/dist/components/app-views/module.d.ts +32 -0
  18. package/dist/components/app-views/module.js +16 -0
  19. package/dist/components/app-views/module.js.map +1 -1
  20. package/dist/components/app-views/sidebar.d.ts +15 -1
  21. package/dist/components/app-views/sidebar.js +10 -0
  22. package/dist/components/app-views/sidebar.js.map +1 -1
  23. package/dist/components/cms/asset-picker.js +4 -4
  24. package/dist/components/cms/asset-picker.js.map +1 -1
  25. package/dist/components/cms/cell.js +16 -13
  26. package/dist/components/cms/cell.js.map +1 -1
  27. package/dist/components/cms/cms-store.d.ts +11 -26
  28. package/dist/components/cms/cms-store.js +25 -160
  29. package/dist/components/cms/cms-store.js.map +1 -1
  30. package/dist/components/cms/collection-table.d.ts +10 -0
  31. package/dist/components/cms/collection-table.js +72 -72
  32. package/dist/components/cms/collection-table.js.map +1 -1
  33. package/dist/components/cms/detail-editor.d.ts +14 -0
  34. package/dist/components/cms/detail-editor.js +224 -72
  35. package/dist/components/cms/detail-editor.js.map +1 -1
  36. package/dist/components/cms/document-editor-context.d.ts +26 -0
  37. package/dist/components/cms/document-editor-context.js.map +1 -1
  38. package/dist/components/cms/editable-cell.js +12 -7
  39. package/dist/components/cms/editable-cell.js.map +1 -1
  40. package/dist/components/cms/field-control.d.ts +2 -0
  41. package/dist/components/cms/field-control.js +40 -30
  42. package/dist/components/cms/field-control.js.map +1 -1
  43. package/dist/components/cms/filter-builder.d.ts +18 -0
  44. package/dist/components/cms/filter-builder.js +47 -6
  45. package/dist/components/cms/filter-builder.js.map +1 -1
  46. package/dist/components/cms/import-export.js +5 -3
  47. package/dist/components/cms/import-export.js.map +1 -1
  48. package/dist/components/cms/reference-confirm.js +8 -9
  49. package/dist/components/cms/reference-confirm.js.map +1 -1
  50. package/dist/components/cms/relation-picker.js +13 -10
  51. package/dist/components/cms/relation-picker.js.map +1 -1
  52. package/dist/components/cms/use-cell-edit.js +13 -7
  53. package/dist/components/cms/use-cell-edit.js.map +1 -1
  54. package/dist/components/cms/use-document-versions.d.ts +16 -0
  55. package/dist/components/cms/use-document-versions.js +31 -0
  56. package/dist/components/cms/use-document-versions.js.map +1 -0
  57. package/dist/components/cms/version-compare-text.d.ts +30 -0
  58. package/dist/components/cms/version-compare-text.js +113 -0
  59. package/dist/components/cms/version-compare-text.js.map +1 -0
  60. package/dist/components/cms/version-compare.d.ts +12 -0
  61. package/dist/components/cms/version-compare.js +271 -0
  62. package/dist/components/cms/version-compare.js.map +1 -0
  63. package/dist/components/cms/versions-pane.d.ts +3 -0
  64. package/dist/components/cms/versions-pane.js +47 -38
  65. package/dist/components/cms/versions-pane.js.map +1 -1
  66. package/dist/components/cms/view-bar.d.ts +21 -6
  67. package/dist/components/cms/view-bar.js +42 -57
  68. package/dist/components/cms/view-bar.js.map +1 -1
  69. package/dist/components/collection-view/cms-collection-view-source.d.ts +15 -0
  70. package/dist/components/collection-view/cms-collection-view-source.js +158 -0
  71. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -0
  72. package/dist/components/collection-view/collection-view-context.d.ts +10 -0
  73. package/dist/components/collection-view/collection-view-context.js +26 -0
  74. package/dist/components/collection-view/collection-view-context.js.map +1 -0
  75. package/dist/components/current-user-menu.js +8 -1
  76. package/dist/components/current-user-menu.js.map +1 -1
  77. package/dist/components/redirects/redirects-collection-view-source.d.ts +18 -0
  78. package/dist/components/redirects/redirects-collection-view-source.js +114 -0
  79. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -0
  80. package/dist/components/redirects/redirects-module.d.ts +18 -0
  81. package/dist/components/redirects/redirects-module.js +43 -0
  82. package/dist/components/redirects/redirects-module.js.map +1 -0
  83. package/dist/components/redirects/redirects-schema.d.ts +10 -0
  84. package/dist/components/redirects/redirects-schema.js +42 -0
  85. package/dist/components/redirects/redirects-schema.js.map +1 -0
  86. package/dist/components/redirects/redirects-store.d.ts +46 -0
  87. package/dist/components/redirects/redirects-store.js +124 -0
  88. package/dist/components/redirects/redirects-store.js.map +1 -0
  89. package/dist/components/references/references-panel.js +2 -2
  90. package/dist/components/references/references-panel.js.map +1 -1
  91. package/dist/components/section-nav.js +14 -5
  92. package/dist/components/section-nav.js.map +1 -1
  93. package/dist/components/users/user-search.d.ts +8 -0
  94. package/dist/components/users/user-search.js +13 -0
  95. package/dist/components/users/user-search.js.map +1 -0
  96. package/dist/components/users/users-collection-view-source.d.ts +28 -0
  97. package/dist/components/users/users-collection-view-source.js +157 -0
  98. package/dist/components/users/users-collection-view-source.js.map +1 -0
  99. package/dist/components/users/users-module.d.ts +23 -0
  100. package/dist/components/users/users-module.js +61 -0
  101. package/dist/components/users/users-module.js.map +1 -0
  102. package/dist/components/users/users-schema.d.ts +19 -0
  103. package/dist/components/users/users-schema.js +38 -0
  104. package/dist/components/users/users-schema.js.map +1 -0
  105. package/dist/components/users/users-store.d.ts +40 -0
  106. package/dist/components/users/users-store.js +87 -0
  107. package/dist/components/users/users-store.js.map +1 -0
  108. package/dist/lib/cms-views/index.d.ts +4 -5
  109. package/dist/lib/cms-views/index.js +4 -5
  110. package/dist/lib/cms-views/index.js.map +1 -1
  111. package/dist/lib/cms-views/view-model.d.ts +48 -1
  112. package/dist/lib/cms-views/view-model.js +77 -1
  113. package/dist/lib/cms-views/view-model.js.map +1 -1
  114. package/dist/lib/collection-view/collection-view-source.d.ts +251 -0
  115. package/dist/lib/collection-view/collection-view-source.js +10 -0
  116. package/dist/lib/collection-view/collection-view-source.js.map +1 -0
  117. package/dist/lib/operations/session-hydration.d.ts +7 -0
  118. package/dist/lib/operations/session-hydration.js +7 -1
  119. package/dist/lib/operations/session-hydration.js.map +1 -1
  120. package/dist/lib/persistence/convex-adapter.d.ts +5 -1
  121. package/dist/lib/persistence/convex-adapter.js +148 -1
  122. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  123. package/dist/lib/workspace/index.d.ts +1 -8
  124. package/dist/lib/workspace/index.js +3 -9
  125. package/dist/lib/workspace/index.js.map +1 -1
  126. package/dist/router/match.d.ts +4 -0
  127. package/dist/router/match.js +8 -0
  128. package/dist/router/match.js.map +1 -1
  129. package/dist/ui/index.d.ts +2 -0
  130. package/dist/ui/index.js +2 -0
  131. package/dist/ui/index.js.map +1 -1
  132. package/dist/ui/popover.d.ts +13 -0
  133. package/dist/ui/popover.js +17 -0
  134. package/dist/ui/popover.js.map +1 -0
  135. package/dist/ui/row-item.d.ts +16 -0
  136. package/dist/ui/row-item.js +18 -0
  137. package/dist/ui/row-item.js.map +1 -0
  138. package/package.json +7 -2
@@ -0,0 +1,15 @@
1
+ import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
2
+ /**
3
+ * Assemble a {@link CollectionViewSource} for a `cmsDocuments` collection from the
4
+ * ambient `CmsStore` + its side contexts (assets, published pool) + the
5
+ * deployment config reads (EC-240 / AD-19). This is the CMS adapter: a
6
+ * behaviour-preserving pass-through so the portable list + edit surfaces render
7
+ * over the source instead of `useCms()` directly, byte-for-byte as before.
8
+ *
9
+ * Returns `null` for an unknown collection id (the caller renders the fallback).
10
+ *
11
+ * Referential stability: collection-parameterised methods are `useCallback`-bound
12
+ * and the source object is `useMemo`-ed, so the surfaces' `useMemo` dependency
13
+ * arrays stay stable across renders (the inline-edit order snapshot depends on it).
14
+ */
15
+ export declare function useCmsCollectionViewSource(collectionId: string): CollectionViewSource | null;
@@ -0,0 +1,158 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { isHierarchical, urlForDocument } from '@elytracms/core/cms-core';
3
+ import { studioPath } from '@elytracms/studio/router';
4
+ import { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
5
+ import { useCms } from '../cms/cms-store';
6
+ import { useOptionalAssets } from '../assets/assets-store';
7
+ import { usePublishedDocuments } from '@elytracms/studio/lib/cms/published-documents';
8
+ /**
9
+ * Assemble a {@link CollectionViewSource} for a `cmsDocuments` collection from the
10
+ * ambient `CmsStore` + its side contexts (assets, published pool) + the
11
+ * deployment config reads (EC-240 / AD-19). This is the CMS adapter: a
12
+ * behaviour-preserving pass-through so the portable list + edit surfaces render
13
+ * over the source instead of `useCms()` directly, byte-for-byte as before.
14
+ *
15
+ * Returns `null` for an unknown collection id (the caller renders the fallback).
16
+ *
17
+ * Referential stability: collection-parameterised methods are `useCallback`-bound
18
+ * and the source object is `useMemo`-ed, so the surfaces' `useMemo` dependency
19
+ * arrays stay stable across renders (the inline-edit order snapshot depends on it).
20
+ */
21
+ export function useCmsCollectionViewSource(collectionId) {
22
+ const cms = useCms();
23
+ const assetStore = useOptionalAssets();
24
+ const collection = cms.getCollection(collectionId);
25
+ const publishedDocs = usePublishedDocuments(cms.projectId, collectionId);
26
+ // The asset collection id is resolved once from the schema (as the cell does).
27
+ const assetCollectionId = useMemo(() => cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset', [cms.collections]);
28
+ // Live-URL column: the published pool × deployment routes, memoised exactly as
29
+ // the table did — derived from PUBLISHED snapshots, never draft rows (EC-205).
30
+ const isRoutable = useMemo(() => resolveDeploymentRoutes().some((route) => route.document?.collection === collectionId), [collectionId]);
31
+ const liveUrlMap = useMemo(() => {
32
+ if (!collection || !isRoutable)
33
+ return null;
34
+ const cfg = {
35
+ routes: resolveDeploymentRoutes(),
36
+ frontendUrl: resolveDeploymentFrontendUrl(),
37
+ localeConfig: cms.localeConfig,
38
+ };
39
+ const siblings = [...publishedDocs.byId.values()];
40
+ const map = new Map();
41
+ for (const published of siblings) {
42
+ const url = urlForDocument(cfg, collection, published, { siblings, locale: cms.activeLocale });
43
+ if (url)
44
+ map.set(published.id, url);
45
+ }
46
+ return map;
47
+ }, [collection, isRoutable, publishedDocs, cms.localeConfig, cms.activeLocale]);
48
+ const documents = useCallback(() => cms.documentsOf(collectionId), [cms, collectionId]);
49
+ const viewState = useCallback(() => cms.tableStateOf(collectionId), [cms, collectionId]);
50
+ const setViewState = useCallback((update) => cms.setTableState(collectionId, update), [cms, collectionId]);
51
+ // Named views (EC-246b): config-as-code lenses the header selector switches between.
52
+ const views = useMemo(() => ({
53
+ list: () => cms.viewsOf(collectionId),
54
+ activeId: () => cms.activeViewId(collectionId),
55
+ setActive: (id) => cms.setActiveView(collectionId, id),
56
+ }), [cms, collectionId]);
57
+ const create = useCallback(() => cms.createDocument(collectionId), [cms, collectionId]);
58
+ const imports = useMemo(() => ({ importDocuments: (items) => cms.importDocuments(collectionId, items) }), [cms, collectionId]);
59
+ const resolveAsset = useCallback((id) => {
60
+ const record = assetStore?.assets.find((asset) => asset.id === id);
61
+ if (!record)
62
+ return undefined;
63
+ return {
64
+ ...(record.url !== undefined ? { url: record.url } : {}),
65
+ contentType: record.contentType,
66
+ ...(record.title !== undefined ? { title: record.title } : {}),
67
+ filename: record.filename,
68
+ ...(record.alt !== undefined ? { alt: record.alt } : {}),
69
+ };
70
+ }, [assetStore]);
71
+ const liveUrls = useCallback(() => liveUrlMap, [liveUrlMap]);
72
+ return useMemo(() => {
73
+ if (!collection)
74
+ return null;
75
+ const capabilities = {
76
+ publish: true,
77
+ versions: true,
78
+ locales: true,
79
+ inlineEdit: true,
80
+ create: true,
81
+ remove: true,
82
+ bulk: true,
83
+ liveUrl: isRoutable,
84
+ hierarchy: isHierarchical(collection),
85
+ importExport: true,
86
+ };
87
+ const source = {
88
+ collectionId,
89
+ schema: collection,
90
+ capabilities,
91
+ ...(cms.projectId !== undefined ? { projectId: cms.projectId } : {}),
92
+ assetCollectionId,
93
+ localeConfig: cms.localeConfig,
94
+ activeLocale: cms.activeLocale,
95
+ documents,
96
+ get: cms.getDocument,
97
+ titleOf: cms.titleOf,
98
+ // References are collection-based (EC-072): resolve candidates of ANY target
99
+ // collection the schema declares — the workspace doc store already serves them.
100
+ referenceCandidates: cms.documentsOf,
101
+ collections: cms.collections,
102
+ isFieldReadOnly: cms.isFieldReadOnly,
103
+ validate: cms.validate,
104
+ resolveAsset,
105
+ lastError: cms.persistError,
106
+ clearLastError: cms.clearPersistError,
107
+ // Baseline + queue flush, so a pre-flight reference-index read is current.
108
+ flush: async () => {
109
+ await cms.ensurePersistedBaseline();
110
+ await cms.flushPersistence();
111
+ },
112
+ detailPath: (id) => studioPath.cmsDocument(collectionId, id),
113
+ listPath: studioPath.cmsCollection(collectionId),
114
+ viewState,
115
+ setViewState,
116
+ views,
117
+ imports,
118
+ create,
119
+ save: cms.saveDocument,
120
+ remove: cms.deleteDocument,
121
+ bulk: (ids, action, setField) => cms.runBulk(collectionId, ids, action, setField),
122
+ publishing: {
123
+ isPublished: cms.isPublished,
124
+ publish: cms.publishDocument,
125
+ unpublish: cms.unpublishDocument,
126
+ liveUrls,
127
+ },
128
+ versioning: {
129
+ restore: (ref, version, scope) => cms.restoreDocumentVersion(ref, version, scope),
130
+ // Flush-before-read ordering folds into the loader (EC-224) so any surface
131
+ // that mutated the history still lists an authoritative version set.
132
+ load: async (ref) => {
133
+ await cms.flushPersistence();
134
+ return cms.loadDocumentVersions(ref);
135
+ },
136
+ },
137
+ inlineEditing: {
138
+ commitField: (input) => cms.commitFieldEdit(input),
139
+ },
140
+ };
141
+ return source;
142
+ }, [
143
+ collection,
144
+ collectionId,
145
+ cms,
146
+ isRoutable,
147
+ documents,
148
+ viewState,
149
+ setViewState,
150
+ views,
151
+ imports,
152
+ create,
153
+ resolveAsset,
154
+ liveUrls,
155
+ assetCollectionId,
156
+ ]);
157
+ }
158
+ //# sourceMappingURL=cms-collection-view-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/collection-view/cms-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport { isHierarchical, urlForDocument } from '@elytracms/core/cms-core'\nimport type { DocumentRef, DocumentUrl } from '@elytracms/core/cms-core'\nimport { studioPath } from '@elytracms/studio/router'\nimport { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n ResolvedAsset,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { usePublishedDocuments } from '@elytracms/studio/lib/cms/published-documents'\n\n/**\n * Assemble a {@link CollectionViewSource} for a `cmsDocuments` collection from the\n * ambient `CmsStore` + its side contexts (assets, published pool) + the\n * deployment config reads (EC-240 / AD-19). This is the CMS adapter: a\n * behaviour-preserving pass-through so the portable list + edit surfaces render\n * over the source instead of `useCms()` directly, byte-for-byte as before.\n *\n * Returns `null` for an unknown collection id (the caller renders the fallback).\n *\n * Referential stability: collection-parameterised methods are `useCallback`-bound\n * and the source object is `useMemo`-ed, so the surfaces' `useMemo` dependency\n * arrays stay stable across renders (the inline-edit order snapshot depends on it).\n */\nexport function useCmsCollectionViewSource(collectionId: string): CollectionViewSource | null {\n const cms = useCms()\n const assetStore = useOptionalAssets()\n const collection = cms.getCollection(collectionId)\n const publishedDocs = usePublishedDocuments(cms.projectId, collectionId)\n\n // The asset collection id is resolved once from the schema (as the cell does).\n const assetCollectionId = useMemo(\n () => cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset',\n [cms.collections],\n )\n\n // Live-URL column: the published pool × deployment routes, memoised exactly as\n // the table did — derived from PUBLISHED snapshots, never draft rows (EC-205).\n const isRoutable = useMemo(\n () => resolveDeploymentRoutes().some((route) => route.document?.collection === collectionId),\n [collectionId],\n )\n const liveUrlMap = useMemo(() => {\n if (!collection || !isRoutable) return null\n const cfg = {\n routes: resolveDeploymentRoutes(),\n frontendUrl: resolveDeploymentFrontendUrl(),\n localeConfig: cms.localeConfig,\n }\n const siblings = [...publishedDocs.byId.values()]\n const map = new Map<string, DocumentUrl>()\n for (const published of siblings) {\n const url = urlForDocument(cfg, collection, published, { siblings, locale: cms.activeLocale })\n if (url) map.set(published.id, url)\n }\n return map\n }, [collection, isRoutable, publishedDocs, cms.localeConfig, cms.activeLocale])\n\n const documents = useCallback(() => cms.documentsOf(collectionId), [cms, collectionId])\n const viewState = useCallback(() => cms.tableStateOf(collectionId), [cms, collectionId])\n const setViewState = useCallback(\n (update: Parameters<CollectionViewSource['setViewState']>[0]) =>\n cms.setTableState(collectionId, update),\n [cms, collectionId],\n )\n // Named views (EC-246b): config-as-code lenses the header selector switches between.\n const views = useMemo(\n () => ({\n list: () => cms.viewsOf(collectionId),\n activeId: () => cms.activeViewId(collectionId),\n setActive: (id: string) => cms.setActiveView(collectionId, id),\n }),\n [cms, collectionId],\n )\n const create = useCallback(() => cms.createDocument(collectionId), [cms, collectionId])\n const imports = useMemo(\n () => ({ importDocuments: (items: unknown[]) => cms.importDocuments(collectionId, items) }),\n [cms, collectionId],\n )\n const resolveAsset = useCallback(\n (id: string): ResolvedAsset | undefined => {\n const record = assetStore?.assets.find((asset) => asset.id === id)\n if (!record) return undefined\n return {\n ...(record.url !== undefined ? { url: record.url } : {}),\n contentType: record.contentType,\n ...(record.title !== undefined ? { title: record.title } : {}),\n filename: record.filename,\n ...(record.alt !== undefined ? { alt: record.alt } : {}),\n }\n },\n [assetStore],\n )\n const liveUrls = useCallback(() => liveUrlMap, [liveUrlMap])\n\n return useMemo(() => {\n if (!collection) return null\n const capabilities: CollectionViewCapabilities = {\n publish: true,\n versions: true,\n locales: true,\n inlineEdit: true,\n create: true,\n remove: true,\n bulk: true,\n liveUrl: isRoutable,\n hierarchy: isHierarchical(collection),\n importExport: true,\n }\n const source: CollectionViewSource = {\n collectionId,\n schema: collection,\n capabilities,\n ...(cms.projectId !== undefined ? { projectId: cms.projectId } : {}),\n assetCollectionId,\n localeConfig: cms.localeConfig,\n activeLocale: cms.activeLocale,\n documents,\n get: cms.getDocument,\n titleOf: cms.titleOf,\n // References are collection-based (EC-072): resolve candidates of ANY target\n // collection the schema declares — the workspace doc store already serves them.\n referenceCandidates: cms.documentsOf,\n collections: cms.collections,\n isFieldReadOnly: cms.isFieldReadOnly,\n validate: cms.validate,\n resolveAsset,\n lastError: cms.persistError,\n clearLastError: cms.clearPersistError,\n // Baseline + queue flush, so a pre-flight reference-index read is current.\n flush: async () => {\n await cms.ensurePersistedBaseline()\n await cms.flushPersistence()\n },\n detailPath: (id: string) => studioPath.cmsDocument(collectionId, id),\n listPath: studioPath.cmsCollection(collectionId),\n viewState,\n setViewState,\n views,\n imports,\n create,\n save: cms.saveDocument,\n remove: cms.deleteDocument,\n bulk: (ids, action, setField) => cms.runBulk(collectionId, ids, action, setField),\n publishing: {\n isPublished: cms.isPublished,\n publish: cms.publishDocument,\n unpublish: cms.unpublishDocument,\n liveUrls,\n },\n versioning: {\n restore: (ref: DocumentRef, version: number, scope) =>\n cms.restoreDocumentVersion(ref, version, scope),\n // Flush-before-read ordering folds into the loader (EC-224) so any surface\n // that mutated the history still lists an authoritative version set.\n load: async (ref: DocumentRef) => {\n await cms.flushPersistence()\n return cms.loadDocumentVersions(ref)\n },\n },\n inlineEditing: {\n commitField: (input) => cms.commitFieldEdit(input),\n },\n }\n return source\n }, [\n collection,\n collectionId,\n cms,\n isRoutable,\n documents,\n viewState,\n setViewState,\n views,\n imports,\n create,\n resolveAsset,\n liveUrls,\n assetCollectionId,\n ])\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
3
+ export declare function CollectionViewProvider({ source, children, }: {
4
+ source: CollectionViewSource;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ /** The active {@link CollectionViewSource}. Throws if used outside a provider. */
8
+ export declare function useCollectionView(): CollectionViewSource;
9
+ /** The active source, or `null` outside a provider (optional consumers). */
10
+ export declare function useOptionalCollectionView(): CollectionViewSource | null;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ /**
4
+ * The React seam for the portable list + edit surfaces (EC-240 / AD-19). The
5
+ * surfaces and their nested pieces (view bar, cells, filter builder, field
6
+ * form) read their {@link CollectionViewSource} from THIS context instead of the
7
+ * concrete `useCms()`, so the SAME components render over the CMS store, the
8
+ * users rail, or any future rail.
9
+ */
10
+ const CollectionViewContext = createContext(null);
11
+ export function CollectionViewProvider({ source, children, }) {
12
+ return _jsx(CollectionViewContext.Provider, { value: source, children: children });
13
+ }
14
+ /** The active {@link CollectionViewSource}. Throws if used outside a provider. */
15
+ export function useCollectionView() {
16
+ const source = useContext(CollectionViewContext);
17
+ if (!source) {
18
+ throw new Error('useCollectionView must be used within a <CollectionViewProvider>');
19
+ }
20
+ return source;
21
+ }
22
+ /** The active source, or `null` outside a provider (optional consumers). */
23
+ export function useOptionalCollectionView() {
24
+ return useContext(CollectionViewContext);
25
+ }
26
+ //# sourceMappingURL=collection-view-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection-view-context.js","sourceRoot":"","sources":["../../../src/components/collection-view/collection-view-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAA;AAGjE;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,aAAa,CAA8B,IAAI,CAAC,CAAA;AAE9E,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YAAG,QAAQ,GAAkC,CAAA;AACnG,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAA;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAA;IACrF,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB;IACvC,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAA;AAC1C,CAAC","sourcesContent":["import { createContext, useContext, type ReactNode } from 'react'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\n\n/**\n * The React seam for the portable list + edit surfaces (EC-240 / AD-19). The\n * surfaces and their nested pieces (view bar, cells, filter builder, field\n * form) read their {@link CollectionViewSource} from THIS context instead of the\n * concrete `useCms()`, so the SAME components render over the CMS store, the\n * users rail, or any future rail.\n */\nconst CollectionViewContext = createContext<CollectionViewSource | null>(null)\n\nexport function CollectionViewProvider({\n source,\n children,\n}: {\n source: CollectionViewSource\n children: ReactNode\n}) {\n return <CollectionViewContext.Provider value={source}>{children}</CollectionViewContext.Provider>\n}\n\n/** The active {@link CollectionViewSource}. Throws if used outside a provider. */\nexport function useCollectionView(): CollectionViewSource {\n const source = useContext(CollectionViewContext)\n if (!source) {\n throw new Error('useCollectionView must be used within a <CollectionViewProvider>')\n }\n return source\n}\n\n/** The active source, or `null` outside a provider (optional consumers). */\nexport function useOptionalCollectionView(): CollectionViewSource | null {\n return useContext(CollectionViewContext)\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LogOut, Moon, Settings, Sun } from 'lucide-react';
3
+ import { roleGrantsCapability } from '@elytracms/core/persistence';
3
4
  import { Avatar, Badge, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Row, Spacer, Stack, Text, } from '@elytracms/studio/ui';
4
5
  import { useAuth, useProjectRole } from './auth-context';
5
6
  import { useTheme } from './theme-context';
@@ -25,7 +26,13 @@ export function CurrentUserMenu({ projectId }) {
25
26
  }, children: [isDark ? _jsx(Sun, {}) : _jsx(Moon, {}), isDark ? 'Light mode' : 'Dark mode'] }), _jsxs(DropdownMenuItem, { disabled: true, children: [_jsx(Settings, {}), "Account settings", _jsx(Spacer, {}), _jsx(Badge, { variant: "outline", children: "Soon" })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onSelect: () => void auth.signOut(), children: [_jsx(LogOut, {}), "Sign out"] })] })] }));
26
27
  }
27
28
  export function RoleBadge({ role }) {
28
- const variant = role === 'admin' ? 'primary' : role === 'editor' ? 'info' : 'outline';
29
+ // Variant by capability tier (EC-240c), not role literals — a custom role set
30
+ // styles by what it grants: administer → primary, write → info, else outline.
31
+ const variant = roleGrantsCapability(role, 'project.administer')
32
+ ? 'primary'
33
+ : roleGrantsCapability(role, 'content.write')
34
+ ? 'info'
35
+ : 'outline';
29
36
  return _jsx(Badge, { variant: variant, children: role });
30
37
  }
31
38
  //# sourceMappingURL=current-user-menu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"current-user-menu.js","sourceRoot":"","sources":["../../src/components/current-user-menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import { LogOut, Moon, Settings, Sun } from 'lucide-react'\nimport type { ProjectRole } from '@elytracms/core/persistence'\nimport {\n Avatar,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { useAuth, useProjectRole } from './auth-context'\nimport { useTheme } from './theme-context'\n\n/**\n * Current-user affordance in the shell header (EC-150): who is signed in,\n * their role on the active project (badge), theme switch, and sign-out. Role\n * display is informational — actual enforcement happens at the operations\n * chokepoint and server-side, never in the UI.\n */\nexport function CurrentUserMenu({ projectId }: { projectId: string | null }) {\n const auth = useAuth()\n const role = useProjectRole(projectId)\n const { theme, setTheme } = useTheme()\n const user = auth.session?.user\n if (!user) return null\n\n // Show one identity line — the name if set, otherwise the email (never both).\n const displayName = user.name ?? user.email\n const isDark = theme === 'dark'\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" className=\"rounded-full\" aria-label=\"Account menu\">\n <Avatar label={displayName} size=\"sm\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-60\">\n {/* Identity header — normal case, single line, with the project role. */}\n <Row gap=\"2\" className=\"px-2 py-2\">\n <Avatar label={displayName} size=\"md\" />\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate leading-tight\">\n {displayName}\n </Text>\n {role ? (\n <Row>\n <RoleBadge role={role} />\n </Row>\n ) : null}\n </Stack>\n </Row>\n <DropdownMenuSeparator />\n {/* Theme switch — preventDefault keeps the menu open so the change is visible. */}\n <DropdownMenuItem\n onSelect={(event) => {\n event.preventDefault()\n setTheme(isDark ? 'light' : 'dark')\n }}\n >\n {isDark ? <Sun /> : <Moon />}\n {isDark ? 'Light mode' : 'Dark mode'}\n </DropdownMenuItem>\n {/* TODO(user-settings): point this at an account-settings surface once one\n exists (today only project-scoped settings are implemented). */}\n <DropdownMenuItem disabled>\n <Settings />\n Account settings\n <Spacer />\n <Badge variant=\"outline\">Soon</Badge>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\" onSelect={() => void auth.signOut()}>\n <LogOut />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nexport function RoleBadge({ role }: { role: ProjectRole }) {\n const variant = role === 'admin' ? 'primary' : role === 'editor' ? 'info' : 'outline'\n return <Badge variant={variant}>{role}</Badge>\n}\n"]}
1
+ {"version":3,"file":"current-user-menu.js","sourceRoot":"","sources":["../../src/components/current-user-menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import { LogOut, Moon, Settings, Sun } from 'lucide-react'\nimport { roleGrantsCapability } from '@elytracms/core/persistence'\nimport type { ProjectRole } from '@elytracms/core/persistence'\nimport {\n Avatar,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { useAuth, useProjectRole } from './auth-context'\nimport { useTheme } from './theme-context'\n\n/**\n * Current-user affordance in the shell header (EC-150): who is signed in,\n * their role on the active project (badge), theme switch, and sign-out. Role\n * display is informational — actual enforcement happens at the operations\n * chokepoint and server-side, never in the UI.\n */\nexport function CurrentUserMenu({ projectId }: { projectId: string | null }) {\n const auth = useAuth()\n const role = useProjectRole(projectId)\n const { theme, setTheme } = useTheme()\n const user = auth.session?.user\n if (!user) return null\n\n // Show one identity line — the name if set, otherwise the email (never both).\n const displayName = user.name ?? user.email\n const isDark = theme === 'dark'\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" className=\"rounded-full\" aria-label=\"Account menu\">\n <Avatar label={displayName} size=\"sm\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-60\">\n {/* Identity header — normal case, single line, with the project role. */}\n <Row gap=\"2\" className=\"px-2 py-2\">\n <Avatar label={displayName} size=\"md\" />\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate leading-tight\">\n {displayName}\n </Text>\n {role ? (\n <Row>\n <RoleBadge role={role} />\n </Row>\n ) : null}\n </Stack>\n </Row>\n <DropdownMenuSeparator />\n {/* Theme switch — preventDefault keeps the menu open so the change is visible. */}\n <DropdownMenuItem\n onSelect={(event) => {\n event.preventDefault()\n setTheme(isDark ? 'light' : 'dark')\n }}\n >\n {isDark ? <Sun /> : <Moon />}\n {isDark ? 'Light mode' : 'Dark mode'}\n </DropdownMenuItem>\n {/* TODO(user-settings): point this at an account-settings surface once one\n exists (today only project-scoped settings are implemented). */}\n <DropdownMenuItem disabled>\n <Settings />\n Account settings\n <Spacer />\n <Badge variant=\"outline\">Soon</Badge>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\" onSelect={() => void auth.signOut()}>\n <LogOut />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nexport function RoleBadge({ role }: { role: ProjectRole }) {\n // Variant by capability tier (EC-240c), not role literals — a custom role set\n // styles by what it grants: administer → primary, write → info, else outline.\n const variant = roleGrantsCapability(role, 'project.administer')\n ? 'primary'\n : roleGrantsCapability(role, 'content.write')\n ? 'info'\n : 'outline'\n return <Badge variant={variant}>{role}</Badge>\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { CmsDocument } from '@elytracms/core/cms-core';
2
+ import type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence';
3
+ import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
4
+ /** Project a stored redirect into the lens's `values` shape. */
5
+ export declare function redirectToDocument(entry: RedirectEntry): CmsDocument;
6
+ /** Map an edited lens document back to the redirect write contract. */
7
+ export declare function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput;
8
+ /**
9
+ * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —
10
+ * the portable list/edit surfaces over the editorial redirects store instead of
11
+ * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users
12
+ * rail this source allows `create`, is NOT admin-only, and needs no principal
13
+ * field-access policy. No publish/versions/locales/inline/bulk/import — a redirect
14
+ * is a small record saved explicitly (a saved+enabled one is live).
15
+ *
16
+ * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).
17
+ */
18
+ export declare function useRedirectsCollectionViewSource(projectId: string): CollectionViewSource;
@@ -0,0 +1,114 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import { studioPath } from '@elytracms/studio/router';
3
+ import { defaultViewState } from '@elytracms/studio/lib/cms-views';
4
+ import { useRedirects } from './redirects-store';
5
+ import { redirectsCollectionDef } from './redirects-schema';
6
+ /** The redirects rail is not localized — one implicit locale keeps the lens honest. */
7
+ const REDIRECTS_LOCALE = 'en';
8
+ const REDIRECTS_LOCALE_CONFIG = {
9
+ default: REDIRECTS_LOCALE,
10
+ locales: [REDIRECTS_LOCALE],
11
+ };
12
+ /** Project a stored redirect into the lens's `values` shape. */
13
+ export function redirectToDocument(entry) {
14
+ return {
15
+ collection: redirectsCollectionDef.id,
16
+ id: entry.id,
17
+ values: {
18
+ from: entry.from,
19
+ to: entry.to,
20
+ permanent: entry.permanent,
21
+ enabled: entry.enabled,
22
+ },
23
+ };
24
+ }
25
+ /** Map an edited lens document back to the redirect write contract. */
26
+ export function documentToSaveRedirect(doc) {
27
+ const values = doc.values ?? {};
28
+ return {
29
+ id: doc.id,
30
+ from: typeof values.from === 'string' ? values.from : '',
31
+ to: typeof values.to === 'string' ? values.to : '',
32
+ permanent: typeof values.permanent === 'boolean' ? values.permanent : true,
33
+ enabled: typeof values.enabled === 'boolean' ? values.enabled : false,
34
+ };
35
+ }
36
+ /**
37
+ * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —
38
+ * the portable list/edit surfaces over the editorial redirects store instead of
39
+ * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users
40
+ * rail this source allows `create`, is NOT admin-only, and needs no principal
41
+ * field-access policy. No publish/versions/locales/inline/bulk/import — a redirect
42
+ * is a small record saved explicitly (a saved+enabled one is live).
43
+ *
44
+ * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).
45
+ */
46
+ export function useRedirectsCollectionViewSource(projectId) {
47
+ const store = useRedirects();
48
+ const [viewState, setViewStateRaw] = useState(() => defaultViewState(redirectsCollectionDef));
49
+ const [lastError, setLastError] = useState(null);
50
+ const documents = useCallback(() => store.redirects.map(redirectToDocument), [store.redirects]);
51
+ const get = useCallback((ref) => {
52
+ const record = store.get(ref.id);
53
+ return record ? redirectToDocument(record) : undefined;
54
+ }, [store]);
55
+ const titleOf = useCallback((ref) => store.get(ref.id)?.from || '(new redirect)', [store]);
56
+ const setViewState = useCallback((update) => {
57
+ setViewStateRaw((prev) => (typeof update === 'function' ? update(prev) : update));
58
+ }, []);
59
+ const create = useCallback(() => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }), [store]);
60
+ const save = useCallback((doc) => {
61
+ void store.save(documentToSaveRedirect(doc)).then((result) => {
62
+ if (!result.ok)
63
+ setLastError({ code: 'unknown', message: result.message });
64
+ });
65
+ }, [store]);
66
+ const remove = useCallback((ref) => {
67
+ void store.remove(ref.id).then((result) => {
68
+ if (!result.ok)
69
+ setLastError({ code: 'unknown', message: result.message });
70
+ });
71
+ }, [store]);
72
+ return useMemo(() => {
73
+ const capabilities = {
74
+ publish: false,
75
+ versions: false,
76
+ locales: false,
77
+ inlineEdit: false,
78
+ create: true, // editors create redirects
79
+ remove: true,
80
+ bulk: false,
81
+ liveUrl: false,
82
+ hierarchy: false,
83
+ importExport: false,
84
+ adminOnly: false, // content, not administration
85
+ };
86
+ const source = {
87
+ collectionId: redirectsCollectionDef.id,
88
+ schema: redirectsCollectionDef,
89
+ capabilities,
90
+ projectId,
91
+ localeConfig: REDIRECTS_LOCALE_CONFIG,
92
+ activeLocale: REDIRECTS_LOCALE,
93
+ documents,
94
+ get,
95
+ titleOf,
96
+ // Redirects carry no relation/asset fields, so nothing cross-collection to resolve.
97
+ referenceCandidates: () => [],
98
+ collections: [redirectsCollectionDef],
99
+ detailPath: (id) => studioPath.redirect(id),
100
+ listPath: studioPath.redirects(),
101
+ isFieldReadOnly: (field) => field.form?.readOnly ?? false,
102
+ validate: () => [],
103
+ lastError,
104
+ clearLastError: () => setLastError(null),
105
+ viewState: () => viewState,
106
+ setViewState,
107
+ create,
108
+ save,
109
+ remove,
110
+ };
111
+ return source;
112
+ }, [projectId, documents, get, titleOf, setViewState, create, save, remove, viewState, lastError]);
113
+ }
114
+ //# sourceMappingURL=redirects-collection-view-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type {\n CmsDocument,\n DocumentRef,\n FieldDef,\n Locale,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence'\nimport type { OperationError } from '@elytracms/core/operations'\nimport { studioPath } from '@elytracms/studio/router'\nimport { defaultViewState } from '@elytracms/studio/lib/cms-views'\nimport type { TableViewState } from '@elytracms/studio/lib/cms-views'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useRedirects } from './redirects-store'\nimport { redirectsCollectionDef } from './redirects-schema'\n\n/** The redirects rail is not localized — one implicit locale keeps the lens honest. */\nconst REDIRECTS_LOCALE: Locale = 'en'\nconst REDIRECTS_LOCALE_CONFIG: LocaleConfig = {\n default: REDIRECTS_LOCALE,\n locales: [REDIRECTS_LOCALE],\n}\n\n/** Project a stored redirect into the lens's `values` shape. */\nexport function redirectToDocument(entry: RedirectEntry): CmsDocument {\n return {\n collection: redirectsCollectionDef.id,\n id: entry.id,\n values: {\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n enabled: entry.enabled,\n },\n }\n}\n\n/** Map an edited lens document back to the redirect write contract. */\nexport function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput {\n const values = doc.values ?? {}\n return {\n id: doc.id,\n from: typeof values.from === 'string' ? values.from : '',\n to: typeof values.to === 'string' ? values.to : '',\n permanent: typeof values.permanent === 'boolean' ? values.permanent : true,\n enabled: typeof values.enabled === 'boolean' ? values.enabled : false,\n }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —\n * the portable list/edit surfaces over the editorial redirects store instead of\n * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users\n * rail this source allows `create`, is NOT admin-only, and needs no principal\n * field-access policy. No publish/versions/locales/inline/bulk/import — a redirect\n * is a small record saved explicitly (a saved+enabled one is live).\n *\n * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).\n */\nexport function useRedirectsCollectionViewSource(projectId: string): CollectionViewSource {\n const store = useRedirects()\n const [viewState, setViewStateRaw] = useState<TableViewState>(() =>\n defaultViewState(redirectsCollectionDef),\n )\n const [lastError, setLastError] = useState<OperationError | null>(null)\n\n const documents = useCallback(\n () => store.redirects.map(redirectToDocument),\n [store.redirects],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? redirectToDocument(record) : undefined\n },\n [store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => store.get(ref.id)?.from || '(new redirect)',\n [store],\n )\n const setViewState = useCallback<CollectionViewSource['setViewState']>((update) => {\n setViewStateRaw((prev) => (typeof update === 'function' ? update(prev) : update))\n }, [])\n\n const create = useCallback(\n (): DocumentRef => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }),\n [store],\n )\n const save = useCallback(\n (doc: CmsDocument) => {\n void store.save(documentToSaveRedirect(doc)).then((result) => {\n if (!result.ok) setLastError({ code: 'unknown', message: result.message })\n })\n },\n [store],\n )\n const remove = useCallback(\n (ref: DocumentRef) => {\n void store.remove(ref.id).then((result) => {\n if (!result.ok) setLastError({ code: 'unknown', message: result.message })\n })\n },\n [store],\n )\n\n return useMemo(() => {\n const capabilities: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true, // editors create redirects\n remove: true,\n bulk: false,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false, // content, not administration\n }\n const source: CollectionViewSource = {\n collectionId: redirectsCollectionDef.id,\n schema: redirectsCollectionDef,\n capabilities,\n projectId,\n localeConfig: REDIRECTS_LOCALE_CONFIG,\n activeLocale: REDIRECTS_LOCALE,\n documents,\n get,\n titleOf,\n // Redirects carry no relation/asset fields, so nothing cross-collection to resolve.\n referenceCandidates: () => [],\n collections: [redirectsCollectionDef],\n detailPath: (id: string) => studioPath.redirect(id),\n listPath: studioPath.redirects(),\n isFieldReadOnly: (field: FieldDef) => field.form?.readOnly ?? false,\n validate: () => [],\n lastError,\n clearLastError: () => setLastError(null),\n viewState: () => viewState,\n setViewState,\n create,\n save,\n remove,\n }\n return source\n }, [projectId, documents, get, titleOf, setViewState, create, save, remove, viewState, lastError])\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The studio Redirects module (EC-303). Editorial redirects as data — the reuse
3
+ * proof (with the users rail) that the portable interaction surfaces serve any
4
+ * rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME
5
+ * `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a
6
+ * redirects {@link useRedirectsCollectionViewSource}. The redirects capability
7
+ * profile (content-gated, create + remove, no publish/versions/locales) drives
8
+ * which regions appear; the edit shell's explicit-save path (no publish) makes a
9
+ * saved+enabled redirect live.
10
+ *
11
+ * `redirectId === null` is the list; a `redirectId` opens that redirect's detail
12
+ * (routed via `studioPath.redirect`). No admin gate — redirects are content, so
13
+ * viewers read and editors write (enforced at the chokepoint + Convex boundary).
14
+ */
15
+ export declare function RedirectsModule({ projectId, redirectId, }: {
16
+ projectId: string;
17
+ redirectId?: string | null;
18
+ }): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangle, RotateCcw } from 'lucide-react';
3
+ import { Button, Row, Stack, Text } from '@elytracms/studio/ui';
4
+ import { RedirectsProvider, useRedirects } from './redirects-store';
5
+ import { useRedirectsCollectionViewSource } from './redirects-collection-view-source';
6
+ import { CollectionViewProvider } from '../collection-view/collection-view-context';
7
+ import { LoadedCollectionTable } from '../cms/collection-table';
8
+ import { PortableDetailEditor } from '../cms/detail-editor';
9
+ /**
10
+ * The studio Redirects module (EC-303). Editorial redirects as data — the reuse
11
+ * proof (with the users rail) that the portable interaction surfaces serve any
12
+ * rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME
13
+ * `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a
14
+ * redirects {@link useRedirectsCollectionViewSource}. The redirects capability
15
+ * profile (content-gated, create + remove, no publish/versions/locales) drives
16
+ * which regions appear; the edit shell's explicit-save path (no publish) makes a
17
+ * saved+enabled redirect live.
18
+ *
19
+ * `redirectId === null` is the list; a `redirectId` opens that redirect's detail
20
+ * (routed via `studioPath.redirect`). No admin gate — redirects are content, so
21
+ * viewers read and editors write (enforced at the chokepoint + Convex boundary).
22
+ */
23
+ export function RedirectsModule({ projectId, redirectId = null, }) {
24
+ return (_jsx(RedirectsProvider, { projectId: projectId, children: _jsx(RedirectsSurface, { projectId: projectId, redirectId: redirectId }) }));
25
+ }
26
+ /** The loaded surface: the portable list (or one redirect's detail) over the source. */
27
+ function RedirectsSurface({ projectId, redirectId, }) {
28
+ const store = useRedirects();
29
+ const source = useRedirectsCollectionViewSource(projectId);
30
+ if (store.status === 'error') {
31
+ return (_jsxs(Stack, { gap: "2", className: "p-6", "data-testid": "redirects-error", children: [_jsxs(Row, { gap: "2", children: [_jsx(AlertTriangle, { className: "size-4 text-destructive" }), _jsx(Text, { weight: "semibold", children: "Redirects failed to load" })] }), _jsx(Text, { size: "sm", tone: "muted", children: store.errorMessage ?? 'The persistence backend is unreachable.' }), _jsx(Row, { children: _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void store.refresh(), children: [_jsx(RotateCcw, {}), " Retry"] }) })] }));
32
+ }
33
+ if (store.status === 'loading') {
34
+ return (_jsx(Stack, { className: "h-full place-items-center p-8", align: "center", justify: "center", children: _jsx(Text, { size: "sm", tone: "muted", children: "Loading redirects\u2026" }) }));
35
+ }
36
+ // One redirect's detail opens in the portable edit shell (AD-19); the list otherwise.
37
+ // The shell mounts its own CollectionViewProvider, so only the list wraps here.
38
+ if (redirectId !== null) {
39
+ return _jsx(PortableDetailEditor, { source: source, projectId: projectId, documentId: redirectId });
40
+ }
41
+ return (_jsx(CollectionViewProvider, { source: source, children: _jsx(LoadedCollectionTable, { projectId: projectId }) }));
42
+ }
43
+ //# sourceMappingURL=redirects-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects-module.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-module.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAA;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAA;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,SAAS,EACT,UAAU,GAAG,IAAI,GAIlB;IACC,OAAO,CACL,KAAC,iBAAiB,IAAC,SAAS,EAAE,SAAS,YACrC,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,GAAI,GAChD,CACrB,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,UAAU,GAIX;IACC,MAAM,KAAK,GAAG,YAAY,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,gCAAgC,CAAC,SAAS,CAAC,CAAA;IAE1D,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,iBAAa,iBAAiB,aAC1D,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,aAAa,IAAC,SAAS,EAAC,yBAAyB,GAAG,EACrD,KAAC,IAAI,IAAC,MAAM,EAAC,UAAU,yCAAgC,IACnD,EACN,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,KAAK,CAAC,YAAY,IAAI,yCAAyC,GAC3D,EACP,KAAC,GAAG,cACF,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,aACrE,KAAC,SAAS,KAAG,cACN,GACL,IACA,CACT,CAAA;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,CACL,KAAC,KAAK,IAAC,SAAS,EAAC,+BAA+B,EAAC,KAAK,EAAC,QAAQ,EAAC,OAAO,EAAC,QAAQ,YAC9E,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,wCAErB,GACD,CACT,CAAA;IACH,CAAC;IAED,sFAAsF;IACtF,gFAAgF;IAChF,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,KAAC,oBAAoB,IAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,GAAI,CAAA;IAC/F,CAAC;IAED,OAAO,CACL,KAAC,sBAAsB,IAAC,MAAM,EAAE,MAAM,YACpC,KAAC,qBAAqB,IAAC,SAAS,EAAE,SAAS,GAAI,GACxB,CAC1B,CAAA;AACH,CAAC","sourcesContent":["import { AlertTriangle, RotateCcw } from 'lucide-react'\nimport { Button, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { RedirectsProvider, useRedirects } from './redirects-store'\nimport { useRedirectsCollectionViewSource } from './redirects-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { PortableDetailEditor } from '../cms/detail-editor'\n\n/**\n * The studio Redirects module (EC-303). Editorial redirects as data — the reuse\n * proof (with the users rail) that the portable interaction surfaces serve any\n * rail: it renders the SAME `LoadedCollectionTable` for the list and the SAME\n * `DetailEditor` body (via {@link PortableDetailEditor}) for one redirect, over a\n * redirects {@link useRedirectsCollectionViewSource}. The redirects capability\n * profile (content-gated, create + remove, no publish/versions/locales) drives\n * which regions appear; the edit shell's explicit-save path (no publish) makes a\n * saved+enabled redirect live.\n *\n * `redirectId === null` is the list; a `redirectId` opens that redirect's detail\n * (routed via `studioPath.redirect`). No admin gate — redirects are content, so\n * viewers read and editors write (enforced at the chokepoint + Convex boundary).\n */\nexport function RedirectsModule({\n projectId,\n redirectId = null,\n}: {\n projectId: string\n redirectId?: string | null\n}) {\n return (\n <RedirectsProvider projectId={projectId}>\n <RedirectsSurface projectId={projectId} redirectId={redirectId} />\n </RedirectsProvider>\n )\n}\n\n/** The loaded surface: the portable list (or one redirect's detail) over the source. */\nfunction RedirectsSurface({\n projectId,\n redirectId,\n}: {\n projectId: string\n redirectId: string | null\n}) {\n const store = useRedirects()\n const source = useRedirectsCollectionViewSource(projectId)\n\n if (store.status === 'error') {\n return (\n <Stack gap=\"2\" className=\"p-6\" data-testid=\"redirects-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text weight=\"semibold\">Redirects failed to load</Text>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? 'The persistence backend is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void store.refresh()}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n }\n\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n Loading redirects…\n </Text>\n </Stack>\n )\n }\n\n // One redirect's detail opens in the portable edit shell (AD-19); the list otherwise.\n // The shell mounts its own CollectionViewProvider, so only the list wraps here.\n if (redirectId !== null) {\n return <PortableDetailEditor source={source} projectId={projectId} documentId={redirectId} />\n }\n\n return (\n <CollectionViewProvider source={source}>\n <LoadedCollectionTable projectId={projectId} />\n </CollectionViewProvider>\n )\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { CollectionDef } from '@elytracms/core/cms-core';
2
+ /**
3
+ * The built-in **redirects** field lens (EC-303). Redirects are a separate
4
+ * editorial rail — NOT a `cmsDocuments` collection — so this `CollectionDef`
5
+ * exists only to drive the portable list/edit surfaces over the redirects
6
+ * {@link CollectionViewSource}. The rail's semantics (content-gated, no
7
+ * publish/versions/locales, create + remove) come from the source's
8
+ * capabilities, never the schema `kind`. The title identity is the `from` path.
9
+ */
10
+ export declare const redirectsCollectionDef: CollectionDef;
@@ -0,0 +1,42 @@
1
+ import { collectionDefSchema } from '@elytracms/core/cms-core';
2
+ /**
3
+ * The built-in **redirects** field lens (EC-303). Redirects are a separate
4
+ * editorial rail — NOT a `cmsDocuments` collection — so this `CollectionDef`
5
+ * exists only to drive the portable list/edit surfaces over the redirects
6
+ * {@link CollectionViewSource}. The rail's semantics (content-gated, no
7
+ * publish/versions/locales, create + remove) come from the source's
8
+ * capabilities, never the schema `kind`. The title identity is the `from` path.
9
+ */
10
+ export const redirectsCollectionDef = collectionDefSchema.parse({
11
+ id: 'redirect',
12
+ // Redirects aren't a CMS `kind`; 'document' just satisfies the lens schema.
13
+ kind: 'document',
14
+ titleField: 'from',
15
+ form: { label: 'Redirects', labelSingular: 'redirect' },
16
+ fields: [
17
+ {
18
+ name: 'from',
19
+ type: 'text',
20
+ filterable: true,
21
+ form: { label: 'From', help: 'The source path to forward, e.g. /old-page.' },
22
+ },
23
+ {
24
+ name: 'to',
25
+ type: 'text',
26
+ filterable: true,
27
+ form: { label: 'To', help: 'The destination path or URL, e.g. /new-page.' },
28
+ },
29
+ {
30
+ name: 'permanent',
31
+ type: 'boolean',
32
+ form: { label: 'Permanent (301)', help: 'On → 301 permanent; off → 302 temporary.' },
33
+ },
34
+ {
35
+ name: 'enabled',
36
+ type: 'boolean',
37
+ filterable: true,
38
+ form: { label: 'Enabled', help: 'Only enabled redirects are served on the live site.' },
39
+ },
40
+ ],
41
+ });
42
+ //# sourceMappingURL=redirects-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects-schema.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAG9D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkB,mBAAmB,CAAC,KAAK,CAAC;IAC7E,EAAE,EAAE,UAAU;IACd,4EAA4E;IAC5E,IAAI,EAAE,UAAU;IAChB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE;IACvD,MAAM,EAAE;QACN;YACE,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,6CAA6C,EAAE;SAC7E;QACD;YACE,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,8CAA8C,EAAE;SAC5E;QACD;YACE,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,0CAA0C,EAAE;SACrF;QACD;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,qDAAqD,EAAE;SACxF;KACF;CACF,CAAC,CAAA","sourcesContent":["import { collectionDefSchema } from '@elytracms/core/cms-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\n\n/**\n * The built-in **redirects** field lens (EC-303). Redirects are a separate\n * editorial rail — NOT a `cmsDocuments` collection — so this `CollectionDef`\n * exists only to drive the portable list/edit surfaces over the redirects\n * {@link CollectionViewSource}. The rail's semantics (content-gated, no\n * publish/versions/locales, create + remove) come from the source's\n * capabilities, never the schema `kind`. The title identity is the `from` path.\n */\nexport const redirectsCollectionDef: CollectionDef = collectionDefSchema.parse({\n id: 'redirect',\n // Redirects aren't a CMS `kind`; 'document' just satisfies the lens schema.\n kind: 'document',\n titleField: 'from',\n form: { label: 'Redirects', labelSingular: 'redirect' },\n fields: [\n {\n name: 'from',\n type: 'text',\n filterable: true,\n form: { label: 'From', help: 'The source path to forward, e.g. /old-page.' },\n },\n {\n name: 'to',\n type: 'text',\n filterable: true,\n form: { label: 'To', help: 'The destination path or URL, e.g. /new-page.' },\n },\n {\n name: 'permanent',\n type: 'boolean',\n form: { label: 'Permanent (301)', help: 'On → 301 permanent; off → 302 temporary.' },\n },\n {\n name: 'enabled',\n type: 'boolean',\n filterable: true,\n form: { label: 'Enabled', help: 'Only enabled redirects are served on the live site.' },\n },\n ],\n})\n"]}