Repository navigation
Preserve literal newlines when inserting text into web composers #76
Description
Found while validating #67 against Ace's actual WKWebView composer, under githubnext/ace2#8 / #65.
desktop_insert turned a newline in its text into an Enter/submit action. The first line became a real message in the isolated validation channel; the rest began entering a new draft before the native receiver check stopped the call with an uncertain outcome. Resolved by #67, merged as 8fb8ccf; the original reproduction is preserved below.
Reproduction and evidence
- Candidate: Add literal desktop insertion with reserved clipboard ownership #67 at
d04c8c489441a8781cad28c0b674a7589f0c4feb, signed development app, embedded Peekaboo 4.8.0. Real local host/channel and WKWebView, no mocked input. - After independent Accessibility inspection and explicit field focus (the separate availability/focus limitations are recorded in Explain native inspection failure when the desktop or target window becomes unavailable desktop-tools#1/Add native targeted scrolling #70), take a fresh native observation of an initially empty
MessageAXTextArea. - Call
desktop_insertwithAce 🧑🏽💻 café\nalpha duplicate omega\nbeta duplicate gamma\nFinish 東京. - Native result:
unknown,window_targeted_events,may_have_dispatched, at least 23 emitted input units. The error reports that the Accessibility text receiver no longer matches the exact target and requires a fresh observation. - Fresh native observation shows a posted message containing
Ace 🧑🏽💻 caféand a remaining draft ofalpha duplic. The text-area frame is still 808×45 at (426,779); an auto-resize explanation is not established. - Evidence on the validation host:
/tmp/ace-actions-validation-xaLuQq/keyboard-webview-TnvO1M/keyboard-attempt-p4VBE1/and the fresh post-failure observation inkeyboard-webview-QJVHFD/. Input was not replayed and channel history was preserved.
Required behavior
Text insertion must preserve literal Unicode and newlines without silently invoking the application's Enter/submit behavior. Explicit key presses remain separate. Determine the correct literal-text path for WKWebView/editable controls and its relationship to the GUI-owned paste work in githubnext/desktop-tools#3/#75. Keep exact target/focus authority, clipboard preservation where applicable, and truthful partial/unknown outcomes; weakening the receiver check or repeating a partially delivered request is not a fix.
Validate the actual Ace composer: inserting multiline text leaves one draft with the intended contents and adds no message; contextual selection and insertion preserve surrounding text. Retain the real AppKit, hosted routing/replay, changed-focus refusal, Stop, and restart guarantees. A constant-size AppKit field alone does not cover this behavior.
Resolution — merged #67
The final signed head eff5e49 replaces character-event insertion with one GUI-owned plain-text paste. Newlines remain text instead of acquiring the editor’s Enter/submit semantics. The existing native lane, clipboard gate, snapshot lease, and exact receiver validation remain in force; uncertain delivery is not retried or reported as completed.
Final dogfooding also exposed ProseMirror’s default plain-text parser turning single newlines into separate paragraphs, which made the actual Accessibility value differ from the intended text. Ace now parses plain-text line breaks as hard_break nodes and preserves them during plain-text copying. Rich HTML and code-block paste retain their existing behavior. Native exact-text verification was not weakened.
On the signed final build, a contextual replacement of the second repeated match in Ace’s actual composer passed with Unicode/emoji, a literal newline, exact surrounding text and UTF-16 caret, observed consumption, restored clipboard, and released ownership. Live observation and independent durable replay confirmed every chat’s messages were unchanged; no text was submitted. Evidence on the validation host: /tmp/ace-literal-composer.bTz4ep/validation-ready/result.json.
Real pi/Anthropic completed-insertion and Stop-during-pending-paste cases also passed. Restarting the isolated channel worker retained exact tool results/images and provider usage without repeating input. Stop preserved the partial-input warning, and the original pending paste arrived once afterward. Evidence: /tmp/ace-literal-channel.5gwa0r/completed/validation-completed/result.json and /tmp/ace-literal-channel.5gwa0r/stop/validation-stop/result.json.
This closes the actual-composer newline/submission defect. Broader hosted validation remains in #65, and the remaining clipboard ownership failure/recovery matrix stays open in #78. Public clipboard tools remain #75. All disposable fixture apps are closed, and final cleanup preserved the current clipboard generation.
Activity
iamnbutler commentedon Oct 5, 2026
The current plain-text paste path prevented submission in Ace's real composer: one contextual replacement preserved the surrounding draft, and read-only watches plus an independent replay confirmed every chat's messages were unchanged.
The native result correctly remained
unknown/retained_unverified/reserved: the intended single newline became a paragraph separator in the composer. BothAXValueand a separate signed, read-onlyAXStringForRangeprobe returned two newlines (100 UTF-16 units), while the intended draft was 99 units. This rules out treating a different AX text reader or newline normalization as proof of the exact edit.Source inspection found ProseMirror's default plain-text clipboard parser splits newline runs into paragraph nodes. #67 will include a small composer correction that preserves plain-text line breaks as the existing
hard_breaknodes. Rich HTML paste and code-block handling keep their existing paths. Native exact-text verification and clipboard ownership remain unchanged. Fresh actual-composer, delayed ownership, real-model completion/replay, and Stop checks remain before merge.Evidence:
/tmp/ace-literal-composer.EEotCj/validation-activated/,/tmp/ace-literal-composer-post-observation.json, and/tmp/ace-literal-ax-window-probe-result.json. The native request drained and its exact receiver process exited before test cleanup; newer clipboard contents were preserved when the explicit recovery check found an ownership mismatch.