@rebasepro/core 0.4.0 → 0.6.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 (66) hide show
  1. package/README.md +142 -210
  2. package/dist/components/AIIcon.d.ts +3 -2
  3. package/dist/components/ConfirmationDialog.d.ts +1 -1
  4. package/dist/components/Debug/UIReferenceView.d.ts +13 -1
  5. package/dist/components/Debug/UIStyleGuide.d.ts +2 -1
  6. package/dist/components/ErrorTooltip.d.ts +2 -1
  7. package/dist/components/LanguageToggle.d.ts +2 -1
  8. package/dist/components/LoginView/LoginView.d.ts +2 -2
  9. package/dist/components/NotFoundPage.d.ts +2 -1
  10. package/dist/components/RebaseLogo.d.ts +1 -1
  11. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  12. package/dist/components/UnsavedChangesDialog.d.ts +1 -1
  13. package/dist/components/UserDisplay.d.ts +1 -1
  14. package/dist/components/UserSelectPopover.d.ts +2 -1
  15. package/dist/components/UserSettingsView.d.ts +2 -1
  16. package/dist/components/index.d.ts +1 -1
  17. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  18. package/dist/contexts/RebaseClientInstanceContext.d.ts +1 -1
  19. package/dist/contexts/index.d.ts +1 -1
  20. package/dist/core/Rebase.d.ts +2 -1
  21. package/dist/core/RebaseProps.d.ts +23 -22
  22. package/dist/core/RebaseRouter.d.ts +1 -1
  23. package/dist/core/RebaseRoutes.d.ts +1 -1
  24. package/dist/hooks/ApiConfigContext.d.ts +1 -1
  25. package/dist/hooks/index.d.ts +1 -2
  26. package/dist/hooks/useComponentOverride.d.ts +32 -0
  27. package/dist/hooks/useRebaseRegistry.d.ts +1 -1
  28. package/dist/hooks/useStudioBridge.d.ts +4 -16
  29. package/dist/i18n/RebaseI18nProvider.d.ts +2 -2
  30. package/dist/index.es.js +12878 -15004
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.umd.js +13192 -15144
  33. package/dist/index.umd.js.map +1 -1
  34. package/dist/util/icons.d.ts +2 -2
  35. package/dist/vitePlugin.js +4 -1
  36. package/package.json +21 -22
  37. package/src/components/ErrorView.tsx +16 -6
  38. package/src/components/LoginView/LoginView.tsx +14 -16
  39. package/src/components/SchemaDriftBanner.tsx +102 -0
  40. package/src/components/common/useDataTableController.tsx +3 -3
  41. package/src/components/index.tsx +1 -1
  42. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  43. package/src/contexts/RebaseClientInstanceContext.tsx +1 -1
  44. package/src/contexts/index.ts +1 -1
  45. package/src/core/Rebase.tsx +16 -18
  46. package/src/core/RebaseProps.tsx +24 -25
  47. package/src/hooks/data/useCollectionFetch.tsx +10 -2
  48. package/src/hooks/data/useRelationSelector.tsx +4 -2
  49. package/src/hooks/index.tsx +1 -3
  50. package/src/hooks/useCollapsedGroups.ts +2 -1
  51. package/src/hooks/useComponentOverride.tsx +59 -0
  52. package/src/hooks/useRebaseContext.tsx +3 -5
  53. package/src/hooks/useResolvedComponent.tsx +1 -1
  54. package/src/hooks/useStudioBridge.tsx +5 -13
  55. package/src/locales/en.ts +3 -0
  56. package/src/util/entity_cache.ts +0 -2
  57. package/src/util/previews.ts +1 -1
  58. package/src/vitePlugin.ts +2 -1
  59. package/dist/components/BootstrapAdminBanner.d.ts +0 -4
  60. package/dist/contexts/InternalUserManagementContext.d.ts +0 -3
  61. package/dist/hooks/data/useUserSelector.d.ts +0 -31
  62. package/dist/hooks/useInternalUserManagementController.d.ts +0 -12
  63. package/src/components/BootstrapAdminBanner.tsx +0 -75
  64. package/src/contexts/InternalUserManagementContext.tsx +0 -4
  65. package/src/hooks/data/useUserSelector.tsx +0 -157
  66. package/src/hooks/useInternalUserManagementController.tsx +0 -17
