From cff7e976e042dd78a7a3241af74b9d66ac0a6a46 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 2 May 2026 22:28:03 +0200 Subject: [PATCH] fix: Tab Height Guard false-positive on author intent with 'tab' substring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- GramAddict/core/perception/intent_resolver.py | 18 +++++++-- tests/e2e/test_system_llm_perception.py | 38 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/GramAddict/core/perception/intent_resolver.py b/GramAddict/core/perception/intent_resolver.py index cc903a2..0b05e57 100644 --- a/GramAddict/core/perception/intent_resolver.py +++ b/GramAddict/core/perception/intent_resolver.py @@ -61,10 +61,22 @@ class IntentResolver: """ intent_lower = intent_description.lower() - # Only apply for tab-related intents OR generic interaction intents - # We want to aggressively protect against clicking 'Create' / 'Camera' / 'Back' by accident + # Only apply for REAL tab navigation intents. + # REGRESSION FIX 2026-05-02: "tab" as a substring was too broad. + # Intent "post author username text (exclude bottom tabs)" matched + # because it contained "tab" → Tab Height Guard nuked the author node. + # Now we require specific tab navigation patterns: + # - "tap profile tab", "tap home tab", "explore tab" + # - NOT "exclude bottom tabs", "tabbar", random mentions + import re + _TAB_PATTERN = re.compile( + r"\btap\s+\w+\s+tab\b" # "tap profile tab", "tap home tab" + r"|\b\w+\s+tab\b" # "profile tab", "explore tab" + r"|^tab\b", # "tab" at start of intent + re.IGNORECASE, + ) filtered = [] - is_tab_intent = "tab" in intent_lower and "back" not in intent_lower + is_tab_intent = bool(_TAB_PATTERN.search(intent_lower)) and "back" not in intent_lower is_create_intent = "create" in intent_lower or "camera" in intent_lower or "story" in intent_lower # REGRESSION FIX 2026-05-01: Author/username intents must never pick nav tabs is_author_intent = any(kw in intent_lower for kw in ["author", "username", "post media"]) diff --git a/tests/e2e/test_system_llm_perception.py b/tests/e2e/test_system_llm_perception.py index 47327aa..63a74fe 100644 --- a/tests/e2e/test_system_llm_perception.py +++ b/tests/e2e/test_system_llm_perception.py @@ -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):