From f6ac1abbf2761181f4efdf8a4723655e28be6c81 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 29 Jul 2026 09:33:16 -0700 Subject: [PATCH] [eric] browser: recognise LinkedIn's post editor by name and say why a compose surface came up empty --- .../apps/agents/browser/browser_send_parse.py | 7 ++++- backend/tests/test_browser_readonly_guard.py | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/backend/apps/agents/browser/browser_send_parse.py b/backend/apps/agents/browser/browser_send_parse.py index e88fb947..723dbba5 100644 --- a/backend/apps/agents/browser/browser_send_parse.py +++ b/backend/apps/agents/browser/browser_send_parse.py @@ -17,10 +17,15 @@ P_COMPOSER_ROW_RE = re.compile(r"\[(\d+)\]\*?<\s*textbox\s+\"([^\"]*)\"", re.I) # A compose-shaped textbox name, generalized across messaging sites: LinkedIn "Write a # message", X/Slack "Message", Discord "Message @user", Gmail "Message Body", "Post your # reply", "What's happening", "Add a comment". Not per-site: one structural shape. +# "text editor" earns its place from a measurement, not a guess: LinkedIn's post box is named +# "Text editor for creating content" and its comment box "Text editor for creating comment", so +# without it the real composer was invisible while the comment box next to it matched on "comment". +# Landing on LinkedIn's own compose surface listed exactly one textbox and we still scored zero. +# Both shapes match now, and telling them apart is surface_mismatch's job, which already does it. P_COMPOSER_NAME_RE = re.compile( r"write|messag|compose|reply|comment|post your|post text|what.?s happening|" r"tweet|caption|say something|start a|new message|body|your (message|note)|" - r"add a comment|write something", + r"add a comment|write something|text editor|creating content", re.I, ) diff --git a/backend/tests/test_browser_readonly_guard.py b/backend/tests/test_browser_readonly_guard.py index ae0c6090..0600767e 100644 --- a/backend/tests/test_browser_readonly_guard.py +++ b/backend/tests/test_browser_readonly_guard.py @@ -119,3 +119,30 @@ def test_a_comment_task_keeps_its_comment_opener(): def test_a_post_task_keeps_a_real_post_opener(): for opener in ("Post", "Compose", "New message", "Message"): assert not sp.surface_mismatch('post this, exactly: "hi"', opener), opener + + +# --- LinkedIn names its editors, and the names are the only thing telling them apart ----------- + +def p_row(name: str) -> str: + return f'[12]*' + + +def test_linkedins_post_editor_is_recognised_as_a_composer(): + """Measured: landing on LinkedIn's own compose surface listed exactly ONE textbox, named + "Text editor for creating content", and the composer picker scored zero because no pattern + matched it. The whole site failed 3/3 on a page that was showing the right box.""" + from backend.apps.agents.browser import browser_send_parse as sp + assert sp.composer_index_in_state(p_row("Text editor for creating content")) is not None + + +def test_linkedins_comment_editor_is_still_refused_for_a_post_task(): + """The names differ by one word, so widening the picker must not swallow the comment box. + It matches now, and surface_mismatch is what rejects it: recognising a box and choosing it are + different jobs, and only the second one is allowed to be wrong here.""" + from backend.apps.agents.browser import browser_send_parse as sp + row = p_row("Text editor for creating comment") + assert sp.composer_index_in_state(row) is not None + assert sp.surface_mismatch('start a post saying "hello there"', + "Text editor for creating comment") is True + assert sp.surface_mismatch('start a post saying "hello there"', + "Text editor for creating content") is False