fix: keep a hard break before an attention run#78
Conversation
When a hard break was immediately followed by an attention run (emphasis or
strong) whose inner side is whitespace, the break was turned into text on a
round-trip:
{type: 'paragraph', children: [
{type: 'break'},
{type: 'strong', children: [{type: 'text', value: ' x'}]}
]}
serialized to `\
** x**`, where the break’s line ending was
character-referenced. The attention run asks to encode the character on its
outer side (here whitespace, to keep the run forming), and that character
happened to be the `\n` of the break, so the break was destroyed.
A line ending can never fuse with the following run — the run starts on a
new line — so skip the surrounding encoding when the preceding character is
a line ending.
|
Hi! It seems you removed the template which we require. Here are our templates (pick the one you want to use and click *raw* to see its source): I won’t send you any further notifications about this, but I’ll keep on updating this comment, and hide it when done! Thanks, |
| // A line ending (such as a hard break) is structural: encoding it would | ||
| // break the break, and it can’t fuse with the following attention run. |
There was a problem hiding this comment.
these comment's aren't needed
Are you sure? |
|
@wooorm fair to call that out, the wording in the description was loose. The real reason isn't "it starts on a new line" by itself. The surrounding whitespace that needs guarding is on the run's inner side and is already character-referenced, so the line after the break never begins with a literal Rather than just assert that, I checked it with a differential fuzz over 60k random paragraphs mixing breaks, emphasis/strong (both
For example, @ChristianMurphy on the use case: this turned up round-tripping rehype/remark content where a hard break sits right before emphasis/strong whose inner edge is whitespace, and |
|
Honest answer: no specific app. I hit this while round-trip testing the serializer. It's real data loss though (the break turns into a text node), so it seemed worth fixing. #12 is a different case (emphasis in emphasis). My change only guards line endings, so it doesn't touch that path and the tests stay green. I didn't try to fix #12. Removed the comments. |
Problem
When a hard
breakis immediately followed by an attention run (emphasis/strong) whose inner side is whitespace, the break is destroyed on a round-trip:The break’s line ending is character-referenced to

, sofromMarkdownre-parses\
as an escaped backslash followed by literal text — thebreaknode becomes atextnode:Cause
An attention run sets
attentionEncodeSurroundingInfo.beforeto encode the character on its outer side when that character is whitespace (so the run keeps forming, e.g.x* *z). IncontainerPhrasing, that character is the last character of the previous sibling’s output. When the previous sibling is a hard break, its last character is the\n, which gets character-referenced — splitting the break.Fix
A line ending can never fuse with the following run, because the run starts on a new line. So skip the surrounding encoding when the preceding character is a line ending (
\n/\r). The inner-side encoding (the leading space → ) is unaffected, so the run still forms:Tests
Added a case under
breakasserting the serialized output keeps the break (test/index.js) — red before, green after. Full suite green (479/479);prettier,xo, andtsc --buildall clean.