@@ -18,6 +18,10 @@ const APPEARANCE_BOOTSTRAP_PATH = path.join(
1818) ;
1919const SETTINGS_REGISTRY = path . join ( REPO_ROOT , 'src/web-ui/src/app/scenes/settings/settingsRegistry.ts' ) ;
2020const SETTINGS_TYPES = path . join ( REPO_ROOT , 'src/web-ui/src/app/scenes/settings/settingsTypes.ts' ) ;
21+ const SETTINGS_MIGRATIONS = JSON . parse ( readFileSync (
22+ path . join ( REPO_ROOT , 'src/web-ui/src/app/scenes/settings/settingsDestinationMigrations.json' ) ,
23+ 'utf8' ,
24+ ) ) ;
2125const PRODUCT_ACTION_CATALOG = path . join ( REPO_ROOT , 'src/web-ui/src/app/global-search/productActionCatalog.ts' ) ;
2226const SCENE_TYPES = path . join ( REPO_ROOT , 'src/web-ui/src/app/components/SceneBar/types.ts' ) ;
2327const TOOL_PROVIDER_GROUPS = path . join (
@@ -381,6 +385,23 @@ function controlCoverage(source) {
381385 return coverage ;
382386}
383387
388+ // Keep historical destinations on the wire for older presentation surfaces.
389+ // Validate coverage against the same migration data used by the current Web UI.
390+ export function projectSettingsCatalogDestination ( capabilityId , destination , itemId ) {
391+ if ( destination . kind !== 'settings' ) return destination ;
392+ const capability = SETTINGS_MIGRATIONS . capabilities [ capabilityId ] ;
393+ const input = capability ?. items ?. [ itemId ] ?? capability ?. destination ?? destination ;
394+ const migratedView = SETTINGS_MIGRATIONS . views [ input . pageId ] ?. [ input . viewId ] ;
395+ const migrated = migratedView ?? SETTINGS_MIGRATIONS . pages [ input . pageId ] ;
396+ const sectionId = input . sectionId ?? migrated ?. sectionId ;
397+ return {
398+ kind : 'settings' ,
399+ pageId : migrated ?. pageId ?? input . pageId ,
400+ ...( sectionId ? { sectionId } : { } ) ,
401+ ...( ! migratedView && input . viewId ? { viewId : input . viewId } : { } ) ,
402+ } ;
403+ }
404+
384405function validateSource ( source ) {
385406 if ( source . schemaVersion !== 5 ) throw new Error ( 'Interactive capability source must use schemaVersion 5' ) ;
386407 const capabilityIds = new Set ( ) ;
@@ -389,21 +410,27 @@ function validateSource(source) {
389410 [ ...settingsSource . matchAll ( / d e f i n e P a g e \( \{ \s * i d : \s * ' ( [ ^ ' ] + ) ' / gu) ] . map ( ( match ) => match [ 1 ] ) ,
390411 ) ;
391412 const settingsViewIds = extractTypeUnionIds ( readFileSync ( SETTINGS_TYPES , 'utf8' ) , 'SettingsViewId' ) ;
413+ const settingsSectionIds = extractTypeUnionIds ( readFileSync ( SETTINGS_TYPES , 'utf8' ) , 'SettingsSectionId' ) ;
392414 const actionIds = extractSingleQuotedIds ( readFileSync ( PRODUCT_ACTION_CATALOG , 'utf8' ) ) ;
393415 const sceneIds = new Set ( [
394416 ...readFileSync ( SCENE_TYPES , 'utf8' ) . matchAll ( / ^ \s * \| \s * ' ( [ ^ ' ] + ) ' / gmu) ,
395417 ] . map ( ( match ) => match [ 1 ] ) ) ;
396418 const productToolNames = extractProductToolNames ( readFileSync ( TOOL_PROVIDER_GROUPS , 'utf8' ) ) ;
397419 const delegatedToolNames = new Set ( ) ;
398420
399- const validateDestination = ( destination , owner ) => {
421+ const validateDestination = ( original , capabilityId , itemId ) => {
422+ const owner = itemId ? `${ capabilityId } .${ itemId } ` : capabilityId ;
423+ const destination = projectSettingsCatalogDestination ( capabilityId , original , itemId ) ;
400424 if ( destination . kind === 'settings' ) {
401425 if ( ! settingsIds . has ( destination . pageId ) ) {
402426 throw new Error ( `${ owner } targets unknown settings page ${ destination . pageId } ` ) ;
403427 }
404428 if ( destination . viewId && ! settingsViewIds . has ( destination . viewId ) ) {
405429 throw new Error ( `${ owner } targets unknown settings view ${ destination . viewId } ` ) ;
406430 }
431+ if ( destination . sectionId && ! settingsSectionIds . has ( destination . sectionId ) ) {
432+ throw new Error ( `${ owner } targets unknown settings section ${ destination . sectionId } ` ) ;
433+ }
407434 return ;
408435 }
409436 if ( destination . kind === 'action' && ! actionIds . has ( destination . actionId ) ) {
@@ -470,7 +497,7 @@ function validateSource(source) {
470497 itemIds . add ( item . id ) ;
471498 assertBilingual ( { ...item , id : `${ capability . id } .${ item . id } ` } , 'title' ) ;
472499 if ( item . destination ) {
473- validateDestination ( item . destination , ` ${ capability . id } . ${ item . id } ` ) ;
500+ validateDestination ( item . destination , capability . id , item . id ) ;
474501 if (
475502 item . destination . kind === 'settings'
476503 && capability . destination . kind === 'settings'
@@ -691,9 +718,16 @@ function validateSource(source) {
691718 throw new Error ( 'User-facing capabilities may not silently ship as unsupported Agent controls' ) ;
692719 }
693720
694- const semanticSettings = new Set ( source . capabilities
721+ const projectedSettingsDestinations = source . capabilities
695722 . filter ( ( { kind } ) => kind === 'setting' )
696- . map ( ( { destination } ) => destination . kind === 'settings' ? destination . pageId : null )
723+ . flatMap ( ( capability ) => [
724+ projectSettingsCatalogDestination ( capability . id , capability . destination ) ,
725+ ...capability . items . map ( ( item ) => projectSettingsCatalogDestination (
726+ capability . id , item . destination ?? capability . destination , item . id ,
727+ ) ) ,
728+ ] ) ;
729+ const semanticSettings = new Set ( projectedSettingsDestinations
730+ . map ( ( destination ) => destination . kind === 'settings' ? destination . pageId : null )
697731 . filter ( Boolean ) ) ;
698732 const missingSettings = [ ...settingsIds ] . filter ( ( id ) => ! semanticSettings . has ( id ) ) ;
699733 const staleSettings = [ ...semanticSettings ] . filter ( ( id ) => ! settingsIds . has ( id ) ) ;
@@ -704,10 +738,9 @@ function validateSource(source) {
704738 ] . filter ( Boolean ) . join ( '\n' ) ) ;
705739 }
706740
707- const semanticSettingsViews = new Set ( source . capabilities . flatMap ( ( capability ) =>
708- capability . items
709- . map ( ( { destination } ) => destination ?. kind === 'settings' ? destination . viewId : null )
710- . filter ( Boolean ) ) ) ;
741+ const semanticSettingsViews = new Set ( projectedSettingsDestinations
742+ . map ( ( destination ) => destination . kind === 'settings' ? destination . viewId : null )
743+ . filter ( Boolean ) ) ;
711744 const missingSettingsViews = [ ...settingsViewIds ] . filter ( ( id ) => ! semanticSettingsViews . has ( id ) ) ;
712745 const staleSettingsViews = [ ...semanticSettingsViews ] . filter ( ( id ) => ! settingsViewIds . has ( id ) ) ;
713746 if ( missingSettingsViews . length || staleSettingsViews . length ) {
@@ -1034,7 +1067,7 @@ function loadOwnerDefinitions() {
10341067 const result = spawnSync (
10351068 'cargo' ,
10361069 [ 'run' , '--quiet' , '-p' , 'openbitfun-product-domains' , '--bin' , PRODUCT_CONTROL_OWNER_EXPORT ] ,
1037- { cwd : REPO_ROOT , encoding : 'utf8' , maxBuffer : 16 * 1024 * 1024 } ,
1070+ { cwd : REPO_ROOT , encoding : 'utf8' , maxBuffer : 16 * 1024 * 1024 , windowsHide : true } ,
10381071 ) ;
10391072 if ( result . status !== 0 ) {
10401073 throw new Error ( [
@@ -1067,7 +1100,7 @@ export function loadRemoteSurfaceRegistry() {
10671100 const result = spawnSync (
10681101 'cargo' ,
10691102 [ 'run' , '--quiet' , '-p' , 'openbitfun-product-domains' , '--bin' , REMOTE_SURFACE_EXPORT ] ,
1070- { cwd : REPO_ROOT , encoding : 'utf8' , maxBuffer : 16 * 1024 * 1024 } ,
1103+ { cwd : REPO_ROOT , encoding : 'utf8' , maxBuffer : 16 * 1024 * 1024 , windowsHide : true } ,
10711104 ) ;
10721105 if ( result . status !== 0 ) {
10731106 throw new Error ( [
0 commit comments