Rich text editor writes an unrenderable closing delimiter when a formatted run ends at a link
Summary
When a bold, italic or strikethrough run in the rich text editor ends with a link and the next character is a letter or digit with no space (for example bold applied over see docs where docs is a link, then ify typed directly after the link), the markdown serializer writes the closing delimiter directly after the link: **see [docs](https://docs.gitlab.com)**ify. CommonMark does not treat a delimiter run that sits between ) and a letter as a closer, so the asterisks render literally.
Before Keep formatting marks in one span around links in the rich text editor (!254236) the serializer split the run instead (**see** [**docs**](https://docs.gitlab.com)ify), which rendered. With Fix reference spacing, intraword emphasis and quoted references on rich text editor save (!254251) the italic form behaves the same way (x*y [z](u)*w).
A second edge shares the root. A run that begins with a link and continues with a space and another marked run, **[docs](url) [more](url2)**, serializes as [**docs **](url)[**more**](url) before and after !254236: prosemirror-markdown's renderInline handles a whitespace-only text node whose marks differ from the open ones by pushing the space into the link text.
Steps to reproduce
- In a description in rich text mode, type
see docs, turndocsinto a link, selectsee docsand apply bold. - Place the caret directly after the link and type
ifywith no space. - Save, then view the rendered description and the plain text source.
Expected: the text renders as bold with the link inside it, and the saved markdown is renderable.
Actual: the rendered text shows literal ** after the link; the saved markdown has a closing delimiter in a position the parser cannot use.
Root cause
app/assets/javascripts/content_editor/services/serializer/link.js sets mixable: true (from !254236), so prosemirror-markdown keeps an open mark across a link, which fixes the common case of formatting wrapping a link. prosemirror-markdown has no flanking check: it does not know that a closer directly after ) and directly before a word character cannot close. The whitespace-only-node edge comes from renderInline reordering marks on a text node that contains only a space.
Proposed fix
In the markdown serializer, when a mark's closing delimiter would land directly after a link and directly before a letter, digit or underscore, emit the split form for that run (close the mark before the link and reopen it inside the link text), which the parser reads correctly. Keep a whitespace-only text node outside the link when its marks differ from the open ones. Cover bold, italic, strikethrough and bold plus italic at word boundaries and inside words in spec/frontend/content_editor/services/serializer/link_spec.js. The same change may be worth proposing upstream to prosemirror-markdown.
Impact
A survey of the 55,755 open gitlab-org/gitlab issue descriptions on 2026-09-09 (local mirror of the tracker) found 1,761 descriptions with a bold, italic or strikethrough run wrapping a link (the shape !254236 protects on the next rich text edit), 553 with a run ending exactly at a link followed by a space or punctuation (unaffected either way), and 3 with a closing delimiter directly before a letter (already unrenderable on master). The edge can only be produced by an edit in rich text; no stored markdown round-trips into it.
Related: Formatting text with links breaks in rich-text editor (RTE) (#514245).