Files
khosra/reference/goldmark-behaviours.md
Claude Opus 5andbdeshi 2609f69a87 record what this session established, with how it was established
Three reference files, each stating whether a fact was measured, read from
source, or asserted — an unattributed number is a rumour.

math-on-the-web: MathML is the only no-JS route, the one pure-Go TeX→MathML
library is an untagged 2023 commit, and writing MathML directly costs nothing
since raw HTML renders.

syntax-highlighting-choices: chroma is two modules and ~5MB, every alternative a
search returns is JavaScript, and custom lexers load from XML at runtime without
a rebuild.

goldmark-behaviours: the seven surprises that caused or nearly caused defects —
strikethrough claiming a single tilde, delimiter runs pairing across whitespace,
per-parse heading id counters, footnote id prefixes, raw HTML being dropped
rather than escaped, the language class already in the output, and ParseFS
globbing.
2026-08-02 01:04:14 +06:00

2.4 KiB

goldmark behaviours worth remembering

Established 2026-08-01/02 against goldmark v1.8.5, each by reading the module source or measuring on the real binary. These are the ones that surprised us; every one caused or nearly caused a defect.

Strikethrough claims a single tilde

extension.Strikethrough matches ~x~ as well as ~~x~~ (extension/strikethrough.go accepts OriginalLength <= 2). With it enabled, H~2~O renders H<del>2</del>O. Two features cannot share the byte, which is why khosra implements strikethrough itself (ADR-0061).

Delimiter runs pair across whitespace

Under goldmark's delimiter machinery x^2 + y^2 = z^2 pairs its carets across the whole expression and yields x<sup>2 + y</sup>2. Sub- and superscript therefore scan to a closing byte and refuse to cross whitespace — Pandoc's rule, and the reason a subscript holds a formula and never a phrase.

Heading ids deduplicate, from -1

parser.ids.Generate appends -1, -2, … to a repeated id — so three ## Description become description, description-1, description-2. The counter lives on the parse context, so separate parses number independently: an included file parsed on its own bytes duplicated ids until its parse was given the parent's set via parser.NewContext(parser.WithIDs(pc.IDs())).

Footnote ids can be namespaced

extension.WithFootnoteIDPrefix and WithFootnoteIDPrefixFunction(func(ast.Node) []byte) exist. The function receives the footnote node, and n.OwnerDocument().Meta() is a place to stash a per-parse prefix — which is how an included file's notes stopped colliding with the page's (ADR-0058).

Raw HTML is dropped, not escaped

Without html.WithUnsafe(), goldmark replaces raw HTML with <!-- raw HTML omitted -->. The text around the tags survives, so H<sub>2</sub>O reads "H2O" on the page: meaning lost with no visible error.

Fenced code already carries the language

The default HTML renderer writes <pre><code class="language-go"> from the info string, so browser-side highlighting needs no engine change — the hook is in the output whether or not anything highlights.

ParseFS accepts globs

template.ParseFS treats names as glob patterns and errors when one matches nothing, which is why khosra's parseSet checks fs.Glob first and skips empty matches. That is what lets a theme supply fragments as one file, a directory, or both.