Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

What we learned

Why the server is built the way it is, what we measured, and what we need upstream. See How it works for the short version.

A build and a live parse

The server reads a project two ways, because neither covers everything.

Closed files’ targets, titles and enumerators (the 1 in Figure 1) come from the build, read page by page from myst start’s content server. We can’t use myst.xref.json instead, because it isn’t rewritten on edits and has no title or enumerator.

Open documents get a live parse with mystmd’s parser and its per-page transforms, on that page alone. So an open page’s enumerators can differ from the build’s when frontmatter sets numbering.

Directive transforms drop position on the nodes they make (see MyST Author’s data and packaging gaps). mystmd-client’s parseMyst copies the directive’s position onto its children, and src/index-targets.ts falls back to the first positioned descendant in built page JSON.

References come from regexes

mystmd’s parser finds targets and warnings, but regexes in src/syntax.ts find where each reference is. The parse tree doesn’t give us usable positions:

Replacing the reference regexes

A parser such as myst-parser-unified could replace the reference regexes if it provides:

The swap point is refsInText and labelDefinition in src/syntax.ts, not mystmd-client/parse. refAt and optionAt read the cursor’s context in half-typed text, so they stay regex-based.

labelChars leaves out <> on purpose, so a rename doesn’t end {ref}`text <label>` early. labelDefinition adds them back when matching (x)=, since mystmd allows them there.

Conformance

npm run conformance compares the regexes with mystmd’s parse. See Conformance for what it checks, and run it for the current numbers.

We fixed known differences with general rules rather than single cases: trimming padded role targets, accepting link titles, and counting Markdown images as path links. :label: and :name: values with spaces don’t count, because on real projects accepting them read frontmatter name: fields, YAML in code blocks, and option text like a button’s :label: as label definitions. The differences left are in known at the top of scripts/conformance.ts, with the reason for each.

We also ran MYST_CORPUS=<path> npm run conformance by hand on 2026-10-13, on a corpus of public MyST projects:

KindMatchedMissedFalse positive
Label roles72906
{doc} roles100
Path links53204
[](#label) links488217
Label definitions5250149

Most false positives are noise: {myst} example bodies that mystmd keeps as source, code blocks, and frontmatter like a kernel’s name: python3. The one real gap is a link written as [](<url>) with parentheses in the URL, which the regexes read as a path link.

The parse-first experiment

The experiment/parse-first branch indexes closed files with the live parser instead of the build. It was never merged. On seven public projects the parse and the build agreed on nearly every target, and the first project-wide completion took about a second with the parse against over a minute for a cold build. The gaps are frontmatter numbering, which the live parse ignores, and mystmd’s own warnings for closed files.

We keep the build because it’s the source of truth for what mystmd renders, including what plugins add.

What we need upstream

This server answers mystmd’s LSP and editor support issue. It would be simpler with: