Skip to content

Fix pasted text colors and Windows line spacing - #2796

Open
Mylosis wants to merge 4 commits into
Foundry376:masterfrom
Mylosis:fix/pasted-dark-mode-formatting
Open

Fix pasted text colors and Windows line spacing#2796
Mylosis wants to merge 4 commits into
Foundry376:masterfrom
Mylosis:fix/pasted-dark-mode-formatting

Conversation

@Mylosis

@Mylosis Mylosis commented Aug 13, 2026

Copy link
Copy Markdown

Summary

  • normalize Windows CRLF line endings before Slate deserializes plain-text paste data
  • discard neutral near-black and near-white source-page text colors while preserving intentional non-neutral colors
  • add regression coverage for both cases

Problem

Mailspring's plain and rich paste paths can fail in opposite ways.

For plain paste on Windows, Slate's plain-text serializer splits input on \n without first normalizing CRLF. This leaves a trailing \r in each block. A blank line becomes a block containing only \r, and Mailspring's existing HTML serialization workaround converts that block to  . The composer can visually compress this structure, while receiving clients render the placeholder blocks at full height, producing recipient-side spacing that does not match the composition view.

For rich paste, source pages using a dark color scheme can put their default near-white text color on the clipboard. Mailspring preserves that foreground color but not the page background, so the sent message can become unreadable in a light mail client. The inverse can happen when near-black defaults cross into a dark presentation.

Implementation

Plain paste is handled explicitly at the composer ingress: CRLF and lone CR are normalized to LF, then the existing Slate plain serializer is used with the current block and marks. This keeps blank lines as genuinely empty Slate blocks instead of \r blocks.

Color filtering treats a color as an inherited page default only when it is nearly neutral, meaning its lightest and darkest RGB channels differ by no more than 12, and it also sits at or below roughly #202020 or at or above roughly #dfdfdf. Named black, white, and transparent are included. Saturated colors and mid-range greys are left alone, so #333 and #ddd both survive.

This is an intentional readability policy rather than a reliable distinction between inherited and deliberate colors. Mailspring has no background-color mark and block deserialization keeps only className, so a copied dark background never survives conversion. Preserving a near-white foreground therefore already produces unreadable output on a light message background, and dropping it restores readable inherited text.

Two behavior changes are worth calling out, and both are covered by specs:

  • Alpha is deliberately ignored, so rgba values are classified from their RGB channels alone.
  • transparent was previously preserved and is now dropped, which makes invisible pasted text visible rather than sending it hidden.

Validation

  • npx tsc -p app/tsconfig.json --noEmit
  • targeted ESLint checks for the changed composer and spec files
  • manual end-to-end validation on Windows with Mailspring 1.23.0 using rich paste, Ctrl+Shift+V, and Paste and Match Style
  • decoded the outgoing multipart message before the change and confirmed both failures in the HTML Mailspring actually sent: CR-derived   placeholder blocks from the plain and match-style sections, and a repeated near-white source text color in the rich-paste section
  • decoded the outgoing message after the change and confirmed newly composed sections had consistent paragraph structure and no CR-derived   placeholder blocks, while   elements inside quoted history from the earlier message were left untouched

The post-change message was not a controlled comparison of the same clipboard payload, so the color behavior is pinned by the regression specs rather than by that sample. The specs cover both thresholds, a value just outside the near-black threshold, alpha, transparent, and that dropping a neutral foreground leaves the source font family intact.

No message bodies or account data are included in the tests or this PR.

Removed from this branch

An earlier revision also converted literal newlines beneath white-space: pre-wrap into <br> elements. That change lived in the shared parseHtml, so it ran for every convertFromHTML call, including draft loading, templates and ordinary quoted blocks, rather than only pasted clipboard HTML. Ordinary blockquote and .gmail_quote content are normal Slate blocks, so an edited draft could be reserialized with newly inserted <br> elements well outside any paste.

I also have no reproducible clipboard payload that demonstrates the newline failure it was meant to fix. It has been removed rather than rescoped. If a payload turns up, it should get its own PR, applied to pasted rich HTML only, after InlineStyleTransformer.runSync() and immediately before convertFromHTML(), with its own tests.

The white-space: pre-wrap conversion lived in the shared parseHtml, so it ran
for every convertFromHTML call, including draft loading, templates and ordinary
quoted blocks, rather than only pasted clipboard HTML. Ordinary blockquote and
.gmail_quote content are normal Slate blocks, so an edited draft could be
reserialized with newly inserted <br> elements outside any paste. No reproducible
clipboard payload demonstrates the newline failure it was meant to fix, so it is
removed here rather than rescoped.

Color specs now assert on the specific rejected value instead of the substring
"color", which would have passed even if mark handling broke entirely. They also
cover the near-black threshold and a value just outside it, alpha handling,
transparent, and that dropping a neutral foreground leaves the source font
family intact.
@Mylosis
Mylosis marked this pull request as ready for review August 14, 2026 12:04
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.

1 participant