@@ -18,6 +18,7 @@ type ModelCategory = 'userland' | 'data' | 'commerce' | 'content' | 'marketing'
1818 * full type imports aren't available. Keep in sync with the source.
1919 */
2020interface DashboardModelOptionsLite {
21+ highlight ?: boolean | number
2122 access ?: boolean
2223 enabled ?: boolean
2324 label ?: string
@@ -102,6 +103,9 @@ export const DEFAULT_TOGGLES: DashboardSectionToggles = {
102103}
103104
104105interface NavItem {
106+ id ?: string
107+ children ?: NavItem [ ]
108+ expanded ?: boolean
105109 to : string
106110 icon : string
107111 text : string
@@ -413,37 +417,50 @@ export function buildNavSections(
413417 ...categoryNavItems ( discoveredModels , 'system' , new Set ( ) ) ,
414418 ] ] )
415419
416- // Data section: configurable basic rows + ALL userland models + any
417- // root-level framework `data` models. Per-row toggles let projects hide
418- // the basic rows they don't need; userland models always appear.
420+ // Data pins model-declared highlights. Other visible models stay accessible
421+ // through a collapsed disclosure using the shared Sidebar component.
419422 // Per-model rows link to `/models/<id>` (dynamic ORM viewer); the
420423 // built-in dashboard / activity / users / teams / subscribers rows keep
421424 // their bespoke pages under `/data/<row>`.
422425 const dataRows = toggles . data ?? DEFAULT_DATA_TOGGLES
426+ const dedicatedDataRows : Record < string , keyof DataRowToggles > = { user : 'users' , team : 'teams' , subscriber : 'subscribers' , activity : 'activity' }
423427 const dataNav : NavItem [ ] = [ ]
424428 if ( dataRows . dashboard ) dataNav . push ( { to : '/data/dashboard' , icon : 'dashboard' , text : 'Dashboard' } )
425429 if ( dataRows . activity ) dataNav . push ( { to : '/data/activity' , icon : 'activity' , text : 'Activity' } )
426430 if ( dataRows . users ) dataNav . push ( { to : '/data/users' , icon : 'users' , text : 'Users' } )
427431 if ( dataRows . teams ) dataNav . push ( { to : '/data/teams' , icon : 'group' , text : 'Teams' } )
428432 if ( dataRows . subscribers ) dataNav . push ( { to : '/data/subscribers' , icon : 'mail' , text : 'Subscribers' } )
429433 if ( dataRows . allModels ) dataNav . push ( { to : '/models' , icon : 'table' , text : 'All Models' } )
430- for ( const m of discoveredModels ) {
434+ const moreModels : NavItem [ ] = [ ]
435+ const highlighted = ( model : DiscoveredModel ) => model . dashboard ?. highlight === true
436+ || ( typeof model . dashboard ?. highlight === 'number' && Number . isFinite ( model . dashboard . highlight ) )
437+ const rank = ( model : DiscoveredModel ) => typeof model . dashboard ?. highlight === 'number' ? model . dashboard . highlight : Number . POSITIVE_INFINITY
438+ const dataModels = [ ...discoveredModels ] . sort ( ( a , b ) => {
439+ if ( highlighted ( a ) !== highlighted ( b ) ) return highlighted ( a ) ? - 1 : 1
440+ if ( highlighted ( a ) ) return rank ( a ) - rank ( b ) || a . name . localeCompare ( b . name )
441+ return a . name . localeCompare ( b . name )
442+ } )
443+ for ( const m of dataModels ) {
431444 // Skip when:
432445 // - explicit `dashboard.enabled === false` (effectiveCategory returns undefined)
433446 // - pinned to a different section via `dashboard.section`
434447 // - already has a dedicated dashboard page
435448 const eff = effectiveCategory ( m )
436449 if ( eff !== 'userland' && eff !== 'data' ) continue
437- if ( m . hasDedicatedPage ) continue
438- dataNav . push ( {
450+ const dedicatedRow = dedicatedDataRows [ m . id ]
451+ if ( m . hasDedicatedPage && ( ! dedicatedRow || dataRows [ dedicatedRow ] ) ) continue
452+ const item : NavItem = {
439453 to : `/models/${ m . id } ` ,
440454 icon : m . dashboard ?. icon ?? m . icon ?? 'table' ,
441455 text : m . dashboard ?. label ?? m . name ,
442456 ...( m . dashboard ?. roles && m . dashboard . roles . length > 0
443457 ? { roles : m . dashboard . roles }
444458 : { } ) ,
445- } )
459+ }
460+ if ( highlighted ( m ) ) dataNav . push ( item )
461+ else moreModels . push ( item )
446462 }
463+ if ( moreModels . length ) dataNav . push ( { id : 'models-more' , to : '' , text : 'More models' , icon : 'table' , expanded : false , children : moreModels } )
447464 sections . push ( [ 'data' , 'data' , dataNav ] )
448465
449466 if ( toggles . commerce ) {
@@ -650,7 +667,9 @@ export interface WebSidebarItem {
650667 icon : string
651668 /** macOS system color name or CSS color for the icon tint. */
652669 iconColor : string
653- href : string
670+ href ?: string
671+ children ?: WebSidebarItem [ ]
672+ expanded ?: boolean
654673 roles ?: string [ ]
655674}
656675
@@ -726,6 +745,15 @@ export function buildWebSidebarSections(): WebSidebarSection[] {
726745 const appNav = manifest . nav . length > 0 ? manifest . nav : loadAppNavFromConfig ( )
727746 const sections = filterSchemaNavSections ( buildNavSections ( manifest . models , manifest . sections , appNav ) , manifest . schemaModels )
728747
748+ const webItem = ( item : NavItem ) : WebSidebarItem => ( {
749+ id : item . id ?? navItemId ( item . to ) ,
750+ label : item . text ,
751+ icon : resolveNavIcon ( item . icon ) ,
752+ iconColor : 'blue' ,
753+ ...( item . to ? { href : item . to } : { } ) ,
754+ ...( item . roles ?. length ? { roles : item . roles } : { } ) ,
755+ ...( item . children ? { children : item . children . map ( webItem ) , expanded : item . expanded ?? false } : { } ) ,
756+ } )
729757 return [
730758 {
731759 id : 'top' ,
@@ -735,14 +763,20 @@ export function buildWebSidebarSections(): WebSidebarSection[] {
735763 ...sections . map ( ( [ key , label , items ] ) => ( {
736764 id : key ,
737765 label : titleCase ( label ) ,
738- items : items . map ( item => ( {
739- id : navItemId ( item . to ) ,
740- label : item . text ,
741- icon : resolveNavIcon ( item . icon ) ,
742- iconColor : 'blue' ,
743- href : item . to ,
744- ...( item . roles && item . roles . length > 0 ? { roles : item . roles } : { } ) ,
745- } ) ) ,
766+ items : items . map ( webItem ) ,
746767 } ) ) ,
747768 ]
748769}
770+
771+ /** Role metadata follows nested model rows as well as pinned rows. */
772+ export function webSidebarRoleMap ( sections : WebSidebarSection [ ] ) : Record < string , string [ ] > {
773+ const roles : Record < string , string [ ] > = Object . create ( null )
774+ const visit = ( items : WebSidebarItem [ ] ) => {
775+ for ( const item of items ) {
776+ if ( item . roles ?. length ) roles [ item . id ] = item . roles
777+ if ( item . children ) visit ( item . children )
778+ }
779+ }
780+ for ( const section of sections ) visit ( section . items )
781+ return roles
782+ }
0 commit comments