# UI Fixes 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:** Fix four post-integration issues: raw NDJSON display, missing start feedback, lost live output, and missing config editors + modal theming. **Architecture:** A new `StreamLineFormatter` in the UI layer parses NDJSON for display. `TaskDetailViewModel` switches from `ObservableCollection` to a single `string` property. Optimistic UI feedback via a local `RunNowRequestedEvent`. Existing editor dialogs get config sections and proper theming. **Tech Stack:** .NET 8, Avalonia 12 (Fluent dark theme), CommunityToolkit.Mvvm, System.Text.Json, xUnit --- ## File Structure ### New Files - `src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs` — NDJSON-to-text parser for UI display - `tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj` — test project for UI helpers - `tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs` — formatter unit tests ### Modified Files - `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` — LiveText, formatter, start feedback, log reload - `src/ClaudeDo.Ui/Views/TaskDetailView.axaml` — TextBox replaces ItemsControl, auto-scroll - `src/ClaudeDo.Ui/Views/TaskDetailView.axaml.cs` — auto-scroll handler - `src/ClaudeDo.Ui/Services/WorkerClient.cs` — RunNowRequestedEvent, GetAgentsAsync, AgentInfo DTO - `src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs` — IsStarting property - `src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs` — wire RunNowRequestedEvent - `src/ClaudeDo.Ui/Views/TaskListView.axaml` — starting state visual - `src/ClaudeDo.Ui/ViewModels/ListEditorViewModel.cs` — config fields, agent loading - `src/ClaudeDo.Ui/Views/ListEditorView.axaml` — config section, theming - `src/ClaudeDo.Ui/ViewModels/TaskEditorViewModel.cs` — config override fields - `src/ClaudeDo.Ui/Views/TaskEditorView.axaml` — config section, theming - `src/ClaudeDo.Worker/Runner/TaskRunner.cs` — default model fallback - `ClaudeDo.slnx` — add new test project --- ### Task 1: Create UI test project **Files:** - Create: `tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj` - [ ] **Step 1: Create the test project** ```xml net8.0 enable false ``` - [ ] **Step 2: Add to solution** Run: `dotnet sln ClaudeDo.slnx add tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj` Expected: Project added successfully. - [ ] **Step 3: Verify build** Run: `dotnet build tests/ClaudeDo.Ui.Tests` Expected: Build succeeded. - [ ] **Step 4: Commit** ```bash git add tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj ClaudeDo.slnx git commit -m "chore(tests): add ClaudeDo.Ui.Tests project" ``` --- ### Task 2: StreamLineFormatter — text deltas (TDD) **Files:** - Create: `src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs` - Create: `tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs` - [ ] **Step 1: Write failing tests for text delta extraction** ```csharp // tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs using ClaudeDo.Ui.Helpers; namespace ClaudeDo.Ui.Tests.Helpers; public class StreamLineFormatterTests { private readonly StreamLineFormatter _sut = new(); [Fact] public void FormatLine_TextDelta_ReturnsTextContent() { var line = """{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello world"}}}"""; var result = _sut.FormatLine(line); Assert.Equal("Hello world", result); } [Fact] public void FormatLine_ConsecutiveTextDeltas_ReturnEachDelta() { var line1 = """{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello "}}}"""; var line2 = """{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"world"}}}"""; Assert.Equal("Hello ", _sut.FormatLine(line1)); Assert.Equal("world", _sut.FormatLine(line2)); } [Fact] public void FormatLine_ContentBlockStop_ReturnsNewline() { var delta = """{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hi"}}}"""; var stop = """{"type":"stream_event","event":{"type":"content_block_stop","index":0}}"""; _sut.FormatLine(delta); Assert.Equal("\n", _sut.FormatLine(stop)); } } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "StreamLineFormatterTests" -v quiet` Expected: Build error — `StreamLineFormatter` does not exist. - [ ] **Step 3: Write minimal StreamLineFormatter** ```csharp // src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs using System.Text.Json; namespace ClaudeDo.Ui.Helpers; public sealed class StreamLineFormatter { public string? FormatLine(string ndjsonLine) { if (string.IsNullOrWhiteSpace(ndjsonLine)) return null; try { using var doc = JsonDocument.Parse(ndjsonLine); var root = doc.RootElement; if (!root.TryGetProperty("type", out var typeProp)) return null; var type = typeProp.GetString(); return type switch { "stream_event" => HandleStreamEvent(root), _ => null, }; } catch (JsonException) { return ndjsonLine; // Fallback: show raw line } } private static string? HandleStreamEvent(JsonElement root) { if (!root.TryGetProperty("event", out var evt)) return null; if (!evt.TryGetProperty("type", out var evtTypeProp)) return null; var evtType = evtTypeProp.GetString(); return evtType switch { "content_block_delta" => HandleDelta(evt), "content_block_stop" => "\n", _ => null, }; } private static string? HandleDelta(JsonElement evt) { if (!evt.TryGetProperty("delta", out var delta)) return null; if (!delta.TryGetProperty("type", out var deltaType)) return null; return deltaType.GetString() switch { "text_delta" => delta.TryGetProperty("text", out var text) ? text.GetString() : null, _ => null, // input_json_delta etc. — skip }; } } ``` - [ ] **Step 4: Run tests to verify they pass** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "StreamLineFormatterTests" -v quiet` Expected: 3 passed. - [ ] **Step 5: Commit** ```bash git add src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs git commit -m "feat(ui): add StreamLineFormatter with text delta parsing (TDD)" ``` --- ### Task 3: StreamLineFormatter — tool use, result, system, fallback (TDD) **Files:** - Modify: `src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs` - Modify: `tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs` - [ ] **Step 1: Write failing tests for remaining event types** Append to `StreamLineFormatterTests.cs`: ```csharp [Fact] public void FormatLine_ToolUseStart_ReturnsToolNameLine() { var line = """{"type":"stream_event","event":{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_xxx","name":"Read","input":{}}}}"""; var result = _sut.FormatLine(line); Assert.Equal("\n[Tool: Read]\n", result); } [Fact] public void FormatLine_InputJsonDelta_ReturnsNull() { var line = """{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"file\":"}}}"""; Assert.Null(_sut.FormatLine(line)); } [Fact] public void FormatLine_Result_ReturnsFormattedResult() { var line = """{"type":"result","result":"Task completed successfully.","session_id":"sess_123"}"""; var result = _sut.FormatLine(line); Assert.Equal("\n--- Result ---\nTask completed successfully.\n", result); } [Fact] public void FormatLine_ApiRetry_ReturnsRetryNotice() { var line = """{"type":"system","subtype":"api_retry","message":"Retrying..."}"""; var result = _sut.FormatLine(line); Assert.Equal("\n[Retrying API call...]\n", result); } [Fact] public void FormatLine_SystemNonRetry_ReturnsNull() { var line = """{"type":"system","subtype":"init"}"""; Assert.Null(_sut.FormatLine(line)); } [Fact] public void FormatLine_AssistantType_ReturnsNull() { var line = """{"type":"assistant","message":{"role":"assistant","content":[]}}"""; Assert.Null(_sut.FormatLine(line)); } [Fact] public void FormatLine_MalformedJson_ReturnsRawLine() { var line = "this is not json"; Assert.Equal("this is not json", _sut.FormatLine(line)); } [Fact] public void FormatLine_MessageStartAndDelta_ReturnsNull() { var start = """{"type":"stream_event","event":{"type":"message_start","message":{"id":"msg_xxx"}}}"""; var delta = """{"type":"stream_event","event":{"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":50}}}"""; Assert.Null(_sut.FormatLine(start)); Assert.Null(_sut.FormatLine(delta)); } ``` - [ ] **Step 2: Run tests to verify new tests fail** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "StreamLineFormatterTests" -v quiet` Expected: Several failures — `FormatLine_ToolUseStart`, `FormatLine_Result`, `FormatLine_ApiRetry` return null instead of expected strings. - [ ] **Step 3: Extend StreamLineFormatter to handle all event types** Update the `FormatLine` method's switch expression in `StreamLineFormatter.cs`: ```csharp return type switch { "stream_event" => HandleStreamEvent(root), "result" => HandleResult(root), "system" => HandleSystem(root), "assistant" => null, _ => null, }; ``` Add `HandleStreamEvent` case for `content_block_start`: ```csharp private static string? HandleStreamEvent(JsonElement root) { if (!root.TryGetProperty("event", out var evt)) return null; if (!evt.TryGetProperty("type", out var evtTypeProp)) return null; var evtType = evtTypeProp.GetString(); return evtType switch { "content_block_start" => HandleBlockStart(evt), "content_block_delta" => HandleDelta(evt), "content_block_stop" => "\n", _ => null, }; } ``` Add new methods: ```csharp private static string? HandleBlockStart(JsonElement evt) { if (!evt.TryGetProperty("content_block", out var block)) return null; if (!block.TryGetProperty("type", out var blockType)) return null; if (blockType.GetString() == "tool_use" && block.TryGetProperty("name", out var name)) { return $"\n[Tool: {name.GetString()}]\n"; } return null; } private static string? HandleResult(JsonElement root) { if (root.TryGetProperty("result", out var resultProp)) { var text = resultProp.GetString(); if (text is not null) return $"\n--- Result ---\n{text}\n"; } return null; } private static string? HandleSystem(JsonElement root) { if (root.TryGetProperty("subtype", out var subtype) && subtype.GetString() == "api_retry") { return "\n[Retrying API call...]\n"; } return null; } ``` - [ ] **Step 4: Run tests to verify all pass** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "StreamLineFormatterTests" -v quiet` Expected: 11 passed. - [ ] **Step 5: Commit** ```bash git add src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs git commit -m "feat(ui): complete StreamLineFormatter with tool use, result, system events" ``` --- ### Task 4: StreamLineFormatter — FormatFile method (TDD) **Files:** - Modify: `src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs` - Modify: `tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs` - [ ] **Step 1: Write failing test for FormatFile** Append to `StreamLineFormatterTests.cs`: ```csharp [Fact] public void FormatFile_ParsesAllLinesAndReturnsFormattedText() { var dir = Path.Combine(Path.GetTempPath(), "claudedo_test_" + Guid.NewGuid().ToString("N")[..8]); Directory.CreateDirectory(dir); var filePath = Path.Combine(dir, "test.ndjson"); try { var lines = new[] { """{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}}""", """{"type":"stream_event","event":{"type":"content_block_stop","index":0}}""", """{"type":"stream_event","event":{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"t1","name":"Edit","input":{}}}}""", """{"type":"stream_event","event":{"type":"content_block_stop","index":1}}""", """{"type":"result","result":"Done.","session_id":"s1"}""", }; File.WriteAllLines(filePath, lines); var result = _sut.FormatFile(filePath); Assert.Contains("Hello", result); Assert.Contains("[Tool: Edit]", result); Assert.Contains("--- Result ---", result); Assert.Contains("Done.", result); } finally { Directory.Delete(dir, true); } } [Fact] public void FormatFile_TrimsLargeContent() { var dir = Path.Combine(Path.GetTempPath(), "claudedo_test_" + Guid.NewGuid().ToString("N")[..8]); Directory.CreateDirectory(dir); var filePath = Path.Combine(dir, "large.ndjson"); try { // Generate enough text deltas to exceed 50k chars var lines = new List(); for (int i = 0; i < 600; i++) { var chunk = new string('x', 100); lines.Add($$"""{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"{{chunk}}\n"}}}"""); } File.WriteAllLines(filePath, lines); var result = _sut.FormatFile(filePath); Assert.True(result.Length <= 50_000 + 200); // some tolerance for trimming at newline boundary } finally { Directory.Delete(dir, true); } } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "FormatFile" -v quiet` Expected: Build error — `FormatFile` method does not exist. - [ ] **Step 3: Implement FormatFile and trimming** Add to `StreamLineFormatter.cs`: ```csharp private const int MaxLength = 50_000; public string FormatFile(string filePath) { var sb = new System.Text.StringBuilder(); foreach (var line in File.ReadLines(filePath)) { var formatted = FormatLine(line); if (formatted is not null) sb.Append(formatted); } return Trim(sb.ToString()); } public static string Trim(string text) { if (text.Length <= MaxLength) return text; var trimStart = text.Length - MaxLength; var newlineAfter = text.IndexOf('\n', trimStart); if (newlineAfter >= 0 && newlineAfter < trimStart + 200) trimStart = newlineAfter + 1; return text[trimStart..]; } ``` Add `using System.Text;` to the top of the file. - [ ] **Step 4: Run tests to verify all pass** Run: `dotnet test tests/ClaudeDo.Ui.Tests --filter "StreamLineFormatterTests" -v quiet` Expected: 13 passed. - [ ] **Step 5: Commit** ```bash git add src/ClaudeDo.Ui/Helpers/StreamLineFormatter.cs tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs git commit -m "feat(ui): add FormatFile and text trimming to StreamLineFormatter" ``` --- ### Task 5: TaskDetailViewModel — replace LiveLines with LiveText **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` - [ ] **Step 1: Replace LiveLines with LiveText and wire formatter** In `TaskDetailViewModel.cs`, make these changes: 1. Add using at top: ```csharp using ClaudeDo.Ui.Helpers; ``` 2. Replace the LiveLines property (line 40) and MaxLiveLines constant (line 47): Remove: ```csharp public ObservableCollection LiveLines { get; } = new(); ``` and: ```csharp private const int MaxLiveLines = 500; ``` Add: ```csharp [ObservableProperty] private string _liveText = ""; private StreamLineFormatter _formatter = new(); ``` 3. Update `LoadAsync` (line 69) — replace `LiveLines.Clear()` with: ```csharp LiveText = ""; _formatter = new StreamLineFormatter(); ``` 4. Update `Clear` method — replace `LiveLines.Clear()` with: ```csharp LiveText = ""; _formatter = new StreamLineFormatter(); ``` 5. Update `OnTaskMessage` (lines 259-265): Replace entire method: ```csharp private void OnTaskMessage(string taskId, string line) { if (taskId != _taskId) return; var formatted = _formatter.FormatLine(line); if (formatted is not null) { LiveText += formatted; if (LiveText.Length > 50_000) LiveText = StreamLineFormatter.Trim(LiveText); } } ``` - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs git commit -m "refactor(ui): replace LiveLines with LiveText + StreamLineFormatter" ``` --- ### Task 6: TaskDetailView — TextBox replaces ItemsControl + auto-scroll **Files:** - Modify: `src/ClaudeDo.Ui/Views/TaskDetailView.axaml` - Modify: `src/ClaudeDo.Ui/Views/TaskDetailView.axaml.cs` - [ ] **Step 1: Replace ItemsControl with TextBox in TaskDetailView.axaml** Replace lines 107-122 (the "Live Output" section heading through the Border/ItemsControl): Old: ```xml ``` New: ```xml ``` - [ ] **Step 2: Add auto-scroll in code-behind** In `TaskDetailView.axaml.cs`, add an `OnDataContextChanged` override and property-change handler: Add using: ```csharp using System.ComponentModel; ``` Add after the `FocusTitle` method: ```csharp protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); if (DataContext is TaskDetailViewModel vm) { vm.PropertyChanged += OnViewModelPropertyChanged; } } private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(TaskDetailViewModel.LiveText)) { var scroll = this.FindControl("LiveOutputScroll"); scroll?.ScrollToEnd(); } } ``` - [ ] **Step 3: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/Views/TaskDetailView.axaml src/ClaudeDo.Ui/Views/TaskDetailView.axaml.cs git commit -m "feat(ui): replace ItemsControl with TextBox for formatted live output" ``` --- ### Task 7: Log reload from disk **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` - [ ] **Step 1: Add log reload to LoadAsync** In `TaskDetailViewModel.cs`, in `LoadAsync`, after `LogPath = task.LogPath;` (around line 81), add: ```csharp // Load historical log for completed tasks if (task.LogPath is not null && task.Status is Data.Models.TaskStatus.Done or Data.Models.TaskStatus.Failed && File.Exists(task.LogPath)) { _formatter = new StreamLineFormatter(); LiveText = _formatter.FormatFile(task.LogPath); } ``` Add `using System.IO;` at the top if not already present. - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs git commit -m "feat(ui): reload formatted log from disk for completed tasks" ``` --- ### Task 8: WorkerClient — RunNowRequestedEvent **Files:** - Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs` - [ ] **Step 1: Add RunNowRequestedEvent and fire it in RunNowAsync** In `WorkerClient.cs`: Add event declaration after the existing events (after line 44): ```csharp public event Action? RunNowRequestedEvent; ``` Update `RunNowAsync` method (lines 163-166): ```csharp public async Task RunNowAsync(string taskId) { RunNowRequestedEvent?.Invoke(taskId); await _hub.InvokeAsync("RunNow", taskId); } ``` - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/Services/WorkerClient.cs git commit -m "feat(ui): add RunNowRequestedEvent for optimistic UI feedback" ``` --- ### Task 9: TaskItemViewModel — IsStarting state **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs` - [ ] **Step 1: Add IsStarting property and update CanRunNow** In `TaskItemViewModel.cs`: Add property after line 16: ```csharp [ObservableProperty] private bool _isStarting; ``` Update `CanRunNow` (line 83-84): ```csharp private bool CanRunNow() => _canRunNow() && Status != TaskStatus.Running && !IsStarting; ``` Add method to set starting state (after `Refresh` method): ```csharp public void SetStarting() { IsStarting = true; StatusText = "starting..."; RunNowCommand.NotifyCanExecuteChanged(); } public void ClearStarting() { IsStarting = false; RunNowCommand.NotifyCanExecuteChanged(); } ``` Update `Refresh` method — add after `OnPropertyChanged(nameof(IsRunning))` (line 68): ```csharp IsStarting = false; ``` - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskItemViewModel.cs git commit -m "feat(ui): add IsStarting state to TaskItemViewModel" ``` --- ### Task 10: TaskListViewModel — wire RunNowRequested to TaskItemViewModels **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs` - [ ] **Step 1: Subscribe to RunNowRequestedEvent and TaskStartedEvent** In `TaskListViewModel.cs` constructor, after the existing `worker.PropertyChanged` subscription (after line 57): ```csharp worker.RunNowRequestedEvent += taskId => { var item = Tasks.FirstOrDefault(t => t.Id == taskId); item?.SetStarting(); }; worker.TaskStartedEvent += (_, taskId, _) => { var item = Tasks.FirstOrDefault(t => t.Id == taskId); item?.ClearStarting(); }; ``` - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskListViewModel.cs git commit -m "feat(ui): wire RunNowRequested to TaskItemViewModel starting state" ``` --- ### Task 11: TaskDetailViewModel — start feedback **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs` - [ ] **Step 1: Subscribe to RunNowRequestedEvent and TaskStartedEvent** In `TaskDetailViewModel.cs` constructor, after `worker.TaskUpdatedEvent += OnTaskUpdated;` (line 63): ```csharp worker.RunNowRequestedEvent += OnRunNowRequested; worker.TaskStartedEvent += OnTaskStarted; ``` Add the handler methods before `OnTaskMessage`: ```csharp private void OnRunNowRequested(string taskId) { if (taskId != _taskId) return; StatusText = "starting..."; LiveText = ""; _formatter = new StreamLineFormatter(); } private void OnTaskStarted(string slot, string taskId, DateTime startedAt) { if (taskId != _taskId) return; StatusText = "running"; } ``` - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/TaskDetailViewModel.cs git commit -m "feat(ui): add optimistic start feedback to TaskDetailViewModel" ``` --- ### Task 12: TaskListView — starting state visual **Files:** - Modify: `src/ClaudeDo.Ui/Views/TaskListView.axaml` - [ ] **Step 1: Add starting indicator next to running indicator** In `TaskListView.axaml`, find the running indicator Ellipse (the orange dot visible when `IsRunning`). After it, add a similar indicator for the starting state. The exact location depends on the layout, but it should be adjacent to the existing status indicators. Find the `IsRunning` Ellipse and add a sibling for `IsStarting`: ```xml ``` Use a gold/yellow color (`#FFD700`) to distinguish "starting" from "running" (orange). - [ ] **Step 2: Verify build** Run: `dotnet build src/ClaudeDo.Ui` Expected: Build succeeded. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/Views/TaskListView.axaml git commit -m "feat(ui): add starting state indicator in task list" ``` --- ### Task 13: Modal theming — ListEditorView **Files:** - Modify: `src/ClaudeDo.Ui/Views/ListEditorView.axaml` - [ ] **Step 1: Apply theme resources to ListEditorView** In `ListEditorView.axaml`, update the `` element — add `Background`: ```xml ``` Add `Foreground="{StaticResource TextSecondaryBrush}"` to each label TextBlock ("Name", "Working Directory", "Default Commit Type"). Style the Save button with accent: ```xml