Rich text editor drops images and links that point at repository files on the first save

Summary

Opening a description in the rich text editor deletes images whose path points at a file in the repository (![shot](docs/img/shot.png)) and turns links to repository files ([readme](README.md)) into plain text. The editor shows nothing where the image was and a reference-styled chip where the link was; the first save after any edit writes the damaged markdown (saving without an edit keeps the source, because the editor only re-serializes a document once it changes). Uploaded images (/uploads/...) and absolute URLs are unaffected. Linked images ([![shot](docs/img/shot.png)](https://example.com)) survive the editor but are saved with the resolved path (/group/project/-/raw/master/docs/img/shot.png), which re-renders as a broken image.

Steps to reproduce

  1. In a project with a repository, create an issue whose description is ![logo](files/images/logo-black.png) and [readme](README.md) (any existing repository image and file). The rendered description shows the image and the link.
  2. Select Edit title and description, then Switch to rich text editing.
  3. Observe: no image in the editor; readme renders as a chip rather than a link.
  4. Type anything (one character anywhere is enough), then select Save changes.
  5. Observe: the description shows no image and readme is plain text. Open the plain text editor: the image markdown is gone and the link brackets are gone. Saving without typing keeps the source, so the loss is easy to miss until a real edit.

Example Project

Any project with a repository on GitLab.com (reproduced against the production markdown renderer on 2026-09-08 with the gitlab-org/gitlab project).

What is the current bug behavior?

The image and the link are lost on the first save after an edit in rich text. A linked image is saved with a resolved path that renders broken.

What is the expected correct behavior?

The editor shows the image and the link, and saving without edits leaves the markdown byte-identical, as it does for uploads and absolute URLs.

Relevant logs and/or screenshots

Rendered HTML for ![shot](doc/img/shot.png) in gitlab-org/gitlab:

<a class="no-attachment-icon gfm" href="https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/img/shot.png" target="_blank" rel="noopener noreferrer"><img class="lazy gfm" data-src="/gitlab-org/gitlab/-/blob/master/doc/img/shot.png" alt="shot"></a>

Rendered HTML for [readme](README.md):

<a href="https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md" class="gfm">readme</a>

Deserializing then serializing ![shot](docs/img/shot.png) and ![abs](https://example.com/a.png) and ![up](/uploads/abc/x.png) with the editor's own services produces and ![abs](https://example.com/a.png) and ![up](/uploads/abc/x.png).

Output of checks

This bug happens on GitLab.com.

Possible fixes

Root cause: app/assets/javascripts/content_editor/extensions/reference.js claims every a.gfm anchor without data-link="true" at the highest parse priority (PARSE_HTML_PRIORITY_HIGHEST), beating extensions/image.js (a.no-attachment-icon, PARSE_HTML_PRIORITY_HIGH) and the link mark. lib/banzai/filter/repository_link_filter.rb adds the gfm class to every relative link and image it rewrites, without data-reference-type or data-link, so the anchor parses as an empty reference node and services/serializer/reference.js writes originalText || text, both empty. The filter also records no data-canonical-src, so the editor has no way to write the path back as written; UploadLinkFilter and WikiLinkFilter do record it. Fix: claim only anchors with data-reference-type in the reference extension, and record data-canonical-src in RepositoryLinkFilter. Merge request: Keep images and links to repository files in the rich text editor (!254235).

Fix: !254235 (merged)