mirror of
https://github.com/block/buzz.git
synced 2026-11-04 08:21:48 +00:00
perf(mobile): format a channel timeline only when its events change (#8929)
## Summary The channel page ran `formatTimeline` on every build. The page also rebuilds for changes unrelated to messages: read state, presence, the roster, keyboard and layout. `formatTimeline` walks every loaded event and JSON-decodes each system row. In #4things (1,235 members, mostly join rows), on a 12 mini Profile build: - 58 ms of main thread over 21 idle seconds. - About 11 ms per frame while the keyboard animated. The messages notifiers publish a new list for every event change, so this memoizes the result on that list, the current pubkey, and the forum flag. `buildMainTimelineEntries` and the relay thread summaries it reads stay outside the memo. A summary-only update that republishes the same list still shows up. ### Related issue None found. ### Testing - Updated `loading_review_tests.dart`: unrelated page rebuilds now format the timeline 0 times, where main formats once per rebuild (the test fails on main). A new live event formats exactly once, and its message appears. - `just mobile-install mobile-check mobile-test` passes. - Device (12 mini, Profile build with this change, #4things idle). Runs are not matched, so this shows the mechanism, not a controlled speedup. - Before: 58 ms over 21 s. - After: 7 ms over 18 s, all from real event changes. - An independent agent review found no defects. It checked that `formatTimeline` reads only its arguments, that every publish path creates a new list or republishes unchanged events, and that nothing downstream mutates the shared result. Not covered: a human test (waived under the mobile testing exception; this was an agent-driven device run), and Android. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -675,12 +675,16 @@ class _ResolvedChannelDetailPage extends HookConsumerWidget {
|
||||
? dmHeader?.presenceLabel
|
||||
: headerMemberLabel;
|
||||
|
||||
final timelineMessages = resolvedChannel.isForum
|
||||
? <TimelineMessage>[]
|
||||
: formatTimeline(
|
||||
messagesState.value ?? const [],
|
||||
currentPubkey: currentPubkey,
|
||||
);
|
||||
// The page also rebuilds for read state, presence, roster and layout
|
||||
// changes. Format only when the events themselves change: the notifier
|
||||
// publishes a new list for every event change.
|
||||
final events = messagesState.value ?? const <NostrEvent>[];
|
||||
final timelineMessages = useMemoized(
|
||||
() => resolvedChannel.isForum
|
||||
? <TimelineMessage>[]
|
||||
: formatTimeline(events, currentPubkey: currentPubkey),
|
||||
[events, currentPubkey, resolvedChannel.isForum],
|
||||
);
|
||||
final timelineEntries = buildMainTimelineEntries(
|
||||
// End events still update the original card through allMessages.
|
||||
timelineMessages
|
||||
|
||||
@@ -446,24 +446,43 @@ void _loadingReviewTests() {
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('loaded page rebuild formats the timeline once', (tester) async {
|
||||
testWidgets('loaded page formats the timeline only when its events change', (
|
||||
tester,
|
||||
) async {
|
||||
var transforms = 0;
|
||||
debugOnFormatTimeline = () => transforms++;
|
||||
addTearDown(() => debugOnFormatTimeline = null);
|
||||
final first = _textMsg(
|
||||
id: 'one',
|
||||
pubkey: 'alice',
|
||||
content: 'Loaded message',
|
||||
);
|
||||
final messages = _FakeMessagesNotifier([first]);
|
||||
await tester.pumpWidget(
|
||||
_buildTestable(
|
||||
messages: [
|
||||
_textMsg(id: 'one', pubkey: 'alice', content: 'Loaded message'),
|
||||
],
|
||||
),
|
||||
_buildTestable(messages: [first], messagesNotifier: messages),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
// Read state, presence and layout changes rebuild the page; formatting
|
||||
// every event again for each of them stalls large channels.
|
||||
for (var i = 0; i < 3; i++) {
|
||||
transforms = 0;
|
||||
tester.element(find.byType(ChannelDetailPage)).markNeedsBuild();
|
||||
await tester.pump();
|
||||
expect(transforms, 1);
|
||||
expect(transforms, 0);
|
||||
}
|
||||
transforms = 0;
|
||||
messages.setMessages([
|
||||
first,
|
||||
_textMsg(
|
||||
id: 'two',
|
||||
pubkey: 'alice',
|
||||
createdAt: first.createdAt + 1,
|
||||
content: 'Live message',
|
||||
),
|
||||
]);
|
||||
await tester.pump();
|
||||
expect(transforms, 1);
|
||||
expect(find.text('Live message', findRichText: true), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('reconnect keeps cached media in place', (tester) async {
|
||||
|
||||
Reference in New Issue
Block a user