66 * the object field definitions.
77 */
88
9- import { useState , useEffect , useCallback } from 'react' ;
9+ import { useState , useEffect , useCallback , useMemo } from 'react' ;
1010import { useParams , useNavigate } from 'react-router-dom' ;
1111import { DetailView , RecordChatterPanel } from '@object-ui/plugin-detail' ;
1212import { Empty , EmptyTitle , EmptyDescription } from '@object-ui/components' ;
@@ -19,7 +19,7 @@ import { MetadataPanel, useMetadataInspector } from './MetadataInspector';
1919import { SkeletonDetail } from './skeletons' ;
2020import { ActionConfirmDialog , type ConfirmDialogState } from './ActionConfirmDialog' ;
2121import { ActionParamDialog , type ParamDialogState } from './ActionParamDialog' ;
22- import type { DetailViewSchema , FeedItem } from '@object-ui/types' ;
22+ import type { DetailViewSchema , FeedItem , HighlightField , SectionGroup } from '@object-ui/types' ;
2323import type { ActionDef , ActionParamDef } from '@object-ui/core' ;
2424
2525interface RecordDetailViewProps {
@@ -30,6 +30,9 @@ interface RecordDetailViewProps {
3030
3131const FALLBACK_USER = { id : 'current-user' , name : 'Demo User' } ;
3232
33+ /** Field names automatically promoted to the highlight banner when present. */
34+ const HIGHLIGHT_FIELD_NAMES = [ 'status' , 'stage' , 'priority' , 'category' , 'type' , 'owner' , 'amount' ] ;
35+
3336export function RecordDetailView ( { dataSource, objects, onEdit } : RecordDetailViewProps ) {
3437 const { objectName, recordId } = useParams ( ) ;
3538 const { showDebug } = useMetadataInspector ( ) ;
@@ -39,6 +42,7 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
3942 const [ feedItems , setFeedItems ] = useState < FeedItem [ ] > ( [ ] ) ;
4043 const [ recordViewers , setRecordViewers ] = useState < PresenceUser [ ] > ( [ ] ) ;
4144 const [ actionRefreshKey , setActionRefreshKey ] = useState ( 0 ) ;
45+ const [ childRelatedData , setChildRelatedData ] = useState < Record < string , any [ ] > > ( { } ) ;
4246 const objectDef = objects . find ( ( o : any ) => o . name === objectName ) ;
4347
4448 // Use the URL recordId as-is — it contains the actual record _id.
@@ -113,6 +117,59 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
113117 }
114118 } , [ dataSource , objectName , pureRecordId ] ) ;
115119
120+ // Discover reverse references: other objects with lookup/master_detail fields
121+ // pointing to the current object (e.g., order_item.order → order).
122+ const childRelations = useMemo ( ( ) => {
123+ if ( ! objectDef || ! objects ) return [ ] ;
124+ const relations : Array < { childObject : string ; childLabel : string ; referenceField : string } > = [ ] ;
125+ for ( const obj of objects ) {
126+ if ( obj . name === objectDef . name ) continue ;
127+ for ( const [ fieldName , fieldDef ] of Object . entries < any > ( obj . fields || { } ) ) {
128+ if (
129+ fieldDef &&
130+ ( fieldDef . type === 'lookup' || fieldDef . type === 'master_detail' ) &&
131+ ( fieldDef . reference_to || fieldDef . reference ) === objectDef . name
132+ ) {
133+ relations . push ( {
134+ childObject : obj . name ,
135+ childLabel : obj . label || obj . name ,
136+ referenceField : fieldName ,
137+ } ) ;
138+ }
139+ }
140+ }
141+ return relations ;
142+ } , [ objectDef , objects ] ) ;
143+
144+ // Fetch related child records for each reverse reference
145+ useEffect ( ( ) => {
146+ if ( ! dataSource || ! pureRecordId || childRelations . length === 0 ) return ;
147+ let cancelled = false ;
148+ Promise . all (
149+ childRelations . map ( ( { childObject, referenceField } ) =>
150+ dataSource . find ( childObject , {
151+ $filter : { [ referenceField ] : pureRecordId } ,
152+ } )
153+ . then ( ( res : any ) => {
154+ const items = Array . isArray ( res ) ? res : res ?. data || [ ] ;
155+ return { childObject, items } ;
156+ } )
157+ . catch ( ( err : any ) => {
158+ console . warn ( `[RecordDetailView] Failed to fetch related ${ childObject } :` , err ) ;
159+ return { childObject, items : [ ] as any [ ] } ;
160+ } )
161+ )
162+ ) . then ( ( results ) => {
163+ if ( cancelled ) return ;
164+ const data : Record < string , any [ ] > = { } ;
165+ for ( const { childObject, items } of results ) {
166+ data [ childObject ] = items ;
167+ }
168+ setChildRelatedData ( data ) ;
169+ } ) ;
170+ return ( ) => { cancelled = true ; } ;
171+ } , [ dataSource , pureRecordId , childRelations ] ) ;
172+
116173 const currentUser = user
117174 ? { id : user . id , name : user . name , avatar : user . image }
118175 : FALLBACK_USER ;
@@ -297,12 +354,13 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
297354 console . warn ( `[RecordDetailView] Field "${ fieldName } " not found in ${ objectDef . name } definition` ) ;
298355 return { name : fieldName , label : fieldName } ;
299356 }
357+ const refTarget = fieldDef . reference_to || fieldDef . reference ;
300358 return {
301359 name : fieldName ,
302360 label : fieldDef . label || fieldName ,
303361 type : fieldDef . type || 'text' ,
304362 ...( fieldDef . options && { options : fieldDef . options } ) ,
305- ...( fieldDef . reference_to && { reference_to : fieldDef . reference_to } ) ,
363+ ...( refTarget && { reference_to : refTarget } ) ,
306364 ...( fieldDef . reference_field && { reference_field : fieldDef . reference_field } ) ,
307365 ...( fieldDef . currency && { currency : fieldDef . currency } ) ,
308366 } ;
@@ -313,12 +371,13 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
313371 title : 'Details' ,
314372 fields : Object . keys ( objectDef . fields || { } ) . map ( key => {
315373 const fieldDef = objectDef . fields [ key ] ;
374+ const refTarget = fieldDef . reference_to || fieldDef . reference ;
316375 return {
317376 name : key ,
318377 label : fieldDef . label || key ,
319378 type : fieldDef . type || 'text' ,
320379 ...( fieldDef . options && { options : fieldDef . options } ) ,
321- ...( fieldDef . reference_to && { reference_to : fieldDef . reference_to } ) ,
380+ ...( refTarget && { reference_to : refTarget } ) ,
322381 ...( fieldDef . reference_field && { reference_field : fieldDef . reference_field } ) ,
323382 ...( fieldDef . currency && { currency : fieldDef . currency } ) ,
324383 } ;
@@ -331,6 +390,28 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
331390 ( a : any ) => a . locations ?. includes ( 'record_header' ) ,
332391 ) ;
333392
393+ // Build highlightFields: prefer explicit config, fallback to auto-detect key fields
394+ const explicitHighlight : HighlightField [ ] | undefined = objectDef . views ?. detail ?. highlightFields ;
395+ const highlightFields : HighlightField [ ] = explicitHighlight
396+ ?? Object . entries ( objectDef . fields || { } )
397+ . filter ( ( [ key ] : [ string , any ] ) => HIGHLIGHT_FIELD_NAMES . includes ( key ) )
398+ . map ( ( [ key , def ] : [ string , any ] ) => ( {
399+ name : key ,
400+ label : def . label || key ,
401+ ...( def . type && { type : def . type } ) ,
402+ } ) ) ;
403+
404+ // Build sectionGroups from objectDef detail/form config if available
405+ const sectionGroups : SectionGroup [ ] | undefined =
406+ objectDef . views ?. detail ?. sectionGroups ?? objectDef . views ?. form ?. sectionGroups ;
407+
408+ // Build related entries from reverse-reference child objects
409+ const related = childRelations . map ( ( { childObject, childLabel } ) => ( {
410+ title : childLabel ,
411+ type : 'table' as const ,
412+ data : childRelatedData [ childObject ] || [ ] ,
413+ } ) ) ;
414+
334415 const detailSchema : DetailViewSchema = {
335416 type : 'detail-view' ,
336417 objectName : objectDef . name ,
@@ -341,6 +422,11 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
341422 title : objectDef . label ,
342423 primaryField,
343424 sections,
425+ autoTabs : true ,
426+ autoDiscoverRelated : true ,
427+ ...( related . length > 0 && { related } ) ,
428+ ...( highlightFields . length > 0 && { highlightFields } ) ,
429+ ...( sectionGroups && sectionGroups . length > 0 && { sectionGroups } ) ,
344430 ...( recordHeaderActions . length > 0 && {
345431 actions : [ {
346432 type : 'action:bar' ,
0 commit comments