@@ -34,12 +34,13 @@ export function AppSidebar({ objects, appMetadata, ...props }: React.ComponentPr
3434 const currentApp = parts [ 1 ] === 'app' ? parts [ 2 ] : null ;
3535 const getObjectPath = ( name : string ) => currentApp ? `/app/${ currentApp } /object/${ name } ` : `/object/${ name } ` ;
3636
37- const rawMenu = appMetadata ?. content ?. menu ;
37+ const rawMenu = appMetadata ?. menu ;
3838
39- // Normalize menu structure to always be a list of sections (groups)
40- // If the first item has a 'type' or doesn't look like a section container, wrap it in a default section.
41- const isGrouped = Array . isArray ( rawMenu ) && rawMenu . length > 0 &&
42- ( rawMenu [ 0 ] . items && ! rawMenu [ 0 ] . type ) ;
39+ // Robust check for grouped vs flat menu structure
40+ // A section has 'items' but NO 'type', 'object', or 'url'
41+ const isSection = ( item : any ) => item && item . items && Array . isArray ( item . items ) && ! item . type && ! item . object && ! item . url ;
42+
43+ const isGrouped = Array . isArray ( rawMenu ) && rawMenu . length > 0 && isSection ( rawMenu [ 0 ] ) ;
4344
4445 const menuSections = rawMenu ? ( isGrouped ? rawMenu : [ { label : 'Menu' , items : rawMenu } ] ) : [ ] ;
4546
@@ -51,48 +52,53 @@ export function AppSidebar({ objects, appMetadata, ...props }: React.ComponentPr
5152 return < div key = { idx } className = "my-2 h-px bg-border" /> ;
5253 }
5354
55+ // Default label if missing (e.g. for dividers context or malformed data)
56+ const label = item . label || '' ;
57+ const itemType = item . type || 'page' ;
58+
5459 // Determine active state
5560 let isActive = false ;
56- if ( item . type === 'object' ) {
61+ if ( itemType === 'object' ) {
5762 isActive = path . includes ( `/object/${ item . object } ` ) ;
58- } else if ( item . type === 'page' || item . type === 'url' ) {
63+ } else if ( itemType === 'page' || itemType === 'url' ) {
5964 isActive = item . url ? path . endsWith ( item . url ) : false ;
6065 }
6166
6267 const handleClick = ( ) => {
63- if ( item . type === 'object' && item . object ) {
68+ if ( itemType === 'object' && item . object ) {
6469 navigate ( getObjectPath ( item . object ) ) ;
65- } else if ( item . type === 'page' && item . url ) {
70+ } else if ( itemType === 'page' && item . url ) {
6671 navigate ( item . url ) ;
67- } else if ( item . type === 'url' && item . url ) {
72+ } else if ( itemType === 'url' && item . url ) {
6873 window . open ( item . url , '_blank' ) ;
6974 }
7075 } ;
7176
7277 // Handle Nested Items (Submenus)
7378 if ( item . items && item . items . length > 0 ) {
7479 return (
75- < Collapsible key = { idx } asChild defaultOpen = { item . collapsed === false } className = "group/collapsible" >
80+ < Collapsible key = { idx } asChild defaultOpen = { false } className = "group/collapsible" >
7681 < SidebarMenuItem >
7782 < CollapsibleTrigger asChild >
78- < SidebarMenuButton tooltip = { item . label } >
79- < DynamicIcon name = { item . icon } fallback = { item . type === 'object' ? LucideIcons . FileText : LucideIcons . Layout } />
80- < span > { item . label } </ span >
83+ < SidebarMenuButton tooltip = { label } >
84+ < DynamicIcon name = { item . icon } fallback = { itemType === 'object' ? LucideIcons . FileText : LucideIcons . Layout } />
85+ < span > { label } </ span >
8186 < LucideIcons . ChevronRight className = "ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
8287 </ SidebarMenuButton >
8388 </ CollapsibleTrigger >
8489 < CollapsibleContent >
8590 < SidebarMenuSub >
8691 { item . items . map ( ( subItem : any , subIdx : number ) => {
8792 if ( subItem . visible === false ) return null ;
93+ const subItemType = subItem . type || 'page' ;
8894 return (
8995 < SidebarMenuSubItem key = { subIdx } >
9096 < SidebarMenuSubButton
91- isActive = { subItem . type === 'object' ? path . includes ( `/object/${ subItem . object } ` ) : path . endsWith ( subItem . url ) }
97+ isActive = { subItemType === 'object' ? path . includes ( `/object/${ subItem . object } ` ) : path . endsWith ( subItem . url ) }
9298 onClick = { ( ) => {
93- if ( subItem . type === 'object' ) navigate ( getObjectPath ( subItem . object ) ) ;
94- else if ( subItem . type === 'page' ) navigate ( subItem . url ) ;
95- else if ( subItem . type === 'url' ) window . open ( subItem . url , '_blank' ) ;
99+ if ( subItemType === 'object' ) navigate ( getObjectPath ( subItem . object ) ) ;
100+ else if ( subItemType === 'page' ) navigate ( subItem . url ) ;
101+ else if ( subItemType === 'url' ) window . open ( subItem . url , '_blank' ) ;
96102 } }
97103 >
98104 < span > { subItem . label } </ span >
@@ -114,8 +120,8 @@ export function AppSidebar({ objects, appMetadata, ...props }: React.ComponentPr
114120 isActive = { isActive }
115121 onClick = { handleClick }
116122 >
117- < DynamicIcon name = { item . icon } fallback = { item . type === 'object' ? LucideIcons . FileText : LucideIcons . Layout } />
118- < span > { item . label } </ span >
123+ < DynamicIcon name = { item . icon } fallback = { itemType === 'object' ? LucideIcons . FileText : LucideIcons . Layout } />
124+ < span > { label } </ span >
119125 { item . badge && < SidebarMenuBadge > { item . badge } </ SidebarMenuBadge > }
120126 </ SidebarMenuButton >
121127 </ SidebarMenuItem >
@@ -132,16 +138,15 @@ export function AppSidebar({ objects, appMetadata, ...props }: React.ComponentPr
132138 < LucideIcons . Database className = "size-4" />
133139 </ div >
134140 < div className = "grid flex-1 text-left text-sm leading-tight" >
135- < span className = "truncate font-semibold" > { appMetadata ?. content ?. name || 'ObjectQL' } </ span >
141+ < span className = "truncate font-semibold" > { appMetadata ?. name || 'ObjectQL' } </ span >
136142 < span className = "truncate text-xs" > Data Browser</ span >
137143 </ div >
138144 </ SidebarMenuButton >
139145 </ SidebarMenuItem >
140146 </ SidebarMenu >
141147 </ SidebarHeader >
142148 < SidebarContent >
143- { menuSections . length > 0 ? (
144- menuSections . map ( ( section : any , idx : number ) => {
149+ { menuSections . map ( ( section : any , idx : number ) => {
145150 const isCollapsible = section . collapsible === true ;
146151 const isCollapsed = section . collapsed === true ;
147152
@@ -184,28 +189,7 @@ export function AppSidebar({ objects, appMetadata, ...props }: React.ComponentPr
184189 </ SidebarGroupContent >
185190 </ SidebarGroup >
186191 ) ;
187- } )
188- ) : (
189- // Render Default Object List
190- < SidebarGroup >
191- < SidebarGroupLabel > Collections</ SidebarGroupLabel >
192- < SidebarGroupContent >
193- < SidebarMenu >
194- { Object . entries ( objects ) . map ( ( [ name , schema ] ) => (
195- < SidebarMenuItem key = { name } >
196- < SidebarMenuButton
197- isActive = { path . includes ( `/object/${ name } ` ) }
198- onClick = { ( ) => navigate ( getObjectPath ( name ) ) }
199- >
200- < LucideIcons . FileText />
201- < span > { schema . label || schema . title || name } </ span >
202- </ SidebarMenuButton >
203- </ SidebarMenuItem >
204- ) ) }
205- </ SidebarMenu >
206- </ SidebarGroupContent >
207- </ SidebarGroup >
208- ) }
192+ } ) }
209193 </ SidebarContent >
210194 < SidebarFooter >
211195 < NavUser user = { {
0 commit comments