From 6cbbea9b96db551e5c0ac26f0ace3d4c3d82a276 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Mon, 06 Apr 2026 15:02:33 +0200
Subject: [PATCH] fix: single pailot/out topic, per-session file locks, merge protection, resume reconnect

---
 lib/screens/chat_screen.dart |   33 +++++++++++++++++++--------------
 1 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index e889469..f331ab9 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -216,12 +216,15 @@
     _ws!.onOpen = () {
       _sessionReady = false; // Gate messages until sessions arrive
       _pendingMessages.clear();
-      final activeId = ref.read(activeSessionIdProvider);
-      _sendCommand('sync', activeId != null ? {'activeSessionId': activeId} : null);
-      // catch_up is sent after sessions arrive (in _handleSessions)
-
-      // Re-register APNs token after reconnect so daemon always has a fresh token
-      _push?.onMqttConnected();
+      // Delay sync slightly to let broker acknowledge our subscriptions first.
+      // Without this, the catch_up response arrives before pailot/control/out
+      // subscription is active, and the message is lost.
+      Future.delayed(const Duration(milliseconds: 500), () {
+        if (!mounted) return;
+        final activeId = ref.read(activeSessionIdProvider);
+        _sendCommand('sync', activeId != null ? {'activeSessionId': activeId} : null);
+        _push?.onMqttConnected();
+      });
     };
     _ws!.onResume = () {
       // App came back from background. The in-memory state already has
@@ -534,8 +537,10 @@
       status: MessageStatus.sent,
     );
 
-    final activeId = ref.read(activeSessionIdProvider);
-    if (sessionId != null && sessionId != activeId) {
+    // Use currentSessionId from notifier (what's actually loaded in the provider)
+    // not activeSessionIdProvider (can be stale after background resume)
+    final currentId = ref.read(messagesProvider.notifier).currentSessionId;
+    if (sessionId != null && sessionId != currentId) {
       // Store message for the other session so it's there when user switches
       TraceService.instance.addTrace(
         'message stored for session',
@@ -608,9 +613,9 @@
       duration: duration,
     );
 
-    final activeId = ref.read(activeSessionIdProvider);
-    _chatLog('voice: sessionId=$sessionId activeId=$activeId audioPath=$savedAudioPath content="${content.substring(0, content.length.clamp(0, 30))}"');
-    if (sessionId != null && sessionId != activeId) {
+    final currentId = ref.read(messagesProvider.notifier).currentSessionId;
+    _chatLog('voice: sessionId=$sessionId currentId=$currentId audioPath=$savedAudioPath content="${content.substring(0, content.length.clamp(0, 30))}"');
+    if (sessionId != null && sessionId != currentId) {
       _chatLog('voice: cross-session, storing for $sessionId');
       await _storeForSession(sessionId, storedMessage);
       _chatLog('voice: stored, incrementing unread');
@@ -679,9 +684,9 @@
       status: MessageStatus.sent,
     );
 
-    // Cross-session routing: store for target session if not active
-    final activeId = ref.read(activeSessionIdProvider);
-    if (sessionId != null && sessionId != activeId) {
+    // Cross-session routing: store for target session if not currently loaded
+    final currentId = ref.read(messagesProvider.notifier).currentSessionId;
+    if (sessionId != null && sessionId != currentId) {
       _storeForSession(sessionId, message);
       _incrementUnread(sessionId);
       return;

--
Gitblit v1.3.1