--- id: "7dd944dc-c072-4b50-b7ee-bbbce6cef25c" name: "ChatGPT Composer Send Mechanics" micro_summary: "ChatGPT web submit requires a real mouse click on a freshly measured submit button. Enter and Cmd+Enter do nothing, and the auto-grown composer invalidates stale coordinates." status: "resolved" see_also: [{"target": "b3dd54e4-b11b-42ea-b9d2-efa678043385", "reason": "General stale-coordinate click law (re-measure the rect after any layout change, verify the point with elementFromPoint) lives under CDP Probing; this node is the composer-specific instance."}] scope: "How to actually submit a populated chatgpt.com composer and locate the resulting conversation." --- Verified 2026-09-17 against the logged-in web UI. Submitting: neither `Enter` nor `Cmd+Enter` through `Input.dispatchKeyEvent` submits a populated composer. A real mouse click via `click_at_xy` on `button.composer-submit-btn` (aria-label `Send prompt`) does submit, and `location.pathname` becomes `/c/` within about a second. Coordinate trap: the composer auto-grows. After a 10.5KB insert its rect measured height 7192 and y -4992, so a rect captured earlier points far off screen. Before every send call `scrollIntoView({block:'center'})` on the submit button, re-read `getBoundingClientRect`, and confirm with `document.elementFromPoint(x,y)` that the point is still the button. `elementFromPoint` and the rect are CSS pixels, which is what `click_at_xy` wants, so do not scale by devicePixelRatio. Reading the answer: capture the id from `location.pathname` immediately after the click, then poll the conversation API per the poll contract. The visible tab title may carry a custom model emoji, and the composer can hold a large payload while the page still reports normal `page_info`. Also observed: `current_tab()` raised `cdp_disconnected` on this daemon while `list_tabs`, `switch_tab` and `js(target_id=...)` worked fine. Pass `target_id` explicitly rather than depending on the daemon's default session, and check `document.querySelectorAll('#prompt-textarea,[contenteditable=true]')` because the element you insert into is not always the one you can see. --- ### 2026-10-10 19:13 UTC ## 2026-10-09 — current composer selector discovery The current UI uses `[role="textbox"][contenteditable="true"].ProseMirror` and its populated composer exposes `button[type="submit"][aria-label="Send"]`, not `button.composer-submit-btn[aria-label="Send prompt"]`. Discover by AX name before relying on historical selectors. A populated draft was retained when the old selector failed; no reinsertion occurred. Continue from that verified draft, re-measure the current submit button and verify `elementFromPoint` before a session-scoped mouse click. Do not restart the whole fill/send script after a selector mismatch. The sibling mechanics node ChatGPT Composer Payload Limits records the 94KB page-global / single execCommand insertion verified on this ProseMirror composer.