Fix links of footnotes referenced more than once

What does this MR do and why?

When a footnote is referenced more than once, the engine gives the later references ids such as fnref-1-2, all linking to #fn-1. FootnoteFilter, which adds a random suffix to footnote ids, took the footnote name from the reference id, found no footnote named 1-2 and skipped the reference, so every reference after the first linked to a missing anchor and only the first back-reference got the suffix.

This MR takes the footnote from the href and adds the suffix to every back-reference. It finds the footnote by comparing ids instead of building a CSS selector from its name, so a name with a space or a line break in hand-written HTML no longer raises Nokogiri::CSS::SyntaxError.

This MR needed !265760 (merged), which merged on 2026-10-30. The rich text editor read a reference's name from its id, and its pattern would read the fixed references as [^null]; !265760 (merged) makes it read the href. GitLab.com runs mixed versions during a deploy, so the editor change went first.

  • Cached HTML is fixed when next rendered; I did not bump CACHE_COMMONMARK_VERSION, which has its own rollout procedure.
  • No feature flag: a footnote referenced once renders as before.

The new specs fail on master (4 RSpec examples: 3 in the gitlab-banzai gem, where master moved FootnoteFilter, and 1 in the monolith) and pass with this MR.

Specs and checks I ran
  • gems/gitlab-banzai, with the gem's own bundle: 437 examples, 0 failures; with master's filter the 3 new examples in spec/banzai/filter/footnote_filter_spec.rb fail.
  • spec/lib/banzai/filter/footnote_filter_spec.rb (6 examples; with master's filter the new one fails), the sanitization, emoji and Markdown filter specs, the full and GFM pipeline specs, spec/initializers/banzai_spec.rb and spec/features/markdown/markdown_spec.rb: 271 examples, 0 failures.
  • RuboCop on the three files and scripts/lint/commit_linter.rb: clean. Ruby 3.3.11 throughout.
  • The engine renders repeated footnotes the same with gitlab-glfm-markdown 0.0.45 and 0.0.47, and the full pipeline example passes with 0.0.48, the version in master's Gemfile.lock now.

References

Closes #636343 (closed)

Depended on !265760 (merged), merged on 2026-10-30.

Screenshots or screen recordings

No visual change. For a[^1] b[^1] and [^1]: note, with GITLAB_TEST_FOOTNOTE_ID=42:

Element Before After
Second reference href="#fn-1", no footnote-ref href="#fn-1-42" id="fnref-1-2-42", footnote-ref
Its back-reference href="#fnref-1-2", no footnote-backref href="#fnref-1-2-42", footnote-backref
Rich text editor output, with !265760 (merged) b[<sup>1</sup>](#fn-1) b[^1]

How to set up and validate locally

  1. Create an issue with this description:

    First reference[^1] and second reference[^1].
    
    [^1]: The note.
  2. Select the second superscript 1: on master nothing happens; with this MR it scrolls to the note.

  3. Edit the description in the rich text editor (type a character and delete it), then switch back to plain text: on master the second reference becomes [<sup>1</sup>](#fn-1); with this MR it stays [^1].

I checked step 3 with the editor's serializer in Jest, not in a browser.

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 José M. Requena Plens

Merge request reports

Loading
Loading