# Agent Settings UI Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Restore the ability to configure Model / SystemPrompt / AgentPath per list (via a new modal) and per task (via an expander in DetailsIsland), persisting through SignalR hub methods to the existing DB schema. **Architecture:** UI → new `WorkerHub` methods (`UpdateList`, `UpdateListConfig`, `UpdateTaskAgentSettings`, `GetListConfig`) → existing repositories in `ClaudeDo.Data` (schema already in place). Worker broadcasts `ListUpdated` so the lists island refreshes. Per-task settings auto-save on change, debounced. The DB columns/tables already exist; `TaskRunner` + `ClaudeArgsBuilder` already consume them. **Tech Stack:** .NET 8, Avalonia 12, CommunityToolkit.Mvvm, EF Core, SignalR (hub on `127.0.0.1:47821`), xUnit integration tests with real SQLite. **Build tip (from project memory):** `dotnet build ClaudeDo.slnx` fails on .NET 8. Build individual csproj files: ``` dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj ``` --- ## File Structure New files: - `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml` - `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml.cs` - `src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs` - `tests/ClaudeDo.Worker.Tests/Repositories/ListRepositoryDeleteConfigTests.cs` - `tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryAgentSettingsTests.cs` - `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs` Modified files: - `src/ClaudeDo.Data/Repositories/ListRepository.cs` — add `DeleteConfigAsync` - `src/ClaudeDo.Data/Repositories/TaskRepository.cs` — add `UpdateAgentSettingsAsync` - `src/ClaudeDo.Worker/Hub/WorkerHub.cs` — add 4 methods + DTOs - `src/ClaudeDo.Worker/Hub/HubBroadcaster.cs` — add `ListUpdatedAsync` - `src/ClaudeDo.Ui/Services/WorkerClient.cs` — add 4 methods + `ListUpdatedEvent` - `src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs` (no changes expected, only for reference) - `src/ClaudeDo.App/App.axaml.cs` (or DI extension file) — register `ListSettingsModalViewModel` - `src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml` — add context menu + gear button - `src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs` — add `OpenSettingsCommand`, subscribe `ListUpdated` - `src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs` — expose list fields needed by modal - `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` — add agent-settings fields + auto-save - `src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml` — add "Agent settings" expander - `src/ClaudeDo.Data/CLAUDE.md` — refresh with new repo methods + ListConfigEntity - `src/ClaudeDo.Worker/CLAUDE.md` — document new hub methods + `ListUpdated` - `src/ClaudeDo.Ui/CLAUDE.md` — document list settings modal + details expander --- ## Task 1: Add `ListRepository.DeleteConfigAsync` **Files:** - Modify: `src/ClaudeDo.Data/Repositories/ListRepository.cs` - Test: `tests/ClaudeDo.Worker.Tests/Repositories/ListRepositoryDeleteConfigTests.cs` The existing `ListRepository.SetConfigAsync` upserts but never deletes. The UI needs a way to fully remove a list config row (when the user clears all three agent fields). - [ ] **Step 1: Write the failing test** Create `tests/ClaudeDo.Worker.Tests/Repositories/ListRepositoryDeleteConfigTests.cs`: ```csharp using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Tests.Infrastructure; using Xunit; namespace ClaudeDo.Worker.Tests.Repositories; public sealed class ListRepositoryDeleteConfigTests : IAsyncLifetime { private readonly TempDatabase _db = new(); public async Task InitializeAsync() => await _db.InitializeAsync(); public async Task DisposeAsync() => await _db.DisposeAsync(); [Fact] public async Task DeleteConfigAsync_RemovesExistingRow() { await using var ctx = _db.CreateContext(); var repo = new ListRepository(ctx); var listId = Guid.NewGuid().ToString(); await repo.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow, }); await repo.SetConfigAsync(new ListConfigEntity { ListId = listId, Model = "opus", SystemPrompt = "hello", AgentPath = "/tmp/a.md", }); var removed = await repo.DeleteConfigAsync(listId); Assert.True(removed); Assert.Null(await repo.GetConfigAsync(listId)); } [Fact] public async Task DeleteConfigAsync_ReturnsFalseWhenAbsent() { await using var ctx = _db.CreateContext(); var repo = new ListRepository(ctx); var removed = await repo.DeleteConfigAsync(Guid.NewGuid().ToString()); Assert.False(removed); } } ``` > If `TempDatabase` / `Infrastructure` namespace differs, match the existing test infrastructure pattern seen in other `*RepositoryTests` files in `tests/ClaudeDo.Worker.Tests/Repositories/`. Read one existing test file first to confirm the exact helper class name and SQLite setup call. - [ ] **Step 2: Run test to verify it fails** ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~ListRepositoryDeleteConfigTests" ``` Expected: FAIL with `'ListRepository' does not contain a definition for 'DeleteConfigAsync'`. - [ ] **Step 3: Add the method** In `src/ClaudeDo.Data/Repositories/ListRepository.cs`, add below `SetConfigAsync`: ```csharp public async Task DeleteConfigAsync(string listId, CancellationToken ct = default) { var affected = await _context.ListConfigs .Where(c => c.ListId == listId) .ExecuteDeleteAsync(ct); return affected > 0; } ``` - [ ] **Step 4: Run test to verify it passes** ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~ListRepositoryDeleteConfigTests" ``` Expected: 2 tests passed. - [ ] **Step 5: Commit** ``` git add src/ClaudeDo.Data/Repositories/ListRepository.cs tests/ClaudeDo.Worker.Tests/Repositories/ListRepositoryDeleteConfigTests.cs git commit -m "feat(data): add ListRepository.DeleteConfigAsync" ``` --- ## Task 2: Add `TaskRepository.UpdateAgentSettingsAsync` **Files:** - Modify: `src/ClaudeDo.Data/Repositories/TaskRepository.cs` - Test: `tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryAgentSettingsTests.cs` Focused method so the UI can update the three override columns without loading the entity. - [ ] **Step 1: Write the failing test** Create `tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryAgentSettingsTests.cs`: ```csharp using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Tests.Infrastructure; using Xunit; namespace ClaudeDo.Worker.Tests.Repositories; public sealed class TaskRepositoryAgentSettingsTests : IAsyncLifetime { private readonly TempDatabase _db = new(); public async Task InitializeAsync() => await _db.InitializeAsync(); public async Task DisposeAsync() => await _db.DisposeAsync(); private async Task SeedTaskAsync() { await using var ctx = _db.CreateContext(); var listId = Guid.NewGuid().ToString(); var taskId = Guid.NewGuid().ToString(); await new ListRepository(ctx).AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow, }); await new TaskRepository(ctx).AddAsync(new TaskEntity { Id = taskId, ListId = listId, Title = "T", CreatedAt = DateTime.UtcNow, }); return taskId; } [Fact] public async Task UpdateAgentSettingsAsync_SetsAllThreeFields() { var taskId = await SeedTaskAsync(); await using var ctx = _db.CreateContext(); var repo = new TaskRepository(ctx); await repo.UpdateAgentSettingsAsync(taskId, "opus", "system!", "/tmp/a.md"); var entity = await repo.GetByIdAsync(taskId); Assert.NotNull(entity); Assert.Equal("opus", entity!.Model); Assert.Equal("system!", entity.SystemPrompt); Assert.Equal("/tmp/a.md", entity.AgentPath); } [Fact] public async Task UpdateAgentSettingsAsync_NullsClearColumns() { var taskId = await SeedTaskAsync(); await using (var ctx = _db.CreateContext()) { await new TaskRepository(ctx).UpdateAgentSettingsAsync(taskId, "opus", "s", "/a.md"); } await using (var ctx = _db.CreateContext()) { var repo = new TaskRepository(ctx); await repo.UpdateAgentSettingsAsync(taskId, null, null, null); var entity = await repo.GetByIdAsync(taskId); Assert.NotNull(entity); Assert.Null(entity!.Model); Assert.Null(entity.SystemPrompt); Assert.Null(entity.AgentPath); } } } ``` - [ ] **Step 2: Run test to verify it fails** ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~TaskRepositoryAgentSettingsTests" ``` Expected: FAIL with `'TaskRepository' does not contain a definition for 'UpdateAgentSettingsAsync'`. - [ ] **Step 3: Add the method** In `src/ClaudeDo.Data/Repositories/TaskRepository.cs`, add at the end of the `#region Status transitions` block (or a new `#region Agent settings` right after): ```csharp public async Task UpdateAgentSettingsAsync( string taskId, string? model, string? systemPrompt, string? agentPath, CancellationToken ct = default) { await _context.Tasks .Where(t => t.Id == taskId) .ExecuteUpdateAsync(s => s .SetProperty(t => t.Model, model) .SetProperty(t => t.SystemPrompt, systemPrompt) .SetProperty(t => t.AgentPath, agentPath), ct); } ``` - [ ] **Step 4: Run test to verify it passes** ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~TaskRepositoryAgentSettingsTests" ``` Expected: 2 tests passed. - [ ] **Step 5: Commit** ``` git add src/ClaudeDo.Data/Repositories/TaskRepository.cs tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryAgentSettingsTests.cs git commit -m "feat(data): add TaskRepository.UpdateAgentSettingsAsync" ``` --- ## Task 3: Hub methods + DTOs + `ListUpdated` broadcast **Files:** - Modify: `src/ClaudeDo.Worker/Hub/HubBroadcaster.cs` - Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs` - Test: `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs` Add four hub methods (`UpdateList`, `UpdateListConfig`, `UpdateTaskAgentSettings`, `GetListConfig`) plus a `ListUpdated` broadcast event. - [ ] **Step 1: Write the failing test** Create `tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs`: > Pattern notes: the worker test project usually calls hub methods by invoking repositories/services directly when no SignalR host is spun up. Read an existing hub-adjacent test (if one exists) to confirm. If no precedent, assert the underlying persistence via repo calls — the hub methods are thin wrappers. ```csharp using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Tests.Infrastructure; using Xunit; namespace ClaudeDo.Worker.Tests.Hub; // These tests exercise the hub behavior by invoking the underlying repository // chain the hub calls. The hub itself is a thin wrapper around these repos; // full SignalR integration testing is covered manually per the spec. public sealed class AgentSettingsHubTests : IAsyncLifetime { private readonly TempDatabase _db = new(); public async Task InitializeAsync() => await _db.InitializeAsync(); public async Task DisposeAsync() => await _db.DisposeAsync(); [Fact] public async Task UpdateListConfig_AllNull_DeletesRow() { await using var ctx = _db.CreateContext(); var repo = new ListRepository(ctx); var listId = Guid.NewGuid().ToString(); await repo.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow }); await repo.SetConfigAsync(new ListConfigEntity { ListId = listId, Model = "opus", SystemPrompt = null, AgentPath = null, }); // Simulate hub: all three null => delete. const string? model = null; const string? sp = null; const string? ap = null; if (model is null && sp is null && ap is null) await repo.DeleteConfigAsync(listId); else await repo.SetConfigAsync(new ListConfigEntity { ListId = listId, Model = model, SystemPrompt = sp, AgentPath = ap, }); Assert.Null(await repo.GetConfigAsync(listId)); } } ``` - [ ] **Step 2: Run test to verify it fails** It actually passes since it only calls existing code. The purpose of this test is to encode the hub's "all-null → delete" contract so that Task 3's code changes don't break it. ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~AgentSettingsHubTests" ``` Expected: 1 passed (the test is a spec/guardrail, not a red-green). - [ ] **Step 3: Add the broadcaster event** In `src/ClaudeDo.Worker/Hub/HubBroadcaster.cs`, add a new method alongside the existing `TaskUpdatedAsync`: ```csharp public Task ListUpdatedAsync(string listId) => _hub.Clients.All.SendAsync("ListUpdated", listId); ``` - [ ] **Step 4: Add DTOs + hub methods** In `src/ClaudeDo.Worker/Hub/WorkerHub.cs`, add these `record` DTOs at the top (alongside `ActiveTaskDto`, `AppSettingsDto`, etc.): ```csharp public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath); public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath); public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath); ``` Then add these methods to the `WorkerHub` class (place at the end, before the closing brace): ```csharp public async Task UpdateList(UpdateListDto dto) { using var ctx = _dbFactory.CreateDbContext(); var repo = new ListRepository(ctx); var entity = await repo.GetByIdAsync(dto.Id); if (entity is null) throw new HubException("list not found"); entity.Name = dto.Name; entity.WorkingDir = string.IsNullOrWhiteSpace(dto.WorkingDir) ? null : dto.WorkingDir; entity.DefaultCommitType = string.IsNullOrWhiteSpace(dto.DefaultCommitType) ? "chore" : dto.DefaultCommitType; await repo.UpdateAsync(entity); await _broadcaster.ListUpdatedAsync(dto.Id); } public async Task UpdateListConfig(UpdateListConfigDto dto) { using var ctx = _dbFactory.CreateDbContext(); var repo = new ListRepository(ctx); var model = Nullify(dto.Model); var systemPrompt = Nullify(dto.SystemPrompt); var agentPath = Nullify(dto.AgentPath); if (model is null && systemPrompt is null && agentPath is null) { await repo.DeleteConfigAsync(dto.ListId); } else { await repo.SetConfigAsync(new ListConfigEntity { ListId = dto.ListId, Model = model, SystemPrompt = systemPrompt, AgentPath = agentPath, }); } await _broadcaster.ListUpdatedAsync(dto.ListId); } public async Task GetListConfig(string listId) { using var ctx = _dbFactory.CreateDbContext(); var repo = new ListRepository(ctx); var config = await repo.GetConfigAsync(listId); if (config is null) return null; return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath); } public async Task UpdateTaskAgentSettings(UpdateTaskAgentSettingsDto dto) { using var ctx = _dbFactory.CreateDbContext(); var repo = new TaskRepository(ctx); await repo.UpdateAgentSettingsAsync( dto.TaskId, Nullify(dto.Model), Nullify(dto.SystemPrompt), Nullify(dto.AgentPath)); await _broadcaster.TaskUpdatedAsync(dto.TaskId); } private static string? Nullify(string? s) => string.IsNullOrWhiteSpace(s) ? null : s; ``` If `_broadcaster.TaskUpdatedAsync` has a different signature in the existing broadcaster, adapt the call to match — read `HubBroadcaster.cs` for the exact name. - [ ] **Step 5: Build worker** ``` dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj ``` Expected: build succeeded, 0 errors. - [ ] **Step 6: Run the guardrail test** ``` dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter "FullyQualifiedName~AgentSettingsHubTests" ``` Expected: passed. - [ ] **Step 7: Commit** ``` git add src/ClaudeDo.Worker/Hub/WorkerHub.cs src/ClaudeDo.Worker/Hub/HubBroadcaster.cs tests/ClaudeDo.Worker.Tests/Hub/AgentSettingsHubTests.cs git commit -m "feat(worker): add hub methods for list and task agent settings" ``` --- ## Task 4: WorkerClient — 4 new methods + `ListUpdatedEvent` **Files:** - Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs` Add client-side DTOs (mirroring the hub DTOs, or import from `ClaudeDo.Worker` if cross-project references allow — else redeclare as simple records in the UI project), client methods, and subscribe to the new `ListUpdated` event. - [ ] **Step 1: Inspect current WorkerClient shape** Open `src/ClaudeDo.Ui/Services/WorkerClient.cs` and find where `UpdateAppSettings`, `TaskUpdatedEvent`, and the hub event subscriptions are declared. The new code will mirror the exact pattern used there (naming, event signature, async wrapper). - [ ] **Step 2: Add client DTOs** At the top of `WorkerClient.cs` (or in a nested namespace if the file already has DTO records, e.g. `AppSettingsDto`), add: ```csharp public sealed record UpdateListClientDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); public sealed record UpdateListConfigClientDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath); public sealed record UpdateTaskAgentSettingsClientDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath); public sealed record ListConfigClientDto(string? Model, string? SystemPrompt, string? AgentPath); ``` Use the existing file's naming convention instead of "ClientDto" if the file already has a convention (e.g., `AppSettingsDto` with no suffix — then follow suit and just name them `UpdateListDto`, `UpdateListConfigDto`, etc. in the UI namespace). - [ ] **Step 3: Add the four client methods** Inside the `WorkerClient` class, alongside `UpdateAppSettings`: ```csharp public async Task UpdateListAsync(UpdateListClientDto dto, CancellationToken ct = default) { await _hub.InvokeAsync("UpdateList", dto, ct); } public async Task UpdateListConfigAsync(UpdateListConfigClientDto dto, CancellationToken ct = default) { await _hub.InvokeAsync("UpdateListConfig", dto, ct); } public async Task GetListConfigAsync(string listId, CancellationToken ct = default) { return await _hub.InvokeAsync("GetListConfig", listId, ct); } public async Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsClientDto dto, CancellationToken ct = default) { await _hub.InvokeAsync("UpdateTaskAgentSettings", dto, ct); } ``` - [ ] **Step 4: Subscribe to `ListUpdated` event** Find where other hub events are subscribed (usually in `WorkerClient`'s `ConfigureHub` / `StartAsync` / constructor-equivalent setup). Add a line mirroring the pattern used for `TaskUpdated`: ```csharp _hub.On("ListUpdated", listId => ListUpdatedEvent?.Invoke(listId)); ``` And declare the event alongside the other events (e.g. `TaskUpdatedEvent`): ```csharp public event Action? ListUpdatedEvent; ``` - [ ] **Step 5: Build UI** ``` dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj ``` Expected: build succeeded, 0 errors. - [ ] **Step 6: Commit** ``` git add src/ClaudeDo.Ui/Services/WorkerClient.cs git commit -m "feat(ui): WorkerClient supports list/task agent settings + ListUpdated event" ``` --- ## Task 5: `ListSettingsModalViewModel` **Files:** - Create: `src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs` - Modify: `src/ClaudeDo.App/App.axaml.cs` (or wherever DI is registered — look for `AddTransient` / `AddSingleton` to find the registration block) - [ ] **Step 1: Create the ViewModel** Write `src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs`: ```csharp using System.Collections.ObjectModel; using ClaudeDo.Data.Models; using ClaudeDo.Ui.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ClaudeDo.Ui.ViewModels.Modals; public sealed partial class ListSettingsModalViewModel : ViewModelBase { private readonly WorkerClient _worker; // Set by caller before Show public string ListId { get; set; } = ""; [ObservableProperty] private string _name = ""; [ObservableProperty] private string _workingDir = ""; [ObservableProperty] private string _defaultCommitType = "chore"; [ObservableProperty] private string _selectedModel = "(default)"; [ObservableProperty] private string _systemPrompt = ""; [ObservableProperty] private AgentInfo? _selectedAgent; public ObservableCollection ModelOptions { get; } = new() { "(default)", "sonnet", "opus", "haiku", }; public ObservableCollection CommitTypeOptions { get; } = new() { "chore", "feat", "fix", "refactor", "docs", "test", "ci", "perf", "style", "build", }; public ObservableCollection Agents { get; } = new(); public Action? CloseAction { get; set; } public ListSettingsModalViewModel(WorkerClient worker) { _worker = worker; } public async Task LoadAsync( string listId, string name, string? workingDir, string defaultCommitType, CancellationToken ct = default) { ListId = listId; Name = name; WorkingDir = workingDir ?? ""; DefaultCommitType = string.IsNullOrWhiteSpace(defaultCommitType) ? "chore" : defaultCommitType; Agents.Clear(); Agents.Add(new AgentInfo("(none)", "", "")); var agents = await _worker.GetAgentsAsync(); foreach (var a in agents) Agents.Add(a); var config = await _worker.GetListConfigAsync(listId, ct); SelectedModel = string.IsNullOrWhiteSpace(config?.Model) ? "(default)" : config!.Model!; SystemPrompt = config?.SystemPrompt ?? ""; SelectedAgent = string.IsNullOrWhiteSpace(config?.AgentPath) ? Agents[0] : (Agents.FirstOrDefault(a => a.Path == config!.AgentPath) ?? Agents[0]); } [RelayCommand] private async Task SaveAsync() { var model = SelectedModel == "(default)" ? null : SelectedModel; var sp = string.IsNullOrWhiteSpace(SystemPrompt) ? null : SystemPrompt; var ap = SelectedAgent is null || string.IsNullOrWhiteSpace(SelectedAgent.Path) ? null : SelectedAgent.Path; await _worker.UpdateListAsync(new UpdateListClientDto( ListId, string.IsNullOrWhiteSpace(Name) ? "Untitled" : Name, string.IsNullOrWhiteSpace(WorkingDir) ? null : WorkingDir, DefaultCommitType)); await _worker.UpdateListConfigAsync(new UpdateListConfigClientDto( ListId, model, sp, ap)); CloseAction?.Invoke(); } [RelayCommand] private void Cancel() => CloseAction?.Invoke(); [RelayCommand] private void ResetAgentSettings() { SelectedModel = "(default)"; SystemPrompt = ""; SelectedAgent = Agents.Count > 0 ? Agents[0] : null; } } ``` > Match the exact DTO record names used in `WorkerClient.cs` from Task 4. If you named them `UpdateListDto` (without `Client` suffix), update the `new UpdateListClientDto(...)` calls above to `new UpdateListDto(...)`. - [ ] **Step 2: Register in DI** Find the file that registers modal ViewModels (search the repo for `AddTransient` or `AddSingleton` — likely in `src/ClaudeDo.App/App.axaml.cs` or a DI extension). Add: ```csharp services.AddTransient(); ``` - [ ] **Step 3: Build UI** ``` dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj ``` Expected: both build succeeded, 0 errors. - [ ] **Step 4: Commit** ``` git add src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs src/ClaudeDo.App/App.axaml.cs git commit -m "feat(ui): add ListSettingsModalViewModel" ``` --- ## Task 6: `ListSettingsModalView` **Files:** - Create: `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml` - Create: `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml.cs` - [ ] **Step 1: Create the view XAML** Write `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml`: ```xml