@@ -68,6 +68,9 @@ public partial class MainWindowViewModel : ViewModelBase, Commands.ICommandTarge
6868 internal readonly Mediator _mediator = new ( ) ;
6969 private static readonly ILogger _logger = AppLogService . Instance . CreateLogger ( "ViewModel" ) ;
7070
71+ /// <summary>Raised when the active workspace path changes. Child VMs subscribe to refresh reactively.</summary>
72+ public event Action < string > ? WorkspacePathChanged ;
73+
7174 /// <summary>ViewModel for the Todo tab. Created lazily on first access.</summary>
7275 public TodoListViewModel TodoViewModel => _todoViewModel ??= CreateTodoViewModel ( ) ;
7376 private TodoListViewModel ? _todoViewModel ;
@@ -76,6 +79,7 @@ private TodoListViewModel CreateTodoViewModel()
7679 {
7780 var vm = new TodoListViewModel ( _clipboardService , _mcpTodoService ) ;
7881 vm . GlobalStatusChanged += msg => DispatchToUi ( ( ) => StatusMessage = msg ) ;
82+ WorkspacePathChanged += path => DispatchToUi ( ( ) => _ = vm . RefreshForConnectionChangeAsync ( ) ) ;
7983 return vm ;
8084 }
8185
@@ -88,6 +92,7 @@ private WorkspaceViewModel CreateWorkspaceViewModel()
8892 var vm = new WorkspaceViewModel ( _clipboardService , _mcpWorkspaceService ) ;
8993 vm . GlobalStatusChanged += msg => DispatchToUi ( ( ) => StatusMessage = msg ) ;
9094 vm . WorkspaceCatalogChanged += change => _ = RefreshWorkspacePickerAfterCatalogChangeAsync ( change ) ;
95+ WorkspacePathChanged += path => DispatchToUi ( ( ) => _ = vm . RefreshForConnectionChangeAsync ( ) ) ;
9196 return vm ;
9297 }
9398
@@ -128,6 +133,7 @@ private VoiceConversationViewModel CreateVoiceConversationViewModel()
128133 ?? string . Empty
129134 } ;
130135 vm . GlobalStatusChanged += msg => DispatchToUi ( ( ) => StatusMessage = msg ) ;
136+ WorkspacePathChanged += path => DispatchToUi ( ( ) => _ = vm . RefreshForConnectionChangeAsync ( ) ) ;
131137 return vm ;
132138 }
133139
@@ -434,6 +440,9 @@ private void ApplyActiveMcpBaseUrl(string mcpBaseUrl, string? mcpApiKey = null,
434440
435441 if ( _hasRegisteredCqrsHandlers )
436442 RegisterMcpServiceHandlers ( ) ;
443+
444+ // Notify child VMs reactively — they self-refresh without imperative ordering.
445+ WorkspacePathChanged ? . Invoke ( resolvedPath ) ;
437446 }
438447
439448 private async Task < string ? > ResolveActiveConnectionApiKeyAsync ( WorkspaceConnectionOption option , string baseUrl )
@@ -1062,8 +1071,14 @@ private void ApplyWorkspaceConnectionOptions(
10621071 ?? WorkspaceConnections [ 0 ] ;
10631072
10641073 _suppressWorkspaceSelectionChanged = true ;
1065- SelectedWorkspaceConnection = selected ;
1066- _suppressWorkspaceSelectionChanged = false ;
1074+ try
1075+ {
1076+ SelectedWorkspaceConnection = selected ;
1077+ }
1078+ finally
1079+ {
1080+ _suppressWorkspaceSelectionChanged = false ;
1081+ }
10671082
10681083 // Initial picker population suppresses selection-changed switching to avoid duplicate work,
10691084 // but we still need one real connection switch to set workspace path and refresh views.
@@ -1164,6 +1179,7 @@ await DispatchToUiAsync(() =>
11641179
11651180 private async Task RefreshAllViewsForConnectionChangeAsync ( )
11661181 {
1182+ // Session logs are owned by MainWindowViewModel — refresh directly.
11671183 _logger . LogDebug ( "[Workspace Switch] Refreshing session logs..." ) ;
11681184 try
11691185 {
@@ -1176,52 +1192,8 @@ private async Task RefreshAllViewsForConnectionChangeAsync()
11761192 ex . GetType ( ) . Name , ex . Message ) ;
11771193 }
11781194
1179- if ( _todoViewModel != null )
1180- {
1181- _logger . LogDebug ( "[Workspace Switch] Refreshing todo view..." ) ;
1182- try
1183- {
1184- await _todoViewModel . RefreshForConnectionChangeAsync ( ) . ConfigureAwait ( true ) ;
1185- _logger . LogDebug ( "[Workspace Switch] Todo view refreshed OK" ) ;
1186- }
1187- catch ( Exception ex )
1188- {
1189- _logger . LogWarning ( ex , "[Workspace Switch] Todo refresh failed ({ExType}: {ExMsg}); continuing" ,
1190- ex . GetType ( ) . Name , ex . Message ) ;
1191- }
1192- }
1193-
1194- if ( _workspaceViewModel != null )
1195- {
1196- _logger . LogDebug ( "[Workspace Switch] Refreshing workspace view..." ) ;
1197- try
1198- {
1199- await _workspaceViewModel . RefreshForConnectionChangeAsync ( ) . ConfigureAwait ( true ) ;
1200- _logger . LogDebug ( "[Workspace Switch] Workspace view refreshed OK" ) ;
1201- }
1202- catch ( Exception ex )
1203- {
1204- _logger . LogWarning ( ex , "[Workspace Switch] Workspace view refresh failed ({ExType}: {ExMsg}); continuing" ,
1205- ex . GetType ( ) . Name , ex . Message ) ;
1206- }
1207- }
1208-
1209- if ( _voiceConversationViewModel != null )
1210- {
1211- _logger . LogDebug ( "[Workspace Switch] Refreshing voice view..." ) ;
1212- try
1213- {
1214- _voiceConversationViewModel . WorkspacePath = _mcpClient . WorkspacePath ?? string . Empty ;
1215- await _voiceConversationViewModel . RefreshForConnectionChangeAsync ( ) . ConfigureAwait ( true ) ;
1216- _logger . LogDebug ( "[Workspace Switch] Voice view refreshed OK" ) ;
1217- }
1218- catch ( Exception ex )
1219- {
1220- _logger . LogWarning ( ex , "[Workspace Switch] Voice refresh failed ({ExType}: {ExMsg}); continuing" ,
1221- ex . GetType ( ) . Name , ex . Message ) ;
1222- }
1223- }
1224-
1195+ // Child VMs (Todo, Workspace, Voice) are refreshed reactively via
1196+ // WorkspacePathChanged event — no imperative calls needed here.
12251197 _logger . LogDebug ( "[Workspace Switch] All views refreshed." ) ;
12261198 }
12271199
0 commit comments