fix: Tab Height Guard false-positive on author intent with 'tab' substring

Production bug 2026-05-02 22:19:
Intent 'post author username text (exclude bottom tabs)' contained the
word 'tab' in its parenthetical qualifier. The old check:
    is_tab_intent = 'tab' in intent_lower
matched this intent as a TAB navigation intent, activating the Tab
Height Guard which excluded ALL nodes with center_y < 85% of screen.

This nuked the actual clips_author_username node at y=2012, leaving
only 4 irrelevant items for the VLM → bot stuck in a loop unable to
find the author username.

Fix: Replace substring match with precise regex patterns that only
match real tab navigation intents:
  - 'tap profile tab', 'tap home tab' (tap + word + tab)
  - 'profile tab', 'explore tab' (word + tab)
  - 'tab ...' at start of intent

TDD: Red test added first, reproducing exact production state.
81 perception tests + 172 total tests pass.
This commit is contained in:
2026-05-02 22:28:03 +02:00
parent f6f15ebd9a
commit cff7e976e0
2 changed files with 53 additions and 3 deletions

View File

@@ -295,6 +295,44 @@ class TestStructuralGuards:
author_nodes = [n for n in filtered if n.text == "photographer_jane"]
assert len(author_nodes) == 1
def test_author_intent_with_exclude_tabs_suffix_preserves_author(self):
"""
Production bug 2026-05-02 22:19: Intent 'post author username text
(exclude bottom tabs)' contains the word 'tab', which falsely
triggers the Tab Height Guard. This nukes the actual author node
(clips_author_username at y=2012) because 2012 < 85% of 2400.
The fix: is_tab_intent must only match REAL tab navigation intents,
not intents that mention 'tab' in a parenthetical exclusion note.
"""
candidates = [
_make_node(
resource_id="com.instagram.android:id/clips_author_username",
content_desc="trenny_m",
center_y=2012,
),
_make_node(
resource_id="com.instagram.android:id/profile_tab",
content_desc="Profile",
center_y=2350,
),
_make_node(
resource_id="com.instagram.android:id/like_button",
content_desc="Like",
center_y=1353,
),
]
filtered = self.resolver.filter_navigation_conflicts(
candidates, "post author username text (exclude bottom tabs)",
screen_height=2400,
)
# The author username at y=2012 MUST survive
author_nodes = [n for n in filtered if n.content_desc == "trenny_m"]
assert len(author_nodes) == 1, (
f"Author node 'trenny_m' was incorrectly filtered out! "
f"Remaining: {[(n.resource_id, n.content_desc) for n in filtered]}"
)
# ── Structural Fast-Paths ──
def test_fast_path_message_input(self):