Structure travels farther than convention
## Installation is recognisable as a level-two ATX heading across CommonMark
renderers. Installation followed by hyphens is a Setext level-two heading.
That agreement is about document structure.
The fragment after a URL’s # belongs to HTML navigation. A renderer may lower
the heading, remove punctuation and replace spaces with hyphens, but Markdown’s
core specification does not require that algorithm. The difficult cases expose
the gap:
| Visible headings | Questions a host must answer |
|---|---|
Café & crème | Preserve Unicode, decompose accents, or encode the original? |
Install! and Install | Do punctuation differences survive slugging? |
Three copies of Usage | Is the suffix -1, -2, _2, or something else? |
Two conforming Markdown tools can answer differently because heading parsing and automatic id generation are separate jobs.
Parse blocks before scanning hashes
A line beginning ## is not necessarily a document heading. Inside a fenced
code block it is literal example content. Four-space-indented text is code too.
CommonMark’s block phase takes precedence for this reason: decide where code and
other blocks are, then interpret heading syntax in the remaining structure.
Before you read on
A README contains `## Delete everything` inside a fenced shell example. Should a table-of-contents scanner link it?
No. A scanner has to track fenced and indented code before it interprets heading markers. Otherwise documentation examples become false navigation entries.
The Markdown table of contents maker follows that order and publishes its supported subset. It does not call itself a universal renderer for every extension.
Make both sides of the link explicit
An authored <a id="install"></a> supplies the target instead of asking a host
to invent one. A generated list can link to exactly #install, and duplicate
ids can be resolved before the document leaves the tool.
That improves portability, but it is not magic. Raw HTML is allowed by CommonMark and can still be removed by a site’s sanitiser. The honest workflow is to know the destination policy, generate explicit ids when it permits them and preview one published document before applying the convention everywhere.
Generated text should be inspectable and replaceable
A generator needs ownership boundaries. Start and end comments identify the table it may replace later; marked anchor lines identify the targets it owns. Unmarked HTML remains the author’s. That makes regeneration idempotent without turning “remove the old table” into a broad deletion heuristic.
The text diff maker provides the second half of that discipline. Compare source and output with three context lines to see the table and anchors in place, or zero context to isolate only inserted lines. A plain line tool is useful for sorting content, but it does not preserve a document’s heading hierarchy and should not be used as a Markdown parser.
Common questions
Does CommonMark define GitHub-style heading anchors?
No. CommonMark defines headings and links, while GitHub-style automatic ids are a renderer convention. Another host can produce a different fragment from the same visible heading.
Are explicit HTML anchors valid in Markdown?
CommonMark permits raw HTML blocks and inline HTML, and an HTML `id` can be a fragment target. A publishing service can still sanitise that HTML as a product or security policy.
How can I review what a generator changed?
Compare the original and generated Markdown line by line. A unified diff makes every inserted table line, marker and anchor visible without asking you to reread the unchanged prose.