TeX-faithful brace grouping (MTMathGroup) — fix #177#247
Conversation
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
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 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
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;
}| case kMTMathAtomColor: | ||
| case kMTMathAtomColorbox: | ||
| case kMTMathAtomOrdinary: | ||
| case kMTMathAtomOrdGroup: // Ord group is spaced as Ordinary |
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 <[email protected]>
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