diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index 4077eb27..a3d2d657 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -32,6 +32,13 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable public bool IsOffline => Worker?.IsConnected != true && Worker?.IsReconnecting != true; + // Gate for the footer pill: the help dialog says "WORKER NOT REACHABLE", so it must never open + // on a connected worker. Not `IsOffline` — the retry loop stays in "connecting" forever while + // the worker is down, and that's exactly when the dialog's "Start Worker" is needed. + public bool CanOpenWorkerConnectionHelp => DecideCanOpenConnectionHelp(Worker?.IsConnected == true); + + internal static bool DecideCanOpenConnectionHelp(bool isConnected) => !isConnected; + private readonly UpdateCheckService _updateCheck = null!; private readonly InstallerLocator _installerLocator = null!; private readonly WorkerLocator _workerLocator = null!; @@ -346,6 +353,8 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable { OnPropertyChanged(nameof(ConnectionText)); OnPropertyChanged(nameof(IsOffline)); + OnPropertyChanged(nameof(CanOpenWorkerConnectionHelp)); + OpenWorkerConnectionHelpCommand.NotifyCanExecuteChanged(); } }; Worker.WorkerLogReceivedEvent += OnWorkerLogReceived; @@ -517,7 +526,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable if (Dialogs is not null) await Dialogs.ShowWorkerConnectionAsync(vm); } - [RelayCommand] + [RelayCommand(CanExecute = nameof(CanOpenWorkerConnectionHelp))] private Task OpenWorkerConnectionHelp() => OpenWorkerConnectionHelpAsync(); [RelayCommand] diff --git a/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs b/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs index e9ce3da5..7c13eff6 100644 --- a/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs @@ -19,4 +19,20 @@ public class ConnectionPromptGateTests var vm = new IslandsShellViewModel(); Assert.False(vm.DecideShowConnectionPrompt(isOffline: false)); } + + // The footer pill binds OpenWorkerConnectionHelpCommand, and the dialog says "WORKER NOT + // REACHABLE" — so it must stay closed while the worker is connected, and stay reachable + // while it is not (including the endless "connecting" retry state). + [Theory] + [InlineData(true, false)] + [InlineData(false, true)] + public void Connection_help_is_gated_on_connected(bool isConnected, bool canOpen) + => Assert.Equal(canOpen, IslandsShellViewModel.DecideCanOpenConnectionHelp(isConnected)); + + [Fact] + public void Connection_help_command_enabled_without_worker() + { + var vm = new IslandsShellViewModel(); + Assert.True(vm.OpenWorkerConnectionHelpCommand.CanExecute(null)); + } }