Measured while implementing objectui#7644 (PR #7657), which had to derive heading anchors the way the renderers do and therefore compared several candidate rules against the real github-slugger. Not fixed there — different package, different defect class. Unassigned, no labels; routing and grading are the triage seat's.
What was measured
packages/plugin-markdown/src/toc.ts builds a table of contents, and its own doc comment states the contract it is meant to hold:
Slugs are generated with the SAME github-slugger that rehype-slug uses, walking every heading in document order so duplicate-heading -1/-2 suffixes line up — that is what makes a #id TOC link resolve to the rendered heading's anchor.
It does not hold, for one heading shape. stripInline() applies its rules in sequence:
.replace(/`([^`]+)`/g, "$1") // inline code -> its content
.replace(/<[^>]+>/g, "") // raw html -> removed
The second rule runs over text the first has just unwrapped, so angle-bracket text that was inside a code span is deleted. In the rendered DOM it is literal text, because rehype-slug slugs the heading's flattened node value and a code span's content is a value. The two therefore disagree, and the id in the TOC names a heading anchor that does not exist.
Three heading shapes in this repository's own docs hit it. Measured against the real github-slugger resolved from this package's own dependency:
| heading |
extractToc id |
id rehype-slug puts on the heading |
`objectui generate <type> <name>` (alias `g`) |
objectui-generate---alias-g |
objectui-generate-type-name-alias-g |
`objectui add <component>` |
objectui-add |
objectui-add-component |
Serving metadata over HTTP (`?api=<base>`) |
serving-metadata-over-http-api |
serving-metadata-over-http-apibase |
Live instances: content/docs/utilities/cli.mdx (3 headings), content/docs/utilities/runner.mdx (1), packages/cli/README.md (3).
Why it is worth a card
A TOC is exactly the surface where a wrong anchor is invisible: the entry renders, it is clickable, and it silently does nothing. This is the objectui#7644 class inside a published package rather than in a gate — and note that #7644's new check cannot see it, because it resolves anchors written in markdown source, not ids a component computes at runtime.
Reproduce (the flattener rule that is correct is in scripts/check-doc-links.mjs, where a code span's content is kept literal):
node -e "
const s = '\`objectui add <component>\`';
const stripInline = t => t.replace(/\!\[[^\]]*\]\([^)]*\)/g,'').replace(/\[([^\]]+)\]\([^)]*\)/g,'\$1')
.replace(/\`([^\`]+)\`/g,'\$1').replace(/(\*\*|__)(.*?)\1/g,'\$2').replace(/(\*|_)(.*?)\1/g,'\$2')
.replace(/<[^>]+>/g,'').trim();
console.log(stripInline(s)); // 'objectui add' <- angle-bracket text gone
console.log(s.replace(/\`/g,'')); // 'objectui add <component>' <- what the DOM has
"
Not proposed here, deliberately
Whether the repair is reordering the two rules, tokenising code spans before any other inline rule (what PR #7657 does for the gate), or dropping the hand-rolled flattener for the mdast one is a design call for the triage seat. The <...>-removal rule also has a second question behind it — a heading containing genuine raw HTML flattens to its raw text under mdast, not to nothing — which this card measured no instance of.
Refs: objectui#7644 · PR #7657
Measured while implementing objectui#7644 (PR #7657), which had to derive heading anchors the way the renderers do and therefore compared several candidate rules against the real
github-slugger. Not fixed there — different package, different defect class. Unassigned, no labels; routing and grading are the triage seat's.What was measured
packages/plugin-markdown/src/toc.tsbuilds a table of contents, and its own doc comment states the contract it is meant to hold:It does not hold, for one heading shape.
stripInline()applies its rules in sequence:The second rule runs over text the first has just unwrapped, so angle-bracket text that was inside a code span is deleted. In the rendered DOM it is literal text, because
rehype-slugslugs the heading's flattened node value and a code span's content is avalue. The two therefore disagree, and the id in the TOC names a heading anchor that does not exist.Three heading shapes in this repository's own docs hit it. Measured against the real
github-sluggerresolved from this package's own dependency:extractTocidrehype-slugputs on the heading`objectui generate <type> <name>` (alias `g`)objectui-generate---alias-gobjectui-generate-type-name-alias-g`objectui add <component>`objectui-addobjectui-add-componentServing metadata over HTTP (`?api=<base>`)serving-metadata-over-http-apiserving-metadata-over-http-apibaseLive instances:
content/docs/utilities/cli.mdx(3 headings),content/docs/utilities/runner.mdx(1),packages/cli/README.md(3).Why it is worth a card
A TOC is exactly the surface where a wrong anchor is invisible: the entry renders, it is clickable, and it silently does nothing. This is the objectui#7644 class inside a published package rather than in a gate — and note that #7644's new check cannot see it, because it resolves anchors written in markdown source, not ids a component computes at runtime.
Reproduce (the flattener rule that is correct is in
scripts/check-doc-links.mjs, where a code span's content is kept literal):Not proposed here, deliberately
Whether the repair is reordering the two rules, tokenising code spans before any other inline rule (what PR #7657 does for the gate), or dropping the hand-rolled flattener for the
mdastone is a design call for the triage seat. The<...>-removal rule also has a second question behind it — a heading containing genuine raw HTML flattens to its raw text undermdast, not to nothing — which this card measured no instance of.Refs: objectui#7644 · PR #7657