From 9f61cd1449ef62038fde9e1cba783789df22db62 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 14 Apr 2026 10:07:13 +0200 Subject: [PATCH] docs: add UX redesign implementation plan (16 tasks) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plans/2026-04-14-todo-ux-redesign.md | 1655 +++++++++++++++++ 1 file changed, 1655 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-14-todo-ux-redesign.md diff --git a/docs/superpowers/plans/2026-04-14-todo-ux-redesign.md b/docs/superpowers/plans/2026-04-14-todo-ux-redesign.md new file mode 100644 index 0000000..6b9ad19 --- /dev/null +++ b/docs/superpowers/plans/2026-04-14-todo-ux-redesign.md @@ -0,0 +1,1655 @@ +# 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 + + + + + + + + + + + + + + + + + +