bro. i almost cried.
opened my app on the "tips & tricks" topic and the terminal EXPLODED. like, 733 errors per frame. the app became unusable, freezing, everything broken. i thought it was a bug in my checklist (bc it had issues before), but nope.
the investigation (spoiler: it was LaTeX)
spent ages fixing the checklist (NoteChecklistContent). found real bugs there:
IntrinsicWidthfighting withFlexible(constraint conflict)Checkboxwith messed up sizing inside aRowmainAxisSize: MainAxisSize.minon aRowwithFlexiblechildren
fixed everything. swapped Checkbox for Icon + GestureDetector, Flexible → Expanded, etc. valid fixes, but they didnt solve the main problem. (一_一)
then i redirected flutter run output to a log file and finally saw the real cascade:
| Error | Count |
|---|---|
| Invalid argument(s): string is not well-formed UTF-16 | 3 |
| RenderLine does not implement "computeDryBaseline" | 1 |
| RenderBox was not laid out: RenderIntrinsicWidth | 1 |
| RenderBox was not laid out: RenderIndexedSemantics | 288 |
| 'parentDataDirty' assertion failures | 440 |
| TOTAL | ~733/frame |
the root cause
the topic had a note with inline LaTeX: $E = mc^2$. GptMarkdown converts $...$ to \(...\) and sends it to flutter_math_fork to render via Math.tex().
turns out flutter_math_fork v0.7.4 uses a RenderLine class (custom RenderBox) that doesnt implement computeDryBaseline, a method that became mandatory in flutter 3.38.
result: RenderLine fails → parent RenderIntrinsicWidth cant layout → RenderIndexedSemantics (accessibility) fails in cascade → framework detects parentDataDirty on hundreds of render objects → repeats every frame. infinite loop of pain and suffering.
and the worst part: theres no new version of either package (gpt_markdown 1.1.5 and flutter_math_fork 0.7.4). both are abandoned and incompatible with flutter 3.38. (ꐦ°᷄д°᷅)
the workaround (yeah its a hack)
since we cant use Math.tex() without crashing, i made a safe builder that renders LaTeX as styled text (monospace + italic) instead of a pretty formula:
static Widget safeLatexBuilder(
BuildContext context,
String tex,
TextStyle textStyle,
bool inline,
) {
final theme = Theme.of(context);
return Text(
tex,
style: textStyle.copyWith(
fontFamily: 'monospace',
fontStyle: FontStyle.italic,
color: theme.colorScheme.onSurface.withValues(alpha: 0.85),
),
);
}
also made my own preprocessing to convert $...$ → \(...\) outside of GptMarkdown, bc its internal regex was generating malformed UTF-16 strings:
static String _convertDollarLatex(String text) {
var result = text.replaceAllMapped(
RegExp(r'(?<!\\)\$\$(.*?)(?<!\\)\$\$', dotAll: true),
(match) => '\\[${match[1] ?? ""}\\]',
);
if (!result.contains(r'\(')) {
result = result.replaceAllMapped(
RegExp(r'(?<!\\)\$(.*?)(?<!\\)\$'),
(match) => '\\(${match[1] ?? ""}\\)',
);
}
return result;
}
then called GptMarkdown with useDollarSignsForLatex: false (since conversion was done manually) and applied the safe latexBuilder in all places that use GptMarkdown directly:
study_session_screen.dart(3 uses)edit_note_dialog.dart(1 use)flashcard_bubble_content.dart(1 use)
results
| Metric | Before | After |
|---|---|---|
| parentDataDirty assertions | 440/frame | 0 |
| RenderBox was not laid out | 289/frame | 0 |
| computeDryBaseline errors | 1/frame | 0 |
| App usable | no | yes |
the trade-off is that LaTeX doesnt render as a pretty formula anymore (it shows E = mc^2 in monospace instead of the formatted one). but at least the app works. when the package maintainers wake up and release an update, ill revert everything.
always redirect logs to a file. if i had kept staring at the terminal scrolling 733 errors per frame id never find the root cause. and doubt your own guesses, i wasted time on the checklist when the problem was LaTeX all along. ¯\_(ツ)_/¯