Skip fenced code blocks when isolating quick actions in the rich text editor
What does this MR do and why?
Every switch from plain text to rich text adds two blank lines after each line inside a fenced code block that starts with /, and they compound: // comment, /usr/bin/env and /api/v4/projects all grow two more blank lines on each round trip. A Premium customer abandoned the rich text editor over it. Pasting such code into the editor does the same, because the paste path uses the same deserializer.
Root cause: before rendering, transformQuickActions appends two blank lines to every line starting with /, so a quick action renders as its own paragraph. It is a regex over the whole document with no notion of code blocks.
The transform is now fence-aware. It scans line by line, tracks opening and closing fences of backticks or tildes per CommonMark (up to three spaces of indentation, any info string, a closing fence of the same character at least as long with nothing after it), and leaves everything inside a fence alone. An unterminated fence runs to the end of the document. Lines outside fences are transformed exactly as before, including a quick action on the line straight after a closing fence.
Scope:
- Indented code blocks needed no handling: their lines start with four spaces or a tab, so a slash is never at column 0.
- Fences inside list items and blockquotes were already safe for the same reason, so the tracker follows only top-level fences. The unit spec pins both.
- HTML blocks and the quick action grammar are untouched. The complete answer is the AST-level rework kivikakk sketched in the issue: recognise quick actions on the rendered document and drop the regex entirely. This is the smallest change that stops the damage now, and it disappears with the transform when that rework lands.
Unchanged: the reference-definition case the transform exists for, all output outside fences, four-space indentation, and a backtick in an info string not opening a fence.
Resolves #578108 (closed)
Related to #577963 (closed)
Evidence
- Reproduced end to end against the real render API: the issue's own code block, switched and edited twice, comes back with four blank lines after
// testing commentat the merge base and byte-identical here. - Unit spec: 11 of 24 examples fail at the merge base and pass here (backtick and tilde fences, info strings, indentation, longer closing fences, a nested shorter fence, an unterminated fence, quick actions around and after fences). The other 13 pin what must not change.
content_editortree 1219/1219 under Vue 2 and Vue 3. - New end-to-end example in the code block shared examples. Fails at the merge base with two blank lines after each of the two slash lines. Predictive RSpec does not map it, so it first runs at tier-2 or with
pipeline:run-all-rspec. - Root cause independently confirmed in the issue thread by armin.pasalic, with himkp on why quick actions are wrapped and kivikakk on the AST-level fix.
- Nine matrix rows and a GIF pair below. Two planned rows are still missing and follow in a re-capture; see the caption.
Origin of the transform, and duplicates
Fix reference style links after quick actions in RTE (!187001) added it so a reference definition after a quick action parses; Explain quick actions inline in RTE (!158367) is why a quick action needs its own paragraph.
Issue 578107 is the same report filed a minute earlier, closed as a duplicate. Issue 577963 is the same defect reported a day earlier and stays open until this merges.
Screenshots or screen recordings
| Scenario | Before | After |
|---|---|---|
| Fix: the issue's code block with a // comment line, plain text after two round trips | ![]() |
![]() |
| Fix: a shebang line and two lines that start with a path (/usr/local/bin, /opt) | ![]() |
![]() |
| Fix: a tilde fence (the editor writes backtick fences on both sides) | ![]() |
![]() |
| Nested (unchanged on both sides): a code block inside a list item | ![]() |
![]() |
| Nested (unchanged on both sides): a code block inside a blockquote | ![]() |
![]() |
| Edge: /label bug on the line right after the closing fence, in rich text | ![]() |
![]() |
| Unchanged: /label bug on its own line in prose, in rich text | ![]() |
![]() |
| Unchanged: /label bug applied on save after a round trip (the created issue) | ![]() |
![]() |
| Unchanged: /assign on the line after a paragraph line | ![]() |
![]() |
Every row is staged the same way in real headless Chrome, at 9db3b520 against the merge base 26225e7e: the markdown is set in the plain text editor, switched to rich text, a word is typed into the last paragraph so the editor re-serializes, switched back, and the cycle repeated once. The five Unchanged rows are byte-identical between before and after. Two planned rows are not attached because their staging did not complete on both refs (an unterminated fence at the end of the document, and a slash inside prose); they follow in a re-capture.
Recordings
| Story | Before | After |
|---|---|---|
| The issue's code block through three switches to rich text, with a word typed under the block between them | ![]() |
![]() |
Three beats per side, all in rich text: the block after the first, second and third switch. Before, the block gains two blank lines after the comment line on every switch (2, 4, 6). After, the three frames differ only by the typed words.
How to set up and validate locally
-
On the new issue page, keep the description in plain text editing and paste:
Notes ``` var test1 10 // testing comment /usr/local/bin/deploy --dry-run test 1 ``` -
Select Switch to rich text editing. Before: two blank lines after
// testing commentand two after/usr/local/bin/deploy --dry-run. After: the block renders as typed. -
Type a word into
Notesand switch back to plain text. Before: the blank lines are now in the markdown, and every further round trip adds two more. After: unchanged apart from the word. -
Regressions:
/label ~bugon its own line under the block still shows its inline explanation and applies on save; a slash inside prose (see /docs/path) and a fence inside a list item or blockquote are unchanged on both sides; pasting the same block from the clipboard no longer adds blank lines.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.



















