Repository: executablebooks/MyST-Parser
Pull request #678 (2023-01-10 19:02:08)

👌 IMPROVE: Allow for heading anchor links in docutils
This aligns the treatment of `[](#target)` style links for docutils with sphinx, such that they are linked to a heading slug.

The core behaviour for sphinx is not changed,
except that failed reference resolution
now emits a `myst.xref_missing` warning (as opposed to a `std.ref` one), with a clearer warning message. Also on failure, the reference is still created,
for people who wish to suppress the warning (see e.g. #677)

This is another step towards https://github.com/executablebooks/myst-enhancement-proposals/pull/10

codecov[bot]: # [Codecov](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) Report
Base: **89.58**% // Head: **89.66**% // Increases project coverage by **`+0.07%`** :tada:
> Coverage data is based on head [(`da3d6bb`)](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) compared to base [(`797af5f`)](https://codecov.io/gh/executablebooks/MyST-Parser/commit/797af5f66cbaf52bf98f9f2a398163d24b1ac0d2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).
> Patch coverage: 90.38% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #678      +/-   ##
==========================================
+ Coverage   89.58%   89.66%   +0.07%     
==========================================
  Files          23       23              
  Lines        2544     2573      +29     
==========================================
+ Hits         2279     2307      +28     
- Misses        265      266       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| pytests | `89.66% <90.38%> (+0.07%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks) | Coverage Δ | |
|---|---|---|
| [myst\_parser/config/main.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvY29uZmlnL21haW4ucHk=) | `88.42% <ø> (ø)` | |
| [myst\_parser/sphinx\_ext/myst\_refs.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvc3BoaW54X2V4dC9teXN0X3JlZnMucHk=) | `89.04% <86.95%> (-1.47%)` | :arrow_down: |
| [myst\_parser/mdit\_to\_docutils/base.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9iYXNlLnB5) | `92.81% <90.69%> (-0.11%)` | :arrow_down: |
| [myst\_parser/mdit\_to\_docutils/sphinx\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvbWRpdF90b19kb2N1dGlscy9zcGhpbnhfLnB5) | `97.61% <100.00%> (+2.88%)` | :arrow_up: |
| [myst\_parser/warnings\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvd2FybmluZ3NfLnB5) | `95.91% <100.00%> (+0.08%)` | :arrow_up: |
| [myst\_parser/parsers/docutils\_.py](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks#diff-bXlzdF9wYXJzZXIvcGFyc2Vycy9kb2N1dGlsc18ucHk=) | `83.46% <0.00%> (+1.57%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/executablebooks/MyST-Parser/pull/678?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=executablebooks).

chrisjsewell: @choldgraf @rowanc1 please review this asap; it is the final commit before a new version can be released.

Hopefully you can see from the recent commits, the point of these is to facilitate moving towards an implementation for https://github.com/executablebooks/myst-enhancement-proposals/pull/10; implementing the things that are not yet breaking backwards compatibility.

Obviously, that PR is essentially useless if what it stipulates can't actually be implemented, in a robust way
chrisjsewell: > This looks good to me

thanks!

Patch:
diff --git a/docs/intro.md b/docs/intro.md
index 671e4c5..c488496 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -46,7 +46,7 @@ To parse single documents, see the [](docutils.md) section
 ## Write a CommonMark document
 
 MyST is an extension of [CommonMark Markdown](https://commonmark.org/),
-that includes [additional syntax](../syntax/syntax.md) for technical authoring,
+that includes [additional syntax](syntax/syntax.md) for technical authoring,
 which integrates with Docutils and Sphinx.
 
 To start off, create an empty file called `myfile.md` and give it a markdown title and text.
diff --git a/docs/syntax/syntax.md b/docs/syntax/syntax.md
index 5d3ac61..c2f5b27 100644
--- a/docs/syntax/syntax.md
+++ b/docs/syntax/syntax.md
@@ -255,7 +255,8 @@ By default, MyST will resolve link destinations according to the following rules
 3. Destinations which point to a local file path are treated as links to that file.
    - The path must be relative and in [POSIX format](https://en.wikipedia.org/wiki/Path_(computing)#POSIX_and_Unix_paths) (i.e. `/` separators).
    - If the path is to another source file in the project (e.g. a `.md` or `.rst` file),
-    then the link will be to the initial heading in that file.
+    then the link will be to the initial heading in that file or,
+    if the path is appended by a `#target`, to the heading "slug" in that file.
    - If the path is to a non-source file (e.g. a `.png` or `.pdf` file),
     then the link will be to the file itself, e.g. to download it.
 
@@ -290,10 +291,14 @@ Here are some examples:
   - `[Non-source file](example.txt)`
   - [Non-source file](example.txt)
 
-* - Internal heading
+* - Local heading
   - `[Heading](#markdown-links-and-referencing)`
   - [Heading](#markdown-links-and-referencing)
 
+* - Heading in another file
+  - `[Heading](optional.md#auto-generated-header-anchors)`
+  - [Heading](optional.md#auto-generated-header-anchors)
+
 :::
 
 ### Customising destination resolution
diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py
index 7b36b7f..7acc3c5 100644
--- a/myst_parser/config/main.py
+++ b/myst_parser/config/main.py
@@ -170,7 +170,6 @@ class MdParserConfig:
         metadata={
             "validator": optional(in_([1, 2, 3, 4, 5, 6, 7])),
             "help": "Heading level depth to assign HTML anchors",
-            "sphinx_only": True,
         },
     )
 
@@ -180,7 +179,7 @@ class MdParserConfig:
             "validator": optional(is_callable),
             "help": "Function for creating heading anchors",
             "global_only": True,
-            "sphinx_only": True,
+            "sphinx_only": True,  # TODO docutils config doesn't handle callables
         },
     )
 
diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py
index b72ef5f..1c6a001 100644
--- a/myst_parser/mdit_to_docutils/base.py
+++ b/myst_parser/mdit_to_docutils/base.py
@@ -136,6 +136,8 @@ class DocutilsRenderer(RendererProtocol):
         self._level_to_elem: dict[int, nodes.document | nodes.section] = {
             0: self.document
         }
+        # mapping of section slug to section node
+        self._slug_to_section: dict[str, nodes.section] = {}
 
     @property
     def sphinx_env(self) -> BuildEnvironment | None:
@@ -236,6 +238,37 @@ class DocutilsRenderer(RendererProtocol):
     def _render_finalise(self) -> None:
         """Finalise the render of the document."""
 
+        # attempt to replace id_link references with internal links
+        for refnode in findall(self.document)(nodes.reference):
+            if not refnode.get("id_link"):
+                continue
+            target = refnode["refuri"][1:]
+            if target in self._slug_to_section:
+                section_node = self._slug_to_section[target]
+                refnode["refid"] = section_node["ids"][0]
+
+                if not refnode.children:
+                    implicit_text = clean_astext(section_node[0])
+                    refnode += nodes.inline(
+                        implicit_text, implicit_text, classes=["std", "std-ref"]
+                    )
+            else:
+                self.create_warning(
+                    f"local id not found: {refnode['refuri']!r}",
+                    MystWarnings.XREF_MISSING,
+                    line=refnode.line,
+                    append_to=refnode,
+                )
+                refnode["refid"] = target
+            del refnode["refuri"]
+
+        if self._slug_to_section and self.sphinx_env:
+            # save for later reference resolution
+            self.sphinx_env.metadata[self.sphinx_env.docname]["myst_slugs"] = {
+                slug: (snode["ids"][0], clean_astext(snode[0]))
+                for slug, snode in self._slug_to_section.items()
+            }
+
         # log warnings for duplicate reference definitions
         # "duplicate_refs": [{"href": "ijk", "label": "B", "map": [4, 5], "title": ""}],
         for dup_ref in self.md_env.get("duplicate_refs", []):
@@ -713,11 +746,29 @@ class DocutilsRenderer(RendererProtocol):
         with self.current_node_context(title_node):
             self.render_children(token)
 
-        # create a target reference for the section, based on the heading text
+        # create a target reference for the section, based on the heading text.
+        # Note, this is an implicit target, meaning that it is not prioritised,
+        # and is not stored by sphinx for ref resolution
         name = nodes.fully_normalize_name(title_node.astext())
         new_section["names"].append(name)
         self.document.note_implicit_target(new_section, new_section)
 
+        # add possible reference slug, this may be different to the standard name above,
+        # and does not have to be normalised, so we treat it separately
+        if "id" in token.attrs:
+            slug = str(token.attrs["id"])
+            new_section["slug"] = slug
+            if slug in self._slug_to_section:
+                other_node = self._slug_to_section[slug]
+                self.create_warning(
+                    f"duplicate heading slug {slug!r}, other at line {other_node.line}",
+                    MystWarnings.ANCHOR_DUPE,
+                    line=new_section.line,
+                )
+            else:
+                # we store this for later processing on finalise
+                self._slug_to_section[slug] = new_section
+
         # set the section as the current node for subsequent rendering
         self.current_node = new_section
 
@@ -736,19 +787,19 @@ class DocutilsRenderer(RendererProtocol):
             or self.md_config.gfm_only
             or self.md_config.all_links_external
         ):
-            if token.info == "auto":  # handles both autolink and linkify
-                return self.render_autolink(token)
-            else:
-                return self.render_external_url(token)
+            return self.render_external_url(token)
 
         href = cast(str, token.attrGet("href") or "")
 
+        if href.startswith("#"):
+            return self.render_id_link(token)
+
         # TODO ideally whether inv_link is enabled could be precomputed
         if "inv_link" in self.md_config.enable_extensions and href.startswith("inv:"):
             return self.create_inventory_link(token)
 
         if token.info == "auto":  # handles both autolink and linkify
-            return self.render_autolink(token)
+            return self.render_external_url(token)
 
         # Check for external URL
         url_scheme = urlparse(href).scheme
@@ -761,20 +812,27 @@ class DocutilsRenderer(RendererProtocol):
         return self.render_internal_link(token)
 
     def render_external_url(self, token: SyntaxTreeNode) -> None:
-        """Render link token `[text](link "title")`,
-        where the link has been identified as an external URL::
-
-            <reference refuri="link" title="title">
-                text
-
-        `text` can contain nested syntax, e.g. `[**bold**](url "title")`.
+        """Render link token (including autolink and linkify),
+        where the link has been identified as an external URL.
         """
         ref_node = nodes.reference()
         self.add_line_and_source_path(ref_node, token)
         self.copy_attributes(
             token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"}
         )
-        ref_node["refuri"] = cast(str, token.attrGet("href") or "")
+        ref_node["refuri"] = escapeHtml(token.attrGet("href") or "")  # type: ignore[arg-type]
+        with self.current_node_context(ref_node, append=True):
+            self.render_children(token)
+
+    def render_id_link(self, token: SyntaxTreeNode) -> None:
+        """Render link token like `[text](#id)`, to a local target."""
+        ref_node = nodes.reference()
+        self.add_line_and_source_path(ref_node, token)
+        ref_node["id_link"] = True
+        ref_node["refuri"] = token.attrGet("href") or ""
+        self.copy_attributes(
+            token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"}
+        )
         with self.current_node_context(ref_node, append=True):
             self.render_children(token)
 
@@ -799,17 +857,6 @@ class DocutilsRenderer(RendererProtocol):
         with self.current_node_context(ref_node, append=True):
             self.render_children(token)
 
-    def render_autolink(self, token: SyntaxTreeNode) -> None:
-        refuri = escapeHtml(token.attrGet("href") or "")  # type: ignore[arg-type]
-        ref_node = nodes.reference()
-        self.copy_attributes(
-            token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"}
-        )
-        ref_node["refuri"] = refuri
-        self.add_line_and_source_path(ref_node, token)
-        with self.current_node_context(ref_node, append=True):
-            self.render_children(token)
-
     def create_inventory_link(self, token: SyntaxTreeNode) -> None:
         r"""Create a link to an inventory object.
 
@@ -1641,3 +1688,15 @@ def html_meta_to_nodes(
         output.append(pending)
 
     return output
+
+
+def clean_astext(node: nodes.Element) -> str:
+    """Like node.astext(), but ignore images.
+    Copied from sphinx.
+    """
+    node = node.deepcopy()
+    for img in findall(node)(nodes.image):
+        img["alt"] = ""
+    for raw in list(findall(node)(nodes.raw)):
+        raw.parent.remove(raw)
+    return node.astext()
diff --git a/myst_parser/mdit_to_docutils/sphinx_.py b/myst_parser/mdit_to_docutils/sphinx_.py
index c1989ed..af65c1c 100644
--- a/myst_parser/mdit_to_docutils/sphinx_.py
+++ b/myst_parser/mdit_to_docutils/sphinx_.py
@@ -11,15 +11,12 @@ from docutils import nodes
 from markdown_it.tree import SyntaxTreeNode
 from sphinx import addnodes
 from sphinx.domains.math import MathDomain
-from sphinx.domains.std import StandardDomain
 from sphinx.environment import BuildEnvironment
 from sphinx.ext.intersphinx import InventoryAdapter
 from sphinx.util import logging
-from sphinx.util.nodes import clean_astext
 
 from myst_parser import inventory
 from myst_parser.mdit_to_docutils.base import DocutilsRenderer
-from myst_parser.warnings_ import MystWarnings
 
 LOGGER = logging.getLogger(__name__)
 
@@ -49,38 +46,42 @@ class SphinxRenderer(DocutilsRenderer):
             destination = os.path.relpath(
                 os.path.join(include_dir, os.path.normpath(destination)), source_dir
             )
-
+        kwargs = {
+            "refdoc": self.sphinx_env.docname,
+            "reftype": "myst",
+            "refexplicit": len(token.children or []) > 0,
+        }
+        path_dest, *_path_ids = destination.split("#", maxsplit=1)
+        path_id = _path_ids[0] if _path_ids else None
         potential_path = (
-            Path(self.sphinx_env.doc2path(self.sphinx_env.docname)).parent / destination
+            Path(self.sphinx_env.doc2path(self.sphinx_env.docname)).parent / path_dest
             if self.sphinx_env.srcdir  # not set in some test situations
             else None
         )
-        if (
-            potential_path
-            and potential_path.is_file()
-            and not any(
-                destination.endswith(suffix)
-                for suffix in self.sphinx_env.config.source_suffix
-            )
-        ):
-            wrap_node = addnodes.download_reference(
-                refdoc=self.sphinx_env.docname,
-                reftarget=destination,
-                reftype="myst",
-                refdomain=None,  # Added to enable cross-linking
-                refexplicit=len(token.children or []) > 0,
-                refwarn=False,
+        if path_dest == "./":
+            # this is a special case, where we want to reference the current document
+            potential_path = (
+                Path(self.sphinx_env.doc2path(self.sphinx_env.docname))
+                if self.sphinx_env.srcdir
+                else None
             )
-            classes = ["xref", "download", "myst"]
-            text = destination if not token.children else ""
+        if potential_path and potential_path.is_file():
+            docname = self.sphinx_env.path2doc(str(potential_path))
+            if docname:
+                wrap_node = addnodes.pending_xref(
+                    refdomain="doc", reftarget=docname, reftargetid=path_id, **kwargs
+                )
+                classes = ["xref", "myst"]
+                text = ""
+            else:
+                wrap_node = addnodes.download_reference(
+                    refdomain=None, reftarget=path_dest, refwarn=False, **kwargs
+                )
+                classes = ["xref", "download", "myst"]
+                text = destination if not token.children else ""
         else:
             wrap_node = addnodes.pending_xref(
-                refdoc=self.sphinx_env.docname,
-                reftarget=destination,
-                reftype="myst",
-                refdomain=None,  # Added to enable cross-linking
-                refexplicit=len(token.children or []) > 0,
-                refwarn=True,
+                refdomain=None, reftarget=destination, refwarn=True, **kwargs
             )
             classes = ["xref", "myst"]
             text = ""
@@ -112,48 +113,6 @@ class SphinxRenderer(DocutilsRenderer):
             )
         )
 
-    def render_heading(self, token: SyntaxTreeNode) -> None:
-        """This extends the docutils method, to allow for the addition of heading ids.
-        These ids are computed by the ``markdown-it-py`` ``anchors_plugin``
-        as "slugs" which are unique to a document.
-
-        The approach is similar to ``sphinx.ext.autosectionlabel``
-        """
-        super().render_heading(token)
-
-        if not isinstance(self.current_node, nodes.section):
-            return
-
-        # create the slug string
-        slug = cast(str, token.attrGet("id"))
-        if slug is None:
-            return
-
-        section = self.current_node
-        doc_slug = (
-            self.sphinx_env.doc2path(self.sphinx_env.docname, base=False) + "#" + slug
-        )
-
-        # save the reference in the standard domain, so that it can be handled properly
-        domain = cast(StandardDomain, self.sphinx_env.get_domain("std"))
-        if doc_slug in domain.labels:
-            other_doc = self.sphinx_env.doc2path(domain.labels[doc_slug][0])
-            self.create_warning(
-                f"duplicate label {doc_slug}, other instance in {other_doc}",
-                MystWarnings.ANCHOR_DUPE,
-                line=section.line,
-            )
-        labelid = section["ids"][0]
-        domain.anonlabels[doc_slug] = self.sphinx_env.docname, labelid
-        domain.labels[doc_slug] = (
-            self.sphinx_env.docname,
-            labelid,
-            clean_astext(section[0]),
-        )
-
-        self.sphinx_env.metadata[self.sphinx_env.docname]["myst_anchors"] = True
-        section["myst-anchor"] = doc_slug
-
     def render_math_block_label(self, token: SyntaxTreeNode) -> None:
         """Render math with referencable labels, e.g. ``$a=1$ (label)``."""
         label = token.info
diff --git a/myst_parser/sphinx_ext/myst_refs.py b/myst_parser/sphinx_ext/myst_refs.py
index 948303a..dc06abf 100644
--- a/myst_parser/sphinx_ext/myst_refs.py
+++ b/myst_parser/sphinx_ext/myst_refs.py
@@ -3,7 +3,6 @@
 This is applied to MyST type references only, such as ``[text](target)``,
 and allows for nested syntax
 """
-import os
 from typing import Any, List, Optional, Tuple, cast
 
 from docutils import nodes
@@ -11,6 +10,7 @@ from docutils.nodes import Element, document
 from sphinx import addnodes, version_info
 from sphinx.addnodes import pending_xref
 from sphinx.domains.std import StandardDomain
+from sphinx.errors import NoUri
 from sphinx.locale import __
 from sphinx.transforms.post_transforms import ReferencesResolver
 from sphinx.util import docname_join, logging
@@ -19,13 +19,12 @@ from sphinx.util.nodes import clean_astext, make_refnode
 from myst_parser._compat import findall
 from myst_parser.warnings_ import MystWarnings
 
-try:
-    from sphinx.errors import NoUri
-except ImportError:
-    # sphinx < 2.1
-    from sphinx.environment import NoUri  # type: ignore
+LOGGER = logging.getLogger(__name__)
 
-logger = logging.getLogger(__name__)
+
+def log_warning(msg: str, subtype: MystWarnings, **kwargs: Any):
+    """Log a warning, with a myst type and specific subtype."""
+    LOGGER.warning(msg, type="myst", subtype=subtype.value, **kwargs)
 
 
 class MystReferenceResolver(ReferencesResolver):
@@ -42,6 +41,10 @@ class MystReferenceResolver(ReferencesResolver):
             if node["reftype"] != "myst":
                 continue
 
+            if node["refdomain"] == "doc":
+                self.resolve_myst_ref_doc(node)
+                continue
+
             contnode = cast(nodes.TextElement, node[0].deepcopy())
             newnode = None
 
@@ -50,7 +53,7 @@ class MystReferenceResolver(ReferencesResolver):
             domain = None
 
             try:
-                newnode = self.resolve_myst_ref(refdoc, node, contnode)
+                newnode = self.resolve_myst_ref_any(refdoc, node, contnode)
                 if newnode is None:
                     # no new node found? try the missing-reference event
                     # but first we change the the reftype to 'any'
@@ -84,7 +87,58 @@ class MystReferenceResolver(ReferencesResolver):
 
             node.replace_self(newnode or contnode)
 
-    def resolve_myst_ref(
+    def resolve_myst_ref_doc(self, node: pending_xref):
+        """Resolve a reference, from a markdown link, to another document,
+        optionally with a target id within that document.
+        """
+        from_docname = node.get("refdoc", self.env.docname)
+        ref_docname: str = node["reftarget"]
+        ref_id: Optional[str] = node["reftargetid"]
+
+        if ref_docname not in self.env.all_docs:
+            log_warning(
+                f"Unknown source document {ref_docname!r}",
+                MystWarnings.XREF_MISSING,
+                location=node,
+            )
+            node.replace_self(node[0].deepcopy())
+            return
+
+        targetid = ""
+        implicit_text = ""
+        inner_classes = ["std", "std-doc"]
+
+        if ref_id:
+            slug_to_section = self.env.metadata[ref_docname].get("myst_slugs", {})
+            if ref_id not in slug_to_section:
+                log_warning(
+                    f"local id not found in doc {ref_docname!r}: {ref_id!r}",
+                    MystWarnings.XREF_MISSING,
+                    location=node,
+                )
+                targetid = ref_id
+            else:
+                targetid, implicit_text = slug_to_section[ref_id]
+            inner_classes = ["std", "std-ref"]
+        else:
+            implicit_text = clean_astext(self.env.titles[ref_docname])
+
+        if node["refexplicit"]:
+            caption = node.astext()
+            innernode = nodes.inline(caption, "", classes=inner_classes)
+            innernode.extend(node[0].children)
+        else:
+            innernode = nodes.inline(
+                implicit_text, implicit_text, classes=inner_classes
+            )
+
+        assert self.app.builder
+        ref_node = make_refnode(
+            self.app.builder, from_docname, ref_docname, targetid, innernode
+        )
+        node.replace_self(ref_node)
+
+    def resolve_myst_ref_any(
         self, refdoc: str, node: pending_xref, contnode: Element
     ) -> Element:
         """Resolve reference generated by the "myst" role; ``[text](reference)``.
@@ -100,22 +154,15 @@ class MystReferenceResolver(ReferencesResolver):
         target: str = node["reftarget"]
         results: List[Tuple[str, Element]] = []
 
-        res_anchor = self._resolve_anchor(node, refdoc)
-        if res_anchor:
-            results.append(("std:doc", res_anchor))
-        else:
-            # if we've already found an anchored doc,
-            # don't search in the std:ref/std:doc (leads to duplication)
-
-            # resolve standard references
-            res = self._resolve_ref_nested(node, refdoc)
-            if res:
-                results.append(("std:ref", res))
+        # resolve standard references
+        res = self._resolve_ref_nested(node, refdoc)
+        if res:
+            results.append(("std:ref", res))
 
-            # resolve doc names
-            res = self._resolve_doc_nested(node, refdoc)
-            if res:
-                results.append(("std:doc", res))
+        # resolve doc names
+        res = self._resolve_doc_nested(node, refdoc)
+        if res:
+            results.append(("std:doc", res))
 
         # get allowed domains for referencing
         ref_domains = self.env.config.myst_ref_domains
@@ -153,11 +200,10 @@ class MystReferenceResolver(ReferencesResolver):
                 # the domain doesn't yet support the new interface
                 # we have to manually collect possible references (SLOW)
                 if not (getattr(domain, "__module__", "").startswith("sphinx.")):
-                    logger.warning(
+                    log_warning(
                         f"Domain '{domain.__module__}::{domain.name}' has not "
                         "implemented a `resolve_any_xref` method [myst.domains]",
-                        type="myst",
-                        subtype=MystWarnings.LEGACY_DOMAIN.value,
+                        MystWarnings.LEGACY_DOMAIN,
                         once=True,
                     )
                 for role in domain.roles:
@@ -177,14 +223,13 @@ class MystReferenceResolver(ReferencesResolver):
                 return f":{name}:`{reftitle}`"
 
             candidates = " or ".join(stringify(name, role) for name, role in results)
-            logger.warning(
+            log_warning(
                 __(
                     f"more than one target found for 'myst' cross-reference {target}: "
                     f"could be {candidates} [myst.ref]"
                 ),
+                MystWarnings.XREF_AMBIGUOUS,
                 location=node,
-                type="myst",
-                subtype=MystWarnings.XREF_AMBIGUOUS.value,
             )
 
         res_role, newnode = results[0]
@@ -199,29 +244,6 @@ class MystReferenceResolver(ReferencesResolver):
 
         return newnode
 
-    def _resolve_anchor(
-        self, node: pending_xref, fromdocname: str
-    ) -> Optional[Element]:
-        """Resolve doc with anchor."""
-        if self.env.config.myst_heading_anchors is None:
-            # no target anchors will have been created, so we don't look for them
-            return None
-        target: str = node["reftarget"]
-        if "#" not in target:
-            return None
-        # the link may be a heading anchor; we need to first get the relative path
-        rel_path, anchor = target.rsplit("#", 1)
-        rel_path = os.path.normpath(rel_path)
-        if rel_path == ".":
-            # anchor in the same doc as the node
-            doc_path = self.env.doc2path(node.get("refdoc", fromdocname), base=False)
-        else:
-            # anchor in a different doc from the node
-            doc_path = os.path.normpath(
-                os.path.join(node.get("refdoc", fromdocname), "..", rel_path)
-            )
-        return self._resolve_ref_nested(node, fromdocname, doc_path + "#" + anchor)
-
     def _resolve_ref_nested(
         self, node: pending_xref, fromdocname: str, target=None
     ) -> Optional[Element]:
@@ -258,16 +280,9 @@ class MystReferenceResolver(ReferencesResolver):
 
         It also allows for extensions on document names.
         """
-        # directly reference to document by source name; can be absolute or relative
-        refdoc = node.get("refdoc", fromdocname)
-        docname = docname_join(refdoc, node["reftarget"])
-
+        docname = docname_join(node.get("refdoc", fromdocname), node["reftarget"])
         if docname not in self.env.all_docs:
-            # try stripping known extensions from doc name
-            if os.path.splitext(docname)[1] in self.env.config.source_suffix:
-                docname = os.path.splitext(docname)[0]
-            if docname not in self.env.all_docs:
-                return None
+            return None
 
         if node["refexplicit"]:
             # reference with explicit title
@@ -275,7 +290,6 @@ class MystReferenceResolver(ReferencesResolver):
             innernode = nodes.inline(caption, "", classes=["doc"])
             innernode.extend(node[0].children)
         else:
-            # TODO do we want nested syntax for titles?
             caption = clean_astext(self.env.titles[docname])
             innernode = nodes.inline(caption, caption, classes=["doc"])
 
diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py
index 6325c4a..85d18e2 100644
--- a/myst_parser/warnings_.py
+++ b/myst_parser/warnings_.py
@@ -32,6 +32,8 @@ class MystWarnings(Enum):
     # cross-reference resolution
     XREF_AMBIGUOUS = "xref_ambiguous"
     """Multiple targets were found for a cross-reference."""
+    XREF_MISSING = "xref_missing"
+    """A target was not found for a cross-reference."""
     INV_LOAD = "inv_retrieval"
     """Failure to retrieve or load an inventory."""
     IREF_MISSING = "iref_missing"


Test patch:
diff --git a/tests/test_renderers/fixtures/docutil_syntax_elements.md b/tests/test_renderers/fixtures/docutil_syntax_elements.md
index 91123a5..bbcbf8b 100644
--- a/tests/test_renderers/fixtures/docutil_syntax_elements.md
+++ b/tests/test_renderers/fixtures/docutil_syntax_elements.md
@@ -340,8 +340,11 @@ Title
             <reference refuri="https://www.google.com">
                 alt2
         <paragraph>
-            <reference refname="#target3">
+            <reference id_link="True" refid="target3">
                 alt3
+                <system_message level="2" line="12" source="notset" type="WARNING">
+                    <paragraph>
+                        local id not found: '#target3' [myst.xref_missing]
 .
 
 Comments:
diff --git a/tests/test_renderers/fixtures/myst-config.txt b/tests/test_renderers/fixtures/myst-config.txt
index 0f898cb..7f997e9 100644
--- a/tests/test_renderers/fixtures/myst-config.txt
+++ b/tests/test_renderers/fixtures/myst-config.txt
@@ -156,6 +156,20 @@ www.commonmark.org/he<lp
         <lp
 .
 
+[heading_anchors] --myst-heading-anchors=1
+.
+# My title
+[](#my-title)
+.
+<document ids="my-title" names="my\ title" slug="my-title" source="<string>" title="My title">
+    <title>
+        My title
+    <paragraph>
+        <reference id_link="True" refid="my-title">
+            <inline classes="std std-ref">
+                My title
+.
+
 [html_meta] --myst-html-meta='{"keywords": "Sphinx, MyST"}'
 .
 text
diff --git a/tests/test_renderers/fixtures/sphinx_syntax_elements.md b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
index 2d40bdb..838cf99 100644
--- a/tests/test_renderers/fixtures/sphinx_syntax_elements.md
+++ b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
@@ -324,7 +324,7 @@ Title
 
 [alt2](https://www.google.com)
 
-[alt3](#target3)
+[alt3](#title)
 .
 <document source="<src>/index.md">
     <target ids="target" names="target">
@@ -342,9 +342,11 @@ Title
             <reference refuri="https://www.google.com">
                 alt2
         <paragraph>
-            <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="#target3" reftype="myst" refwarn="True">
-                <inline classes="xref myst">
-                    alt3
+            <reference id_link="True" refid="title">
+                alt3
+                <system_message level="2" line="12" source="<src>/index.md" type="WARNING">
+                    <paragraph>
+                        local id not found: '#title' [myst.xref_missing]
 .
 
 Comments:
diff --git a/tests/test_renderers/test_myst_refs/doc_with_extension.xml b/tests/test_renderers/test_myst_refs/doc_with_extension.xml
index 55cb74c..0e8f467 100644
--- a/tests/test_renderers/test_myst_refs/doc_with_extension.xml
+++ b/tests/test_renderers/test_myst_refs/doc_with_extension.xml
@@ -1,5 +1,5 @@
 <document source="root/index.md">
     <paragraph>
         <reference internal="True" refuri="">
-            <inline classes="doc std std-doc">
+            <inline classes="std std-doc">
                 <no title>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml
index e48908d..818a2a7 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml
@@ -1,7 +1,7 @@
 <document source="index.md">
-    <section ids="hyphen-1" myst-anchor="index.md#hyphen-1" names="hyphen\ -\ 1">
+    <section ids="hyphen-1" names="hyphen\ -\ 1" slug="hyphen-1">
         <title>
             Hyphen - 1
-        <section ids="dot-1-1" myst-anchor="index.md#dot-1-1" names="dot\ 1.1">
+        <section ids="dot-1-1" names="dot\ 1.1" slug="dot-1-1">
             <title>
                 Dot 1.1
diff --git a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml
index e48908d..818a2a7 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml
@@ -1,7 +1,7 @@
 <document source="index.md">
-    <section ids="hyphen-1" myst-anchor="index.md#hyphen-1" names="hyphen\ -\ 1">
+    <section ids="hyphen-1" names="hyphen\ -\ 1" slug="hyphen-1">
         <title>
             Hyphen - 1
-        <section ids="dot-1-1" myst-anchor="index.md#dot-1-1" names="dot\ 1.1">
+        <section ids="dot-1-1" names="dot\ 1.1" slug="dot-1-1">
             <title>
                 Dot 1.1
diff --git a/tests/test_sphinx/test_sphinx_builds/test_includes.html b/tests/test_sphinx/test_sphinx_builds/test_includes.html
index 41eb1ee..2c1b6d8 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_includes.html
+++ b/tests/test_sphinx/test_sphinx_builds/test_includes.html
@@ -83,7 +83,7 @@
      </p>
      <p>
       <a class="reference internal" href="#">
-       <span class="doc std std-doc">
+       <span class="std std-doc">
         text
        </span>
       </a>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_includes.xml b/tests/test_sphinx/test_sphinx_builds/test_includes.xml
index 6602407..64ff16a 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_includes.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_includes.xml
@@ -37,7 +37,7 @@
             <paragraph>
                 <image alt="alt" candidates="{'?': 'https://example.com'}" uri="https://example.com">
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+                <pending_xref refdoc="index" refdomain="doc" refexplicit="True" reftarget="index" reftargetid="True" reftype="myst">
                     <inline classes="xref myst">
                         text
             <paragraph>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.html b/tests/test_sphinx/test_sphinx_builds/test_references.html
index a6d4036..63297b9 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references.html
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.html
@@ -59,21 +59,21 @@
     </p>
     <p>
      <a class="reference internal" href="#">
-      <span class="doc std std-doc">
+      <span class="std std-doc">
        Title with nested a=1
       </span>
      </a>
     </p>
     <p>
      <a class="reference internal" href="#">
-      <span class="doc std std-doc">
+      <span class="std std-doc">
        plain text
       </span>
      </a>
     </p>
     <p>
      <a class="reference internal" href="#">
-      <span class="doc std std-doc">
+      <span class="std std-doc">
        nested
        <em>
         syntax
@@ -155,35 +155,35 @@
      </div>
      <p>
       <a class="reference internal" href="#title-anchors">
-       <span class="std std-doc">
+       <span class="std std-ref">
         Title anchors
        </span>
       </a>
      </p>
      <p>
       <a class="reference internal" href="#title-anchors">
-       <span class="std std-doc">
+       <span class="std std-ref">
         Title anchors
        </span>
       </a>
      </p>
      <p>
       <a class="reference internal" href="other.html#title-anchors">
-       <span class="std std-doc">
+       <span class="std std-ref">
         Title anchors
        </span>
       </a>
      </p>
      <p>
       <a class="reference internal" href="other.html#title-anchors">
-       <span class="std std-doc">
+       <span class="std std-ref">
         Title anchors
        </span>
       </a>
      </p>
      <p>
       <a class="reference internal" href="subfolder/other2.html#title-anchors">
-       <span class="std std-doc">
+       <span class="std std-ref">
         Title anchors
        </span>
       </a>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml
index 9c6a4ca..957af06 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml
@@ -1,6 +1,6 @@
 <document source="index.md">
     <target refid="title">
-    <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" myst-anchor="index.md#title-with-nested" names="title\ with\ nested\ a=1 title">
+    <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" names="title\ with\ nested\ a=1 title" slug="title-with-nested">
         <title>
             Title with 
             <strong>
@@ -34,15 +34,15 @@
                         syntax
         <paragraph>
             <reference internal="True" refuri="">
-                <inline classes="doc std std-doc">
+                <inline classes="std std-doc">
                     Title with nested a=1
         <paragraph>
             <reference internal="True" refuri="">
-                <inline classes="doc std std-doc">
+                <inline classes="std std-doc">
                     plain text
         <paragraph>
             <reference internal="True" refuri="">
-                <inline classes="doc std std-doc">
+                <inline classes="std std-doc">
                     nested 
                     <emphasis>
                         syntax
@@ -73,7 +73,7 @@
             <reference internal="True" refid="insidecodeblock">
                 <inline classes="std std-ref">
                     fence
-        <section ids="title-anchors" myst-anchor="index.md#title-anchors" names="title\ anchors">
+        <section ids="title-anchors" names="title\ anchors" slug="title-anchors">
             <title>
                 Title 
                 <emphasis>
@@ -94,22 +94,22 @@
                                     <emphasis>
                                         anchors
             <paragraph>
-                <reference internal="True" refid="title-anchors">
-                    <inline classes="std std-doc">
+                <reference id_link="True" refid="title-anchors">
+                    <inline classes="std std-ref">
                         Title anchors
             <paragraph>
                 <reference internal="True" refid="title-anchors">
-                    <inline classes="std std-doc">
+                    <inline classes="std std-ref">
                         Title anchors
             <paragraph>
                 <reference internal="True" refuri="other.html#title-anchors">
-                    <inline classes="std std-doc">
+                    <inline classes="std std-ref">
                         Title anchors
             <paragraph>
                 <reference internal="True" refuri="other.html#title-anchors">
-                    <inline classes="std std-doc">
+                    <inline classes="std std-ref">
                         Title anchors
             <paragraph>
                 <reference internal="True" refuri="subfolder/other2.html#title-anchors">
-                    <inline classes="std std-doc">
+                    <inline classes="std std-ref">
                         Title anchors
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.xml b/tests/test_sphinx/test_sphinx_builds/test_references.xml
index 03bfb8d..4cb99ad 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.xml
@@ -1,6 +1,6 @@
 <document source="index.md">
     <target refid="title">
-    <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" myst-anchor="index.md#title-with-nested" names="title\ with\ nested\ a=1 title">
+    <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" names="title\ with\ nested\ a=1 title" slug="title-with-nested">
         <title>
             Title with 
             <strong>
@@ -32,14 +32,14 @@
                     <emphasis>
                         syntax
         <paragraph>
-            <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="index.md" reftype="myst" refwarn="True">
+            <pending_xref refdoc="index" refdomain="doc" refexplicit="False" reftarget="index" reftargetid="True" reftype="myst">
                 <inline classes="xref myst">
         <paragraph>
-            <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+            <pending_xref refdoc="index" refdomain="doc" refexplicit="True" reftarget="index" reftargetid="True" reftype="myst">
                 <inline classes="xref myst">
                     plain text
         <paragraph>
-            <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+            <pending_xref refdoc="index" refdomain="doc" refexplicit="True" reftarget="index" reftargetid="True" reftype="myst">
                 <inline classes="xref myst">
                     nested 
                     <emphasis>
@@ -71,7 +71,7 @@
             <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="insidecodeblock" reftype="myst" refwarn="True">
                 <inline classes="xref myst">
                     fence
-        <section ids="title-anchors" myst-anchor="index.md#title-anchors" names="title\ anchors">
+        <section ids="title-anchors" names="title\ anchors" slug="title-anchors">
             <title>
                 Title 
                 <emphasis>
@@ -79,17 +79,18 @@
             <compound classes="toctree-wrapper">
                 <toctree caption="True" entries="(None,\ 'other') (None,\ 'subfolder/other2')" glob="False" hidden="False" includefiles="other subfolder/other2" includehidden="False" maxdepth="-1" numbered="0" parent="index" rawentries="" titlesonly="False">
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="#title-anchors" reftype="myst" refwarn="True">
-                    <inline classes="xref myst">
+                <reference id_link="True" refid="title-anchors">
+                    <inline classes="std std-ref">
+                        Title anchors
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="./#title-anchors" reftype="myst" refwarn="True">
+                <pending_xref refdoc="index" refdomain="doc" refexplicit="False" reftarget="index" reftargetid="title-anchors" reftype="myst">
                     <inline classes="xref myst">
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="./other.md#title-anchors" reftype="myst" refwarn="True">
+                <pending_xref refdoc="index" refdomain="doc" refexplicit="False" reftarget="other" reftargetid="title-anchors" reftype="myst">
                     <inline classes="xref myst">
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="other.md#title-anchors" reftype="myst" refwarn="True">
+                <pending_xref refdoc="index" refdomain="doc" refexplicit="False" reftarget="other" reftargetid="title-anchors" reftype="myst">
                     <inline classes="xref myst">
             <paragraph>
-                <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="subfolder/other2.md#title-anchors" reftype="myst" refwarn="True">
+                <pending_xref refdoc="index" refdomain="doc" refexplicit="False" reftarget="subfolder/other2" reftargetid="title-anchors" reftype="myst">
                     <inline classes="xref myst">
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html
index b3d98a9..643e868 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html
@@ -44,14 +44,14 @@
         </p>
         <p>
          <a class="reference internal" href="index.html#document-other/other2">
-          <span class="doc std std-doc">
+          <span class="std std-doc">
            Other 2 Title
           </span>
          </a>
         </p>
         <p>
          <a class="reference internal" href="index.html#title">
-          <span class="std std-doc">
+          <span class="std std-ref">
            Title
           </span>
          </a>
@@ -86,21 +86,21 @@
     </p>
     <p>
      <a class="reference internal" href="index.html#document-other/other">
-      <span class="doc std std-doc">
+      <span class="std std-doc">
        Other Title
       </span>
      </a>
     </p>
     <p>
      <a class="reference internal" href="#title">
-      <span class="std std-doc">
+      <span class="std std-ref">
        Title
       </span>
      </a>
     </p>
     <p>
      <a class="reference internal" href="index.html#other-title">
-      <span class="std std-doc">
+      <span class="std std-ref">
        Other Title
       </span>
      </a>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml
index 606e769..249957f 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml
@@ -1,5 +1,5 @@
 <document source="other.md">
-    <section ids="other-title" myst-anchor="other/other.md#other-title" names="other\ title">
+    <section ids="other-title" names="other\ title" slug="other-title">
         <title>
             Other Title
         <paragraph>
@@ -12,9 +12,9 @@
                     Other 2 Title
         <paragraph>
             <reference internal="True" refuri="index.html#document-other/other2">
-                <inline classes="doc std std-doc">
+                <inline classes="std std-doc">
                     Other 2 Title
         <paragraph>
             <reference internal="True" refuri="index.html#document-index#title">
-                <inline classes="std std-doc">
+                <inline classes="std std-ref">
                     Title
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml
index a209b4f..b2f0cd6 100644
--- a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml
@@ -1,5 +1,5 @@
 <document source="other.md">
-    <section ids="other-title" myst-anchor="other/other.md#other-title" names="other\ title">
+    <section ids="other-title" names="other\ title" slug="other-title">
         <title>
             Other Title
         <paragraph>
@@ -11,8 +11,8 @@
                 <literal classes="xref any">
                     other2
         <paragraph>
-            <pending_xref refdoc="other/other" refdomain="True" refexplicit="False" reftarget="./other2.md" reftype="myst" refwarn="True">
+            <pending_xref refdoc="other/other" refdomain="doc" refexplicit="False" reftarget="other/other2" reftargetid="True" reftype="myst">
                 <inline classes="xref myst">
         <paragraph>
-            <pending_xref refdoc="other/other" refdomain="True" refexplicit="False" reftarget="../index.md#title" reftype="myst" refwarn="True">
+            <pending_xref refdoc="other/other" refdomain="doc" refexplicit="False" reftarget="index" reftargetid="title" reftype="myst">
                 <inline classes="xref myst">