@@ -14,7 +14,7 @@ import { DriverInterface, IDataEngine, Logger, createLogger } from '@objectstack
1414import { CoreServiceName } from '@objectstack/spec/system' ;
1515import { IRealtimeService , RealtimeEventPayload } from '@objectstack/spec/contracts' ;
1616import { pluralToSingular } from '@objectstack/spec/shared' ;
17- import { SchemaRegistry } from './registry.js' ;
17+ import { SchemaRegistry , computeFQN } from './registry.js' ;
1818
1919export type HookHandler = ( context : HookContext ) => Promise < void > | void ;
2020
@@ -372,12 +372,15 @@ export class ObjectQL implements IDataEngine {
372372 }
373373
374374 // 3. Register apps (UI navigation definitions) as their own metadata type
375+ // Resolve short objectName references in navigation to FQN so the
376+ // Console UI can match them against the object registry.
375377 if ( Array . isArray ( manifest . apps ) && manifest . apps . length > 0 ) {
376378 this . logger . debug ( 'Registering apps from manifest' , { id, count : manifest . apps . length } ) ;
377379 for ( const app of manifest . apps ) {
378380 const appName = app . name || app . id ;
379381 if ( appName ) {
380- SchemaRegistry . registerApp ( app , id ) ;
382+ const resolved = namespace ? this . resolveNavObjectNames ( app , namespace ) : app ;
383+ SchemaRegistry . registerApp ( resolved , id ) ;
381384 this . logger . debug ( 'Registered App' , { app : appName , from : id } ) ;
382385 }
383386 }
@@ -386,7 +389,8 @@ export class ObjectQL implements IDataEngine {
386389 // 4. If manifest itself looks like an App (has navigation), also register as app
387390 // This handles the case where the manifest IS the app definition (legacy/simple packages)
388391 if ( manifest . name && manifest . navigation && ! manifest . apps ?. length ) {
389- SchemaRegistry . registerApp ( manifest , id ) ;
392+ const resolved = namespace ? this . resolveNavObjectNames ( manifest , namespace ) : manifest ;
393+ SchemaRegistry . registerApp ( resolved , id ) ;
390394 this . logger . debug ( 'Registered manifest-as-app' , { app : manifest . name , from : id } ) ;
391395 }
392396
@@ -453,6 +457,30 @@ export class ObjectQL implements IDataEngine {
453457 }
454458 }
455459
460+ /**
461+ * Deep-clone an app definition, resolving short objectName references in
462+ * navigation items to their fully-qualified names (FQN).
463+ *
464+ * e.g. `objectName: 'lead'` in namespace 'crm' → `objectName: 'crm__lead'`
465+ */
466+ private resolveNavObjectNames ( app : any , namespace : string ) : any {
467+ if ( ! app . navigation ) return app ;
468+
469+ const resolveItems = ( items : any [ ] ) : any [ ] =>
470+ items . map ( ( item : any ) => {
471+ const resolved = { ...item } ;
472+ if ( resolved . objectName && ! resolved . objectName . includes ( '__' ) ) {
473+ resolved . objectName = computeFQN ( namespace , resolved . objectName ) ;
474+ }
475+ if ( Array . isArray ( resolved . children ) ) {
476+ resolved . children = resolveItems ( resolved . children ) ;
477+ }
478+ return resolved ;
479+ } ) ;
480+
481+ return { ...app , navigation : resolveItems ( app . navigation ) } ;
482+ }
483+
456484 /**
457485 * Register a nested plugin's metadata (objects, actions, views, etc.)
458486 *
@@ -499,7 +527,8 @@ export class ObjectQL implements IDataEngine {
499527 // Register plugin as app if it has navigation (for sidebar display)
500528 if ( plugin . name && plugin . navigation ) {
501529 try {
502- SchemaRegistry . registerApp ( plugin , ownerId ) ;
530+ const resolved = pluginNamespace ? this . resolveNavObjectNames ( plugin , pluginNamespace ) : plugin ;
531+ SchemaRegistry . registerApp ( resolved , ownerId ) ;
503532 this . logger . debug ( 'Registered plugin-as-app' , { app : plugin . name , from : pluginName } ) ;
504533 } catch ( err : any ) {
505534 this . logger . warn ( 'Failed to register plugin as app' , { pluginName, error : err . message } ) ;
0 commit comments