diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs index 0d6baf0..c15abd5 100644 --- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs @@ -52,6 +52,7 @@ public interface IWorkerClient : INotifyPropertyChanged Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default); Task GetWeekReportAsync(DateOnly start, DateOnly end); Task GenerateWeekReportAsync(DateOnly start, DateOnly end); + Task RunDailyPrepNowAsync(); Task GetAppSettingsAsync(); Task> GetDailyNotesAsync(DateOnly day); Task AddDailyNoteAsync(DateOnly day, string text); diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs index 9ccd448..21ee5b1 100644 --- a/src/ClaudeDo.Ui/Services/WorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs @@ -334,6 +334,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC public Task GenerateWeekReportAsync(DateOnly start, DateOnly end) => _hub.InvokeAsync("GenerateWeekReport", IsoDay(start), IsoDay(end)); + public Task RunDailyPrepNowAsync() + => _hub.InvokeAsync("RunDailyPrepNow"); + public async Task> GetDailyNotesAsync(DateOnly day) => await TryInvokeAsync>("GetDailyNotes", IsoDay(day)) ?? new List(); @@ -496,7 +499,8 @@ public sealed record AppSettingsDto( bool WorktreeAutoCleanupEnabled, int WorktreeAutoCleanupDays, string? ReportExcludedPaths, - int StandupWeekday); + int StandupWeekday, + int DailyPrepMaxTasks); public sealed record WorktreeCleanupDto(int Removed); public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks); diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs index c4b4c5f..2b74a5d 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs @@ -22,6 +22,8 @@ public sealed partial class SettingsModalViewModel : ViewModelBase [ObservableProperty] private bool _isBusy; [ObservableProperty] private string _statusMessage = ""; + private int _loadedDailyPrepMaxTasks = 5; + public Action? CloseAction { get; set; } public SettingsModalViewModel(WorkerClient worker, PrimeClaudeTabViewModel prime, @@ -60,6 +62,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase : string.Join(Environment.NewLine, System.Text.Json.JsonSerializer.Deserialize>(dto.ReportExcludedPaths) ?? new()); General.StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday; + _loadedDailyPrepMaxTasks = dto.DailyPrepMaxTasks < 1 ? 5 : dto.DailyPrepMaxTasks; } else StatusMessage = Loc.T("vm.settingsModal.workerOffline"); @@ -91,7 +94,8 @@ public sealed partial class SettingsModalViewModel : ViewModelBase System.Text.Json.JsonSerializer.Serialize( General.ReportExcludedPaths .Split('\n').Select(l => l.Trim().TrimEnd('\r')).Where(l => l.Length > 0).ToList()), - General.StandupWeekday); + General.StandupWeekday, + _loadedDailyPrepMaxTasks); await _worker.UpdateAppSettingsAsync(dto); await Prime.SaveAsync(); CloseAction?.Invoke(); diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs index 9a07fe3..b19ab98 100644 --- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs +++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs @@ -31,7 +31,8 @@ public record AppSettingsDto( bool WorktreeAutoCleanupEnabled, int WorktreeAutoCleanupDays, string? ReportExcludedPaths, - int StandupWeekday); + int StandupWeekday, + int DailyPrepMaxTasks); public record WorktreeCleanupDto(int Removed); public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks); @@ -79,6 +80,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub private readonly PlanningMergeOrchestrator _planningMergeOrchestrator; private readonly PlanningChainCoordinator _planningChain; private readonly IPrimeScheduleSignal _primeSignal; + private readonly IPrimeRunner _primeRunner; private readonly ITaskStateService _state; private readonly IWeekReportService _report; @@ -98,6 +100,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub PlanningMergeOrchestrator planningMergeOrchestrator, PlanningChainCoordinator planningChain, IPrimeScheduleSignal primeSignal, + IPrimeRunner primeRunner, ITaskStateService state, IWeekReportService report) { @@ -116,6 +119,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub _planningMergeOrchestrator = planningMergeOrchestrator; _planningChain = planningChain; _primeSignal = primeSignal; + _primeRunner = primeRunner; _state = state; _report = report; } @@ -217,7 +221,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub row.WorktreeAutoCleanupEnabled, row.WorktreeAutoCleanupDays, row.ReportExcludedPaths, - row.StandupWeekday); + row.StandupWeekday, + row.DailyPrepMaxTasks); } public async Task UpdateAppSettings(AppSettingsDto dto) @@ -238,6 +243,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub WorktreeAutoCleanupDays = dto.WorktreeAutoCleanupDays, ReportExcludedPaths = dto.ReportExcludedPaths, StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday, + DailyPrepMaxTasks = dto.DailyPrepMaxTasks, }); } @@ -533,6 +539,15 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub _primeSignal.Signal(); } + public async Task RunDailyPrepNow() + { + var schedule = new PrimeScheduleDto(Guid.Empty, 0, TimeSpan.Zero, true, null, null); + var firedAt = DateTimeOffset.Now; + var outcome = await _primeRunner.FireAsync(schedule, Context.ConnectionAborted); + await _broadcaster.PrimeFired(Guid.Empty, outcome.Success, outcome.Message, firedAt); + return outcome.Success; + } + private static DateOnly Day(string iso) => DateOnly.ParseExact(iso, "yyyy-MM-dd", CultureInfo.InvariantCulture); public Task GetWeekReport(string startIso, string endIso) => diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs index 9abae72..1a612b9 100644 --- a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs +++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs @@ -62,6 +62,7 @@ public abstract class StubWorkerClient : IWorkerClient public virtual Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask; public virtual Task GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(null); public virtual Task GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(""); + public virtual Task RunDailyPrepNowAsync() => Task.FromResult(false); public virtual Task GetAppSettingsAsync() => Task.FromResult(null); public virtual Task> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List()); public virtual Task AddDailyNoteAsync(DateOnly day, string text) => Task.FromResult(null); diff --git a/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs b/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs index 2beab44..2fba972 100644 --- a/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs @@ -55,7 +55,7 @@ public sealed class PlanningHubTests : IDisposable { var hub = new WorkerHub( null!, null!, null!, null!, null!, null!, null!, null!, null!, - _planning, _launcher, null!, null!, null!, null!, null!, null!); + _planning, _launcher, null!, null!, null!, null!, null!, null!, null!); hub.Clients = new FakeHubCallerClients(_proxy); hub.Context = new FakeHubCallerContext(); return hub; diff --git a/tests/ClaudeDo.Worker.Tests/Hub/WorktreeStateHubTests.cs b/tests/ClaudeDo.Worker.Tests/Hub/WorktreeStateHubTests.cs index 8f047b9..87b845b 100644 --- a/tests/ClaudeDo.Worker.Tests/Hub/WorktreeStateHubTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Hub/WorktreeStateHubTests.cs @@ -19,7 +19,7 @@ public sealed class WorktreeStateHubTests : IDisposable var broadcaster = new HubBroadcaster(new CapturingHubContext()); var hub = new WorkerHub( null!, null!, null!, null!, broadcaster, _db.CreateFactory(), - null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!); + null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!); hub.Clients = new FakeHubCallerClients(new RecordingClientProxy()); hub.Context = new FakeHubCallerContext(); return hub; diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs index 311057c..d69019a 100644 --- a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs +++ b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs @@ -76,6 +76,7 @@ sealed class FakeWorkerClient : IWorkerClient public Task GetAppSettingsAsync() => Task.FromResult(null); public Task GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(null); public Task GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(""); + public Task RunDailyPrepNowAsync() => Task.FromResult(false); public Task> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List()); public Task AddDailyNoteAsync(DateOnly day, string text) => Task.FromResult(null); public Task UpdateDailyNoteAsync(string id, string text) => Task.CompletedTask;