@@ -1114,8 +1114,67 @@ function RemoteFunctions(config = {}) {
11141114 redrawEverything ( ) ;
11151115 }
11161116 } else {
1117- // Suppression is active - re-apply outline since attrChange may have wiped it
1118- if ( previouslySelectedElement && previouslySelectedElement . isConnected ) {
1117+ // Suppression is active (e.g., control box initiated a source edit)
1118+ if ( previouslySelectedElement && ! previouslySelectedElement . isConnected ) {
1119+ let freshElement = null ;
1120+
1121+ // Strategy 1: Tree path (most reliable — works even with duplicate
1122+ // text content and tag changes). Stored when suppression was activated.
1123+ if ( SHARED_STATE . _suppressedElementPath ) {
1124+ freshElement = _getElementByTreePath ( SHARED_STATE . _suppressedElementPath ) ;
1125+ }
1126+
1127+ // Strategy 2: brackets-id (works when IDs are preserved)
1128+ if ( ! freshElement ) {
1129+ const bracketsId = previouslySelectedElement . getAttribute ( GLOBALS . DATA_BRACKETS_ID_ATTR ) ;
1130+ if ( bracketsId ) {
1131+ freshElement = document . querySelector (
1132+ '[' + GLOBALS . DATA_BRACKETS_ID_ATTR + '="' + bracketsId + '"]'
1133+ ) ;
1134+ }
1135+ }
1136+
1137+ // Strategy 3: Text + tag match (fallback — search reverse for deepest match)
1138+ if ( ! freshElement ) {
1139+ const oldText = previouslySelectedElement . textContent ;
1140+ const oldTag = previouslySelectedElement . tagName ;
1141+ let candidates = document . querySelectorAll (
1142+ oldTag . toLowerCase ( ) + '[' + GLOBALS . DATA_BRACKETS_ID_ATTR + ']'
1143+ ) ;
1144+ for ( let i = candidates . length - 1 ; i >= 0 ; i -- ) {
1145+ if ( candidates [ i ] . textContent === oldText ) {
1146+ freshElement = candidates [ i ] ;
1147+ break ;
1148+ }
1149+ }
1150+ // Broaden if tag changed (e.g. h2→footer)
1151+ if ( ! freshElement ) {
1152+ candidates = document . querySelectorAll ( '[' + GLOBALS . DATA_BRACKETS_ID_ATTR + ']' ) ;
1153+ for ( let i = candidates . length - 1 ; i >= 0 ; i -- ) {
1154+ if ( candidates [ i ] . textContent === oldText ) {
1155+ freshElement = candidates [ i ] ;
1156+ break ;
1157+ }
1158+ }
1159+ }
1160+ }
1161+
1162+ if ( freshElement ) {
1163+ freshElement . _originalOutline = freshElement . style . outline ;
1164+ const isEditable = freshElement . hasAttribute ( GLOBALS . DATA_BRACKETS_ID_ATTR ) ;
1165+ const outlineColor = isEditable
1166+ ? COLORS . outlineEditable : COLORS . outlineNonEditable ;
1167+ freshElement . style . outline = `1px solid ${ outlineColor } ` ;
1168+ if ( _clickHighlight ) {
1169+ _clickHighlight . clear ( ) ;
1170+ _clickHighlight . add ( freshElement ) ;
1171+ }
1172+ previouslySelectedElement = freshElement ;
1173+ window . __current_ph_lp_selected = freshElement ;
1174+ redrawEverything ( ) ;
1175+ }
1176+ } else if ( previouslySelectedElement && previouslySelectedElement . isConnected ) {
1177+ // Re-apply outline since attrChange may have wiped it
11191178 const isEditable = previouslySelectedElement . hasAttribute ( GLOBALS . DATA_BRACKETS_ID_ATTR ) ;
11201179 const outlineColor = isEditable ? COLORS . outlineEditable : COLORS . outlineNonEditable ;
11211180 previouslySelectedElement . style . outline = `1px solid ${ outlineColor } ` ;
@@ -1216,6 +1275,43 @@ function RemoteFunctions(config = {}) {
12161275 }
12171276 }
12181277
1278+ /**
1279+ * Compute the tree path of an element as an array of child indices
1280+ * from <html> down. Used to re-locate the element after re-instrumentation
1281+ * when data-brackets-id changes and text matching is ambiguous.
1282+ * E.g. [1, 0, 0, 1] means html > 2nd child > 1st child > 1st child > 2nd child.
1283+ */
1284+ function _getTreePath ( element ) {
1285+ const path = [ ] ;
1286+ let el = element ;
1287+ while ( el && el . parentElement ) {
1288+ const parent = el . parentElement ;
1289+ const children = parent . children ;
1290+ for ( let i = 0 ; i < children . length ; i ++ ) {
1291+ if ( children [ i ] === el ) {
1292+ path . unshift ( i ) ;
1293+ break ;
1294+ }
1295+ }
1296+ el = parent ;
1297+ }
1298+ return path ;
1299+ }
1300+
1301+ /**
1302+ * Find an element by its tree path (array of child indices from <html>).
1303+ */
1304+ function _getElementByTreePath ( path ) {
1305+ let el = document . documentElement ;
1306+ for ( let i = 0 ; i < path . length ; i ++ ) {
1307+ if ( ! el || ! el . children || ! el . children [ path [ i ] ] ) {
1308+ return null ;
1309+ }
1310+ el = el . children [ path [ i ] ] ;
1311+ }
1312+ return el ;
1313+ }
1314+
12191315 /**
12201316 * Temporarily suppress the DOM edit dismissal check in apply()
12211317 * Used when source is modified from UI panels to prevent
@@ -1228,9 +1324,14 @@ function RemoteFunctions(config = {}) {
12281324 clearTimeout ( SHARED_STATE . _suppressDOMEditDismissalTimeout ) ;
12291325 }
12301326 SHARED_STATE . _suppressDOMEditDismissal = true ;
1327+ // Store the tree path while the element is still connected
1328+ if ( previouslySelectedElement && previouslySelectedElement . isConnected ) {
1329+ SHARED_STATE . _suppressedElementPath = _getTreePath ( previouslySelectedElement ) ;
1330+ }
12311331 SHARED_STATE . _suppressDOMEditDismissalTimeout = setTimeout ( function ( ) {
12321332 SHARED_STATE . _suppressDOMEditDismissal = false ;
12331333 SHARED_STATE . _suppressDOMEditDismissalTimeout = null ;
1334+ SHARED_STATE . _suppressedElementPath = null ;
12341335 } , durationMs ) ;
12351336 }
12361337
0 commit comments