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.
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.