diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index 818bb7e..ca50168 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -51,6 +51,9 @@ public sealed partial class IslandsShellViewModel : ViewModelBase // Set by MainWindow to open the global worktrees overview dialog. public Func? ShowWorktreesOverviewModal { get; set; } + // Set by MainWindow to open the worker-connection help dialog. + public Func? ShowWorkerConnectionModal { get; set; } + [ObservableProperty] private bool _isUpdateBannerVisible; [ObservableProperty] private string? _updateBannerLatestVersion; [ObservableProperty] private string? _inlineUpdateStatus; @@ -72,6 +75,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase public bool ShowLists => WindowWidth >= 780; private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false }; + private readonly System.Timers.Timer _connectTimer = new(12_000) { AutoReset = false }; [ObservableProperty] private string? _primeStatus; private readonly System.Timers.Timer _primeStatusTimer = new(5_000) { AutoReset = false }; @@ -220,6 +224,11 @@ public sealed partial class IslandsShellViewModel : ViewModelBase }; _primeStatusTimer.Elapsed += (_, _) => Avalonia.Threading.Dispatcher.UIThread.Post(() => PrimeStatus = null); + _connectTimer.Elapsed += (_, _) => Dispatcher.UIThread.Post(() => + { + if (DecideShowConnectionPrompt(IsOffline)) _ = OpenWorkerConnectionHelpAsync(); + }); + _connectTimer.Start(); _ = Lists.LoadAsync(); _updateCheck.PropertyChanged += (_, e) => { @@ -269,6 +278,25 @@ public sealed partial class IslandsShellViewModel : ViewModelBase if (ShowAboutModal is not null) await ShowAboutModal(vm); } + private bool _connectionPromptShown; + + internal bool DecideShowConnectionPrompt(bool isOffline) + { + if (!isOffline) return false; + if (_connectionPromptShown) return false; + _connectionPromptShown = true; + return true; + } + + private async Task OpenWorkerConnectionHelpAsync() + { + var vm = new WorkerConnectionModalViewModel(_workerLocator, _installerLocator); + if (ShowWorkerConnectionModal is not null) await ShowWorkerConnectionModal(vm); + } + + [RelayCommand] + private Task OpenWorkerConnectionHelp() => OpenWorkerConnectionHelpAsync(); + [RelayCommand] private async Task OpenRepoImport() { diff --git a/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs b/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs new file mode 100644 index 0000000..e9ce3da --- /dev/null +++ b/tests/ClaudeDo.Ui.Tests/ConnectionPromptGateTests.cs @@ -0,0 +1,22 @@ +using ClaudeDo.Ui.ViewModels; +using Xunit; + +namespace ClaudeDo.Ui.Tests; + +public class ConnectionPromptGateTests +{ + [Fact] + public void Shows_once_when_offline() + { + var vm = new IslandsShellViewModel(); + Assert.True(vm.DecideShowConnectionPrompt(isOffline: true)); + Assert.False(vm.DecideShowConnectionPrompt(isOffline: true)); // not a second time + } + + [Fact] + public void Does_not_show_when_connected_before_grace() + { + var vm = new IslandsShellViewModel(); + Assert.False(vm.DecideShowConnectionPrompt(isOffline: false)); + } +}