Matthias Nott
2026-03-22 6a336bbd8dfc03aa2d061ca4cf0f2750ea925638
lib/screens/chat_screen.dart
....@@ -207,15 +207,31 @@
207207 final sessionId = msg['sessionId'] as String?;
208208 if (sessionId != null) _incrementUnread(sessionId);
209209 case 'catch_up':
210
- // Only update lastSeq — don't replay messages.
211
- // Local storage already has our messages. Replaying causes duplicates
212
- // and overwrites user messages. New messages arriving while offline
213
- // are stored via _storeForSession from the DIRECT broadcast path.
214210 final serverSeq = msg['serverSeq'] as int?;
215211 if (serverSeq != null && serverSeq > _lastSeq) {
216212 _lastSeq = serverSeq;
217213 _saveLastSeq();
218214 }
215
+ // Merge catch_up messages: only add messages not already in local storage.
216
+ // We check by content match against existing messages to avoid duplicates
217
+ // while still picking up messages that arrived while the app was backgrounded.
218
+ final catchUpMsgs = msg['messages'] as List<dynamic>?;
219
+ if (catchUpMsgs != null && catchUpMsgs.isNotEmpty) {
220
+ _isCatchingUp = true;
221
+ final existing = ref.read(messagesProvider);
222
+ final existingContents = existing
223
+ .where((m) => m.role == MessageRole.assistant)
224
+ .map((m) => m.content)
225
+ .toSet();
226
+ for (final m in catchUpMsgs) {
227
+ final content = (m as Map<String, dynamic>)['content'] as String? ?? '';
228
+ // Skip if we already have this message locally
229
+ if (content.isNotEmpty && existingContents.contains(content)) continue;
230
+ _handleMessage(m);
231
+ if (content.isNotEmpty) existingContents.add(content);
232
+ }
233
+ _isCatchingUp = false;
234
+ }
219235 case 'pong':
220236 break; // heartbeat response, ignore
221237 case 'delete_message':