11import { BrowserRouter , Routes , Route , Navigate , useNavigate , useLocation , useSearchParams } from 'react-router-dom' ;
2- import { useState , useEffect , lazy , Suspense , useMemo } from 'react' ;
2+ import { useState , useEffect , lazy , Suspense , useMemo , type ReactNode } from 'react' ;
33import { ObjectForm } from '@object-ui/plugin-form' ;
44import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , Empty , EmptyTitle } from '@object-ui/components' ;
55import { toast } from 'sonner' ;
66import { SchemaRendererProvider } from '@object-ui/react' ;
7- import { ObjectStackAdapter } from './dataSource' ;
87import type { ConnectionState } from './dataSource' ;
9- import appConfig from '../objectstack.shared' ;
108import { AuthGuard , useAuth , PreviewBanner } from '@object-ui/auth' ;
9+ import { MetadataProvider , useMetadata } from './context/MetadataProvider' ;
10+ import { AdapterProvider , useAdapter } from './context/AdapterProvider' ;
1111
1212// Components (eagerly loaded — always needed)
1313import { ConsoleLayout } from './components/ConsoleLayout' ;
@@ -46,17 +46,42 @@ import { useParams } from 'react-router-dom';
4646import { ThemeProvider } from './components/theme-provider' ;
4747import { ConsoleToaster } from './components/ConsoleToaster' ;
4848
49+ /**
50+ * ConnectedShell
51+ *
52+ * Creates the ObjectStackAdapter (via AdapterProvider), waits for connection,
53+ * then wraps children in MetadataProvider for API-driven metadata.
54+ */
55+ function ConnectedShell ( { children } : { children : ReactNode } ) {
56+ return (
57+ < AdapterProvider >
58+ < ConnectedShellInner > { children } </ ConnectedShellInner >
59+ </ AdapterProvider >
60+ ) ;
61+ }
62+
63+ function ConnectedShellInner ( { children } : { children : ReactNode } ) {
64+ const adapter = useAdapter ( ) ;
65+ if ( ! adapter ) return < LoadingScreen /> ;
66+
67+ return (
68+ < MetadataProvider adapter = { adapter } >
69+ { children }
70+ </ MetadataProvider >
71+ ) ;
72+ }
73+
4974export function AppContent ( ) {
50- const [ dataSource , setDataSource ] = useState < ObjectStackAdapter | null > ( null ) ;
5175 const [ connectionState , setConnectionState ] = useState < ConnectionState > ( 'disconnected' ) ;
5276 const { user } = useAuth ( ) ;
77+ const dataSource = useAdapter ( ) ;
5378
5479 // App Selection
5580 const navigate = useNavigate ( ) ;
5681 const location = useLocation ( ) ;
5782 const [ , setSearchParams ] = useSearchParams ( ) ;
5883 const { appName } = useParams ( ) ;
59- const apps = appConfig . apps || [ ] ;
84+ const { apps, objects : allObjects , loading : metadataLoading } = useMetadata ( ) ;
6085
6186 // Determine active app based on URL
6287 const activeApps = apps . filter ( ( a : any ) => a . active !== false ) ;
@@ -70,48 +95,19 @@ export function AppContent() {
7095 // Branding is now applied by AppShell via ConsoleLayout
7196
7297 useEffect ( ( ) => {
73- let cancelled = false ;
74-
75- async function initializeDataSource ( ) {
76- try {
77- const adapter = new ObjectStackAdapter ( {
78- baseUrl : '' ,
79- autoReconnect : true ,
80- maxReconnectAttempts : 5 ,
81- reconnectDelay : 1000 ,
82- cache : { maxSize : 50 , ttl : 300_000 } ,
83- } ) ;
84-
85- // Monitor connection state
86- adapter . onConnectionStateChange ( ( event ) => {
87- if ( cancelled ) return ;
88- setConnectionState ( event . state ) ;
89- if ( event . error ) {
90- console . error ( '[Console] Connection error:' , event . error ) ;
91- }
92- } ) ;
93-
94- await adapter . connect ( ) ;
95-
96- if ( ! cancelled ) {
97- setDataSource ( adapter ) ;
98- }
99- } catch ( err ) {
100- if ( ! cancelled ) {
101- console . error ( '[Console] Failed to initialize:' , err ) ;
102- setConnectionState ( 'error' ) ;
103- }
98+ if ( ! dataSource ) return ;
99+ const unsub = dataSource . onConnectionStateChange ( ( event ) => {
100+ setConnectionState ( event . state ) ;
101+ if ( event . error ) {
102+ console . error ( '[Console] Connection error:' , event . error ) ;
104103 }
105- }
106-
107- initializeDataSource ( ) ;
104+ } ) ;
105+ // Sync current state
106+ setConnectionState ( dataSource . getConnectionState ( ) ) ;
107+ return unsub ;
108+ } , [ dataSource ] ) ;
108109
109- return ( ) => {
110- cancelled = true ;
111- } ;
112- } , [ ] ) ;
113-
114- const allObjects = appConfig . objects || [ ] ;
110+ // allObjects already derived from useMetadata() above
115111
116112 // Find current object for Dialog
117113 // Path is now relative to /apps/:appName/
@@ -140,7 +136,7 @@ export function AppContent() {
140136 objName = '' ;
141137 }
142138 const basePath = `/apps/${ activeApp . name } ` ;
143- const objects = appConfig . objects || [ ] ;
139+ const objects = allObjects ;
144140 if ( objName ) {
145141 const obj = objects . find ( ( o : any ) => o . name === objName ) ;
146142 if ( obj ) {
@@ -199,7 +195,7 @@ export function AppContent() {
199195 [ user , activeApp , editingRecord ]
200196 ) ;
201197
202- if ( ! dataSource ) return < LoadingScreen /> ;
198+ if ( ! dataSource || metadataLoading ) return < LoadingScreen /> ;
203199 if ( ! activeApp ) return (
204200 < div className = "h-screen flex items-center justify-center" >
205201 < Empty >
@@ -372,10 +368,11 @@ function findFirstRoute(items: any[]): string {
372368
373369// Redirect root to default app
374370function RootRedirect ( ) {
375- const apps = appConfig . apps || [ ] ;
371+ const { apps, loading } = useMetadata ( ) ;
376372 const activeApps = apps . filter ( ( a : any ) => a . active !== false ) ;
377373 const defaultApp = activeApps . find ( ( a : any ) => a . isDefault === true ) || activeApps [ 0 ] ;
378374
375+ if ( loading ) return < LoadingScreen /> ;
379376 if ( defaultApp ) {
380377 return < Navigate to = { `/apps/${ defaultApp . name } ` } replace /> ;
381378 }
@@ -396,10 +393,16 @@ export function App() {
396393 < Route path = "/forgot-password" element = { < ForgotPasswordPage /> } />
397394 < Route path = "/apps/:appName/*" element = {
398395 < AuthGuard fallback = { < Navigate to = "/login" /> } loadingFallback = { < LoadingScreen /> } >
399- < AppContent />
396+ < ConnectedShell >
397+ < AppContent />
398+ </ ConnectedShell >
400399 </ AuthGuard >
401400 } />
402- < Route path = "/" element = { < RootRedirect /> } />
401+ < Route path = "/" element = {
402+ < ConnectedShell >
403+ < RootRedirect />
404+ </ ConnectedShell >
405+ } />
403406 </ Routes >
404407 </ Suspense >
405408 </ BrowserRouter >
0 commit comments