@@ -117,22 +117,26 @@ extension BFSExplorer {
117117 return plan
118118 }
119119
120- /// Find OCR elements matching APP.md tab names by case-insensitive substring match.
121- /// Returns ranked elements with breadth_navigation priority.
120+ /// Find OCR elements matching APP.md tab names.
121+ ///
122+ /// Primary strategy: case-insensitive substring text match (works when tab bar has labels).
123+ /// Fallback: ordinal position mapping in the tab_bar zone (for icon-only tab bars like
124+ /// Instagram). Sorts tab_bar-zone elements by X coordinate and maps them to APP.md tab
125+ /// names by index — the developer's tab list order matches the visual left-to-right order.
122126 private func findTabTargets(
123127 tabNames: [ String ] , elements: [ TapPoint ] , visitedElements: Set < String >
124128 ) -> [ RankedElement ] {
125129 var results : [ RankedElement ] = [ ]
126- let visited = visitedElements
130+ var matchedTabs : Set < String > = [ ]
127131
132+ // Strategy 1: text matching
128133 for tabName in tabNames {
129134 let tabLower = tabName. lowercased ( )
130- // Find the best matching element for this tab name
131135 guard let match = elements. first ( where: { el in
132136 let elLower = el. text. lowercased ( )
133137 return ( elLower == tabLower || elLower. contains ( tabLower) || tabLower. contains ( elLower) )
134- && !visited . contains ( el. text)
135- && !visited . contains ( tabName)
138+ && !visitedElements . contains ( el. text)
139+ && !visitedElements . contains ( tabName)
136140 } ) else { continue }
137141
138142 results. append ( RankedElement (
@@ -142,6 +146,33 @@ extension BFSExplorer {
142146 displayLabel: tabName,
143147 isBreadthNavigation: true
144148 ) )
149+ matchedTabs. insert ( tabName)
150+ }
151+
152+ // Strategy 2: ordinal mapping for unmatched tabs (icon-only tab bars)
153+ let unmatchedTabs = tabNames. filter { !matchedTabs. contains ( $0) }
154+ if !unmatchedTabs. isEmpty {
155+ let tabBarTop = windowSize. height * 0.88 // bottom 12% is tab_bar zone
156+ let tabBarElements = elements
157+ . filter { $0. tapY >= tabBarTop }
158+ . sorted { $0. tapX < $1. tapX }
159+
160+ if tabBarElements. count >= tabNames. count {
161+ // Map each tab name to the element at its index, skipping already-matched
162+ for (index, tabName) in tabNames. enumerated ( ) {
163+ guard !matchedTabs. contains ( tabName) ,
164+ index < tabBarElements. count,
165+ !visitedElements. contains ( tabName) else { continue }
166+ let target = tabBarElements [ index]
167+ results. append ( RankedElement (
168+ point: target,
169+ score: ScreenPlanner . breadthRoleWeight + ScreenPlanner. highPriorityWeight,
170+ reason: " tab-ordinal( \( tabName) @ \( index) ) " ,
171+ displayLabel: tabName,
172+ isBreadthNavigation: true
173+ ) )
174+ }
175+ }
145176 }
146177
147178 return results
0 commit comments