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; withmaster's filter the 3 new examples inspec/banzai/filter/footnote_filter_spec.rbfail.spec/lib/banzai/filter/footnote_filter_spec.rb(6 examples; withmaster's filter the new one fails), the sanitization, emoji and Markdown filter specs, the full and GFM pipeline specs,spec/initializers/banzai_spec.rbandspec/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-markdown0.0.45 and 0.0.47, and the full pipeline example passes with 0.0.48, the version inmaster'sGemfile.locknow.
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
-
Create an issue with this description:
First reference[^1] and second reference[^1]. [^1]: The note. -
Select the second superscript
1: onmasternothing happens; with this MR it scrolls to the note. -
Edit the description in the rich text editor (type a character and delete it), then switch back to plain text: on
masterthe 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.