diff --git a/GramAddict/core/perception/screen_identity.py b/GramAddict/core/perception/screen_identity.py
index c57f7bc..0f8f668 100644
--- a/GramAddict/core/perception/screen_identity.py
+++ b/GramAddict/core/perception/screen_identity.py
@@ -23,6 +23,7 @@ class ScreenType(Enum):
COMMENTS = "comments"
MODAL = "modal"
FOREIGN_APP = "foreign_app"
+ NOTIFICATIONS = "notifications"
UNKNOWN = "unknown"
@@ -94,7 +95,14 @@ class ScreenIdentity:
resource_ids.add(short_id)
# Track which tab is selected
- if selected and short_id in ("feed_tab", "search_tab", "clips_tab", "profile_tab", "direct_tab"):
+ if selected and short_id in (
+ "feed_tab",
+ "search_tab",
+ "clips_tab",
+ "profile_tab",
+ "direct_tab",
+ "news_tab",
+ ):
selected_tab = short_id
if text:
@@ -180,7 +188,15 @@ class ScreenIdentity:
# Structural detection is O(1), zero LLM calls, and cannot be fooled.
# EXCEPTION: If Qdrant has explicitly learned this screen as NORMAL (via LLM unlearning),
# we skip the structural check to prevent false-positive infinite loops.
- creation_flow_markers = ("quick_capture", "gallery_cancel_button", "creation_flow", "reel_camera")
+ creation_flow_markers = (
+ "quick_capture",
+ "gallery_cancel_button",
+ "creation_flow",
+ "reel_camera",
+ "gallery_grid_item_thumbnail",
+ "camera_cancel_button",
+ "next_button_textview",
+ )
browser_markers = ("ig_browser_text_title", "ig_browser_close_button", "ig_chrome_subsection")
if not is_normal_override and any(marker in ids_str for marker in creation_flow_markers):
logger.info("🛡️ [ScreenIdentity] Content-creation overlay detected → MODAL")
@@ -239,6 +255,11 @@ class ScreenIdentity:
if any(marker in ids for marker in STORY_MARKERS):
return ScreenType.STORY_VIEW
+ # Notifications / Activity structural markers
+ NOTIFICATIONS_MARKERS = ("row_newsfeed_text", "newsfeed_tab", "newsfeed_user_imageview")
+ if any(marker in ids for marker in NOTIFICATIONS_MARKERS) or selected_tab == "news_tab":
+ return ScreenType.NOTIFICATIONS
+
# DM thread detection — Structural markers (header and input fields)
if "direct_thread_header" in ids or "direct_text_input" in ids or "message_composer_container" in ids:
return ScreenType.DM_THREAD
@@ -357,6 +378,7 @@ class ScreenIdentity:
"clips_tab": "tap reels tab",
"profile_tab": "tap profile tab",
"direct_tab": "tap messages tab",
+ "news_tab": "tap activity heart icon notifications",
}
for tab_id, action in tab_map.items():
if tab_id in resource_ids:
diff --git a/GramAddict/core/screen_topology.py b/GramAddict/core/screen_topology.py
index dd483ee..b4782f8 100644
--- a/GramAddict/core/screen_topology.py
+++ b/GramAddict/core/screen_topology.py
@@ -33,6 +33,7 @@ class ScreenTopology:
"tap profile tab": ScreenType.OWN_PROFILE,
"tap reels tab": ScreenType.REELS_FEED,
"tap messages tab": ScreenType.DM_INBOX,
+ "tap activity heart icon notifications": ScreenType.NOTIFICATIONS,
"tap story ring avatar": ScreenType.STORY_VIEW,
},
ScreenType.EXPLORE_GRID: {
@@ -84,6 +85,12 @@ class ScreenTopology:
"tap home tab": ScreenType.HOME_FEED,
# NOTE: 'press back' intentionally omitted — destination is non-deterministic
},
+ ScreenType.NOTIFICATIONS: {
+ "tap home tab": ScreenType.HOME_FEED,
+ "press back": ScreenType.HOME_FEED,
+ "tap profile tab": ScreenType.OWN_PROFILE,
+ "tap explore tab": ScreenType.EXPLORE_GRID,
+ },
ScreenType.UNKNOWN: {
"tap home tab": ScreenType.HOME_FEED,
},
@@ -110,6 +117,7 @@ class ScreenTopology:
"open user profile": ScreenType.OTHER_PROFILE,
"open search": ScreenType.SEARCH_RESULTS,
"view comments": ScreenType.COMMENTS,
+ "open notifications": ScreenType.NOTIFICATIONS,
}
@classmethod
diff --git a/GramAddict/core/telepathic_engine.py b/GramAddict/core/telepathic_engine.py
index 180a3bb..bf1ea1c 100644
--- a/GramAddict/core/telepathic_engine.py
+++ b/GramAddict/core/telepathic_engine.py
@@ -253,9 +253,12 @@ class TelepathicEngine:
# ZERO-TRUST: NO LOCALIZED STRINGS.
res_id = (node.resource_id or "").lower()
- # Avoid clicking on audio/trending/camera elements unless explicitly requested
- if any(trap in res_id for trap in ["album_art", "use_in_camera", "trending", "audio"]):
- if not any(word in intent for word in ["audio", "trending", "camera"]):
+ # Avoid clicking on audio/trending/camera/creation elements unless explicitly requested
+ if any(
+ trap in res_id
+ for trap in ["album_art", "use_in_camera", "trending", "audio", "creation", "camera", "gallery"]
+ ):
+ if not any(word in intent for word in ["audio", "trending", "camera", "create", "gallery"]):
return False
# Avoid clicking share, save, menu, options unless explicitly requested
diff --git a/tests/tdd/test_context_gate_matrix.py b/tests/tdd/test_context_gate_matrix.py
index b0c777b..deac25b 100644
--- a/tests/tdd/test_context_gate_matrix.py
+++ b/tests/tdd/test_context_gate_matrix.py
@@ -141,6 +141,7 @@ class TestScreenTopologyBackTransitions:
ScreenType.FOLLOW_LIST,
ScreenType.STORY_VIEW,
ScreenType.COMMENTS,
+ ScreenType.NOTIFICATIONS,
):
# These are "leaf" screens with a deterministic parent — OK to keep
continue
diff --git a/tests/tdd/test_gallery_trap_guard.py b/tests/tdd/test_gallery_trap_guard.py
new file mode 100644
index 0000000..1471744
--- /dev/null
+++ b/tests/tdd/test_gallery_trap_guard.py
@@ -0,0 +1,39 @@
+from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType
+
+
+def test_gallery_screen_is_modal():
+ """
+ Test that the Gallery/New Post screen is correctly identified as a MODAL,
+ preventing it from being hallucinated as POST_DETAIL or HOME_FEED.
+ """
+ xml_dump = """
+
+
+
+
+
+
+
+"""
+ identity = ScreenIdentity("bot_user")
+ result = identity.identify(xml_dump)
+
+ assert result["screen_type"] == ScreenType.MODAL
+
+
+def test_camera_screen_is_modal():
+ """
+ Test that the Camera creation screen is correctly identified as a MODAL.
+ """
+ xml_dump = """
+
+
+
+
+
+
+"""
+ identity = ScreenIdentity("bot_user")
+ result = identity.identify(xml_dump)
+
+ assert result["screen_type"] == ScreenType.MODAL
diff --git a/tests/tdd/test_notifications_screen.py b/tests/tdd/test_notifications_screen.py
new file mode 100644
index 0000000..cab48a1
--- /dev/null
+++ b/tests/tdd/test_notifications_screen.py
@@ -0,0 +1,22 @@
+from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType
+
+
+def test_notifications_screen_identification():
+ """
+ Test that the Notifications/Activity screen is correctly identified,
+ preventing it from being hallucinated as POST_DETAIL.
+ """
+ xml_dump = """
+
+
+
+
+
+
+
+"""
+ identity = ScreenIdentity("bot_user")
+ result = identity.identify(xml_dump)
+
+ assert result["screen_type"] == ScreenType.NOTIFICATIONS
+ assert "tap home tab" in result["available_actions"] or "press back" in result["available_actions"]
diff --git a/tests/tdd/test_obstacle_keyboard.py b/tests/tdd/test_obstacle_keyboard.py
index ba99aca..b650d86 100644
--- a/tests/tdd/test_obstacle_keyboard.py
+++ b/tests/tdd/test_obstacle_keyboard.py
@@ -28,7 +28,8 @@ def test_sae_detects_obstacle_keyboard():
xml_with_keyboard = """
-
+
+
"""
@@ -45,7 +46,8 @@ def test_obstacle_guard_dismisses_keyboard():
xml_with_keyboard = """
-
+
+
"""