diff --git a/src/TeamsISO.App/ViewModels/ParticipantViewModel.cs b/src/TeamsISO.App/ViewModels/ParticipantViewModel.cs index 6a9a03d..712f89a 100644 --- a/src/TeamsISO.App/ViewModels/ParticipantViewModel.cs +++ b/src/TeamsISO.App/ViewModels/ParticipantViewModel.cs @@ -501,6 +501,27 @@ public sealed class ParticipantViewModel : ObservableObject IsEnabled = true; } } + catch (InvalidOperationException) + { + // Race window: participant left the meeting between when the operator + // clicked Enable/Disable and when the engine resolved the ID. The + // controller throws InvalidOperationException with a "not currently + // visible on the network" message in this case. Surface it as a soft + // warning toast rather than letting it escape into the dispatcher's + // unhandled-exception channel (which fires a fatal crash dialog). + // + // Leave IsEnabled at its current value — the engine refused the state + // change, so the VM should reflect the actual engine state. + _toast?.Warn($"{DisplayName} just left the meeting"); + } + catch (Exception ex) + { + // Defensive catch-all for any other engine-side failure (port bind + // race, pipeline factory throw, etc.). Same reasoning as above — + // an exception from an operator click should never tear down the + // dispatcher. + _toast?.Warn($"Couldn't toggle ISO for {DisplayName}: {ex.Message}"); + } finally { IsProcessing = false;