| .. | .. |
|---|
| 216 | 216 | _ws!.onOpen = () { |
|---|
| 217 | 217 | _sessionReady = false; // Gate messages until sessions arrive |
|---|
| 218 | 218 | _pendingMessages.clear(); |
|---|
| 219 | | - final activeId = ref.read(activeSessionIdProvider); |
|---|
| 220 | | - _sendCommand('sync', activeId != null ? {'activeSessionId': activeId} : null); |
|---|
| 221 | | - // catch_up is sent after sessions arrive (in _handleSessions) |
|---|
| 222 | | - |
|---|
| 223 | | - // Re-register APNs token after reconnect so daemon always has a fresh token |
|---|
| 224 | | - _push?.onMqttConnected(); |
|---|
| 219 | + // Delay sync slightly to let broker acknowledge our subscriptions first. |
|---|
| 220 | + // Without this, the catch_up response arrives before pailot/control/out |
|---|
| 221 | + // subscription is active, and the message is lost. |
|---|
| 222 | + Future.delayed(const Duration(milliseconds: 500), () { |
|---|
| 223 | + if (!mounted) return; |
|---|
| 224 | + final activeId = ref.read(activeSessionIdProvider); |
|---|
| 225 | + _sendCommand('sync', activeId != null ? {'activeSessionId': activeId} : null); |
|---|
| 226 | + _push?.onMqttConnected(); |
|---|
| 227 | + }); |
|---|
| 225 | 228 | }; |
|---|
| 226 | 229 | _ws!.onResume = () { |
|---|
| 227 | 230 | // App came back from background. The in-memory state already has |
|---|
| .. | .. |
|---|
| 534 | 537 | status: MessageStatus.sent, |
|---|
| 535 | 538 | ); |
|---|
| 536 | 539 | |
|---|
| 537 | | - final activeId = ref.read(activeSessionIdProvider); |
|---|
| 538 | | - if (sessionId != null && sessionId != activeId) { |
|---|
| 540 | + // Use currentSessionId from notifier (what's actually loaded in the provider) |
|---|
| 541 | + // not activeSessionIdProvider (can be stale after background resume) |
|---|
| 542 | + final currentId = ref.read(messagesProvider.notifier).currentSessionId; |
|---|
| 543 | + if (sessionId != null && sessionId != currentId) { |
|---|
| 539 | 544 | // Store message for the other session so it's there when user switches |
|---|
| 540 | 545 | TraceService.instance.addTrace( |
|---|
| 541 | 546 | 'message stored for session', |
|---|
| .. | .. |
|---|
| 608 | 613 | duration: duration, |
|---|
| 609 | 614 | ); |
|---|
| 610 | 615 | |
|---|
| 611 | | - final activeId = ref.read(activeSessionIdProvider); |
|---|
| 612 | | - _chatLog('voice: sessionId=$sessionId activeId=$activeId audioPath=$savedAudioPath content="${content.substring(0, content.length.clamp(0, 30))}"'); |
|---|
| 613 | | - if (sessionId != null && sessionId != activeId) { |
|---|
| 616 | + final currentId = ref.read(messagesProvider.notifier).currentSessionId; |
|---|
| 617 | + _chatLog('voice: sessionId=$sessionId currentId=$currentId audioPath=$savedAudioPath content="${content.substring(0, content.length.clamp(0, 30))}"'); |
|---|
| 618 | + if (sessionId != null && sessionId != currentId) { |
|---|
| 614 | 619 | _chatLog('voice: cross-session, storing for $sessionId'); |
|---|
| 615 | 620 | await _storeForSession(sessionId, storedMessage); |
|---|
| 616 | 621 | _chatLog('voice: stored, incrementing unread'); |
|---|
| .. | .. |
|---|
| 679 | 684 | status: MessageStatus.sent, |
|---|
| 680 | 685 | ); |
|---|
| 681 | 686 | |
|---|
| 682 | | - // Cross-session routing: store for target session if not active |
|---|
| 683 | | - final activeId = ref.read(activeSessionIdProvider); |
|---|
| 684 | | - if (sessionId != null && sessionId != activeId) { |
|---|
| 687 | + // Cross-session routing: store for target session if not currently loaded |
|---|
| 688 | + final currentId = ref.read(messagesProvider.notifier).currentSessionId; |
|---|
| 689 | + if (sessionId != null && sessionId != currentId) { |
|---|
| 685 | 690 | _storeForSession(sessionId, message); |
|---|
| 686 | 691 | _incrementUnread(sessionId); |
|---|
| 687 | 692 | return; |
|---|