feat(claude-do): UI: Gate-Schwellen im Settings-Modal (General)

Macht die zwei Schwellen einstellbar. Setzt den Data-Subtask (Felder in `app_settings`) und den Hub-Subtask voraus.

**Zu bauen**
1. `AppSettingsDto` durchziehen: `WorkerHub` (`GetAppSettings` / `UpdateAppSettings`) und der UI-seitige Record in `WorkerClient.cs` bekommen `UsageGateFiveHourPct` und `UsageGateSevenDayPct`.
2. `GeneralSettingsTabViewModel`: zwei `[ObservableProperty]`-Felder, Validie

ClaudeDo-Task: 06068810-5b5c-4635-80dd-62eeba89fb8c
This commit is contained in:
mika kuns
2026-08-05 13:38:05 +02:00
parent 7661129202
commit 1e88fefbda
9 changed files with 152 additions and 4 deletions
@@ -45,4 +45,30 @@ public class GeneralSettingsTabViewModelTests
Assert.Equal(new List<string> { "a" }, vm.SelectedSessionSkillNames());
}
[Theory]
[InlineData(-1, false)]
[InlineData(0, true)]
[InlineData(80, true)]
[InlineData(100, true)]
[InlineData(101, false)]
public void Validate_UsageGateFiveHourPct_bounds(int value, bool expectedValid)
{
var vm = new GeneralSettingsTabViewModel { UsageGateFiveHourPct = value };
Assert.Equal(expectedValid, vm.Validate() is null);
}
[Theory]
[InlineData(-1, false)]
[InlineData(0, true)]
[InlineData(80, true)]
[InlineData(100, true)]
[InlineData(101, false)]
public void Validate_UsageGateSevenDayPct_bounds(int value, bool expectedValid)
{
var vm = new GeneralSettingsTabViewModel { UsageGateSevenDayPct = value };
Assert.Equal(expectedValid, vm.Validate() is null);
}
}
@@ -0,0 +1,76 @@
using ClaudeDo.Localization;
using ClaudeDo.Ui;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Modals;
using ClaudeDo.Ui.ViewModels.Modals.Settings;
using Xunit;
namespace ClaudeDo.Ui.Tests.ViewModels;
public class SettingsModalViewModelTests
{
private sealed class FakeWorker : StubWorkerClient
{
public AppSettingsDto? AppToReturn;
public AppSettingsDto? Saved;
public override Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult(AppToReturn);
public override Task UpdateAppSettingsAsync(AppSettingsDto dto) { Saved = dto; return Task.CompletedTask; }
}
private sealed class FakePrimeApi : IPrimeScheduleApi
{
public Task<List<PrimeScheduleDto>> ListAsync() => Task.FromResult(new List<PrimeScheduleDto>());
public Task<PrimeScheduleDto?> UpsertAsync(PrimeScheduleDto dto) => Task.FromResult<PrimeScheduleDto?>(dto);
public Task DeleteAsync(Guid id) => Task.CompletedTask;
}
private static Localizer MakeLocalizer()
{
var dir = Path.Combine(Path.GetTempPath(), "loc_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
File.WriteAllText(Path.Combine(dir, "en.json"),
"""{ "metadata": { "code": "en", "name": "English" }, "settings": { "save": "Save" } }""");
File.WriteAllText(Path.Combine(dir, "de.json"),
"""{ "metadata": { "code": "de", "name": "Deutsch" }, "settings": { "save": "Speichern" } }""");
return new Localizer(LocaleStore.Load(dir), "en");
}
private static AppSettingsDto DtoWith(int fiveHourPct, int sevenDayPct) =>
new(DefaultClaudeInstructions: "", DefaultModel: "sonnet", DefaultMaxTurns: 30,
DefaultPermissionMode: "auto", MaxParallelExecutions: 1, WorktreeStrategy: "sibling",
CentralWorktreeRoot: null, WorktreeAutoCleanupEnabled: false, WorktreeAutoCleanupDays: 7,
ReportExcludedPaths: null, StandupWeekday: 3, DailyPrepMaxTasks: 5,
SessionSkills: null, ModelPresets: null,
UsageGateFiveHourPct: fiveHourPct, UsageGateSevenDayPct: sevenDayPct);
private static SettingsModalViewModel MakeVm(FakeWorker worker) =>
new(worker, new PrimeClaudeTabViewModel(new FakePrimeApi()), new OnlineLoginService(),
MakeLocalizer(), new AppSettings());
[Fact]
public async Task LoadAsync_fills_usage_gate_fields_from_dto()
{
var worker = new FakeWorker { AppToReturn = DtoWith(65, 95) };
var vm = MakeVm(worker);
await vm.LoadAsync();
Assert.Equal(65, vm.General.UsageGateFiveHourPct);
Assert.Equal(95, vm.General.UsageGateSevenDayPct);
}
[Fact]
public async Task Save_sends_usage_gate_fields_in_dto()
{
var worker = new FakeWorker();
var vm = MakeVm(worker);
vm.General.UsageGateFiveHourPct = 42;
vm.General.UsageGateSevenDayPct = 77;
await vm.SaveCommand.ExecuteAsync(null);
Assert.NotNull(worker.Saved);
Assert.Equal(42, worker.Saved!.UsageGateFiveHourPct);
Assert.Equal(77, worker.Saved.UsageGateSevenDayPct);
}
}