From 06bb73662d32d65d1e775a4dd35f67d82d673e40 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Mon, 06 Apr 2026 15:32:03 +0200
Subject: [PATCH] feat: rewrite message store - append-only log, sync routing, eliminates async race conditions

---
 lib/providers/providers.dart |   73 ++++++++----------------------------
 1 files changed, 16 insertions(+), 57 deletions(-)

diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart
index 6155189..06fe159 100644
--- a/lib/providers/providers.dart
+++ b/lib/providers/providers.dart
@@ -98,73 +98,48 @@
 
   String? get currentSessionId => _currentSessionId;
 
-  /// Switch to a new session and load its messages.
-  Future<void> switchSession(String sessionId) async {
-    final trace = StackTrace.current.toString().split('\n').take(4).join(' | ');
-    TraceService.instance.addTrace(
-      'switchSession',
-      'from=${_currentSessionId?.substring(0, 8) ?? "null"}(${state.length}) → ${sessionId.substring(0, 8)} | $trace',
-    );
-    // Write current session to disk
-    if (_currentSessionId != null && state.isNotEmpty) {
-      await MessageStore.writeDirect(_currentSessionId!, state);
-    }
-
-    // Skip reload if staying on the same session — messages are already in memory
+  /// Switch to a session. SYNCHRONOUS — no async gap, no race with incoming
+  /// messages. MessageStoreV2.loadSession reads from the in-memory index.
+  void switchSession(String sessionId) {
     if (_currentSessionId == sessionId) {
-      TraceService.instance.addTrace('switchSession SKIP', 'already on ${sessionId.substring(0, 8)}');
+      TraceService.instance.addTrace(
+          'switchSession SKIP', 'already on ${sessionId.substring(0, 8)}');
       return;
     }
-
+    TraceService.instance.addTrace(
+      'switchSession',
+      'from=${_currentSessionId?.substring(0, 8) ?? "null"}(${state.length}) → ${sessionId.substring(0, 8)}',
+    );
     _currentSessionId = sessionId;
-    final messages = await MessageStore.loadAll(sessionId);
-    // Merge: if addMessage ran during loadAll and added messages for THIS session,
-    // they'll be in state but not in the loaded messages. Keep the longer list.
-    if (state.length > messages.length && _currentSessionId == sessionId) {
-      TraceService.instance.addTrace('switchSession MERGE', 'kept ${state.length} (loaded ${messages.length})');
-    } else {
-      state = messages;
-    }
+    state = MessageStoreV2.loadSession(sessionId);
   }
 
-  /// Add a message to the current session.
+  /// Add a message to the current session (display + append-only persist).
   void addMessage(Message message) {
     state = [...state, message];
     if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
+      MessageStoreV2.append(_currentSessionId!, message);
     }
   }
 
-  /// Update a message by ID.
+  /// Update a message by ID (in-memory only — patch is not persisted to log).
   void updateMessage(String id, Message Function(Message) updater) {
     state = state.map((m) => m.id == id ? updater(m) : m).toList();
-    if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
-    }
   }
 
-  /// Remove a message by ID.
+  /// Remove a message by ID (in-memory only).
   void removeMessage(String id) {
     state = state.where((m) => m.id != id).toList();
-    if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
-    }
   }
 
-  /// Remove all messages matching a predicate.
+  /// Remove all messages matching a predicate (in-memory only).
   void removeWhere(bool Function(Message) test) {
     state = state.where((m) => !test(m)).toList();
-    if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
-    }
   }
 
-  /// Clear all messages for the current session.
+  /// Clear all messages for the current session (in-memory only).
   void clearMessages() {
     state = [];
-    if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
-    }
   }
 
   void updateContent(String messageId, String content) {
@@ -185,22 +160,6 @@
         else
           m,
     ];
-    if (_currentSessionId != null) {
-      MessageStore.save(_currentSessionId!, state);
-    }
-  }
-
-  /// Load more (older) messages for pagination.
-  Future<void> loadMore() async {
-    if (_currentSessionId == null) return;
-    final older = await MessageStore.load(
-      _currentSessionId!,
-      offset: state.length,
-      limit: 50,
-    );
-    if (older.isNotEmpty) {
-      state = [...older, ...state];
-    }
   }
 }
 

--
Gitblit v1.3.1