You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A mark that wraps a link is dropped from every inline node that follows a nested mark of the same type inside the link's label. The loss happens while the file is parsed into the editor document, so an ordinary open and save writes the emphasis away. In one shape the emphasis is not merely lost but lands on different words than the author wrote, and in another the loss reaches text that sits outside the link.
The scope is the parse: carry the wrapping mark across the whole run, without changing how redundant same-type nesting collapses.
Steps to reproduce
Create a Markdown file containing **bold [**a** b](./doc.md) tail**.
Open it in Leafdown.
Edit elsewhere in the document and save.
Reopen the file.
Expected behavior
The authored span stays bold end to end: **bold [**a** b](./doc.md) tail**.
Existing behavior stays stable: a label nesting a different mark type is unaffected, and redundant same-type nesting may still collapse, since **[a **b**](./doc.md)** and **[a b](./doc.md)** mean the same thing.
Actual behavior
The file is written as **bold** [**a** b](./doc.md) tail. Only the leading word keeps its bold; the link and the trailing text lose it.
Measured across shapes:
Input
Saved as
Result
**[**a** b](./doc.md)**
[**a** b](./doc.md)
the wrapping bold is dropped
**[a **b** c](./doc.md)**
[**a b** c](./doc.md)
bold covers different words than authored
**bold [**a** b](./doc.md) tail**
**bold** [**a** b](./doc.md) tail
text outside the link loses its bold
*[*a* b](./doc.md)*
[*a* b](./doc.md)
same for emphasis
~~[~~a~~ b](./doc.md)~~
[~~a~~ b](./doc.md)
same for strikethrough
**[*a* b](./doc.md)**
unchanged
a different mark type is unaffected
**[a **b**](./doc.md)**
**[a b](./doc.md)**
acceptable, the nesting is redundant
Related context
Related issues or pull requests: feat: project a link label that contains an image #212, where this surfaced while carrying an image through a projected link label. It is independent of images and of source projection.
Documentation, decisions, or prior investigation: measured against main at b4d9e0d through the editor test harness, by mounting each input and reading getMarkdown() back. Round-trip coverage for link labels lives in markdownCompatibility.test.tsx.
Done when
A mark wrapping a link survives load and save when the label nests the same mark type, including for text outside the link inside the same wrapper.
A different nested mark type, and redundant same-type nesting, keep their current behavior.
Regression coverage asserts the round trip through the serialized Markdown.
Changelog entry, since authored formatting is currently lost on save.
Notes, logs, screenshots
Diagnosis
remark parses the input correctly. For **[**a** b](./doc.md)** the AST is strong(link(strong("a"), text(" b"))), so the wrapping mark is present after parsing. The document produced by the mdast to ProseMirror conversion is not: its inline nodes are "a"[strong,link] + " b"[link]. The mark is therefore already missing from the document model before any serializer runs, so no serializer change can recover it.
Nesting in the final child keeps the wrapper, while nesting in the first or a middle child loses it. That asymmetry points at the mark set being closed once too often when a nested mark of the same type ends, rather than at anything specific to links.
Implementation direction
The approach is intentionally open, with one constraint: the fix has to land at or before the parse, because the document is already wrong by the time serialization runs.
One untested starting point is to flatten redundant same-type nesting in the mdast before conversion, so strong(link(strong(x), y)) becomes strong(link(x, y)), which would remove the case without depending on how the conversion handles overlapping marks. Whether this is better fixed upstream in Milkdown's parser has not been investigated.
Out of scope
Images and source projection. Neither is involved; the case reproduces with plain text in a closed document.
How redundant same-type nesting collapses. **[a **b**](./doc.md)** saving as **[a b](./doc.md)** is correct and should stay.
Summary
A mark that wraps a link is dropped from every inline node that follows a nested mark of the same type inside the link's label. The loss happens while the file is parsed into the editor document, so an ordinary open and save writes the emphasis away. In one shape the emphasis is not merely lost but lands on different words than the author wrote, and in another the loss reaches text that sits outside the link.
The scope is the parse: carry the wrapping mark across the whole run, without changing how redundant same-type nesting collapses.
Steps to reproduce
**bold [**a** b](./doc.md) tail**.Expected behavior
The authored span stays bold end to end:
**bold [**a** b](./doc.md) tail**.Existing behavior stays stable: a label nesting a different mark type is unaffected, and redundant same-type nesting may still collapse, since
**[a **b**](./doc.md)**and**[a b](./doc.md)**mean the same thing.Actual behavior
The file is written as
**bold** [**a** b](./doc.md) tail. Only the leading word keeps its bold; the link and the trailing text lose it.Measured across shapes:
**[**a** b](./doc.md)**[**a** b](./doc.md)**[a **b** c](./doc.md)**[**a b** c](./doc.md)**bold [**a** b](./doc.md) tail****bold** [**a** b](./doc.md) tail*[*a* b](./doc.md)*[*a* b](./doc.md)~~[~~a~~ b](./doc.md)~~[~~a~~ b](./doc.md)**[*a* b](./doc.md)****[a **b**](./doc.md)****[a b](./doc.md)**Related context
mainat b4d9e0d through the editor test harness, by mounting each input and readinggetMarkdown()back. Round-trip coverage for link labels lives inmarkdownCompatibility.test.tsx.Done when
Notes, logs, screenshots
Diagnosis
remark parses the input correctly. For
**[**a** b](./doc.md)**the AST isstrong(link(strong("a"), text(" b"))), so the wrapping mark is present after parsing. The document produced by the mdast to ProseMirror conversion is not: its inline nodes are"a"[strong,link] + " b"[link]. The mark is therefore already missing from the document model before any serializer runs, so no serializer change can recover it.Nesting in the final child keeps the wrapper, while nesting in the first or a middle child loses it. That asymmetry points at the mark set being closed once too often when a nested mark of the same type ends, rather than at anything specific to links.
Implementation direction
The approach is intentionally open, with one constraint: the fix has to land at or before the parse, because the document is already wrong by the time serialization runs.
One untested starting point is to flatten redundant same-type nesting in the mdast before conversion, so
strong(link(strong(x), y))becomesstrong(link(x, y)), which would remove the case without depending on how the conversion handles overlapping marks. Whether this is better fixed upstream in Milkdown's parser has not been investigated.Out of scope
**[a **b**](./doc.md)**saving as**[a b](./doc.md)**is correct and should stay.