Matthias Nott
2026-03-22 1c57bb6f40304efceadfff17aefbbfcc64c9ad5f
fix: disable catch_up replay to prevent message loss

Catch-up was replaying all server messages on every reconnect,
causing duplicate assistant messages and overwriting user messages.
Local storage is now the source of truth. Catch-up only updates
lastSeq for protocol tracking. New messages while offline are
stored via _storeForSession from the DIRECT broadcast path.

Also: SharedPreferences save awaited properly, image cache for
flicker prevention, screenshot snackbar indicator.
1 files modified
changed files
lib/screens/chat_screen.dart patch | view | blame | history
lib/screens/chat_screen.dart
....@@ -73,10 +73,11 @@
7373 _initConnection();
7474 }
7575
76
- void _saveLastSeq() {
77
- SharedPreferences.getInstance().then((prefs) {
78
- prefs.setInt('lastSeq', _lastSeq);
79
- });
76
+ SharedPreferences? _prefs;
77
+
78
+ Future<void> _saveLastSeq() async {
79
+ _prefs ??= await SharedPreferences.getInstance();
80
+ await _prefs!.setInt('lastSeq', _lastSeq);
8081 }
8182
8283 @override
....@@ -206,18 +207,14 @@
206207 final sessionId = msg['sessionId'] as String?;
207208 if (sessionId != null) _incrementUnread(sessionId);
208209 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.
209214 final serverSeq = msg['serverSeq'] as int?;
210215 if (serverSeq != null && serverSeq > _lastSeq) {
211216 _lastSeq = serverSeq;
212217 _saveLastSeq();
213
- }
214
- final messages = msg['messages'] as List<dynamic>?;
215
- if (messages != null && messages.isNotEmpty) {
216
- _isCatchingUp = true;
217
- for (final m in messages) {
218
- _handleMessage(m as Map<String, dynamic>);
219
- }
220
- _isCatchingUp = false;
221218 }
222219 case 'pong':
223220 break; // heartbeat response, ignore