From 2609f69a87fedbd6f6cfa601c93853ee3bcad512 Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Sun, 2 Aug 2026 01:04:14 +0600 Subject: [PATCH] record what this session established, with how it was established MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- reference/README.md | 3 ++ reference/goldmark-behaviours.md | 45 ++++++++++++++++++++++++ reference/math-on-the-web.md | 28 +++++++++++++++ reference/syntax-highlighting-choices.md | 34 ++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 reference/goldmark-behaviours.md create mode 100644 reference/math-on-the-web.md create mode 100644 reference/syntax-highlighting-choices.md diff --git a/reference/README.md b/reference/README.md index 5523e96..40786de 100644 --- a/reference/README.md +++ b/reference/README.md @@ -18,3 +18,6 @@ Out of agent context by default, this index included. Opened only when the human ## Index - [agent-session-costs.md](agent-session-costs.md) — how context and token cost accumulate in an agent session +- [math-on-the-web.md](math-on-the-web.md) — rendering maths with no JavaScript, and the state of Go TeX→MathML +- [syntax-highlighting-choices.md](syntax-highlighting-choices.md) — chroma's cost, what it gives, and why there is no lighter option +- [goldmark-behaviours.md](goldmark-behaviours.md) — the goldmark surprises that caused or nearly caused defects diff --git a/reference/goldmark-behaviours.md b/reference/goldmark-behaviours.md new file mode 100644 index 0000000..9bfc074 --- /dev/null +++ b/reference/goldmark-behaviours.md @@ -0,0 +1,45 @@ +# 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 `H2O`. 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 `x2 + y2`. 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 ``. The *text* around +the tags survives, so `H2O` reads "H2O" on the page: meaning lost with no visible error. + +## Fenced code already carries the language + +The default HTML renderer writes `
` 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.
diff --git a/reference/math-on-the-web.md b/reference/math-on-the-web.md
new file mode 100644
index 0000000..dce0183
--- /dev/null
+++ b/reference/math-on-the-web.md
@@ -0,0 +1,28 @@
+# Rendering maths without JavaScript
+
+Established 2026-08-02 by searching, then querying the Go module proxy directly. Re-check before relying
+on the library situation; the browser facts are stable.
+
+## The shape of the problem
+
+KaTeX and MathJax are JavaScript. A site that refuses client-side script cannot use them at runtime, so the
+only no-JS route is **TeX → MathML at render time**. MathML is native browser markup: Firefox and Safari
+have supported it for years, Chromium since 2023 (MathML Core). No script, no font loading, no layout pass
+of our own.
+
+## Go libraries, as of August 2026
+
+| Module | State | Verdict |
+|---|---|---|
+| `git.sr.ht/~mekyt/latex2mathml` | Pure Go, a port of the Python library of the same name. **No tagged release** — latest resolves to `v0.0.0-20231214134936-808832af73fc`, a bare commit from December 2023. sourcehut returned a 502 while this was checked | The only real candidate, and the weakest dependency this project would have taken |
+| `codeberg.org/go-latex/latex` | Properly versioned (v0.3.0), maintained | **Wrong output** — it draws equations, MathJax-style, rather than emitting MathML |
+| `jgm/texmath` | Mature, well maintained | Haskell. Usable only as a service, which fails the sovereignty test |
+
+`go list -m -versions git.sr.ht/~mekyt/latex2mathml` prints the module path with no versions after it, which
+is how an untagged module reports.
+
+## The option that costs nothing
+
+Since raw HTML renders for site-root content (ADR-0060), **MathML can be written straight into a bundle**.
+Verbose by hand, no dependency, no script, correct output. `$…$` sugar can be added later over the same
+output without changing anything downstream, so choosing this now forecloses nothing.
diff --git a/reference/syntax-highlighting-choices.md b/reference/syntax-highlighting-choices.md
new file mode 100644
index 0000000..5a91ddf
--- /dev/null
+++ b/reference/syntax-highlighting-choices.md
@@ -0,0 +1,34 @@
+# Syntax highlighting, and what it costs
+
+Established 2026-08-02: module footprint and binary sizes measured by building a probe module, the
+alternatives surveyed by search. Numbers are for chroma v2.27.0 on darwin/arm64.
+
+## There is one mature pure-Go option
+
+chroma. Every "alternative to chroma" a search returns — Prism, highlight.js, Rainbow — is JavaScript, and
+therefore unavailable to a theme that ships no script. `zyedidia/highlight` exists but needs syntax files
+supplied per language and covers far less.
+
+## What chroma costs
+
+- **Two modules**: `github.com/alecthomas/chroma/v2` and `github.com/dlclark/regexp2/v2`. Nothing else.
+- **~5MB of binary.** A probe importing lexers, formatters and styles built at 7.4MB against a 2.4MB
+  baseline; khosra itself went from ~15MB to ~19MB.
+- chroma is larger than khosra.
+
+## What it gives beyond colour
+
+`html.WithLineNumbers`, `html.BaseLineNumber(n)` for a custom starting number, and
+`html.HighlightLines([][2]int)`. With `html.WithClasses(true)` it emits token classes and no inline colour,
+so the palette stays in a stylesheet the theme owns.
+
+## Custom lexers need no rebuild
+
+`chroma.Unmarshal(data []byte) (*RegexLexer, error)` reads a lexer from XML — the same format chroma's own
+lexers ship in, under `lexers/embedded/*.xml`. With `chroma.NewLexerRegistry()` a site root could carry its
+own language definition and register it at startup. Not built; the door is open by construction.
+
+## What chroma does not give
+
+A filename or title on a block, and content read from a file. Those are the engine's, whatever highlights.
+So a design that parses the fence's info string works identically with chroma, without it, or after it.