@@ -148,6 +148,29 @@ function applyTextChanges(fileContent, changes) {
148148function applyPatternChanges ( fileContent , changes ) {
149149 let modifiedContent = fileContent ;
150150
151+ // Detect potential conflicts by checking for duplicate find patterns
152+ const findPatterns = new Map ( ) ;
153+ changes . forEach ( ( change , index ) => {
154+ if ( change . find ) {
155+ if ( findPatterns . has ( change . find ) ) {
156+ const existingIndex = findPatterns . get ( change . find ) ;
157+ console . warn (
158+ `\x1b[33m⚠ CONFLICT DETECTED: Multiple changes target the same pattern\x1b[0m`
159+ ) ;
160+ console . warn (
161+ ` Change ${ existingIndex + 1 } : ${ changes [ existingIndex ] . explanation } `
162+ ) ;
163+ console . warn ( ` Change ${ index + 1 } : ${ change . explanation } ` ) ;
164+ console . warn ( ` Pattern: ${ change . find . substring ( 0 , 100 ) } ...` ) ;
165+ console . warn (
166+ `\x1b[33m → Only the first change will be applied\x1b[0m`
167+ ) ;
168+ } else {
169+ findPatterns . set ( change . find , index ) ;
170+ }
171+ }
172+ } ) ;
173+
151174 for ( const change of changes ) {
152175 const { type, find, replaceWith, insert, explanation } = change ;
153176
@@ -157,12 +180,31 @@ function applyPatternChanges(fileContent, changes) {
157180 case "replace" :
158181 if ( find && replaceWith !== undefined ) {
159182 if ( modifiedContent . includes ( find ) ) {
183+ // Check if replaceWith contains malformed JSX
184+ const openTags = ( replaceWith . match ( / < [ ^ \/ ] [ ^ > ] * > / g) || [ ] ) . length ;
185+ const closeTags = ( replaceWith . match ( / < \/ [ ^ > ] * > / g) || [ ] ) . length ;
186+
187+ if ( openTags !== closeTags ) {
188+ console . warn (
189+ `\x1b[33m⚠ POTENTIAL JSX MALFORMATION: Unmatched tags in replacement\x1b[0m`
190+ ) ;
191+ console . warn (
192+ ` Open tags: ${ openTags } , Close tags: ${ closeTags } `
193+ ) ;
194+ console . warn (
195+ ` Replacement: ${ replaceWith . substring ( 0 , 200 ) } ...`
196+ ) ;
197+ }
198+
160199 modifiedContent = modifiedContent . replace ( find , replaceWith ) ;
161200 console . log ( `\x1b[32m✓ Replaced pattern successfully\x1b[0m` ) ;
162201 } else {
163202 console . warn (
164203 `\x1b[33m⚠ Pattern not found for replace: ${ find . substring ( 0 , 50 ) } ...\x1b[0m`
165204 ) ;
205+ console . warn (
206+ `\x1b[33m This might be due to a previous change modifying the content\x1b[0m`
207+ ) ;
166208 }
167209 } else {
168210 console . warn ( "Invalid 'replace' change object:" , change ) ;
@@ -729,6 +771,7 @@ function createApp() {
729771 `\x1b[36mℹ Visual Editor API Request: Received ${ changes . length } changes for repo ${ github_repo_name } \x1b[0m`
730772 ) ;
731773
774+ // Group changes by file to eliminate duplication
732775 const changesByFile = new Map ( ) ;
733776 for ( const change of changes ) {
734777 try {
@@ -738,6 +781,7 @@ function createApp() {
738781 ) ;
739782 continue ;
740783 }
784+
741785 const encodedFilePath = change . encoded_location . split ( ":" ) [ 0 ] ;
742786 const targetFile = decode ( encodedFilePath ) ;
743787 if ( ! targetFile ) {
@@ -746,13 +790,40 @@ function createApp() {
746790 ) ;
747791 continue ;
748792 }
793+
794+ // Initialize file entry if not exists
749795 if ( ! changesByFile . has ( targetFile ) ) {
750- changesByFile . set ( targetFile , [ ] ) ;
796+ changesByFile . set ( targetFile , {
797+ encoded_location : change . encoded_location ,
798+ file_content : null , // Will be set below
799+ changes : [ ] ,
800+ } ) ;
801+ }
802+
803+ // Add this element change to the file
804+ const elementChange = {
805+ style_changes : change . style_changes || [ ] ,
806+ text_changes : [ ] ,
807+ } ;
808+
809+ // Create text_changes from old_html/new_html if present
810+ if ( change . old_html !== undefined && change . new_html !== undefined ) {
811+ elementChange . text_changes . push ( {
812+ old_text : change . old_html ,
813+ new_text : change . new_html ,
814+ } ) ;
815+ }
816+
817+ // Only add the change if it has actual content
818+ if (
819+ elementChange . style_changes . length > 0 ||
820+ elementChange . text_changes . length > 0
821+ ) {
822+ changesByFile . get ( targetFile ) . changes . push ( elementChange ) ;
751823 }
752- changesByFile . get ( targetFile ) . push ( change ) ;
753824 } catch ( e ) {
754825 console . error (
755- `\x1b[31m✖ Error decoding location: ${ change . encoded_location } \x1b[0m`
826+ `\x1b[31m✖ Error processing change for location: ${ change . encoded_location } \x1b[0m`
756827 ) ;
757828 }
758829 }
@@ -764,51 +835,42 @@ function createApp() {
764835 }
765836 }
766837
767- const fileDataPromises = Array . from ( changesByFile . entries ( ) ) . map (
768- async ( [ targetFile , fileChanges ] ) => {
838+ // Read file content for each file and create the grouped structure
839+ const fileChangesForBackend = [ ] ;
840+ for ( const [ targetFile , fileData ] of changesByFile . entries ( ) ) {
841+ try {
842+ // Read file content once per file
769843 const { fileContent } = readFileFromEncodedLocation (
770- fileChanges [ 0 ] . encoded_location
844+ fileData . encoded_location
845+ ) ;
846+ fileData . file_content = fileContent ;
847+
848+ // Only include files that have actual changes
849+ if ( fileData . changes . length > 0 ) {
850+ fileChangesForBackend . push ( {
851+ encoded_location : fileData . encoded_location ,
852+ file_content : fileData . file_content ,
853+ changes : fileData . changes ,
854+ } ) ;
855+ }
856+ } catch ( error ) {
857+ console . error (
858+ `\x1b[31m✖ Error reading file content for ${ targetFile } : ${ error . message } \x1b[0m`
771859 ) ;
772- return {
773- encoded_location : fileChanges [ 0 ] . encoded_location ,
774- file_content : fileContent ,
775- style_changes : fileChanges . flatMap ( ( c ) => c . style_changes || [ ] ) ,
776- text_changes : fileChanges
777- . filter ( ( c ) => c . old_html !== undefined )
778- . map ( ( change ) => ( {
779- ...change ,
780- old_text : change . old_html ,
781- new_text : change . new_html ,
782- } ) ) ,
783- targetFile : targetFile ,
784- } ;
785860 }
786- ) ;
787-
788- const fileDataForBackend = ( await Promise . all ( fileDataPromises ) ) . filter (
789- ( f ) => f . style_changes . length > 0 || f . text_changes . length > 0
790- ) ;
861+ }
791862
792- if ( fileDataForBackend . length === 0 ) {
863+ if ( fileChangesForBackend . length === 0 ) {
793864 return reply . code ( 200 ) . send ( {
794865 message : "No changes to apply." ,
795866 updatedFiles : [ ] ,
796867 } ) ;
797868 }
798869
799870 console . log (
800- `\x1b[36mℹ Preparing bulk request for ${ fileDataForBackend . length } files. \x1b[0m`
871+ `\x1b[36mℹ Sending grouped request for ${ fileChangesForBackend . length } files with ${ changes . length } total changes \x1b[0m`
801872 ) ;
802873
803- const fileChangesForBackend = fileDataForBackend . map ( ( f ) => {
804- return {
805- encoded_location : f . encoded_location ,
806- file_content : f . file_content ,
807- style_changes : f . style_changes ,
808- text_changes : f . text_changes ,
809- } ;
810- } ) ;
811-
812874 const backendResponse = await getChanges ( {
813875 githubRepoName : github_repo_name ,
814876 fileChanges : fileChangesForBackend ,
@@ -840,7 +902,7 @@ function createApp() {
840902 return reply . code ( 200 ) . send ( {
841903 message : `Changes applied successfully to ${
842904 updatedFiles . size
843- } file(s).`,
905+ } file(s). Processed ${ changes . length } individual changes across ${ fileChangesForBackend . length } files. `,
844906 updatedFiles : Array . from ( updatedFiles ) ,
845907 } ) ;
846908 } catch ( err ) {
0 commit comments