@@ -20,6 +20,7 @@ import {
2020 flattenObject ,
2121 unflattenObject ,
2222 serializeKVPairs ,
23+ deepSerializeKVPairs ,
2324 cn ,
2425 normalizeImportConfig ,
2526 hasConfigCapability ,
@@ -269,7 +270,32 @@ export function ConfigPage({ initialTab, highlightField, initialScope }: t.Confi
269270 return unflattenObject ( scopeResolvedValues ) as Record < string , t . ConfigValue > ;
270271 } , [ isEditingScope , scopeResolvedValues ] ) ;
271272
272- const activeConfigValues = isEditingScope ? scopeConfigValues : configValues ;
273+ const baseActiveConfigValues = isEditingScope ? scopeConfigValues : configValues ;
274+
275+ const activeConfigValues = useMemo ( ( ) => {
276+ if ( ! baseActiveConfigValues ) return baseActiveConfigValues ;
277+ const indexedEdits = Object . entries ( editedValues ) . filter ( ( [ k ] ) => / \. \d + $ / . test ( k ) ) ;
278+ if ( indexedEdits . length === 0 ) return baseActiveConfigValues ;
279+ const merged = { ...baseActiveConfigValues } ;
280+ for ( const [ path , value ] of indexedEdits ) {
281+ const segments = path . split ( '.' ) ;
282+ const index = Number ( segments . pop ( ) ! ) ;
283+ const arrayPath = segments ;
284+ let parent : Record < string , t . ConfigValue > = merged ;
285+ for ( let i = 0 ; i < arrayPath . length - 1 ; i ++ ) {
286+ const seg = arrayPath [ i ] ;
287+ if ( parent [ seg ] && typeof parent [ seg ] === 'object' && ! Array . isArray ( parent [ seg ] ) ) {
288+ parent [ seg ] = { ...( parent [ seg ] as Record < string , t . ConfigValue > ) } ;
289+ parent = parent [ seg ] as Record < string , t . ConfigValue > ;
290+ } else break ;
291+ }
292+ const lastSeg = arrayPath [ arrayPath . length - 1 ] ;
293+ const arr = Array . isArray ( parent [ lastSeg ] ) ? [ ...( parent [ lastSeg ] as t . ConfigValue [ ] ) ] : [ ] ;
294+ arr [ index ] = value ;
295+ parent [ lastSeg ] = arr ;
296+ }
297+ return merged ;
298+ } , [ baseActiveConfigValues , editedValues ] ) ;
273299
274300 const scopeConfiguredPaths = useMemo ( ( ) => {
275301 if ( ! scopeChangedPaths ) return new Set < string > ( ) ;
@@ -356,7 +382,16 @@ export function ConfigPage({ initialTab, highlightField, initialScope }: t.Confi
356382 delete next [ path ] ;
357383 return next ;
358384 }
359- return { ...prev , [ path ] : value } ;
385+ const next = { ...prev , [ path ] : value } ;
386+ if ( Array . isArray ( value ) ) {
387+ const prefix = `${ path } .` ;
388+ for ( const k of Object . keys ( next ) ) {
389+ if ( k . startsWith ( prefix ) && / \. \d + $ / . test ( k ) ) delete next [ k ] ;
390+ }
391+ }
392+ const indexMatch = / ^ ( .+ ) \. \d + $ / . exec ( path ) ;
393+ if ( indexMatch ) delete next [ indexMatch [ 1 ] ] ;
394+ return next ;
360395 } ) ;
361396 } ) ;
362397 } ,
@@ -437,7 +472,10 @@ export function ConfigPage({ initialTab, highlightField, initialScope }: t.Confi
437472
438473 const saves = touched
439474 . filter ( ( p ) => editedValues [ p ] !== undefined )
440- . map ( ( p ) => ( { fieldPath : p , value : serializeKVPairs ( editedValues [ p ] ) } ) ) ;
475+ . map ( ( p ) => ( {
476+ fieldPath : p ,
477+ value : / \. \d + $ / . test ( p ) ? deepSerializeKVPairs ( editedValues [ p ] ) : serializeKVPairs ( editedValues [ p ] ) ,
478+ } ) ) ;
441479 const resets = touched . filter ( ( p ) => editedValues [ p ] === undefined ) ;
442480
443481 setSaving ( true ) ;
@@ -506,11 +544,29 @@ export function ConfigPage({ initialTab, highlightField, initialScope }: t.Confi
506544 const serializedEditedValues = useMemo ( ( ) => {
507545 const result : t . FlatConfigMap = { } ;
508546 for ( const [ k , v ] of Object . entries ( editedValues ) ) {
509- result [ k ] = serializeKVPairs ( v ) ;
547+ result [ k ] = / \. \d + $ / . test ( k ) ? deepSerializeKVPairs ( v ) : serializeKVPairs ( v ) ;
510548 }
511549 return result ;
512550 } , [ editedValues ] ) ;
513551
552+ const originalValuesForDialog = useMemo ( ( ) => {
553+ const baseline = isEditingScope ? scopeBaseline : flatBaseline ;
554+ const result : t . FlatConfigMap = { ...baseline } ;
555+ for ( const path of Object . keys ( editedValues ) ) {
556+ if ( path in result ) continue ;
557+ const segments = path . split ( '.' ) ;
558+ let current : t . ConfigValue = configValues ;
559+ for ( const seg of segments ) {
560+ if ( current == null || typeof current !== 'object' ) { current = undefined ; break ; }
561+ current = Array . isArray ( current )
562+ ? ( current as t . ConfigValue [ ] ) [ Number ( seg ) ]
563+ : ( current as Record < string , t . ConfigValue > ) [ seg ] ;
564+ }
565+ if ( current !== undefined ) result [ path ] = current ;
566+ }
567+ return result ;
568+ } , [ editedValues , flatBaseline , isEditingScope , scopeBaseline , configValues ] ) ;
569+
514570 const [ importSuccessMessage , setImportSuccessMessage ] = useState < string | null > ( null ) ;
515571
516572 const showImportSuccess = useCallback ( ( message ?: string ) => {
@@ -846,7 +902,7 @@ export function ConfigPage({ initialTab, highlightField, initialScope }: t.Confi
846902 < ConfirmSaveDialog
847903 open = { confirmSaveOpen }
848904 editedValues = { serializedEditedValues }
849- originalValues = { isEditingScope ? scopeBaseline : flatBaseline }
905+ originalValues = { originalValuesForDialog }
850906 saving = { saving }
851907 error = { saveError }
852908 onConfirm = { handleConfirmSave }
0 commit comments