Keep the rich text editor document load out of the undo history

What does this MR do and why?

The rich text editor loads a document by replacing the editor content in one ProseMirror transaction (ContentEditor#setSerializedContent) that only sets preventUpdate. prosemirror-history records that transaction like any edit, so it is the first undoable step of a freshly mounted editor. markdown_editor.vue renders the plain text field and the rich text editor with v-if and v-else, so every switch back to rich text mounts a new editor whose only history entry is that load. The first Cmd+Z after opening a field in rich text mode, or after any plain text to rich text switch, therefore removes the whole document in one step. Redo brings it back only while nothing has been typed since, because typing truncates the redo stack.

The load transaction now carries addToHistory: false, in the plain branch and in the collaborative seed (y-prosemirror honours the same meta through captureTransaction, so the seeding client's Yjs undo manager skips the seed too). setSerializedContent takes an addToHistory option that defaults to false; the component passes true from its markdown prop watcher, the path taken when a description template is applied on the create form, a saved reply is appended to a comment, or generated content is inserted into a merge request description. Those replace a document the user is working in, so Cmd+Z keeps reverting them as it does today. Undo and redo of edits made after the load are unchanged.

Related to Rich text <-> Markdown source toggle collapses undo history to one step (607833) and When using undo (Cmd+Z), Content Editor removes the entire content (390831).

Resolves #628277 (closed)

References

  • Reproduced in jest against the real ContentEditor service with the History extension: at the merge base, undo() right after setSerializedContent returns true and empties the document; with this change it returns false and the document is intact. Seven jest examples are new and six fail at the merge base: the loaded document is not undone (a paragraph, and a document with a heading, a list nested in a blockquote, a table and a code block); undo and redo touch only the edits made after the load; a replacement loaded with addToHistory: true is undone as one step with the earlier typing still undoable behind it; the collaborative seed dispatches with addToHistory: false; the component passes addToHistory: true from its markdown watcher. The service and component specs pass 61/61 and the whole spec/frontend/content_editor tree 1205/1205, under Vue 2 and Vue 3.
  • The wipe does not need an idle editor. prosemirror-history folds an edit made within its 500 ms newGroupDelay into the previous history entry, so at the merge base typing that starts within half a second of the load joins the load's undo group and a single Cmd+Z removes the typing and the whole document together; after a longer pause the first Cmd+Z removes only the typing and the second one wipes the document. With this change the load is not an entry, so both paces leave the document alone (matrix rows: typed within half a second of the load, and Cmd+Z twice).
  • A new end-to-end example in the rich text editor common shared examples types a sentence in plain text, switches to rich text, presses Cmd+Z and expects the sentence to survive, then types a word and presses Cmd+Z again to check that only the word goes. It fails at the merge base and passes with this change, and is included by the new issue form, issue comment, merge request comment and merge request edit specs. Pre-approval pipelines select RSpec predictively and do not map the shared example to the specs that include it, so it first runs in CI at tier-2 or tier-3, or with pipeline:run-all-rspec.
  • The load has been a history entry since the editor moved to Tiptap v2 in Upgrade TipTap to v2 (!60006); Separate Markdown Serializer and Deserializer in the Content Editor (!81034) added preventUpdate to the same transaction and Update Content Editor Packages (!88851) gave it its current replaceWith shape. Tiptap's own setContent command has the same gap, which is why the collaborative branch sets the meta explicitly.
  • Both earlier reports describe this mechanism. 607833 (2026-08) was closed by its reporter after the undo part was assessed as a nice-to-have; 390831 (2023) was closed as undo grouping of fast typing, with recordings of the wipe when starting an editing session and when switching editors.

Screenshots or screen recordings

Scenario Before After
Fix: description opened in rich text, Cmd+Z once 01_fix_open_description_in_rich_text 01_fix_open_description_in_rich_text
Fix: comment typed in rich text, switched to plain text and back, Cmd+Z once 02_fix_comment_switched_to_plain_and_back 02_fix_comment_switched_to_plain_and_back
Fix: complex document (heading, quote with a nested list, table, code block, task list), Cmd+Z once 03_fix_complex_document 03_fix_complex_document
Fix: text typed before a plain-text round trip, Cmd+Z once after switching back 07_edge_typed_before_the_switch 07_edge_typed_before_the_switch
Fix: typed after the load, Cmd+Z twice (the second press) 04_fix_second_undo_after_typing 04_fix_second_undo_after_typing
Fix: typed within half a second of the load, Cmd+Z once (at the base the typing joins the load's undo group) 11_fix_typed_immediately_then_undo 11_fix_typed_immediately_then_undo
Unchanged: description template applied on the create form, Cmd+Z removes the template (it stays undoable) 05_edge_template_applied_then_undo 05_edge_template_applied_then_undo
Unchanged: pasted text, Cmd+Z removes only the paste 06_edge_paste_then_undo 06_edge_paste_then_undo
Unchanged: typed after the load, Cmd+Z removes only the typing 08_same_undo_removes_only_the_typing 08_same_undo_removes_only_the_typing
Unchanged: Cmd+Shift+Z restores the typing 09_same_redo_restores_the_typing 09_same_redo_restores_the_typing
Unchanged: edit inside a table cell, Cmd+Z reverts only the cell edit 10_same_undo_inside_a_table_cell 10_same_undo_inside_a_table_cell

Every row is staged in real headless Chrome: documents are loaded the way users load them (an existing description opened with Edit under a rich text preference, a comment round-tripped through plain text, a template chosen from the dropdown) and undo is the real Cmd+Z keystroke. The Unchanged rows are byte-identical between before and after, except the template row, which differs only by caret antialiasing (mean pixel error 0.0001).

Recordings

Story Before After
A comment typed in rich text, switched to plain text and back, then Cmd+Z once comment_switch_before comment_switch_after
A description opened in rich text, typed into, then Cmd+Z twice typed_then_undo_twice_before typed_then_undo_twice_after

Three beats for the comment story (typed, switched back, Cmd+Z once) and four for the description (opened, typed, Cmd+Z once, Cmd+Z again). The frames are byte-identical between the sides until the undo beat this change affects, the third for the comment and the fourth for the description, where the base empties the editor and this change leaves the document alone.

How to set up and validate locally

  1. Open an issue that has a description and select Edit. If the editor opens in plain text, select Switch to rich text editing.
  2. Press Cmd+Z (Ctrl+Z on Windows and Linux) once. Before this change the description empties; after it nothing changes.
  3. In the comment box, switch to rich text, type a sentence, switch to plain text and back to rich text, then press Cmd+Z once. Before this change the comment empties; after it nothing changes.
  4. Type a few words after the loaded text and press Cmd+Z: only the typed words go (both). Press Cmd+Shift+Z: they come back (both). From the typed state press Cmd+Z twice: before this change the second press wipes the document; after it the second press does nothing. Start typing within half a second of the editor loading and press Cmd+Z once: before this change the typing and the document go together; after it only the typing goes.
  5. Regression checks: on the new issue form choose a description template, then press Cmd+Z in the editor: the template is removed (unchanged). Paste text and press Cmd+Z: the paste is removed and the rest stays (unchanged). Edit a table cell and press Cmd+Z: only the cell edit reverts (unchanged).

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.

Edited by Paul W

Merge request reports

Loading
Loading