Skip to content

content: Fix bugs where TextSpan styles clobber each other with InlineContent.style attributes #1820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 21, 2025

Conversation

chrisbobbe
Copy link
Collaborator

Fixes #806.
Fixes #1812.
Fixes #1817.
Fixes #1818.

There are other issues with inline-span styles:

but I think those all involve WidgetSpans in InlineContent's span tree and can be addressed separately.

Before After
image image
image image

@chrisbobbe chrisbobbe added the maintainer review PR ready for review by Zulip maintainers label Aug 19, 2025
Copy link
Member

@rajveermalviya rajveermalviya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chrisbobbe! LGTM, moving over to Greg's review.

@rajveermalviya rajveermalviya added integration review Added by maintainers when PR may be ready for integration and removed maintainer review PR ready for review by Zulip maintainers labels Aug 20, 2025
@rajveermalviya rajveermalviya requested a review from gnprice August 20, 2025 18:22
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing these! Generally all looks good; a couple of nits.

Comment on lines 733 to 737
testFontWeight('is bold in bold span',
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1812
expectedWght: 600,
// **bold**
content: plainContent('<p><strong><code>bold</code></strong></p>'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
testFontWeight('is bold in bold span',
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1812
expectedWght: 600,
// **bold**
content: plainContent('<p><strong><code>bold</code></strong></p>'),
testFontWeight('is bold in bold span',
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1812
expectedWght: 600,
// **`bold`**
content: plainContent('<p><strong><code>bold</code></strong></p>'),

right?

Comment on lines 1075 to 1081
content: plainContent('<p><strong><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow>'
'<annotation encoding="application/x-tex"> \\lambda </annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base unknown">' // Server doesn't generate this 'unknown' class.
'<span class="strut" style="height:0.6944em;"></span>'
'<span class="mord mathnormal">λ</span></span></span></span></strong></p>'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about something like:

Suggested change
content: plainContent('<p><strong><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow>'
'<annotation encoding="application/x-tex"> \\lambda </annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base unknown">' // Server doesn't generate this 'unknown' class.
'<span class="strut" style="height:0.6944em;"></span>'
'<span class="mord mathnormal">λ</span></span></span></span></strong></p>'),
content: plainContent('<p><strong>$unsupportedKatexHtml</strong></p>'),

where that string is shared between these two new tests and the existing one above? That way it's clear that the bulk of the logic is in common, and one can more easily focus on the bits that differ.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; see what you think in my next revision

@chrisbobbe chrisbobbe force-pushed the pr-spans-clobbering-styles-fix branch from b19b946 to 0fe4f77 Compare August 20, 2025 22:31
@chrisbobbe
Copy link
Collaborator Author

Thanks for the reviews! Revision pushed.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Comments below.

Comment on lines 1049 to 1050
testWidgets('fallback to displaying KaTeX source if unsupported KaTeX HTML', (tester) async {
await prepareContent(tester, plainContent(ContentExample.mathInlineUnknown.html));
tester.widget(find.text(r'\lambda'));
testContentSmoke(ContentExample.mathInlineUnknown);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test nested in a test, right? So the outer should become a group instead. (Like it does in the next commit anyway.)

Comment on lines 1066 to 1053
testContentSmoke(ContentExample.mathInlineUnknown);

assert(ContentExample.mathInlineUnknown.html.startsWith('<p>'));
assert(ContentExample.mathInlineUnknown.html.endsWith('</p>'));
final unsupportedKatexHtml = ContentExample.mathInlineUnknown.html
.substring(3, ContentExample.mathInlineUnknown.html.length - 4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I see.

Can this also apply to the test above that has similar HTML?

    testWidgets('maintains font-size ratio with surrounding text, when falling back to TeX source', (tester) async {
      const unsupportedHtml = '<span class="katex">'
        '<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow>'
          '<annotation encoding="application/x-tex"> \\lambda </annotation></semantics></math></span>'
        '<span class="katex-html" aria-hidden="true">'
          '<span class="base unknown">' // Server doesn't generate this 'unknown' class.
          '<span class="strut" style="height:0.6944em;"></span>'
          '<span class="mord mathnormal">λ</span></span></span></span>';
      await checkFontSizeRatio(tester,
        targetHtml: unsupportedHtml,
        targetFontSizeFinder: mkTargetFontSizeFinderFromPattern(r'\lambda'));
    });

That's the one I had in mind at #1820 (comment) 🙂 — I guess that was ambiguous.

Comment on lines 1076 to 1065
// **$$ \lambda $$**
content: plainContent('<p><strong>$unsupportedKatexHtml</strong></p>'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
// **$$ \lambda $$**
content: plainContent('<p><strong>$unsupportedKatexHtml</strong></p>'),
content: plainContent('<p><strong>$unsupportedKatexHtml</strong></p>'),

This isn't HTML that we'd expect the server to emit for that input (or any input).

expectedWght: 600,
// **$$ \lambda $$**
content: plainContent('<p><strong>$unsupportedKatexHtml</strong></p>'),
styleFinder: (tester) => mergedStyleOf(tester, r'\lambda')!,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can avoid the need to look up the example to see that \lambda is the right text to look for:

Suggested change
styleFinder: (tester) => mergedStyleOf(tester, r'\lambda')!,
styleFinder: (tester) => mergedStyleOf(tester, ContentExample.mathInlineUnknown.expectedText)!,

(or maybe pull that into a variable next to unsupportedKatexHtml)

@chrisbobbe chrisbobbe force-pushed the pr-spans-clobbering-styles-fix branch 2 times, most recently from 7a485d5 to e0e6f12 Compare August 21, 2025 00:20
@chrisbobbe
Copy link
Collaborator Author

Thanks! Revision pushed.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning up these tests! Just a couple of nits.


testWidgets('displays KaTeX content', (tester) async {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Looks like the behavior of that test differs from this one, just in
not passing `findRichText: true`. But that didn't stop it from
finding the expected test and passing, so, shrug.

s/expected test/expected text/ ?

'<span class="mord mathnormal">λ</span></span></span></span>';
await checkFontSizeRatio(tester,
targetHtml: unsupportedHtml,
targetFontSizeFinder: mkTargetFontSizeFinderFromPattern(r'\lambda'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: squash these together:

9cc505f content test [nfc]: Get unsupported-katex HTML from ContentExample
bf823e1 content test [nfc]: Pull out an expectedText variable from ContentExample

since the first one leaves the literal r'\lambda' unexplained

Fixes zulip#1818.

The InlineContent widget creates a Text.rich with a tree of
`InlineSpan`s, and the widget's `style` param is passed to the root
span in that tree. That style's attributes will propagate down the
tree as defaults, which means that a non-root node will still be
styled with e.g. h1 font size if it doesn't `TextStyle.merge` that
root style into its own style, as was happening here.

That merging can also be unhelpful, not just unnecessary, because it
can clobber attributes that were set at nodes between the current
node and the root. This kind of clobbering causes some known issues;
the first of these is fixed in this commit, and we'll fix the rest,
coming up:
- zulip#1818 content: Unicode emoji in strikethrough should have
  strikethrough line, but doesn't
- zulip#1817 content: Bold text in strikethrough should have
  strikethrough line, but doesn't
- zulip#1812 content: Inline code in a bold span should be bold (but
  isn't)
- zulip#806 content: Inline code links should be rendered with the link
  color

We'll fix the others, coming up.
Fixes zulip#1817.

Similar reasoning as in the previous commit; see there. The bold
span-node was clobbering the strikethrough attribute when that is
set by an ancestor strikethrough node.

(This is bolderWghtTextStyle's only caller, so it's simple to change
its interface this way.)
In the 'inline math' group just above this, we have the following test:

  testContentSmoke(ContentExample.mathInline);

Looks like the behavior of that test differs from this one, just in
not passing `findRichText: true`. But that didn't stop it from
finding the expected text and passing, so, shrug.
This just calls `testWidgets` twice; there's no Future that callers
should want to await.
Also in rendering inline TeX source when it doesn't parse...but
that's decreasingly visible as we implement more TeX. :)

Fixes zulip#806.
Fixes zulip#1812.

Similar reasoning as in the previous commits; see there. The
inline-code span-node was clobbering the link-color and font-weight
attributes when those are set by ancestor link and emphasis nodes.
@chrisbobbe chrisbobbe force-pushed the pr-spans-clobbering-styles-fix branch from e0e6f12 to 06f6281 Compare August 21, 2025 00:32
@chrisbobbe
Copy link
Collaborator Author

Thanks! Revision pushed.

@gnprice
Copy link
Member

gnprice commented Aug 21, 2025

Thanks! Looks good; all clear to merge, pending CI.

@chrisbobbe chrisbobbe merged commit 3dc2669 into zulip:main Aug 21, 2025
1 check passed
@chrisbobbe chrisbobbe deleted the pr-spans-clobbering-styles-fix branch August 21, 2025 00:53
@chrisbobbe
Copy link
Collaborator Author

Thanks! Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration
Projects
None yet
3 participants