@rebasepro/admin 0.6.1 → 0.7.0

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 (134) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
  2. package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
  4. package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
  7. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  8. package/dist/collection_editor/index.d.ts +6 -0
  9. package/dist/collection_editor/serializable_types.d.ts +374 -0
  10. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  11. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
  12. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  13. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  14. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  15. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  16. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  17. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  18. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  19. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  20. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  21. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  22. package/dist/collection_editor_ui.d.ts +4 -0
  23. package/dist/collection_editor_ui.js +33 -4
  24. package/dist/collection_editor_ui.js.map +1 -0
  25. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  26. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  27. package/dist/components/EntityEditView.d.ts +3 -2
  28. package/dist/components/RebaseCMS.d.ts +1 -1
  29. package/dist/form/EntityForm.d.ts +10 -9
  30. package/dist/form/EntityFormBinding.d.ts +27 -0
  31. package/dist/form/form_utils.d.ts +8 -0
  32. package/dist/form/index.d.ts +5 -1
  33. package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
  34. package/dist/{history-BO6VT85a.js.map → history-ChHgyyvM.js.map} +1 -1
  35. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  36. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  37. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  38. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  39. package/dist/hooks/navigation/utils.d.ts +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +2 -2
  42. package/dist/routes/CustomViewRoute.d.ts +2 -1
  43. package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
  44. package/dist/src-DOX2nOyh.js.map +1 -0
  45. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  46. package/dist/types/components/EntityFormProps.d.ts +59 -0
  47. package/dist/types/fields.d.ts +6 -1
  48. package/dist/util/dataControllerAdapter.d.ts +30 -0
  49. package/dist/util/navigation_utils.d.ts +1 -1
  50. package/dist/util/propertyConfigMapper.d.ts +17 -0
  51. package/package.json +18 -18
  52. package/src/collection_editor/extensibility_types.ts +168 -0
  53. package/src/collection_editor/index.ts +46 -0
  54. package/src/collection_editor/serializable_types.ts +487 -0
  55. package/src/collection_editor/serializable_utils.ts +588 -0
  56. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
  57. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
  58. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  59. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  60. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  61. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  62. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  63. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  64. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
  65. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  66. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  67. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  68. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  69. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  70. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  71. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  72. package/src/collection_editor_ui.ts +6 -0
  73. package/src/components/DefaultDrawer.tsx +13 -3
  74. package/src/components/DrawerNavigationGroup.tsx +2 -2
  75. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  76. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -11
  77. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  78. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
  79. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  80. package/src/components/EntityEditView.tsx +5 -4
  81. package/src/components/EntityEditViewFormActions.tsx +32 -26
  82. package/src/components/HomePage/ContentHomePage.tsx +13 -4
  83. package/src/components/RebaseCMS.tsx +3 -2
  84. package/src/components/RebaseNavigation.tsx +71 -7
  85. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  86. package/src/components/SideDialogs.tsx +0 -7
  87. package/src/components/common/default_entity_actions.tsx +9 -9
  88. package/src/contexts/BreacrumbsContext.tsx +15 -6
  89. package/src/editor/selectors/ai-selector.tsx +0 -1
  90. package/src/form/EntityForm.tsx +191 -428
  91. package/src/form/EntityFormActions.tsx +33 -47
  92. package/src/form/EntityFormBinding.tsx +323 -0
  93. package/src/form/components/FormLayout.tsx +1 -1
  94. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  95. package/src/form/form_utils.ts +163 -0
  96. package/src/form/index.tsx +14 -5
  97. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  98. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  99. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  100. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  101. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  102. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  103. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  104. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  105. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  106. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  107. package/src/hooks/navigation/utils.ts +7 -4
  108. package/src/index.ts +5 -0
  109. package/src/routes/CustomViewRoute.tsx +14 -6
  110. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  111. package/src/types/components/EntityFormProps.tsx +68 -0
  112. package/src/types/fields.tsx +7 -1
  113. package/src/util/dataControllerAdapter.ts +93 -0
  114. package/src/util/navigation_utils.ts +1 -1
  115. package/src/util/propertyConfigMapper.ts +134 -0
  116. package/LICENSE +0 -21
  117. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  118. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  119. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  120. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  121. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  122. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  123. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  124. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  125. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  126. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  127. package/dist/src-BYniefVu.js.map +0 -1
  128. package/src/components/EntityCollectionView/Board.tsx +0 -475
  129. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  130. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  131. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  132. package/src/components/EntityCollectionView/board_types.ts +0 -113
  133. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  134. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -123,7 +123,7 @@ type ActionsViewProps<M extends Record<string, unknown>> = {
123
123
  status: "new" | "existing" | "copy",
124
124
  sideDialogContext: SideDialogController,
125
125
  pluginActions?: React.ReactNode[],
126
- openEntityMode: "side_panel" | "full_screen" | "split" | "dialog";
126
+ openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
127
127
  navigateBack: () => void;
128
128
  formContext: FormContext,
129
129
  formex: FormexController<Record<string, unknown>>;
@@ -164,6 +164,7 @@ function buildBottomActions<M extends Record<string, unknown>>({
164
164
  {formActions.length > 0 && <div className="grow flex overflow-auto no-scrollbar">
165
165
  {formActions.map((action, index) => {
166
166
 
167
+
167
168
  const props = {
168
169
  view: "form",
169
170
  entity,
@@ -208,17 +209,19 @@ function buildBottomActions<M extends Record<string, unknown>>({
208
209
  {t("back_to_detail") ?? "Back to details"}
209
210
  </Button>
210
211
  )}
211
- <Button variant={canClose ? "text" : "filled"}
212
- color="primary"
213
- type="submit"
214
- disabled={disabled || formex.isSubmitting}
215
- onClick={() => {
216
- sideDialogContext.setPendingClose(false);
217
- }}>
218
- {status === "existing" && t("save")}
219
- {status === "copy" && t("create_copy")}
220
- {status === "new" && t("create")}
221
- </Button>
212
+ <Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
213
+ <Button variant={canClose ? "text" : "filled"}
214
+ color="primary"
215
+ type="submit"
216
+ disabled={disabled || formex.isSubmitting}
217
+ onClick={() => {
218
+ sideDialogContext.setPendingClose(false);
219
+ }}>
220
+ {status === "existing" && t("save")}
221
+ {status === "copy" && t("create_copy")}
222
+ {status === "new" && t("create")}
223
+ </Button>
224
+ </Tooltip>
222
225
  {canClose && <LoadingButton variant="filled"
223
226
  color="primary"
224
227
  type="submit"
@@ -256,19 +259,22 @@ function buildSideActions<M extends Record<string, unknown>>({
256
259
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
257
260
  return <div
258
261
  className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className)}>
259
- <LoadingButton fullWidth={true}
260
- variant="filled"
261
- color="primary"
262
- type="submit"
263
- startIcon={hasErrors ? <AlertCircleIcon/> : undefined}
264
- disabled={disabled || formex.isSubmitting}
265
- onClick={() => {
266
- sideDialogContext.setPendingClose?.(false);
267
- }}>
268
- {status === "existing" && t("save")}
269
- {status === "copy" && t("create_copy")}
270
- {status === "new" && t("create")}
271
- </LoadingButton>
262
+ <Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
263
+ <LoadingButton fullWidth={true}
264
+ variant="filled"
265
+ color="primary"
266
+ type="submit"
267
+ loading={formex.isSubmitting}
268
+ startIcon={hasErrors ? <AlertCircleIcon/> : undefined}
269
+ disabled={disabled || formex.isSubmitting}
270
+ onClick={() => {
271
+ sideDialogContext.setPendingClose?.(false);
272
+ }}>
273
+ {status === "existing" && t("save")}
274
+ {status === "copy" && t("create_copy")}
275
+ {status === "new" && t("create")}
276
+ </LoadingButton>
277
+ </Tooltip>
272
278
 
273
279
  <Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
274
280
  {status === "existing" ? t("discard") : t("clear")}
@@ -293,7 +299,7 @@ function buildSideActions<M extends Record<string, unknown>>({
293
299
  openEntityMode,
294
300
  navigateBack,
295
301
  formContext
296
- } satisfies EntityActionClickProps<any>;
302
+ } satisfies EntityActionClickProps<Record<string, unknown>>;
297
303
  const isEnabled = !action.isEnabled || action.isEnabled(props);
298
304
  return (
299
305
  <EntityActionButton key={action.key ?? action.name ?? index} action={action} enabled={isEnabled} props={props}/>
@@ -63,14 +63,23 @@ export function ContentHomePage({
63
63
  onNavigationEntriesUpdate = () => {}
64
64
  } = navigationStateController.topLevelNavigation || {};
65
65
 
66
- // Studio mode shows view-type entries (devViews) + admin entries (Users/Roles).
67
- // Content mode shows collections, admin entries (Users/Roles), and custom entries — but not studio views.
66
+ // Build a set of Studio-only dev view slugs so we can distinguish them from
67
+ // CMS custom views added via <RebaseCMS views={...}> or plugins.
68
+ const studioViewSlugs = useMemo(() => {
69
+ const slugs = new Set<string>();
70
+ (registry.studioConfig?.devViews ?? []).forEach(v => slugs.add(v.slug));
71
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
72
+ return slugs;
73
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
74
+
75
+ // Studio mode shows Studio dev views + admin entries (Users/Roles).
76
+ // Content mode shows collections, CMS custom views, and admin entries — but not Studio dev views.
68
77
  const rawNavigationEntries = useMemo(() => {
69
78
  if (adminModeController.mode === "studio") {
70
79
  return unFilteredNavigationEntries.filter(e => e.type === "view" || e.type === "admin");
71
80
  }
72
- return unFilteredNavigationEntries.filter(e => e.type !== "view");
73
- }, [unFilteredNavigationEntries, adminModeController.mode]);
81
+ return unFilteredNavigationEntries.filter(e => e.type !== "view" || !studioViewSlugs.has(e.slug));
82
+ }, [unFilteredNavigationEntries, adminModeController.mode, studioViewSlugs]);
74
83
 
75
84
  const groupOrderFromNavController = useMemo(() => {
76
85
  const entryGroups = new Set(rawNavigationEntries.map(e => e.group).filter(Boolean));
@@ -10,11 +10,12 @@ import type { RebaseCMSConfig } from "@rebasepro/types";
10
10
  * is auto-wired as a native feature (slots, provider, Studio view) without
11
11
  * needing any external plugin.
12
12
  */
13
- export function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }: RebaseCMSConfig) {
13
+ export function RebaseCMS({ collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }: RebaseCMSConfig) {
14
14
  const dispatch = useRebaseRegistryDispatch();
15
15
 
16
16
  useLayoutEffect(() => {
17
17
  dispatch.registerCMS({ collections,
18
+ views,
18
19
  homePage,
19
20
  entityViews,
20
21
  entityActions,
@@ -22,7 +23,7 @@ plugins,
22
23
  collectionEditor,
23
24
  navigationGroupMappings });
24
25
  return () => dispatch.unregisterCMS();
25
- }, [dispatch, collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings]);
26
+ }, [dispatch, collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings]);
26
27
 
27
28
  return null;
28
29
  }
@@ -1,4 +1,4 @@
1
- import React, { useMemo, lazy, Suspense } from "react";
1
+ import React, { useMemo, useRef, useEffect, lazy, Suspense } from "react";
2
2
  import {
3
3
  useRebaseRegistry,
4
4
  useRebaseContext,
@@ -7,10 +7,14 @@ import {
7
7
  useAdminModeController,
8
8
  useBuildLocalConfigurationPersistence,
9
9
  useRebaseClient,
10
+ useData,
11
+ useDataSources,
10
12
  StudioBridgeRegistryProvider,
11
13
  useBridgeRegistration,
12
- CustomizationControllerContext
14
+ CustomizationControllerContext,
15
+ RebaseDataContext
13
16
  } from "@rebasepro/core";
17
+ import { buildRoutedRebaseData, resolveDataSource } from "@rebasepro/common";
14
18
  import { CircularProgressCenter } from "@rebasepro/ui";
15
19
  import type { AppView, CollectionEditorOptions, EntityCustomView, EntityAction, EntityCollection, RebasePlugin } from "@rebasepro/types";
16
20
  import type { CollectionRegistryController } from "@rebasepro/types";
@@ -35,8 +39,8 @@ import { ConfigControllerProvider } from "../collection_editor/ConfigControllerP
35
39
 
36
40
  // Lazy-load the schema view — only fetched when studio schema tool is active
37
41
  const CollectionsStudioView = lazy(() =>
38
- import("../collection_editor/ui/collection_editor/CollectionsStudioView")
39
- .then(m => ({ default: m.CollectionsStudioView }))
42
+ import("../collection_editor/ui/collection_editor/RouterCollectionsStudioView")
43
+ .then(m => ({ default: m.RouterCollectionsStudioView }))
40
44
  );
41
45
 
42
46
  export interface RebaseNavigationProps {
@@ -86,7 +90,24 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
86
90
  // ── Build the navigation controllers ──────────────────────────────
87
91
  const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
88
92
 
89
- const collectionRegistryController = useBuildCollectionRegistryController({ userConfigPersistence });
93
+ const dataSources = useDataSources();
94
+ const collectionRegistryController = useBuildCollectionRegistryController({ userConfigPersistence, dataSources: dataSources.registry });
95
+
96
+ // ── Multi-data-source routing ─────────────────────────────────────
97
+ // Combine the default data source (server transport) with the registered
98
+ // direct/custom sources from <Rebase> and route each collection by its
99
+ // resolved data-source key, looked up by path against the collection
100
+ // registry. A stable resolver ref keeps the routed data instance
101
+ // referentially stable across registry rebuilds, so data-effect
102
+ // dependencies don't thrash.
103
+ const defaultData = useData();
104
+ const getCollectionRef = useRef(collectionRegistryController.getCollection);
105
+ getCollectionRef.current = collectionRegistryController.getCollection;
106
+ const routedData = useMemo(() => buildRoutedRebaseData({
107
+ defaultData,
108
+ sources: dataSources.sources,
109
+ resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
110
+ }), [defaultData, dataSources]);
90
111
 
91
112
  const urlController = useBuildUrlController({
92
113
  basePath: "/",
@@ -102,6 +123,26 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
102
123
  [cmsCollections]
103
124
  );
104
125
 
126
+ // ── Dev-only data-source sanity check ─────────────────────────────
127
+ // Warn about data sources declared with a direct/custom transport that
128
+ // have no client-side driver — these silently fall back to the default
129
+ // (server) source, a common source of misrouting. Unambiguous: it never
130
+ // false-positives on server-mediated sources (which intentionally have
131
+ // no client driver).
132
+ useEffect(() => {
133
+ if (process.env.NODE_ENV === "production") return;
134
+ const { registry, sources } = dataSources;
135
+ const missing = Object.values(registry)
136
+ .filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key])
137
+ .map((d) => d.key);
138
+ if (missing.length > 0) {
139
+ console.warn(
140
+ `[Rebase] These data source(s) declare a direct/custom transport but have no client-side driver and will fall back to the default data source: ${missing.map(k => `"${k}"`).join(", ")}. ` +
141
+ `Provide a \`driver\` for them in \`dataSources\` on <Rebase>.`
142
+ );
143
+ }
144
+ }, [dataSources]);
145
+
105
146
  const internalConfigController = useLocalCollectionsConfigController(
106
147
  rebaseClient,
107
148
  resolvedCollections,
@@ -120,6 +161,7 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
120
161
  return {
121
162
  slug: "schema",
122
163
  name: "Edit collections",
164
+ group: "Database",
123
165
  icon: "LayoutList",
124
166
  nestedRoutes: true,
125
167
  view: (
@@ -136,13 +178,30 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
136
178
  return base;
137
179
  }, [registry.studioConfig?.devViews, schemaView]);
138
180
 
181
+ // Merge CMS-registered views with Studio dev views.
182
+ // Order: CMS views (developer's primary content) → Studio dev views (tooling).
183
+ // Plugin views are merged later inside resolveAppViews.
184
+ const cmsViews = registry.cmsConfig?.views;
185
+ const mergedViews = useMemo(() => {
186
+ if (!cmsViews) return devViews;
187
+ if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
188
+ // When cmsViews is a builder function, wrap it to append devViews after resolution
189
+ if (typeof cmsViews === "function") {
190
+ return async (params: Parameters<typeof cmsViews>[0]) => {
191
+ const resolved = await cmsViews(params) ?? [];
192
+ return [...resolved, ...devViews];
193
+ };
194
+ }
195
+ return [...cmsViews, ...devViews];
196
+ }, [cmsViews, devViews]);
197
+
139
198
  const navigationStateController = useBuildNavigationStateController({
140
199
  plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
141
200
  collections: collectionsBuilder,
142
- views: devViews,
201
+ views: mergedViews,
143
202
  navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
144
203
  authController: context.authController!,
145
- data: context.data,
204
+ data: routedData,
146
205
  collectionRegistryController,
147
206
  urlController,
148
207
  adminMode: adminModeController?.mode
@@ -175,7 +234,11 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
175
234
  }, [parentCustomizationController, registry.cmsConfig?.entityViews, registry.cmsConfig?.entityActions]);
176
235
 
177
236
  // ── Inner content with all context providers ──────────────────────
237
+ // Re-provide RebaseDataContext with the routed data so that every CMS
238
+ // consumer (list/entity views, references, board, import/export, and
239
+ // `context.data`) is routed to the correct driver by collection path.
178
240
  const navigationContent = (
241
+ <RebaseDataContext.Provider value={routedData}>
179
242
  <CustomizationControllerContext.Provider value={enrichedCustomizationController}>
180
243
  <StudioBridgeRegistryProvider>
181
244
  <CollectionRegistryContext.Provider value={collectionRegistryController}>
@@ -194,6 +257,7 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
194
257
  </CollectionRegistryContext.Provider>
195
258
  </StudioBridgeRegistryProvider>
196
259
  </CustomizationControllerContext.Provider>
260
+ </RebaseDataContext.Provider>
197
261
  );
198
262
 
199
263
  // ── Wrap with ConfigControllerProvider when collection editor is enabled ──
@@ -2,7 +2,7 @@ import { createSelectionStore } from "./SelectionStore";
2
2
  import type { Property } from "@rebasepro/types";
3
3
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
4
4
  import { CollectionSize, Entity, EntityRelation, EntityTableController, FilterValues, SelectedCellProps } from "@rebasepro/types";
5
- import { CellRendererParams, VirtualTable, VirtualTableColumn, VirtualTableFilterValues, OnRowClickParams, VirtualTableWhereFilterOp } from "@rebasepro/ui";
5
+ import { CellRendererParams, TableView, VirtualTableColumn, VirtualTableFilterValues, OnRowClickParams, VirtualTableWhereFilterOp } from "@rebasepro/ui";
6
6
  import { enumToObjectEntries } from "@rebasepro/common";
7
7
  import { DEFAULT_PAGE_SIZE, EntityCollectionTableController, OnCellValueChange, OnColumnResizeParams } from "@rebasepro/core";
8
8
  import { FilterFormFieldProps } from "@rebasepro/ui";
@@ -244,7 +244,7 @@ export const SelectableTable = function SelectableTable<M extends Record<string,
244
244
  <div className="h-full w-full flex flex-col bg-white dark:bg-surface-900"
245
245
  ref={ref}>
246
246
 
247
- <VirtualTable
247
+ <TableView
248
248
  data={data as unknown as Record<string, unknown>[]}
249
249
  columns={columns}
250
250
  cellRenderer={(props) => cellRenderer(props as unknown as CellRendererParams<Entity<M>>)}
@@ -179,13 +179,6 @@ function SideDialogView({
179
179
  style={{ zIndex: 45 + panelIndex * 10 }}
180
180
  onOpenChange={(open) => {
181
181
  if (!open) {
182
- // Check if any suggestion menu is visible in DOM
183
- const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
184
- if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") {
185
- // Don't close the sheet if a suggestion menu is visible
186
- // Let Tiptap handle closing the menu first
187
- return;
188
- }
189
182
  onCloseRequest();
190
183
  }
191
184
  }}
@@ -36,7 +36,7 @@ export const editEntityAction: EntityAction = {
36
36
 
37
37
  highlightEntity?.(entity);
38
38
 
39
- context.analyticsController?.onAnalyticsEvent?.("entity_click", {
39
+ context?.analyticsController?.onAnalyticsEvent?.("entity_click", {
40
40
  path: entity.path,
41
41
  entityId: entity.id
42
42
  });
@@ -62,7 +62,7 @@ export const editEntityAction: EntityAction = {
62
62
  path: newFullIdPath,
63
63
  sideEntityController,
64
64
  onClose: () => unhighlightEntity?.(entity),
65
- navigation: context.urlController!,
65
+ navigation: context?.urlController!,
66
66
  selectedTab: defaultSelectedView
67
67
  });
68
68
 
@@ -92,7 +92,7 @@ export const copyEntityAction: EntityAction = {
92
92
  throw new Error("INTERNAL: copyEntityAction: sideEntityController is undefined");
93
93
  }
94
94
  highlightEntity?.(entity);
95
- context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
95
+ context?.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
96
96
  path: entity.path,
97
97
  entityId: entity.id
98
98
  });
@@ -106,7 +106,7 @@ export const copyEntityAction: EntityAction = {
106
106
  copy: true,
107
107
  sideEntityController,
108
108
  onClose: () => unhighlightEntity?.(entity),
109
- navigation: context.urlController!
109
+ navigation: context?.urlController!
110
110
  });
111
111
 
112
112
  return Promise.resolve(undefined);
@@ -131,7 +131,7 @@ export const deleteEntityAction: EntityAction = {
131
131
  if (!entity) {
132
132
  throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
133
133
  }
134
- if (!context.dialogsController) {
134
+ if (!context?.dialogsController) {
135
135
  throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
136
136
  }
137
137
  const { closeDialog } = context.dialogsController.open({
@@ -146,14 +146,14 @@ export const deleteEntityAction: EntityAction = {
146
146
  callbacks={collection.callbacks}
147
147
  open={open}
148
148
  onEntityDelete={() => {
149
- context.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", {
149
+ context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", {
150
150
  path
151
151
  });
152
152
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
153
153
  onCollectionChange?.();
154
154
  // In full-screen mode, navigateBack would go to the deleted entity's
155
155
  // detail URL, which no longer exists. Navigate to the parent collection instead.
156
- if (openEntityMode === "full_screen" && context.urlController) {
156
+ if (openEntityMode === "full_screen" && context?.urlController) {
157
157
  const collectionUrl = context.urlController.buildUrlCollectionPath(path);
158
158
  context.urlController.navigate(collectionUrl, { replace: true });
159
159
  } else {
@@ -258,7 +258,7 @@ export const resetPasswordAction: EntityAction = {
258
258
  if (!entity) {
259
259
  throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
260
260
  }
261
- if (!context.dialogsController) {
261
+ if (!context?.dialogsController) {
262
262
  throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
263
263
  }
264
264
 
@@ -272,7 +272,7 @@ export const resetPasswordAction: EntityAction = {
272
272
  roles: entity.values?.roles as string[] || []
273
273
  };
274
274
 
275
- const { closeDialog } = context.dialogsController.open({
275
+ const { closeDialog } = context!.dialogsController.open({
276
276
  key: "reset_password_dialog_" + entity.id,
277
277
  Component: ({ open }) => (
278
278
  <ResetPasswordActionDialog
@@ -22,14 +22,23 @@ export const BreadcrumbsProvider: React.FC<BreadcrumbsProviderProps> = ({ childr
22
22
  const set = useCallback((props: {
23
23
  breadcrumbs: BreadcrumbEntry[];
24
24
  }) => {
25
- setBreadcrumbs(prev => props.breadcrumbs.map(newEntry => {
26
- const prevEntry = newEntry.id ? prev.find(p => p.id === newEntry.id) : undefined;
27
- if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") {
28
- return { ...newEntry,
25
+ setBreadcrumbs(prev => {
26
+ const next = props.breadcrumbs.map(newEntry => {
27
+ const prevEntry = newEntry.id ? prev.find(p => p.id === newEntry.id) : undefined;
28
+ if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") {
29
+ return { ...newEntry,
29
30
  count: prevEntry.count };
31
+ }
32
+ return newEntry;
33
+ });
34
+ // Bail out if nothing changed — return same reference to skip re-render
35
+ if (prev.length === next.length && prev.every((p, i) =>
36
+ p.title === next[i].title && p.url === next[i].url && p.id === next[i].id && p.count === next[i].count
37
+ )) {
38
+ return prev;
30
39
  }
31
- return newEntry;
32
- }));
40
+ return next;
41
+ });
33
42
  }, []);
34
43
 
35
44
  const updateCount = useCallback((id: string, count: number | null | undefined) => {
@@ -36,7 +36,6 @@
36
36
  // },
37
37
  // ];
38
38
  //
39
- // //TODO: I think it makes more sense to create a custom Tiptap extension for this functionality https://tiptap.dev/docs/editor/ai/introduction
40
39
  //
41
40
  // interface AISelectorProps {
42
41
  // open: boolean;