Skip to content

finding(plugin-markdown): extractToc strips tag-shaped text INSIDE inline code, so its #id links miss the heading they name #7658

Description

@os-sam

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfinding

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions