Skip to content

Preserve literal newlines when inserting text into web composers #76

Closed

Description

@iamnbutler

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 Message AXTextArea.
  • Call desktop_insert with Ace 🧑🏽‍💻 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 of alpha 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 in keyboard-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

  1. iamnbutler commented on Oct 5, 2026

    @iamnbutler
    ContributorAuthor

    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. Both AXValue and a separate signed, read-only AXStringForRange probe 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_break nodes. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions