Skip to content

✨ NEW: docutils heading-anchor slug preset, and a versioned anchor contract#1168

Merged
chrisjsewell merged 3 commits into
masterfrom
claude/myst-parser-triage-u28jxh
Jul 15, 2026
Merged

✨ NEW: docutils heading-anchor slug preset, and a versioned anchor contract#1168
chrisjsewell merged 3 commits into
masterfrom
claude/myst-parser-triage-u28jxh

Conversation

@chrisjsewell

Copy link
Copy Markdown
Member

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 docutils slug preset

myst_heading_slug_func = "docutils" produces slugs byte-identical to docutils.nodes.make_id — the id style reStructuredText headings receive — so a Sphinx project mixing .rst and .md sources can choose one uniform anchor rule.

  • The algorithm and its translate tables are ported into the pure, stdlib-only myst_parser.slugs (no docutils import there; provenance noted — docutils is public domain), keeping the module's replicate-from-one-file property.
  • Fidelity is pinned by CI, not by care: the corpus plus adversarial vectors (digraphs, the full 33-entry translate table, combining marks, compatibility/ligature forms, mathematical alphanumerics, Turkish dotless-i, whitespace runs) are asserted byte-equal against the real 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 changes make_id, the preset and corpus stay byte-stable — they are the contract — and the divergence gets documented.
  • The myst-anchors CLI picks the preset up automatically (--slug-func docutils), with renderer/CLI cross-check tests.

Empty slugs mean no anchor

make_id returns "" 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 -1 HTML 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.json now carries version: 1 and a policy statement: append-only within a version; version bump on any semantic change to an existing vector or algorithm. New docutils vectors were generated against the real make_id at authoring time.
  • A new normative docs subsection, The anchor contract (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' own idN dedup deliberately not emulated), the empty-slug rule, explicit-id priority, HTML id emission (id_prefix bypassed), and the drift policy. The myst-anchors CLI 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/gitlab slug outputs; no new configuration surface beyond the preset name.


Generated by Claude Code

…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
chrisjsewell marked this pull request as ready for review July 15, 2026 11:58
@chrisjsewell
chrisjsewell merged commit 4ec4cf8 into master Jul 15, 2026
23 checks passed
@chrisjsewell
chrisjsewell deleted the claude/myst-parser-triage-u28jxh branch July 15, 2026 12:00
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.

1 participant