Skip to content

fix: use grapheme cluster count instead of UTF-16 code units for animation resume index#9

Closed
WilliamRWalsh wants to merge 1 commit intohooshyar:mainfrom
WilliamRWalsh:fix/grapheme-cluster-index-mismatch
Closed

fix: use grapheme cluster count instead of UTF-16 code units for animation resume index#9
WilliamRWalsh wants to merge 1 commit intohooshyar:mainfrom
WilliamRWalsh:fix/grapheme-cluster-index-mismatch

Conversation

@WilliamRWalsh
Copy link
Copy Markdown

@WilliamRWalsh WilliamRWalsh commented Mar 9, 2026

Summary

  • Fixes a bug where characters are dropped during streaming text animation when the text contains emoji or other multi-code-unit Unicode characters (surrogate pairs).
  • In _resumeCharacterByCharacterTyping and _resumeCharacterByCharacterTypingFromOldText, _displayedText.length returns UTF-16 code units, but is used as an index into Characters().toList() which is indexed by grapheme clusters. For emoji (e.g. 👍, which is 2 UTF-16 code units but 1 grapheme), the index overshoots, skipping the next character.
  • The fix replaces _displayedText.length with _displayedText.characters.length in these two methods.

Reproduction

  1. Stream text that contains emoji followed by more text (e.g. "Great 👍 Keep going")
  2. Ensure chunks arrive such that the animation is mid-progress when a new chunk appends
  3. A character after the emoji is skipped in the displayed output

Changes

Two lines in lib/src/streaming/streaming_text.dart:

Method Before After
_resumeCharacterByCharacterTyping _displayedText.length _displayedText.characters.length
_resumeCharacterByCharacterTypingFromOldText _displayedText.length _displayedText.characters.length

…ation resume index

In `_resumeCharacterByCharacterTyping` and `_resumeCharacterByCharacterTypingFromOldText`,
`_displayedText.length` returns the count of UTF-16 code units, but the value is used as an
index into `Characters().toList()` which is indexed by grapheme clusters.

For characters that occupy multiple UTF-16 code units (e.g. emoji like 👍 which is a surrogate
pair), the index overshoots, causing the character immediately after the emoji to be skipped
when the animation resumes after new text is appended.

The fix replaces `_displayedText.length` with `_displayedText.characters.length` in these two
methods so the index correctly counts grapheme clusters.
@WilliamRWalsh
Copy link
Copy Markdown
Author

Hey @hooshyar! Thanks for making this package! Let me know if you need more information about this change 🙏

@hooshyar
Copy link
Copy Markdown
Owner

Thanks for the fix @WilliamRWalsh! Great catch on the grapheme cluster vs UTF-16 code unit issue.

We've applied this fix directly to main (along with tests for emoji text). Since the changes are identical, I'm going to close this PR. Your contribution is credited in the commit. Appreciate it! 🎉

@hooshyar hooshyar closed this Apr 12, 2026
hooshyar added a commit that referenced this pull request Apr 12, 2026
- Add 7 custom builder parameters (imageBuilder, onLinkTap, codeBuilder,
  latexBuilder, sourceTagBuilder, highlightBuilder, linkBuilder) to
  StreamingTextMarkdown and all named constructors (closes #10)
- Fix emoji character skipping: use grapheme cluster count instead of
  UTF-16 code units for animation resume index (closes PR #9)
- Add tests for emoji text and custom builders
- Rewrite CLAUDE.md for accuracy and conciseness

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants