Skip to content

fix(ui): share the markdown style sheet between message texts - #192

Merged
xsahil03x merged 4 commits into
mainfrom
fix/message-text-style-sheet-cache
Oct 2, 2026
Merged

xsahil03x merged 4 commits into
mainfrom
fix/message-text-style-sheet-cache

Conversation

@renefloor

@renefloor renefloor commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Why

A livestream load test on stream_chat_flutter showed about one janky frame per incoming message, with build time over budget. On every build of every message, DefaultStreamMessageText.build:

  • copied ThemeData with textTheme.apply(...)
  • built a full MarkdownStyleSheet.fromTheme(...)
  • created a new _StreamMentionSyntax, recompiling its RegExp

What

  • The theme-derived style sheet is cached per ThemeData instance (held weakly in an Expando), keyed by the effective text style and link style. Messages with the same styles share one sheet. The cache holds at most 32 entries per theme and drops the oldest when it's full.
  • The caller's styleSheet is merged on top after the lookup.
  • The mention syntax is stateless, so it is now a single static final instance.

Testing

  • stream_message_text_style_sheet_test.dart covers:

    • messages with the same styles get the identical sheet;
    • a different text style gets its own sheet;
    • each message's styleSheet override stays separate;
    • a new theme produces a new sheet.
  • The full stream_core_flutter suite passes. Against the CI goldens, the same 50 local host-drift failures occur with and without this change, so there is no visual diff.

  • Pixel 4, profile mode, 100 messages at 10/s, mean build time per message:

    Chat message list 0.5.2 This PR
    Every row rebuilds 25.2 ms 23.1 ms
    With row caching 12.3 ms 12.0 ms

Companion to the stream-chat-flutter PR that stops StreamMessageListView rebuilding unchanged rows.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance
    • Improved message rendering performance in long lists by reusing markdown styles when theme and text styles match.
    • Updated theme typography and custom style overrides continue to apply correctly.

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d11224e8-4505-49f5-8954-c7770a9b8e94

📥 Commits

Reviewing files that changed from the base of the PR and between 0c66009 and 758c541.

📒 Files selected for processing (3)
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/src/components/message/stream_message_text.dart
  • packages/stream_core_flutter/test/components/message/stream_message_text_style_sheet_test.dart
 ___________________________________________
< I am a verified code reviewer on Twitter. >
 -------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.05%. Comparing base (0c66009) to head (758c541).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #192      +/-   ##
==========================================
+ Coverage   68.77%   70.05%   +1.28%     
==========================================
  Files         213      213              
  Lines        8762     8773      +11     
==========================================
+ Hits         6026     6146     +120     
+ Misses       2736     2627     -109     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

renefloor added a commit to GetStream/stream-chat-flutter that referenced this pull request Sep 30, 2026
The row cache was cleared on every didUpdateWidget, so a parent that
rebuilds StreamMessageListView often (for example a call-state builder
around a livestream overlay) emptied it on each rebuild. It is now only
cleared when a value the rows are built from changes: the configuration,
the parent message, the message and row builders, and the row callbacks.

Also bumps stream_core_flutter to include the shared markdown style
sheet (GetStream/stream-core-flutter#192).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renefloor added a commit to GetStream/flutter-video-samples that referenced this pull request Oct 1, 2026
…hmark

Points stream_chat_flutter at GetStream/stream-chat-flutter#3024 (message
rows are only rebuilt when they change), stream_core_flutter at
GetStream/stream-core-flutter#192 (shared markdown style sheet), and
stream_video_flutter at GetStream/stream-video-flutter#1392 (repaint
boundaries around the livestream player).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renefloor added a commit to GetStream/stream-chat-flutter that referenced this pull request Oct 1, 2026
The row cache was cleared on every didUpdateWidget, so a parent that
rebuilds StreamMessageListView often (for example a call-state builder
around a livestream overlay) emptied it on each rebuild. It is now only
cleared when a value the rows are built from changes: the configuration,
the parent message, the message and row builders, and the row callbacks.

