buildHTML only lifts an equation tag when the root has exactly two elements
buildHTML moves a tag out of .katex-base only when the root expression has exactly two elements:
// src/buildHTML.ts
let eqnNum;
if (expression.length === 2 && expression[1].hasClass("katex-tag")) {
// An environment with automatic equation numbers, e.g. {gather}.
eqnNum = expression.pop();
}Every numbered environment satisfies this today because they all return makeFragment([tableBody, tagCol]). An environment that returns anything else silently loses its numbers.
Why it is easy to trip over. An environment whose html builder returns more than two root elements — say a left-hand side, a delimiter, the alignment and the tag — renders with no number anywhere, and nothing throws. Working out why means reading buildHTML.ts: neither the environment API nor any comment mentions the constraint. The workaround is to collapse the other parts into one span purely so the count comes to two.
What the stylesheet actually requires is only that the tag is a direct child of .katex-html:
.katex-display > .katex > .katex-html > .katex-tag { position: absolute; right: 0; }
.katex-base { position: relative; }No rule looks at the number of siblings. Measured in Chrome on a 600px container: as a direct child the tag occupies 572…600; moved inside a .katex-base it lands at 299…354, because the base becomes its containing block.
Where the number came from. The check was added in 80b0e3dc ("Support {gather} and {gather*}", #2183). Neither the commit message nor the PR description mentions it, and all 11 inline review comments on that PR are on other files — buildHTML.js received none. It reads as a shape-match against the only producer that existed at the time.
What it changes today
| input | .katex-html children |
number |
|---|---|---|
\begin{gather}a=b\end{gather} |
katex-base, katex-tag |
shown |
x=\begin{align}a&=b\end{align} |
katex-base, katex-base |
lost |
\begin{gather}a=b\end{gather}z |
katex-base |
lost |
The last two are not valid LaTeX — these environments are paragraph-mode only — so the rendering is arguably free. No test and no screenshot covers any of them.
Suggested change
If you would like this relaxed, the condition becomes "the last root element is a tag":
let eqnNum;
- if (expression.length === 2 && expression[1].hasClass("katex-tag")) {
- // An environment with automatic equation numbers, e.g. {gather}.
+ if (expression.length > 0 &&
+ expression[expression.length - 1].hasClass("katex-tag")) {
+ // An environment with automatic equation numbers, e.g. {gather}.
eqnNum = expression.pop();
}I ran the suite with this applied on top of main: 1305/1305 pass. The behaviour it changes is row 2 of the table above — x=\begin{align}… would put the number at the right margin instead of dropping it. Row 3 is unaffected, since its last element is not a tag.
What I'd want decided first
Whether a numbered environment that is not the whole formula should show its number at all. Today it is dropped silently; relaxing the condition shows it; a third option is to reject the input, since it is invalid LaTeX either way. That is a call about intended behaviour rather than a refactor, and it wants a test either way.
Happy to open a PR for whichever you prefer, or to leave it as is.
Source: KaTeX/KaTeX