TeX-faithful brace grouping (MTMathGroup) — fix #177#247
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also handle \over/\atop/\choose family correctly: these TeX group-transformation commands replace the enclosing group with a fraction at the parent level (TeX semantics), so their result is NOT wrapped in MTMathGroup. A private ivar _groupWasTransformedByStopCommand tracks this. Update testSqrtInGroup to expect the new MTMathGroup wrapping (semantically correct). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces MTMathGroup (representing kMTMathAtomOrdGroup) to properly wrap brace groups {...} in math mode as Ord subformulas. This change scopes interior style nodes (fixing issue #177), allows scripts to target the entire group, and prevents Bin/Ord reclassification from crossing brace boundaries. Feedback on these changes highlights two critical issues in MTMathListBuilder.m: first, prevAtom is not updated when a group is transformed, which can break subsequent scripting; second, the _groupWasTransformedByStopCommand flag can leak to parent groups and should be reset immediately after being read. Additionally, a redundant kMTMathAtomOrdGroup case was identified in MTTypesetter.m since these atoms are already reclassified to kMTMathAtomOrdinary before spacing lookup.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| BOOL transformed = _groupWasTransformedByStopCommand; | ||
| if (oneCharOnly || transformed) { | ||
| // Field brace (^{…}, _{…}, \frac{…}, command argument): the {…} | ||
| // *is* the field. Flatten and return it as the field — unchanged. | ||
| // Also: a group-transforming command (\over, \atop, \choose, | ||
| // \brack, \brace) fired inside this group. The resulting fraction | ||
| // replaces the group in the parent list (TeX behavior) — do NOT | ||
| // wrap in MTMathGroup. Fall through to continue after appending. | ||
| [list append:sublist]; | ||
| if (oneCharOnly) { | ||
| return list; | ||
| } | ||
| continue; | ||
| } |
There was a problem hiding this comment.
There are two critical issues in this block:
prevAtomis not updated: In thetransformedpath,sublistis appended tolistand the loop continues, butprevAtomis never updated to the last atom ofsublist. This causes any subsequent scripts (like^2) or operators to attach to the wrong atom (or create a new empty atom) instead of the transformed fraction._groupWasTransformedByStopCommandleak: Since_groupWasTransformedByStopCommandis a shared instance variable, when a nested group is transformed (e.g.,{a {b \over c}}), the flag remainsYESeven after returning to the parent group. This causes grandparent groups to also skip wrapping inMTMathGroup. Resetting it toNOimmediately after reading prevents this leak.
BOOL transformed = _groupWasTransformedByStopCommand;
_groupWasTransformedByStopCommand = NO;
if (oneCharOnly || transformed) {
// Field brace (^{…}, _{…}, \frac{…}, command argument): the {…}
// *is* the field. Flatten and return it as the field — unchanged.
// Also: a group-transforming command (\over, \atop, \choose,
// \brack, \brace) fired inside this group. The resulting fraction
// replaces the group in the parent list (TeX behavior) — do NOT
// wrap in MTMathGroup. Fall through to continue after appending.
prevAtom = [sublist.atoms lastObject];
[list append:sublist];
if (oneCharOnly) {
return list;
}
continue;
}There was a problem hiding this comment.
Verified both points against the current head (adbe7f9).
1. prevAtom not updated — real bug, fixed in adbe7f9. Confirmed by reproducing it: {a \over b}^2 serialized as \frac{a}{b}{}^{2} — the ^2 attached to a spurious empty Ord instead of the fraction, because the transformed branch appended sublist without setting prevAtom (the ^/_/' branches all key off prevAtom). Restored prevAtom = [sublist.atoms lastObject] in that path, matching the pre-grouping behavior and the shared append path below. Regression test testScriptAfterOverTransformedGroupAttachesToFraction added.
2. _groupWasTransformedByStopCommand leak — already fixed in f99517a. This review was filed against b7a705a (3rd commit). The 4th commit f99517a ("Fix stale _groupWasTransformedByStopCommand leaking into enclosing group") landed afterward and does exactly the read-and-clear suggested here — BOOL transformed = _groupWasTransformedByStopCommand; _groupWasTransformedByStopCommand = NO; at MTMathListBuilder.m:244–245, read and cleared in the {…} branch before any fallthrough. The {{a \over b}\scriptstyle c}z leak variant is covered by testBraceGroupingAroundOverTransform. No further change.
| case kMTMathAtomColor: | ||
| case kMTMathAtomColorbox: | ||
| case kMTMathAtomOrdinary: | ||
| case kMTMathAtomOrdGroup: // Ord group is spaced as Ordinary |
There was a problem hiding this comment.
Confirmed redundant — removed in adbe7f9. createDisplayAtoms reclassifies kMTMathAtomOrdGroup → kMTMathAtomOrdinary (MTTypesetter.m:722–723) before any inter-element lookup, and passes currentType:kMTMathAtomOrdinary explicitly, so this case was unreachable. This matches the direct analog kMTMathAtomBox, which has no switch entry and relies on the same reclassify-before-lookup pattern; the reclassification in createDisplayAtoms is now the single source of truth.
Code review — TeX-faithful brace grouping (
|
Read-and-clear the flag in the {…} branch. Previously a \over/\atop
transform inside an INNER group left the flag set, so the ENCLOSING
group was wrongly treated as transformed and dropped — reintroducing
the #177 \scriptstyle leak when a leading inner group was \over-ed
(e.g. {{a \over b}\scriptstyle c}z). Adds nested regression tests.
Co-Authored-By: Claude <noreply@anthropic.com>
…undant spacing case
Issue 1 (HIGH, from review): in the {…} transformed path — when a
group-transforming command (\over/\atop/\choose/\brack/\brace) fires
inside a {…} group — prevAtom was not updated after appending the
resulting fraction. A following ^ / _ / ' then attached to a spurious
empty Ord instead of the fraction (e.g. {a \over b}^2 serialized as
\frac{a}{b}{}^{2}). Restore prevAtom = [sublist.atoms lastObject],
mirroring the pre-grouping behavior and the shared append path below.
Adds regression test testScriptAfterOverTransformedGroupAttachesToFraction.
Issue 3 (MEDIUM, from review): remove the redundant kMTMathAtomOrdGroup
case from getInterElementSpaceArrayIndexForType. createDisplayAtoms
reclassifies OrdGroup -> Ordinary before any spacing lookup (mirroring
kMTMathAtomBox, which has no switch entry), so the case was unreachable
dead code; the reclassification is now the single source of truth.
Issue 2 (the _groupWasTransformedByStopCommand leak) was already fixed
in f99517a (read-and-clear at lines 244-245), pushed after this review
was filed against b7a705a; no change here.
Co-Authored-By: Claude <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the MTMathGroup class and the kMTMathAtomOrdGroup atom type to represent brace groups {...} in math mode, aligning with TeX's Ord noad with a sub-mlist and KaTeX's ordgroup. This change scopes interior style nodes (such as \scriptstyle) to the group, preventing style leaks to the outer context and resolving issue #177. The math list builder is updated to wrap brace groups appropriately while handling TeX group-transformation commands like \over, and the typesetter is updated to render these groups and apply scripts correctly. Comprehensive unit tests have been added to verify brace grouping, scoping, and spacing. I have no feedback to provide as there are no review comments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
TeX-faithful brace grouping (
MTMathGroup)Fixes #177.
Makes a main-list
{…}brace group build a nested Ord subformula (MTMathGroup/kMTMathAtomOrdGroup) instead of flattening, so\scriptstyle(and Bin/Ord reclassification) is scoped to the group and scripts target the whole group. This is the honest analog of TeX's Ord-noad-with-sub_mlist/ KaTeX'sordgroup. Field braces (^{…},_{…},\frac{…}, command args) keep flattening.Goal: Introduce the
MTMathGroupatom, wire it into the parser for main-list braces, and render it in the typesetter — fixing thex{\scriptstyle y}zstyle-leak, with the branch buildable and all tests green at each commit.Design docs
docs/plans/2026-06-30-tex-faithful-brace-grouping.mddocs/lld/2026-06-30-tex-faithful-brace-grouping.mdCommits
[item 1] Add MTMathGroup atom (kMTMathAtomOrdGroup) for brace groups[item 2] Wrap main-list {…} as MTMathGroup in the parser[item 3] Render MTMathGroup in the typesetter (fix #177 style leak)Notes / deviations from the plan
Beyond the plan's stated 5 data rows, three additional tests needed handling to keep the suite TeX-faithful and green:
testSqrtInGroup—{\sqrt}now correctly wraps the radical in anMTMathGroup; expectation updated toMTMathGroup{Radical}/{\sqrt{}}.testOverInParens/testAtopInParens—\over/\atop-family commands transform the enclosing group into a parent-level fraction (TeX group-transformation), so these groups must NOT be wrapped. A private_groupWasTransformedByStopCommandflag skips wrapping in that case; both tests keep their original bare-fraction expectations.Full test suite passes (all 7 test classes).
🤖 Generated with Claude Code