Also bumps stream_core_flutter to include the shared markdown style
sheet (GetStream/stream-core-flutter#192).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renefloor added a commit to GetStream/stream-chat-flutter that referenced this pull request Oct 1, 2026
The row cache was cleared on every didUpdateWidget, so a parent that
rebuilds StreamMessageListView often (for example a call-state builder
around a livestream overlay) emptied it on each rebuild. It is now only
cleared when a value the rows are built from changes: the configuration,
the parent message, the message and row builders, and the row callbacks.

Also bumps stream_core_flutter to include the shared markdown style
sheet (GetStream/stream-core-flutter#192).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xsahil03x xsahil03x self-assigned this Oct 2, 2026
renefloor and others added 3 commits October 2, 2026 17:50
DefaultStreamMessageText built a ThemeData copy, a full MarkdownStyleSheet
and a new mention syntax (recompiling its pattern) on every build of every
message. Style sheets are now memoized per theme and style inputs, and the
mention syntax is a single shared instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MarkdownStyleSheet's == and hashCode skip tableScrollbarThumbVisibility,
so keying the cache on the caller's sheet let two different sheets share
one entry. Key on the text and link style only and merge the caller's
sheet after the lookup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the fix/message-text-style-sheet-cache branch from b6f5e1c to 801c431 Compare October 2, 2026 15:54
@xsahil03x
xsahil03x marked this pull request as ready for review October 2, 2026 15:56
@xsahil03x
xsahil03x requested a review from a team as a code owner October 2, 2026 15:56
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x enabled auto-merge (squash) October 2, 2026 16:06
@xsahil03x
xsahil03x merged commit 1546087 into main Oct 2, 2026
16 of 17 checks passed
@xsahil03x
xsahil03x deleted the fix/message-text-style-sheet-cache branch October 2, 2026 16:07
xsahil03x added a commit to GetStream/stream-chat-flutter that referenced this pull request Oct 3, 2026
* fix(ui): only rebuild message rows that changed

StreamMessageListView rebuilt every visible row in full on each new
message, costing about one janky frame per message in busy channels.
Rows are now reused while their message, neighbours and the current user
are unchanged, so a new message rebuilds only its own row and the one it
lands next to. The cache is cleared whenever the list widget or its
dependencies change.

StreamMessageText also resubscribed to the current user stream on every
rebuild because it passed a freshly mapped stream; it now passes the
stream unmapped and compares on language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): cap the message row cache at 64 rows

Only rows that are still mounted benefit from the cache, so it is now a
least-recently-used cache of at most 64 rows instead of one entry per
loaded message. This keeps memory constant in long-running channels
where maximumMessageLimit is not set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): keep cached message rows when the list's parent rebuilds

The row cache was cleared on every didUpdateWidget, so a parent that
rebuilds StreamMessageListView often (for example a call-state builder
around a livestream overlay) emptied it on each rebuild. It is now only
cleared when a value the rows are built from changes: the configuration,
the parent message, the message and row builders, and the row callbacks.

Also bumps stream_core_flutter to include the shared markdown style
sheet (GetStream/stream-core-flutter#192).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(ui): reuse message rows until what they're built from changes

Replace the message row cache with an internal MemoizedBuilder keyed on
each row's StreamMessageItemProps, layout data and builder. A new message
now rebuilds its own row, plus its neighbor's when that row's place in a
run of messages changes; an edit or reaction rebuilds only its own row.
This covers threads and lists with any number of mounted rows, and removes
the 64-row cap and the cache clears.

- StreamMessageItemProps now compares by value (Equatable).
- The thread parent row is memoized the same way. System, ephemeral and
  moderated rows are unchanged, so custom builders keep the list's
  context and can still reach the page's ScaffoldMessenger.
- The content kind is resolved only when a row rebuilds.
- Point stream_core_flutter at the merged core#192 (1546087), which also
  restores #193.
- Document when messageBuilder and the parent message builder run again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: Update Goldens

* test(docs): stub isMarkedAsUnread in the docs channel state mock

The message list reads it when it attaches to a channel; unstubbed, the
mock returned null and the docs screenshots rendered an error screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: Update Goldens

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Sahil Kumar <sahil@getstream.io>
Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.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