Skip to content

fix: a paste with CR line breaks draws as the paragraphs it is - #388

Merged
sepehr-safari merged 1 commit into
mainfrom
a-paste-with-cr-line-breaks-draws-cleanly
Sep 25, 2026
Merged

sepehr-safari merged 1 commit into
mainfrom
a-paste-with-cr-line-breaks-draws-cleanly

Conversation

@sepehr-safari

Copy link
Copy Markdown
Contributor

Closes #165

What it was

The Native SDK lays text out breaking only at LF (and wraps only at space and tab), so end.\rNext is one word on one row. On macOS the host draws each laid-out row through AppKit, which breaks at a lone CR, so the words after it are drawn a row lower, on top of the next row. The same holds for vertical tab, form feed, NEL, U+2028 and U+2029. CRLF draws correctly, because its CR is always the last byte of a row.

Reproduced for the first time by pasting through the real clipboard into the composer of an automation build (isolated home, fixture key, loopback relay). Before the fix, bare CR, U+2028 and vertical tab each overprint in the host render; CRLF and LF are clean. The field-width change the old comment credited was not the cause; the comment is corrected.

The fix

  • plainLineBreaks turns those separators into LF. applyPlainEdit runs every insert into the draft, the reply box and the profile's about field through it, and drafts restored from disk go through setPlain.
  • The editor keeps its own copy of the text and holds far more than the draft's 4096 bytes. Whenever the stored text ends up different from what the editor inserted (a separator replaced, or a paste cut to fit), the editor takes the stored text and puts its caret at the end; ui.el has no way to pass it a selection. So the model's caret moves to the end too. Before this, a paste cut to fit in the middle of a note left the two carets apart, and the next key landed out of sight. A cut that splits a CRLF would leave a lone CR, so the whole text is made plain again then. An insert refused outright changes nothing and moves nothing.

What it costs

Only a paste that had to change pays: the caret ends at the end of the text, the editor's undo history for that box restarts, and the box does not scroll to the caret until the next key. CRLF and ordinary pastes are untouched, caret, undo and scroll included.

Verified

  • Live, real Cmd+V into the composer, host render inspected: bare CR, U+2028 and VT overprint before and draw cleanly after; CRLF stays byte-identical with caret, scroll and Cmd+Z intact; after a mid-text bare-CR paste, typed keys land at the drawn caret.
  • zig build test: 661 pass, including new tests for every separator, near-miss byte sequences, the UTF-8 boundary when the buffer fills, all three boxes, the caret after a changed paste, a clamp that splits a CRLF, a clamped mid-text paste, and a refused paste. Six deliberate breaks of the fix were each caught by those tests.

Not changed

  • An about field read from a relay is shown as published, separators and all. Normalizing it would rewrite the profile on the next save.
  • A paste into a box that is already full is refused by the draft while the editor keeps showing it. That predates this change and is left as it was.

The toolkit lays text out breaking only at LF, while on macOS the host draws each laid-out row through AppKit, which also breaks at a lone CR, vertical tab, form feed, NEL, U+2028 and U+2029. Pasting paragraphs separated by any of those drew the words after each break one row lower, on top of the next row. The text was intact; only the drawing was wrong.

Those separators now become LF before an edit reaches the draft, the reply box or the profile's about field, and before a draft restored from disk is shown. CRLF is left alone: its CR ends a row, so it always drew correctly.

When the stored text ends up different from what the editor inserted, because a separator was replaced or the paste was cut to fit, the editor takes the stored text and moves its caret to the end, and the toolkit has no way to say otherwise. The model's caret now goes there too, so typing lands where the caret is drawn; before, a paste cut to fit in the middle of a note left the two carets apart. A cut that splits a CRLF is made plain as well. The comment that blamed the field's width for this is corrected.
@sepehr-safari
sepehr-safari merged commit 31b402b into main Sep 25, 2026
11 of 12 checks passed
@sepehr-safari
sepehr-safari deleted the a-paste-with-cr-line-breaks-draws-cleanly branch September 25, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pasting a note draws the text scrambled

1 participant