@@ -12,9 +12,9 @@ export type IconViewProps = {
12
12
  group?: string;
13
13
  icon?: string | React.ReactNode;
14
14
  };
15
- export declare const IconForView: React.NamedExoticComponent<{
15
+ export declare const IconForView: React.MemoExoticComponent<({ collectionOrView, className, color, size }: {
16
16
  collectionOrView?: IconViewProps;
17
17
  color?: IconColor;
18
18
  className?: string;
19
19
  size?: "smallest" | "small" | "medium" | "large" | number;
20
- }>;
20
+ }) => React.ReactElement>;
@@ -62,7 +62,10 @@ function rebaseCollectionsPlugin(options) {
62
62
  return `${prefix}{ __rebaseLazy: true, load: () => import(${quote}${importPath}${quote}) }`;
63
63
  }
64
64
  );
65
- return { code: transformed, map: null };
65
+ return {
66
+ code: transformed,
67
+ map: null
68
+ };
66
69
  }
67
70
  };
68
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/core",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.6.0",
5
5
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -48,16 +48,15 @@
48
48
  "dependencies": {
49
49
  "compressorjs": "^1.3.0",
50
50
  "fast-equals": "6.0.0",
51
- "fuse.js": "^7.3.0",
52
- "i18next": "^23.16.8",
51
+ "fuse.js": "^7.4.2",
52
+ "i18next": "^26.3.1",
53
53
  "notistack": "^3.0.2",
54
- "react-compiler-runtime": "1.0.0",
55
- "react-i18next": "^14.1.3",
56
- "@rebasepro/types": "0.4.0",
57
- "@rebasepro/common": "0.4.0",
58
- "@rebasepro/ui": "0.4.0",
59
- "@rebasepro/formex": "0.4.0",
60
- "@rebasepro/utils": "0.4.0"
54
+ "react-i18next": "^17.0.8",
55
+ "@rebasepro/common": "0.6.0",
56
+ "@rebasepro/formex": "0.6.0",
57
+ "@rebasepro/ui": "0.6.0",
58
+ "@rebasepro/types": "0.6.0",
59
+ "@rebasepro/utils": "0.6.0"
61
60
  },
62
61
  "peerDependencies": {
63
62
  "react": ">=19.0.0",
@@ -69,22 +68,22 @@
69
68
  "@jest/globals": "^30.4.1",
70
69
  "@testing-library/react": "^16.3.2",
71
70
  "@testing-library/user-event": "^14.6.1",
72
- "@types/jest": "^29.5.14",
73
- "@types/node": "^20.19.41",
74
- "@types/react": "^19.2.15",
71
+ "@types/jest": "^30.0.0",
72
+ "@types/node": "^25.9.3",
73
+ "@types/react": "^19.2.17",
75
74
  "@types/react-dom": "^19.2.3",
76
75
  "@types/react-measure": "^2.0.12",
77
- "@vitejs/plugin-react": "^4.7.0",
76
+ "@vitejs/plugin-react": "^6.0.2",
78
77
  "babel-plugin-react-compiler": "beta",
79
- "cross-env": "^7.0.3",
80
- "esbuild": "^0.25.12",
81
- "jest": "^29.7.0",
78
+ "cross-env": "^10.1.0",
79
+ "esbuild": "^0.28.1",
80
+ "jest": "^30.4.2",
82
81
  "jest-environment-jsdom": "^30.4.1",
83
- "react-router-dom": "^7.15.1",
84
- "ts-jest": "^29.4.10",
85
- "tsd": "^0.31.2",
86
- "typescript": "^5.9.3",
87
- "vite": "^7.3.3"
82
+ "react-router-dom": "^7.17.0",
83
+ "ts-jest": "^29.4.11",
84
+ "tsd": "^0.33.0",
85
+ "typescript": "^6.0.3",
86
+ "vite": "^8.0.16"
88
87
  },
89
88
  "files": [
90
89
  "dist",
@@ -28,19 +28,29 @@ export function ErrorView({
28
28
  tooltip,
29
29
  onRetry
30
30
  }: ErrorViewProps): React.ReactElement {
31
- const component = error instanceof Error ? error.message : error;
32
- console.warn("ErrorView", JSON.stringify(error))
31
+ const message = error instanceof Error ? error.message : error;
32
+ // Extract error code from ApiError instances (e.g. PG error codes like "42P01")
33
+ const errorCode = error instanceof Error && "code" in error
34
+ ? (error as Error & { code?: string }).code
35
+ : undefined;
33
36
 
34
37
  const body = (
35
38
  <div
36
39
  className="flex flex-col m-2">
37
- <div className="flex items-start">
38
- <AlertTriangleIcon className="mx-2 mt-0.5"/>
39
- <div className="pl-2">
40
+ <div className="flex items-center gap-2">
41
+ <AlertTriangleIcon className="shrink-0"/>
42
+ <div>
40
43
  {title && <Typography
41
44
  variant={"body2"}
42
45
  className="font-medium text-text-primary">{title}</Typography>}
43
- <Typography variant={"body2"} className="text-text-secondary">{component}</Typography>
46
+ <Typography variant={"body2"} className="text-text-secondary">{message}</Typography>
47
+ {errorCode && (
48
+ <span
49
+ className="inline-block mt-1 px-1.5 py-0.5 text-[10px] font-mono rounded bg-surface-200 dark:bg-surface-700 text-text-secondary"
50
+ >
51
+ {errorCode}
52
+ </span>
53
+ )}
44
54
  {onRetry && (
45
55
  <div className="mt-3">
46
56
  <Button
@@ -88,7 +88,6 @@ export interface LoginViewProps {
88
88
  notAllowedError?: string | Error;
89
89
 
90
90
 
91
-
92
91
  /**
93
92
  * Google client ID for Google OAuth.
94
93
  * Required when Google login is enabled via ID token flow.
@@ -208,7 +207,7 @@ export function LoginView({
208
207
  // Clear URL search params without page reload
209
208
  const cleanUrl = window.location.origin + window.location.pathname;
210
209
  window.history.replaceState({}, document.title, cleanUrl);
211
-
210
+
212
211
  if (authController.oauthLogin) {
213
212
  authController.oauthLogin(provider, {
214
213
  code,
@@ -220,18 +219,7 @@ export function LoginView({
220
219
  }
221
220
  }, [authController]);
222
221
 
223
- function buildErrorView() {
224
- if (!authController.authProviderError) return null;
225
- if (authController.user != null) return null;
226
- const errorMsg = authController.authProviderError instanceof Error
227
- ? authController.authProviderError.message
228
- : String(authController.authProviderError);
229
- return (
230
- <div className="w-full">
231
- <ErrorView error={errorMsg}/>
232
- </div>
233
- );
234
- }
222
+
235
223
 
236
224
  let logoComponent;
237
225
  if (logo) {
@@ -297,12 +285,11 @@ export function LoginView({
297
285
  </div>
298
286
  )}
299
287
 
300
- {mode !== "forgot" && buildErrorView()}
301
-
302
288
  <div className={cls(
303
289
  "w-full transition-opacity duration-150",
304
290
  viewVisible ? "opacity-100" : "opacity-0"
305
291
  )}>
292
+
306
293
  {/* Bootstrap mode: show setup form directly */}
307
294
  {isBootstrapMode && !authController.user && (
308
295
  <LoginForm
@@ -673,6 +660,17 @@ function LoginForm({
673
660
  </div>
674
661
  )}
675
662
 
663
+ {(() => {
664
+ const err = authController.authProviderError;
665
+ if (!err || authController.user) return null;
666
+ const msg: string = err instanceof Error ? err.message : String(err);
667
+ return (
668
+ <div className="w-full mb-2">
669
+ <ErrorView error={msg}/>
670
+ </div>
671
+ );
672
+ })()}
673
+
676
674
  <Typography variant="h6" className="mb-0.5">
677
675
  {title}
678
676
  </Typography>
@@ -0,0 +1,102 @@
1
+ import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
2
+ import { Alert, Typography } from "@rebasepro/ui";
3
+
4
+ // ── Schema Drift Context ────────────────────────────────────────────────
5
+
6
+ interface SchemaDriftContextValue {
7
+ /** Report a schema drift error (call from any data-loading hook). */
8
+ reportSchemaDrift: (message: string) => void;
9
+ /** The most recent schema drift message, or null. */
10
+ schemaDriftMessage: string | null;
11
+ /** Dismiss the banner for this session. */
12
+ dismiss: () => void;
13
+ }
14
+
15
+ const SchemaDriftContext = createContext<SchemaDriftContextValue>({
16
+ reportSchemaDrift: () => {},
17
+ schemaDriftMessage: null,
18
+ dismiss: () => {}
19
+ });
20
+
21
+ export function useSchemaDriftContext(): SchemaDriftContextValue {
22
+ return useContext(SchemaDriftContext);
23
+ }
24
+
25
+ /**
26
+ * Detects schema drift from an error object.
27
+ * Checks both the error code (from `RebaseApiError`) and the message
28
+ * for schema drift indicators.
29
+ */
30
+ export function isSchemaDriftError(error: unknown): boolean {
31
+ if (!error || typeof error !== "object") return false;
32
+ // RebaseApiError from the SDK has a `code` property
33
+ if ("code" in error && (error as { code?: string }).code === "SCHEMA_DRIFT") {
34
+ return true;
35
+ }
36
+ // Fallback: check the message for the telltale phrase
37
+ if (error instanceof Error && error.message.includes("Schema drift:")) {
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ // ── Provider ─────────────────────────────────────────────────────────────
44
+
45
+ export function SchemaDriftProvider({ children }: { children: React.ReactNode }) {
46
+ const [schemaDriftMessage, setSchemaDriftMessage] = useState<string | null>(null);
47
+ const [dismissed, setDismissed] = useState(false);
48
+
49
+ const reportSchemaDrift = useCallback((message: string) => {
50
+ setSchemaDriftMessage(prev => prev ?? message);
51
+ }, []);
52
+
53
+ const dismiss = useCallback(() => {
54
+ setDismissed(true);
55
+ }, []);
56
+
57
+ const value = useMemo<SchemaDriftContextValue>(() => ({
58
+ reportSchemaDrift,
59
+ schemaDriftMessage: dismissed ? null : schemaDriftMessage,
60
+ dismiss
61
+ }), [reportSchemaDrift, schemaDriftMessage, dismissed, dismiss]);
62
+
63
+ return (
64
+ <SchemaDriftContext.Provider value={value}>
65
+ {children}
66
+ </SchemaDriftContext.Provider>
67
+ );
68
+ }
69
+
70
+ // ── Banner Component ─────────────────────────────────────────────────────
71
+
72
+ export interface SchemaDriftBannerProps {
73
+ className?: string;
74
+ }
75
+
76
+ /**
77
+ * Persistent banner shown when a schema drift error is detected.
78
+ */
79
+ export function SchemaDriftBanner({ className }: SchemaDriftBannerProps) {
80
+ const { schemaDriftMessage, dismiss } = useSchemaDriftContext();
81
+
82
+ if (!schemaDriftMessage) return null;
83
+
84
+ return (
85
+ <Alert
86
+ color="warning"
87
+ size="small"
88
+ onDismiss={dismiss}
89
+ className={className}
90
+ >
91
+ <div className="flex flex-col gap-1">
92
+ <Typography variant="label" className="text-amber-800 dark:text-amber-200">
93
+ Schema drift detected
94
+ </Typography>
95
+ <Typography variant="body2" className="text-amber-700 dark:text-amber-300">
96
+ {schemaDriftMessage}{" "}
97
+ Run <code className="bg-amber-200 dark:bg-amber-800 px-1 rounded text-xs">pnpm db:push</code> in your backend terminal to sync.
98
+ </Typography>
99
+ </div>
100
+ </Alert>
101
+ );
102
+ }
@@ -128,7 +128,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
128
128
 
129
129
  const { filterValues: urlFilterValues, sortBy: urlSortBy } = parseFilterAndSort(location.search);
130
130
  if (!fixedFilter) {
131
- setFilterValues(urlFilterValues as FilterValues<Extract<keyof M, string> | (string & {})> | undefined);
131
+ setFilterValues((urlFilterValues ?? defaultFilter) as FilterValues<Extract<keyof M, string> | (string & {})> | undefined);
132
132
  }
133
133
  if (urlSortBy && fixedFilter && !checkFilterCombination(fixedFilter, urlSortBy)) {
134
134
  console.warn("URL sort is not compatible with the force filter.");
@@ -186,7 +186,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
186
186
  const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
187
187
  const [noMoreToLoad, setNoMoreToLoad] = useState<boolean>(false);
188
188
 
189
- const clearFilter = useCallback(() => setFilterValues(fixedFilter ?? undefined), [fixedFilter]);
189
+ const clearFilter = useCallback(() => setFilterValues(fixedFilter ?? defaultFilter ?? undefined), [fixedFilter, defaultFilter]);
190
190
 
191
191
  const updateFilterValues = useCallback((updatedFilter: FilterValues<Extract<keyof M, string> | (string & {})> | undefined) => {
192
192
  if (fixedFilter) {
@@ -235,7 +235,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
235
235
  const onError = (error: Error) => {
236
236
  console.error("ERROR", error);
237
237
  setDataLoading(false);
238
- setRawData([]);
238
+ setRawData((prev) => prev && prev.length > 0 ? prev : []);
239
239
  setDataLoadingError(error);
240
240
  };
241
241
 
@@ -22,4 +22,4 @@ export * from "./LoginView";
22
22
 
23
23
  export * from "./RebaseAuth";
24
24
 
25
- export * from "./BootstrapAdminBanner";
25
+ export * from "./SchemaDriftBanner";
@@ -0,0 +1,81 @@
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+ import type { ComponentOverrideMap } from "@rebasepro/types";
3
+
4
+ /** Stable empty reference to avoid re-creating context values on every render. */
5
+ const EMPTY_OVERRIDES: ComponentOverrideMap = {};
6
+
7
+ /**
8
+ * Internal state for the component override resolution chain.
9
+ * Holds both global overrides (set on `<Rebase>`) and collection-scoped
10
+ * overrides (set on individual collections).
11
+ *
12
+ * Resolution priority: collection > global > default.
13
+ *
14
+ * @internal
15
+ */
16
+ interface ComponentOverrideState {
17
+ globalOverrides: ComponentOverrideMap;
18
+ collectionOverrides: ComponentOverrideMap;
19
+ }
20
+
21
+ export const ComponentOverrideContext = createContext<ComponentOverrideState>({
22
+ globalOverrides: EMPTY_OVERRIDES,
23
+ collectionOverrides: EMPTY_OVERRIDES
24
+ });
25
+
26
+ /**
27
+ * Provider set at the `<Rebase>` root level to supply global component overrides.
28
+ *
29
+ * @internal — Used by the Rebase component. End users set overrides via
30
+ * the `components` prop on `<Rebase>`.
31
+ */
32
+ export function GlobalComponentOverrideProvider({
33
+ overrides,
34
+ children
35
+ }: {
36
+ overrides?: ComponentOverrideMap;
37
+ children: React.ReactNode;
38
+ }) {
39
+ const value = useMemo<ComponentOverrideState>(() => ({
40
+ globalOverrides: overrides ?? EMPTY_OVERRIDES,
41
+ collectionOverrides: EMPTY_OVERRIDES
42
+ }), [overrides]);
43
+
44
+ return (
45
+ <ComponentOverrideContext.Provider value={value}>
46
+ {children}
47
+ </ComponentOverrideContext.Provider>
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Provider set at the collection level to layer collection-scoped overrides
53
+ * on top of global overrides.
54
+ *
55
+ * When a collection defines `components`, this provider is mounted
56
+ * around the collection's view subtree. Components within the subtree
57
+ * will resolve overrides in order: collection → global → default.
58
+ *
59
+ * @internal — Used by EntityCollectionView when a collection has
60
+ * `components` defined.
61
+ */
62
+ export function CollectionComponentOverrideProvider({
63
+ overrides,
64
+ children
65
+ }: {
66
+ overrides?: ComponentOverrideMap;
67
+ children: React.ReactNode;
68
+ }) {
69
+ const parent = useContext(ComponentOverrideContext);
70
+
71
+ const value = useMemo<ComponentOverrideState>(() => ({
72
+ globalOverrides: parent.globalOverrides,
73
+ collectionOverrides: overrides ?? EMPTY_OVERRIDES
74
+ }), [parent.globalOverrides, overrides]);
75
+
76
+ return (
77
+ <ComponentOverrideContext.Provider value={value}>
78
+ {children}
79
+ </ComponentOverrideContext.Provider>
80
+ );
81
+ }
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  /**
4
- * Context that exposes the full RebaseClient instance (from `@rebasepro/client`).
4
+ * Context that exposes the full RebaseClient instance.
5
5
  * Used by the JS Editor to give developer scripts access to `client.data`, `client.auth`, etc.
6
6
  */
7
7
  export const RebaseClientInstanceContext = React.createContext<any>(undefined);
@@ -8,6 +8,6 @@ export * from "./AnalyticsContext";
8
8
  export * from "./StorageSourceContext";
9
9
  export * from "./UserConfigurationPersistenceContext";
10
10
  export * from "./DialogsProvider";
11
- export * from "./InternalUserManagementContext";
12
11
  export * from "./RebaseClientInstanceContext";
13
12
  export * from "./CustomizationControllerContext";
13
+ export * from "./ComponentOverrideContext";
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import type { RebaseProps } from "./RebaseProps";
3
3
  import type { CustomizationController, RebasePlugin, SlotContribution } from "@rebasepro/types";
4
+ import type { ComponentOverrideMap } from "@rebasepro/types";
4
5
 
5
6
  import React, { useMemo } from "react";
6
7
  import { CenteredView, Typography } from "@rebasepro/ui";
7
- import { RebaseContext, User, UserManagementDelegate, CollectionRegistryController } from "@rebasepro/types";
8
+ import { RebaseContext, User, CollectionRegistryController } from "@rebasepro/types";
8
9
  import { PluginProviderStack } from "./PluginProviderStack";
9
10
  import { PluginLifecycleManager } from "./PluginLifecycleManager";
10
11
  import { AuthControllerContext } from "../contexts";
@@ -18,6 +19,8 @@ import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
18
19
  import { ModeControllerProvider, AdminModeControllerProvider, SnackbarProvider } from "../contexts";
19
20
  import { RebaseI18nProvider } from "../i18n/RebaseI18nProvider";
20
21
  import { RebaseRegistryProvider } from "../hooks/useRebaseRegistry";
22
+ import { SchemaDriftProvider } from "../components/SchemaDriftBanner";
23
+ import { GlobalComponentOverrideProvider } from "../contexts/ComponentOverrideContext";
21
24
  import { useBuildModeController } from "../hooks/useBuildModeController";
22
25
  import { useBuildAdminModeController } from "../hooks/useBuildAdminModeController";
23
26
  import { RebaseClientInstanceContext } from "../contexts/RebaseClientInstanceContext";
@@ -25,7 +28,7 @@ import { DialogsProvider } from "../contexts/DialogsProvider";
25
28
  import { buildRebaseData, CollectionRegistry } from "@rebasepro/common";
26
29
  import { CustomizationControllerContext } from "../contexts/CustomizationControllerContext";
27
30
  import { AnalyticsContext } from "../contexts/AnalyticsContext";
28
- import { InternalUserManagementContext } from "../contexts/InternalUserManagementContext";
31
+
29
32
  import { EffectiveRoleControllerContext } from "../contexts/EffectiveRoleController";
30
33
  import { useBuildEffectiveRoleController } from "../hooks/useBuildEffectiveRoleController";
31
34
 
@@ -60,12 +63,12 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
60
63
  propertyConfigs,
61
64
  entityViews,
62
65
  entityActions,
63
- components,
64
66
  apiKey,
65
- userManagement: _userManagement,
67
+
66
68
  effectiveRoleController,
67
69
  apiUrl,
68
- translations
70
+ translations,
71
+ components: componentsProp
69
72
  } = props;
70
73
 
71
74
  const plugins = pluginsProp;
@@ -84,13 +87,6 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
84
87
  ...((plugins ?? []).flatMap((p) => p.slots ?? []))
85
88
  ], [directSlots, plugins]);
86
89
 
87
- const userManagement = useMemo(() => plugins?.find((p) => p.userManagement)?.userManagement
88
- ?? _userManagement
89
- ?? {
90
- loading: false,
91
- users: [],
92
- getUser: (uid: string) => null
93
- } as UserManagementDelegate<USER>, [plugins, _userManagement]);
94
90
 
95
91
  // Auth fallback logic
96
92
  const clientAuthController = useAuthSubscription(authControllerProp ? undefined : client?.auth);
@@ -159,8 +155,8 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
159
155
  entityViews: entityViews ?? [],
160
156
  entityActions: entityActions ?? [],
161
157
  propertyConfigs: propertyConfigs ?? {},
162
- components
163
- }), [dateTimeFormat, locale, entityLinkBuilder, plugins, resolvedSlots, entityViews, entityActions, propertyConfigs, components]);
158
+ components: componentsProp
159
+ }), [dateTimeFormat, locale, entityLinkBuilder, plugins, resolvedSlots, entityViews, entityActions, propertyConfigs, componentsProp]);
164
160
 
165
161
  const analyticsController = useMemo(() => ({
166
162
  onAnalyticsEvent
@@ -184,6 +180,7 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
184
180
 
185
181
  const content = (
186
182
  <RebaseI18nProvider locale={locale} translations={translations}>
183
+ <GlobalComponentOverrideProvider overrides={componentsProp}>
187
184
  <SnackbarProvider>
188
185
  <ModeControllerProvider value={modeController}>
189
186
  <AdminModeControllerProvider value={adminModeController}>
@@ -200,18 +197,18 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
200
197
  value={resolvedDatabaseAdmin}>
201
198
  <AuthControllerContext.Provider
202
199
  value={authController}>
203
- <InternalUserManagementContext.Provider value={userManagement}>
204
200
  <EffectiveRoleControllerContext.Provider value={activeEffectiveRoleController}>
205
201
  <DialogsProvider>
202
+ <SchemaDriftProvider>
206
203
  <RebaseRegistryProvider>
207
204
  <RebaseInternal
208
205
  loading={loading}>
209
206
  {children}
210
207
  </RebaseInternal>
211
208
  </RebaseRegistryProvider>
209
+ </SchemaDriftProvider>
212
210
  </DialogsProvider>
213
211
  </EffectiveRoleControllerContext.Provider>
214
- </InternalUserManagementContext.Provider>
215
212
  </AuthControllerContext.Provider>
216
213
  </DatabaseAdminContext.Provider>
217
214
  </RebaseDataContext.Provider>
@@ -223,6 +220,7 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
223
220
  </AdminModeControllerProvider>
224
221
  </ModeControllerProvider>
225
222
  </SnackbarProvider>
223
+ </GlobalComponentOverrideProvider>
226
224
  </RebaseI18nProvider>
227
225
  );
228
226
 
@@ -262,13 +260,13 @@ function RebaseInternal({
262
260
  && !authController.authLoading
263
261
  && (Boolean(authController.user) || authController.loginSkipped);
264
262
 
265
- if (authReady && plugins && plugins.length > 0) {
263
+ if (plugins && plugins.length > 0) {
266
264
  return (
267
265
  <PluginProviderStack
268
266
  plugins={plugins}
269
267
  scope="root"
270
268
  scopeProps={{ context }}>
271
- <PluginLifecycleManager plugins={plugins} context={context}/>
269
+ {authReady && <PluginLifecycleManager plugins={plugins} context={context}/>}
272
270
  {childrenResult}
273
271
  </PluginProviderStack>
274
272
  );
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Locale, User, AuthController, AnalyticsEvent, DataDriver, StorageSource, UserConfigurationPersistence, CollectionRegistryController, DatabaseAdmin, UrlController, NavigationStateController, RebaseData, RebaseClient, RebaseContext, UserManagementDelegate, EntityLinkBuilder, RebasePlugin, SlotContribution, PropertyConfig, EntityCustomView, EntityAction, RebaseTranslations } from "@rebasepro/types";
2
+ import { Locale, User, AuthController, AnalyticsEvent, DataDriver, StorageSource, UserConfigurationPersistence, CollectionRegistryController, DatabaseAdmin, UrlController, NavigationStateController, RebaseData, RebaseClient, RebaseContext, EntityLinkBuilder, RebasePlugin, SlotContribution, PropertyConfig, EntityCustomView, EntityAction, RebaseTranslations, ComponentOverrideMap } from "@rebasepro/types";
3
3
 
4
4
  /** DeepPartial helper — allows partial overrides at any nesting level */
5
5
  type DeepPartial<T> = T extends object
@@ -111,19 +111,6 @@ export type RebaseProps<USER extends User> = {
111
111
  */
112
112
  entityLinkBuilder?: EntityLinkBuilder;
113
113
 
114
- /**
115
- * You can use this props to provide your own user management implementation.
116
- * Note that this will not affect the UI, but it will be used to show user information
117
- * in various places of the CMS, for example, to show who created or modified an entity,
118
- * or to assign ownership of an entity.
119
- *
120
- * You can also use this data to be retrieved in your custom properties,
121
- * for example, to show a list of users in a dropdown.
122
- *
123
- * If you are using the Rebase user management plugin, this
124
- * prop will be implemented automatically.
125
- */
126
- userManagement?: UserManagementDelegate<USER>;
127
114
 
128
115
  /**
129
116
  * Plugins loaded in the CMS
@@ -150,17 +137,6 @@ export type RebaseProps<USER extends User> = {
150
137
  */
151
138
  entityActions?: EntityAction[];
152
139
 
153
- components?: {
154
-
155
- /**
156
- * Component to render when a reference is missing
157
- */
158
- missingReference?: React.ComponentType<{
159
- path: string,
160
- }>;
161
-
162
- };
163
-
164
140
  /**
165
141
  * Controller to simulate different roles when dev mode is active.
166
142
  */
@@ -173,5 +149,28 @@ export type RebaseProps<USER extends User> = {
173
149
  [locale: string]: DeepPartial<RebaseTranslations>;
174
150
  };
175
151
 
152
+ /**
153
+ * Override built-in UI components with custom implementations.
154
+ *
155
+ * Keys are component names from {@link OverridableComponentName}.
156
+ * Values specify the replacement component and an optional `wrap`
157
+ * flag for the wrapping pattern.
158
+ *
159
+ * @example
160
+ * ```tsx
161
+ * <Rebase
162
+ * client={client}
163
+ * components={{
164
+ * "Shell.AppBar": { Component: MyCustomAppBar },
165
+ * "Entity.FormActions": {
166
+ * Component: MyFormActions,
167
+ * wrap: true
168
+ * }
169
+ * }}
170
+ * >
171
+ * ```
172
+ */
173
+ components?: ComponentOverrideMap;
174
+
176
175
  };
177
176