Skip to content

🐛 Keep link titles through cross-reference resolution - #1196

Merged
chrisjsewell merged 7 commits into
executablebooks:masterfrom
dylanpulver:fix-link-title-cross-references
Nov 9, 2026
Merged

chrisjsewell merged 7 commits into
executablebooks:masterfrom
dylanpulver:fix-link-title-cross-references

Conversation

@dylanpulver

Copy link
Copy Markdown
Contributor

[text](#target "title") renders as a tooltip in CommonMark, and the docs say so (docs/syntax/cross-referencing.md: "in HTML titles are rendered as tooltips"). Most links lose it.

Checked against markdown-it-py's own RendererHTML over the same markdown, one Sphinx build:

link before after
[a](#index "T") doc root no yes
[b](#explicit-target "T") yes yes
[c](#some-section "T") no yes
[d](other.md "T") no yes
[e](other.md#anchor "T") no yes
[f](https://example.com "T") yes yes
[g](#nope "T") unresolved no yes

The reference renderer keeps all 7; MyST kept 2. Two places drop it:

  • transforms.py rebuilds the reference as a pending_xref and copies ids, names, dupnames — not reftitle.
  • _process_wrap_node copies title rather than aliasing it to reftitle, which is what every other copy_attributes call does.

Resolution then builds a fresh node, so the carry-over in myst_refs.py is needed too — but alone it fixes nothing: the title is already gone by then. With only that one-line change it is still 2 of 7, though it looks like the whole fix.

1247 passed, 12 skipped. The 3 failures (test_cmdline[40-linkify], test_extended_syntaxes, test_extended_syntaxes_text) are identical on unpatched master in the same session. ruff 0.15.20 and ruff-format clean.

Drafted with Claude Opus 5 (claude-opus-5); I ran and checked every number above.

A CommonMark link title, [text](#target "title"), was dropped whenever
the reference was rebuilt: the transform that turns a local anchor into a
pending_xref copied ids/names/dupnames but not reftitle, and
_process_wrap_node copied "title" instead of aliasing it to "reftitle".
The carry-over in `MystReferenceResolver.run` only copied the authored
title when the resolved node had none, so a domain that sets its own
`reftitle` (e.g. `py:function` sets the object name) beat what the author
wrote. The authored title now always wins, as it already does for
intersphinx and for links to documents.

The title is also only set on a `nodes.reference`: when the builder has no
URI for the target (`NoUri`), the link becomes its bare inline content,
which takes no title.
Pin the title through `myst_heading_anchors` (slug and beyond-depth links),
across two documents, from a `{title="..."}` attribute, and into the `<a>`
of the HTML output; and pin that a link without a title gets no `reftitle`.
Pin the title on a link with no `#` and no extension (resolved as an
unknown target) and on a reference definition (`[a][r1]`), and check the
HTML build of the title test is warning-free.
@chrisjsewell
chrisjsewell merged commit fc00785 into executablebooks:master Nov 9, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants