[eric] browser: a comment task must not click Create-new-post

This commit is contained in:
ciregenz
2026-08-04 16:11:51 -07:00
parent b62a29357e
commit bd24d0bf6c
3 changed files with 56 additions and 3 deletions
@@ -9,6 +9,7 @@ imports from here, never the reverse.
"""
import re
from typing import Optional
# Double quotes are unambiguous. Single quotes only delimit when the opener is at a word boundary (start/space/colon), so an in-word apostrophe like "chen's" is never mistaken for a payload quote, that mispairing was silently corrupting the canonical "text him '...'" errand.
P_QUOTED_DQ_RE = re.compile(r'"([^"]{4,300})"')
@@ -249,21 +250,48 @@ def quoted_payload(task: str) -> str:
return sq.pop() if len(sq) == 1 else ""
def opener_index_in_state(state_text: str):
def opener_index_in_state(state_text: str, task: Optional[str] = None):
"""(index, name) of the single composer OPENER, or None.
An exact name, or a verb+noun compose phrase anywhere in a longer label. The second half is
what reaches the openers whose label is a whole sentence, and it keeps the exactness the first
half was buying: an upsell ('Send InMail') has the wrong noun and a count ('526 comments') has
no verb. Still a SINGLETON, so two candidates stay the model's problem, not a coin flip."""
no verb. Still a SINGLETON, so two candidates stay the model's problem, not a coin flip.
Pass `task` to drop openers that contradict it, which is what keeps a comment task off the
'New post' upload button. Optional so existing read-only callers are unaffected."""
hits = [(int(m.group(1)), m.group(2)) for m in P_OPENER_ROW_RE.finditer(state_text or "")]
if not hits:
hits = [(int(m.group(1)), m.group(2))
for m in P_CONTROL_ROW_RE.finditer(state_text or "")
if P_OPENER_PHRASE_RE.search(m.group(2) or "")]
if task is not None:
hits = [h for h in hits if not opener_contradicts_task(task, h[1])]
return hits[0] if len(hits) == 1 else None
# "New post", "Create", "Compose" open a BLANK thing. Fine when the task is to write something new;
# wrong when the task is to respond to something that already exists.
P_CREATE_OPENER_RE = re.compile(r"\b(new|create|compose|start a)\b", re.I)
def opener_contradicts_task(task: str, opener_name: str) -> bool:
"""True when this opener would take us somewhere the task did not ask to go.
Measured on instagram 2026-08-04: a "write a comment on the first post" task matched the opener
`New post Create`, which is the UPLOAD flow, so the run left the feed for a file picker and
never saw a post. Nothing downstream could catch it, because by then the only evidence left was
a page with no composer on it.
Only the create-vs-respond direction, because that is the one with a wrong destination. A
respond-shaped opener on a create task is left alone: some sites really do route a new post
through a control labelled "Write".
"""
t, name = task or "", opener_name or ""
responding = bool(P_COMMENT_INTENT_RE.search(t))
return responding and bool(P_CREATE_OPENER_RE.search(name))
def composer_index_in_state(state_text: str):
"""(index, name) of the single compose-shaped textbox, or None. Two
candidates = ambiguous = model's problem."""
@@ -293,7 +293,7 @@ async def run_send_script(
p_struct_selector: str = ""
if not composer:
# Reversible-opener hop: prestage often stops on the profile with the "Message" opener visible (its settle raced the overlay). Opening a composer is the allowed opener class; the irreversible bar is unchanged.
opener = browser_send_parse.opener_index_in_state(state_text)
opener = browser_send_parse.opener_index_in_state(state_text, task_sans_brief)
if opener and browser_send_parse.surface_mismatch(task_sans_brief, opener[1]):
# The same wrong-surface rule the composer already enforces, applied one step earlier.
# Measured on linkedin.com with "start a post": the only opener listed was 'Comment', so
@@ -57,3 +57,28 @@ def test_the_exact_rule_is_preferred_over_the_phrase_rule():
hit = sp.opener_index_in_state(
'[3]<button "Comment">\n[9]<button "Read or add comments 526 comments">')
assert hit is not None and hit[0] == 3
def test_a_comment_task_must_not_click_create_new_post():
"""Measured live on instagram 2026-08-04, and caused by widening the opener match above.
"New post Create" contains the phrase "new post", so a task that says "write a comment on the
first post" matched Instagram's UPLOAD button. The run left the feed for a file picker and never
saw a post, and nothing downstream could catch it: by then the only evidence left was a page
with no composer on it, which reads as a missing composer rather than a wrong destination.
Only the create-vs-respond direction is guarded. A respond-shaped label on a create task is left
alone, because some sites really do route a new post through a control labelled "Write"."""
ig = '[11]<button "New post Create">'
assert sp.opener_index_in_state(ig, "write a comment on the first post") is None
# the same button is exactly right when the task IS to create something new
assert sp.opener_index_in_state(ig, "create a new post saying hi") is not None
# and a genuine comment opener still resolves for the comment task
assert sp.opener_index_in_state(
'[11]<button "Read or add comments 526 comments">', "write a comment on the first post")
def test_omitting_the_task_leaves_opener_matching_exactly_as_it_was():
"""The task argument is optional so read-only callers (the perception summary line) keep their
behaviour; a guard that changed what `opener=` reports would corrupt the dryrun report."""
assert sp.opener_index_in_state('[11]<button "New post Create">') is not None