From 209b643cd5a3a3187acfacd80cfcffba2673fe0a Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Fri, 15 May 2026 14:27:17 -0400 Subject: [PATCH] fix(wpf): MainViewModel subscription via direct Subscribe + Dispatcher marshal The .ObserveOn(SynchronizationContextScheduler(SyncContext.Current)) path captured a synchronization context at subscribe time that didn't pump subsequent OnNext emissions in WPF startup, leaving the Participants collection empty even though the engine's discovery was firing. Console probe confirmed engine sees Teams sources; only the GUI consumer was broken. Switched to direct Subscribe + Dispatcher.InvokeAsync inside the callback (same pattern proven by Console.Program.cs). Subscribe-time context capture is gone; every emission marshals to the UI thread on its own. --- src/TeamsISO.App/ViewModels/MainViewModel.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/TeamsISO.App/ViewModels/MainViewModel.cs b/src/TeamsISO.App/ViewModels/MainViewModel.cs index b5730de..af785b9 100644 --- a/src/TeamsISO.App/ViewModels/MainViewModel.cs +++ b/src/TeamsISO.App/ViewModels/MainViewModel.cs @@ -346,10 +346,18 @@ public sealed class MainViewModel : ObservableObject, IDisposable // Apply the operator's saved sort preference, if any. ApplySortFromPrefs(); + // Subscribe directly (no ObserveOn) and marshal to the UI thread inside + // the callback via Dispatcher.InvokeAsync. The previous ObserveOn( + // SynchronizationContextScheduler) path captured SynchronizationContext + // .Current at subscribe time — fragile in WPF startup ordering, where + // the UI thread's SyncContext can be in a transitional state during + // App.OnStartup and the captured context never pumps subsequent + // OnNext calls. Direct subscribe + explicit dispatcher marshal is the + // pattern proven by Console.Program.cs (engine emits, consumer marshals). _participantsSub = controller.Participants - .ObserveOn(new SynchronizationContextScheduler( - System.Threading.SynchronizationContext.Current ?? new DispatcherSynchronizationContext(_dispatcher))) - .Subscribe(OnParticipantsChanged); + .Subscribe(snapshot => _dispatcher.InvokeAsync( + () => OnParticipantsChanged(snapshot), + DispatcherPriority.Background)); _alertsSub = controller.Alerts .ObserveOn(new SynchronizationContextScheduler(