✨ NEW: docutils heading-anchor slug preset, and a versioned anchor contract#1168
Merged
Conversation
…ntract - `myst_heading_slug_func = "docutils"`: byte-identical to `docutils.nodes.make_id` (algorithm and translate tables ported into the pure `myst_parser.slugs` module; fidelity to the real docutils pinned by CI), giving mixed reStructuredText + Markdown projects one uniform anchor style across both source formats. - An empty slug (a punctuation-only title, or e.g. a non-Latin title under the `docutils` preset) now means the heading gets no anchor, rather than the empty string entering deduplication and later duplicates receiving nonsense `-1`, `-2` anchors. - `tests/fixtures/slugs.json` is now a versioned conformance corpus (`version: 1`): append-only within a version, version bump on any semantic change, so alternative MyST implementations can pin to it.
…-checks Document the normative anchor behaviour in one place (slug presets and the versioned conformance corpus, fixed-base deduplication, empty-slug rule, explicit-id priority, HTML id emission, docutils drift policy), and cross-check the new preset between the renderer and the `myst-anchors` CLI.
- config help text lists all three presets (propagates to the docutils CLI --help and the generated configuration reference) - document that the `myst-anchors` CLI preview does not apply the renderer-only empty-slug rule - fidelity test vectors now cover the whole docutils translate table - `docutils_slugify` docstring: note the whitespace-normalization step and that the result can be empty
chrisjsewell
marked this pull request as ready for review
July 15, 2026 11:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finishes the job #1158's slug module started: the anchor behaviour becomes a formal, versioned contract that alternative implementations and tooling can pin against, plus a third slug preset for mixed-format projects.
A
docutilsslug presetmyst_heading_slug_func = "docutils"produces slugs byte-identical todocutils.nodes.make_id— the id style reStructuredText headings receive — so a Sphinx project mixing.rstand.mdsources can choose one uniform anchor rule.myst_parser.slugs(no docutils import there; provenance noted — docutils is public domain), keeping the module's replicate-from-one-file property.make_id. Review additionally fuzzed 210k random strings and swept every codepoint U+0020–U+2FFF with zero divergences. Drift policy (documented): if a future docutils changesmake_id, the preset and corpus stay byte-stable — they are the contract — and the divergence gets documented.myst-anchorsCLI picks the preset up automatically (--slug-func docutils), with renderer/CLI cross-check tests.Empty slugs mean no anchor
make_idreturns""for any title without Latin letters (Привет,2.0,!!!) — far more often than the github preset. Previously (unreleased, since #1158) an empty slug was stored, and a second empty-slugging heading received a literal-1HTML anchor. Now an empty slug means the heading simply gets no anchor — not stored, no dedup participation, no id emitted — for all presets and custom functions. Two behavioural nuances from review, both strictly-better and test-covered:<span id="-1">anchors no longer appear for duplicate punctuation-only headings;[](#)(an empty fragment) no longer silently resolves to a punctuation-only heading — it now warns as an unresolved reference.The versioned contract
tests/fixtures/slugs.jsonnow carriesversion: 1and a policy statement: append-only within a version; version bump on any semantic change to an existing vector or algorithm. Newdocutilsvectors were generated against the realmake_idat authoring time.docs/syntax/optional.md), states in one place: slug generation (presets + corpus as the machine-readable half), fixed-base deduplication (x, x-1, x-2; docutils' ownidNdedup deliberately not emulated), the empty-slug rule, explicit-id priority, HTML id emission (id_prefixbypassed), and the drift policy. Themyst-anchorsCLI preview's one divergence (it cannot apply the renderer-only empty-slug rule) is documented alongside the CLI example.Review
Two implementation agents + a combined adversarial/behaviour review with old-vs-new probes against master: github/gitlab documents render byte-identically (docutils publish, sphinx HTML, searchindex, warnings) except the intended empty-slug fix; docutils-preset end-to-end resolution (same-doc, cross-doc, dedup-slug, explicit-target priority, slug==primary-id coincidence) verified with zero warnings. One pre-existing bug surfaced by review (topmatter clobbers validated config values, breaking all preset strings in frontmatter since #1158) is tracked separately in #1167 rather than scope-creeping here.
Full suite: 1245 passed. No changes to the
github/gitlabslug outputs; no new configuration surface beyond the preset name.Generated by Claude Code