# ClaudeDo UX Redesign — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Redesign ClaudeDo's Avalonia UI for Microsoft To Do-style usability — inline task creation, editable detail pane with auto-save, keyboard shortcuts, and visual polish with Forest Teal accent. **Architecture:** All changes are in ClaudeDo.Ui (Views, ViewModels, Converters) and ClaudeDo.App (App.axaml resources, DI registration). No changes to Data, Worker, or database schema. The existing MVVM + CommunityToolkit.Mvvm pattern is preserved. **Tech Stack:** .NET 8.0, Avalonia 12.0.0 (Fluent theme, dark variant), CommunityToolkit.Mvvm, SQLite via raw ADO.NET **Spec:** `docs/superpowers/specs/2026-04-14-todo-ux-redesign-design.md` --- ## File Structure ### Modify | File | Responsibility | |------|---------------| | `src/ClaudeDo.App/App.axaml` | Accent color brush resources, force dark theme | | `src/ClaudeDo.App/Program.cs` | DI registration: add TagRepository to TaskDetailViewModel | | `src/ClaudeDo.Ui/ViewModels/ListItemViewModel.cs` | Add `DotBrush` property for sidebar colored dots | | `src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs` | Add `ToggleDoneCommand`, `IsDone`, checkbox state helpers | | `src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs` | Inline add logic, toggle-done callback, list name property | | `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` | Editable properties, auto-save, tag CRUD, TaskChanged event | | `src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs` | Wire TaskDetail.TaskChanged → TaskList.RefreshSingleAsync | | `src/ClaudeDo.Ui/Views/MainWindow.axaml` | Reactive layout, sidebar polish, list name header | | `src/ClaudeDo.Ui/Views/MainWindow.axaml.cs` | Global keyboard shortcuts | | `src/ClaudeDo.Ui/Views/TaskListView.axaml` | Checkbox task rows, inline add field, remove toolbar | | `src/ClaudeDo.Ui/Views/TaskListView.axaml.cs` | Inline add KeyDown, task-scoped shortcuts | | `src/ClaudeDo.Ui/Views/TaskDetailView.axaml` | Editable fields, tag chips, auto-save layout | | `src/ClaudeDo.Ui/Views/TaskDetailView.axaml.cs` | LostFocus auto-save handlers, tag input KeyDown | | `src/ClaudeDo.Ui/Converters/StatusColorConverter.cs` | Keep existing, used for status text in detail pane | ### Create | File | Responsibility | |------|---------------| | `src/ClaudeDo.Ui/Converters/CheckboxBorderConverter.cs` | Task status string → checkbox border color brush | --- ## Task 1: Accent Color Resources + Dark Theme **Files:** - Modify: `src/ClaudeDo.App/App.axaml` - [ ] **Step 1: Read current App.axaml** Read `src/ClaudeDo.App/App.axaml` to confirm current content. - [ ] **Step 2: Add resource dictionary and force dark theme** Replace the full `App.axaml` content with: ```xml ``` Key changes: `RequestedThemeVariant="Dark"`, all brush resources added. - [ ] **Step 3: Build to verify** Run: `dotnet build ClaudeDo.slnx` Expected: Build succeeds with no errors. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.App/App.axaml git commit -m "style: add Forest Teal accent resources and force dark theme" ``` --- ## Task 2: CheckboxBorderConverter **Files:** - Create: `src/ClaudeDo.Ui/Converters/CheckboxBorderConverter.cs` - [ ] **Step 1: Create the converter** Create `src/ClaudeDo.Ui/Converters/CheckboxBorderConverter.cs`: ```csharp using System; using System.Globalization; using Avalonia.Data.Converters; using Avalonia.Media; namespace ClaudeDo.Ui.Converters; public sealed class CheckboxBorderConverter : IValueConverter { public static readonly CheckboxBorderConverter Instance = new(); private static readonly ISolidColorBrush Gray = new SolidColorBrush(Color.Parse("#475569")); private static readonly ISolidColorBrush Orange = new SolidColorBrush(Color.Parse("#e67e22")); private static readonly ISolidColorBrush Green = new SolidColorBrush(Color.Parse("#3d9474")); private static readonly ISolidColorBrush Red = new SolidColorBrush(Color.Parse("#ef4444")); public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return value?.ToString()?.ToLowerInvariant() switch { "running" => Orange, "done" => Green, "failed" => Red, _ => Gray, // manual, queued }; } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException(); } ``` - [ ] **Step 2: Build to verify** Run: `dotnet build ClaudeDo.slnx` Expected: Build succeeds. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/Converters/CheckboxBorderConverter.cs git commit -m "feat(ui): add CheckboxBorderConverter for task status circles" ``` --- ## Task 3: ListItemViewModel — DotBrush **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/ListItemViewModel.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/ViewModels/ListItemViewModel.cs`. - [ ] **Step 2: Add DotBrush property** Add the following to `ListItemViewModel`, after the existing properties: ```csharp using Avalonia.Media; ``` Add the static palette and computed property: ```csharp private static readonly IBrush[] DotPalette = [ new SolidColorBrush(Color.Parse("#3d9474")), // green new SolidColorBrush(Color.Parse("#5571a1")), // blue new SolidColorBrush(Color.Parse("#d4964a")), // amber new SolidColorBrush(Color.Parse("#7c6aad")), // purple new SolidColorBrush(Color.Parse("#c25d6a")), // rose ]; public IBrush DotBrush => DotPalette[Math.Abs(Id.GetHashCode()) % DotPalette.Length]; ``` - [ ] **Step 3: Build to verify** Run: `dotnet build ClaudeDo.slnx` Expected: Build succeeds. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/ListItemViewModel.cs git commit -m "feat(ui): add colored dot brush to ListItemViewModel" ``` --- ## Task 4: TaskItemViewModel — ToggleDone + Checkbox State **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs`. - [ ] **Step 2: Add toggle-done callback and properties** Update the constructor to accept a `toggleDone` callback: ```csharp private readonly Func? _toggleDone; public TaskItemViewModel(TaskEntity entity, IReadOnlyList tags, Func? runNow, Func canRunNow, Func? toggleDone) ``` Store it: `_toggleDone = toggleDone;` Add these computed properties: ```csharp public bool IsDone => Status == TaskStatus.Done; public bool IsRunning => Status == TaskStatus.Running; public bool CanToggleDone => Status != TaskStatus.Running && Status != TaskStatus.Failed; ``` Add the ToggleDone command: ```csharp [RelayCommand(CanExecute = nameof(CanToggleDone))] private async Task ToggleDone() { if (_toggleDone is not null) await _toggleDone(Id); } ``` In the `Refresh` method, after updating properties, notify the new computed properties: ```csharp OnPropertyChanged(nameof(IsDone)); OnPropertyChanged(nameof(IsRunning)); OnPropertyChanged(nameof(CanToggleDone)); ToggleDoneCommand.NotifyCanExecuteChanged(); ``` - [ ] **Step 3: Build to verify (expect error)** Run: `dotnet build ClaudeDo.slnx` Expected: Build error in `TaskListViewModel.cs` because constructor call is missing the new parameter. This is expected — we fix it in Task 6. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs git commit -m "feat(ui): add ToggleDone command and checkbox state to TaskItemViewModel" ``` --- ## Task 5: TaskListViewModel — Inline Add + ToggleDone + ListName **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs`. - [ ] **Step 2: Add ListName property** Add a property to expose the current list name for the header: ```csharp [ObservableProperty] private string _listName = "Tasks"; ``` In `LoadAsync`, after setting `CurrentListId`, fetch and set the name: ```csharp if (listId is not null) { var list = await _listRepo.GetByIdAsync(listId); ListName = list?.Name ?? "Tasks"; } else { ListName = "Tasks"; } ``` - [ ] **Step 3: Add inline add properties and command** Add: ```csharp [ObservableProperty] private string _inlineAddTitle = ""; ``` Add the command: ```csharp [RelayCommand(CanExecute = nameof(CanAddTask))] private async Task InlineAdd() { var title = InlineAddTitle.Trim(); if (string.IsNullOrEmpty(title) || CurrentListId is null) return; var list = await _listRepo.GetByIdAsync(CurrentListId); var defaultCommitType = list?.DefaultCommitType ?? "chore"; var entity = new TaskEntity { Id = Guid.NewGuid().ToString(), ListId = CurrentListId, Title = title, Status = TaskStatus.Manual, CommitType = defaultCommitType, CreatedAt = DateTime.UtcNow, }; try { await _taskRepo.AddAsync(entity); var tags = await _taskRepo.GetEffectiveTagsAsync(entity.Id); var vm = new TaskItemViewModel(entity, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync); Tasks.Add(vm); SelectedTask = vm; InlineAddTitle = ""; } catch (Exception ex) { _showMessage($"Error creating task: {ex.Message}"); } } ``` - [ ] **Step 4: Add ToggleDone method** ```csharp private async Task ToggleDoneAsync(string taskId) { var entity = await _taskRepo.GetByIdAsync(taskId); if (entity is null) return; entity.Status = entity.Status == TaskStatus.Done ? TaskStatus.Manual : TaskStatus.Done; if (entity.Status == TaskStatus.Done) entity.FinishedAt = DateTime.UtcNow; await _taskRepo.UpdateAsync(entity); await RefreshSingleAsync(taskId); } ``` - [ ] **Step 5: Update all TaskItemViewModel constructor calls** In `LoadAsync`, update the constructor call: ```csharp Tasks.Add(new TaskItemViewModel(e, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync)); ``` In `AddTask` (the existing modal add), same change: ```csharp Tasks.Add(new TaskItemViewModel(saved, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync)); ``` - [ ] **Step 6: Build to verify** Run: `dotnet build ClaudeDo.slnx` Expected: Build succeeds. - [ ] **Step 7: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs git commit -m "feat(ui): add inline task creation, toggle-done, and list name to TaskListViewModel" ``` --- ## Task 6: TaskDetailViewModel — Editable + Auto-Save + Tags **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs`. - [ ] **Step 2: Add TagRepository dependency and tag collection** Add to constructor parameters: ```csharp private readonly TagRepository _tagRepo; ``` Update constructor signature: ```csharp public TaskDetailViewModel(TaskRepository taskRepo, WorktreeRepository worktreeRepo, ListRepository listRepo, GitService git, WorkerClient worker, TagRepository tagRepo) ``` Store it: `_tagRepo = tagRepo;` Add tag collection and new-tag input: ```csharp public ObservableCollection Tags { get; } = new(); [ObservableProperty] private string _newTagInput = ""; ``` - [ ] **Step 3: Add editable status and commit type properties** Add observable properties and static choice arrays: ```csharp [ObservableProperty] private string _statusChoice = "Manual"; [ObservableProperty] private string _commitType = "chore"; public static string[] StatusChoices { get; } = ["Manual", "Queued", "Running", "Done", "Failed"]; public static string[] CommitTypes { get; } = ["feat", "fix", "refactor", "docs", "test", "chore", "ci", "perf", "style", "build"]; ``` - [ ] **Step 4: Add loading guard and TaskChanged event** ```csharp private bool _isLoading; public event Action? TaskChanged; ``` - [ ] **Step 5: Update LoadAsync to populate editable fields and tags** In `LoadAsync`, after setting the existing properties, add: ```csharp _isLoading = true; try { // ... existing property assignments ... StatusChoice = task.Status.ToString(); CommitType = task.CommitType; Tags.Clear(); var tags = await _taskRepo.GetTagsAsync(taskId); foreach (var tag in tags) Tags.Add(tag); } finally { _isLoading = false; } ``` Wrap the existing assignments inside the try block. The `_isLoading = true` goes before any property sets, `_isLoading = false` in finally. - [ ] **Step 6: Add SaveAsync method** ```csharp public async Task SaveAsync() { if (_isLoading || _taskId is null) return; var entity = await _taskRepo.GetByIdAsync(_taskId); if (entity is null) return; entity.Title = Title; entity.Description = Description; entity.CommitType = CommitType; if (Enum.TryParse(StatusChoice, true, out var status)) entity.Status = status; await _taskRepo.UpdateAsync(entity); StatusText = entity.Status.ToString().ToLowerInvariant(); TaskChanged?.Invoke(_taskId); } ``` - [ ] **Step 7: Add tag CRUD commands** ```csharp [RelayCommand] private async Task AddTag() { var name = NewTagInput.Trim(); if (string.IsNullOrEmpty(name) || _taskId is null) return; var tagId = await _tagRepo.GetOrCreateAsync(name); await _taskRepo.AddTagAsync(_taskId, tagId); // Reload tags to get the full entity Tags.Clear(); var tags = await _taskRepo.GetTagsAsync(_taskId); foreach (var tag in tags) Tags.Add(tag); NewTagInput = ""; TaskChanged?.Invoke(_taskId); } [RelayCommand] private async Task RemoveTag(TagEntity tag) { if (_taskId is null) return; await _taskRepo.RemoveTagAsync(_taskId, tag.Id); Tags.Remove(tag); TaskChanged?.Invoke(_taskId); } ``` - [ ] **Step 8: Update Clear to reset new fields** In `Clear()`, add: ```csharp Tags.Clear(); NewTagInput = ""; StatusChoice = "Manual"; CommitType = "chore"; ``` - [ ] **Step 9: Build to verify (expect error)** Run: `dotnet build ClaudeDo.slnx` Expected: Build error in `Program.cs` because the TaskDetailViewModel constructor now requires `TagRepository`. This is fixed in Task 9. - [ ] **Step 10: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs git commit -m "feat(ui): make TaskDetailViewModel editable with auto-save and tag CRUD" ``` --- ## Task 7: MainWindowViewModel — Wire TaskChanged Event **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs`. - [ ] **Step 2: Subscribe to TaskChanged in constructor** In the constructor, after `TaskList.SelectedTaskChanged += OnSelectedTaskChanged;`, add: ```csharp TaskDetail.TaskChanged += taskId => _ = TaskList.RefreshSingleAsync(taskId); ``` - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/MainWindowViewModel.cs git commit -m "feat(ui): wire TaskDetail changes back to task list refresh" ``` --- ## Task 8: DI Registration Update **Files:** - Modify: `src/ClaudeDo.App/Program.cs` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.App/Program.cs`. - [ ] **Step 2: Update TaskDetailViewModel registration** Change the `TaskDetailViewModel` registration from implicit constructor injection: ```csharp sc.AddSingleton(); ``` to explicit factory registration: ```csharp sc.AddSingleton(sp => new TaskDetailViewModel( sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService())); ``` - [ ] **Step 3: Build to verify** Run: `dotnet build ClaudeDo.slnx` Expected: Build succeeds — the constructor mismatch from Task 6 is now resolved. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.App/Program.cs git commit -m "fix(di): register TagRepository in TaskDetailViewModel constructor" ``` --- ## Task 9: MainWindow.axaml — Reactive Layout + Sidebar + Header **Files:** - Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml` - [ ] **Step 1: Read current file** Read `src/ClaudeDo.Ui/Views/MainWindow.axaml`. - [ ] **Step 2: Replace full MainWindow content** Replace the entire file with: ```xml