# UI Rewrite — Islands Layout 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:** Replace the current `ClaudeDo.Ui` with a high-fidelity three-island Avalonia interface per the design handoff at `docs/UI Rewrite/design_handoff_claudedo/`. **Architecture:** Data-layer additions (`IsStarred`, `IsMyDay`, `Notes` columns; default-list seeding); a new `Design/` resource folder (`Tokens.axaml`, `IslandStyles.axaml`); embedded Inter Tight + JetBrains Mono fonts; a chromeless `MainWindow` containing a three-column `Grid` of island `Border`s — Lists / Tasks / Details — backed by new `IslandsShellViewModel`, `ListsIslandViewModel`, `TasksIslandViewModel`, `DetailsIslandViewModel`. Existing `WorkerClient`, `Repositories` and SignalR plumbing are preserved. **Tech Stack:** .NET 8.0, Avalonia 12.0.0 (Fluent theme), CommunityToolkit.Mvvm, Entity Framework Core (SQLite), xUnit. **Reference files:** - `docs/UI Rewrite/design_handoff_claudedo/README.md` — full handoff - `docs/UI Rewrite/design_handoff_claudedo/Tokens.axaml` — design tokens - `docs/UI Rewrite/design_handoff_claudedo/IslandStyles.axaml` — control styles - `docs/UI Rewrite/design_handoff_claudedo/styles.css` — measurement source of truth - `docs/UI Rewrite/design_handoff_claudedo/ClaudeDo-standalone.html` — interactive reference **Confirmed decisions (from brainstorm):** - Full rewrite of `ClaudeDo.Ui` (Views + ViewModels). `WorkerClient`, repositories, SignalR untouched. - Schema: add `IsStarred`, `IsMyDay`, `Notes` to `TaskEntity`. Seed `My Day`, `Important`, `Planned` Lists on install. - `Running` and `Review` lists are **virtual filters** (status-based), not seeded list rows. - Window: chromeless (`SystemDecorations="None"` + `ExtendClientAreaToDecorationsHint="True"`). - Fonts: embed `Inter Tight` and `JetBrains Mono`. - Resource folder: `src/ClaudeDo.Ui/Design/`. --- ## File Structure **Data layer (new / modified):** - Modify: `src/ClaudeDo.Data/Models/TaskEntity.cs` — add 3 properties - Modify: `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs` — column mappings - Create: `src/ClaudeDo.Data/Migrations/_AddTaskFlagsAndNotes.cs` — EF migration - Modify: `src/ClaudeDo.Data/Migrations/ClaudeDoDbContextModelSnapshot.cs` — generated update - Modify: `src/ClaudeDo.Installer/...` (seed call site — discover during Phase 1) **UI design assets (new):** - Create: `src/ClaudeDo.Ui/Design/Tokens.axaml` - Create: `src/ClaudeDo.Ui/Design/IslandStyles.axaml` - Create: `src/ClaudeDo.Ui/Assets/Fonts/InterTight-*.ttf` (Regular, Medium, SemiBold) - Create: `src/ClaudeDo.Ui/Assets/Fonts/JetBrainsMono-Regular.ttf` - Modify: `src/ClaudeDo.Ui/App.axaml` — merge Tokens + Styles - Modify: `src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` — embed font assets **Shell + Islands (new — replaces existing Views/ViewModels):** - Create: `src/ClaudeDo.Ui/Views/MainWindow.axaml` (replace) — chromeless, 3-column Grid - Create: `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs` (replace `MainWindowViewModel`) - Create: `src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs` - Create: `src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs` - Create: `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml` (UserControl) - Create: `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs` - Create: `src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` - Create: `src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml` - Create: `src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/LogLineViewModel.cs` **Modals (new):** - Create: `src/ClaudeDo.Ui/Views/Modals/DiffModalView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Modals/DiffModalViewModel.cs` - Create: `src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml` - Create: `src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs` **To delete (after rewrite verified working):** - `src/ClaudeDo.Ui/Views/StatusBarView.axaml(.cs)`, `TaskListView.axaml(.cs)`, `TaskDetailView.axaml(.cs)`, `TaskEditorView.axaml(.cs)`, `ListEditorView.axaml(.cs)` - `src/ClaudeDo.Ui/ViewModels/StatusBarViewModel.cs`, `TaskListViewModel.cs`, `TaskDetailViewModel.cs`, `TaskItemViewModel.cs`, `MainWindowViewModel.cs`, `TaskEditorViewModel.cs`, `ListEditorViewModel.cs`, `ListItemViewModel.cs`, `SubtaskItemViewModel.cs` **Tests (new):** - Create: `tests/ClaudeDo.Worker.Tests/UiSchema/TaskEntityFlagsTests.cs` - Create: `tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs` --- ## Phase 1 — Schema and seed ### Task 1: Add `IsStarred`, `IsMyDay`, `Notes` to `TaskEntity` **Files:** - Modify: `src/ClaudeDo.Data/Models/TaskEntity.cs` - Modify: `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs` - Test: `tests/ClaudeDo.Worker.Tests/UiSchema/TaskEntityFlagsTests.cs` (new) - [ ] **Step 1: Write failing test** — `tests/ClaudeDo.Worker.Tests/UiSchema/TaskEntityFlagsTests.cs` ```csharp using ClaudeDo.Data; using ClaudeDo.Data.Models; using Microsoft.EntityFrameworkCore; using Xunit; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Tests.UiSchema; public class TaskEntityFlagsTests : IDisposable { private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo-flags-{Guid.NewGuid():N}.db"); private ClaudeDoDbContext NewContext() { var opts = new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}") .Options; var ctx = new ClaudeDoDbContext(opts); ctx.Database.EnsureCreated(); return ctx; } [Fact] public async Task Persists_IsStarred_IsMyDay_And_Notes() { await using var ctx = NewContext(); var list = new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow }; ctx.Lists.Add(list); ctx.Tasks.Add(new TaskEntity { Id = "t1", ListId = "l1", Title = "T", CreatedAt = DateTime.UtcNow, IsStarred = true, IsMyDay = true, Notes = "hello" }); await ctx.SaveChangesAsync(); await using var ctx2 = NewContext(); var loaded = await ctx2.Tasks.SingleAsync(); Assert.True(loaded.IsStarred); Assert.True(loaded.IsMyDay); Assert.Equal("hello", loaded.Notes); } public void Dispose() { if (File.Exists(_dbPath)) File.Delete(_dbPath); } } ``` - [ ] **Step 2: Run test — verify fail** Run: `dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~TaskEntityFlagsTests` Expected: build error — `TaskEntity` has no `IsStarred`/`IsMyDay`/`Notes`. - [ ] **Step 3: Add properties to `TaskEntity`** — append before navigation block in `src/ClaudeDo.Data/Models/TaskEntity.cs`: ```csharp public bool IsStarred { get; set; } public bool IsMyDay { get; set; } public string? Notes { get; set; } ``` - [ ] **Step 4: Update `TaskEntityConfiguration`** — add column mappings inside `Configure(EntityTypeBuilder b)`: ```csharp b.Property(t => t.IsStarred).HasColumnName("is_starred").HasDefaultValue(false); b.Property(t => t.IsMyDay).HasColumnName("is_my_day").HasDefaultValue(false); b.Property(t => t.Notes).HasColumnName("notes"); ``` (Match existing snake_case style — verify by reading the file first.) - [ ] **Step 5: Run test — verify pass** Run: `dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~TaskEntityFlagsTests` Expected: PASS. - [ ] **Step 6: Commit** ```bash git add src/ClaudeDo.Data/Models/TaskEntity.cs src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs tests/ClaudeDo.Worker.Tests/UiSchema/TaskEntityFlagsTests.cs git commit -m "feat(data): add IsStarred, IsMyDay, Notes to TaskEntity" ``` --- ### Task 2: Generate EF Core migration for new columns **Files:** - Create: `src/ClaudeDo.Data/Migrations/_AddTaskFlagsAndNotes.cs` - Modify: `src/ClaudeDo.Data/Migrations/ClaudeDoDbContextModelSnapshot.cs` (generated) - [ ] **Step 1: Generate migration** Run from repo root: ```bash dotnet ef migrations add AddTaskFlagsAndNotes --project src/ClaudeDo.Data/ClaudeDo.Data.csproj --startup-project src/ClaudeDo.Data/ClaudeDo.Data.csproj ``` If `dotnet-ef` is missing: `dotnet tool install --global dotnet-ef --version 8.*`. - [ ] **Step 2: Inspect the generated `Up`** — confirm three `AddColumn<>` calls for `is_starred`, `is_my_day`, `notes`. If column names mismatch, edit them by hand. - [ ] **Step 3: Apply migration in test (already covered by `EnsureCreated` in tests)** Run: `dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~TaskEntityFlagsTests` Expected: still PASS. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Data/Migrations/ git commit -m "feat(data): migration for IsStarred/IsMyDay/Notes columns" ``` --- ### Task 3: Seed default Lists ("My Day", "Important", "Planned") on install **Files:** - Discover seed call site — search `src/ClaudeDo.Installer/` and `src/ClaudeDo.App/` for `EnsureCreated`, `Migrate`, or existing tag seeding ("agent" tag). - Modify: the discovered seeder OR create `src/ClaudeDo.Data/Seeding/DefaultListsSeeder.cs` if no central seeder exists. - Test: `tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs` (new) - [ ] **Step 1: Locate seed site** Run: ```bash grep -rn "agent.*manual\|GetOrCreateAsync\|EnsureCreated\|Migrate(" src/ClaudeDo.Installer src/ClaudeDo.App ``` If a central seeder exists, add list-seeding there. Otherwise create `DefaultListsSeeder`. - [ ] **Step 2: Write failing test** — `tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs` ```csharp using ClaudeDo.Data; using ClaudeDo.Data.Seeding; // adjust namespace if seeded elsewhere using Microsoft.EntityFrameworkCore; using Xunit; namespace ClaudeDo.Worker.Tests.UiSchema; public class DefaultListSeedTests : IDisposable { private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo-seed-{Guid.NewGuid():N}.db"); private ClaudeDoDbContext NewContext() { var opts = new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}").Options; var ctx = new ClaudeDoDbContext(opts); ctx.Database.EnsureCreated(); return ctx; } [Fact] public async Task Seeds_MyDay_Important_Planned_Lists_Idempotently() { await using (var ctx = NewContext()) { await DefaultListsSeeder.SeedAsync(ctx); await DefaultListsSeeder.SeedAsync(ctx); // idempotent } await using var verify = NewContext(); var names = verify.Lists.Select(l => l.Name).OrderBy(n => n).ToList(); Assert.Equal(new[] { "Important", "My Day", "Planned" }, names); } public void Dispose() { if (File.Exists(_dbPath)) File.Delete(_dbPath); } } ``` - [ ] **Step 3: Run test — verify fail** Run: `dotnet test ... --filter FullyQualifiedName~DefaultListSeedTests` Expected: build error. - [ ] **Step 4: Implement seeder** — create `src/ClaudeDo.Data/Seeding/DefaultListsSeeder.cs`: ```csharp using ClaudeDo.Data.Models; using Microsoft.EntityFrameworkCore; namespace ClaudeDo.Data.Seeding; public static class DefaultListsSeeder { private static readonly string[] Defaults = { "My Day", "Important", "Planned" }; public static async Task SeedAsync(ClaudeDoDbContext ctx, CancellationToken ct = default) { var existing = await ctx.Lists.Select(l => l.Name).ToListAsync(ct); var now = DateTime.UtcNow; foreach (var name in Defaults.Where(n => !existing.Contains(n))) { ctx.Lists.Add(new ListEntity { Id = Guid.NewGuid().ToString("N"), Name = name, CreatedAt = now, }); } await ctx.SaveChangesAsync(ct); } } ``` - [ ] **Step 5: Wire seeder into install path** — call `DefaultListsSeeder.SeedAsync(ctx)` from the same code path that seeds the "agent"/"manual" tags. If none exists, call it once on app startup after `Database.Migrate()` in `ClaudeDo.App`. - [ ] **Step 6: Run test — verify pass** Run: `dotnet test ... --filter FullyQualifiedName~DefaultListSeedTests` Expected: PASS. - [ ] **Step 7: Commit** ```bash git add src/ClaudeDo.Data/Seeding/ tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs git commit -m "feat(data): seed default Lists (My Day, Important, Planned)" ``` --- ## Phase 2 — Design tokens, fonts, and shell wiring ### Task 4: Embed Inter Tight + JetBrains Mono fonts **Files:** - Create: `src/ClaudeDo.Ui/Assets/Fonts/InterTight-Regular.ttf`, `InterTight-Medium.ttf`, `InterTight-SemiBold.ttf` - Create: `src/ClaudeDo.Ui/Assets/Fonts/JetBrainsMono-Regular.ttf` - Modify: `src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` - [ ] **Step 1: Download font files** — fetch from Google Fonts (`https://fonts.google.com/specimen/Inter+Tight`, `https://fonts.google.com/specimen/JetBrains+Mono`). Place TTFs in `src/ClaudeDo.Ui/Assets/Fonts/`. SIL OFL license — include `OFL.txt` next to each family. - [ ] **Step 2: Mark as Avalonia resources** — in `src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` add (or extend the existing `` for `AvaloniaResource`): ```xml ``` - [ ] **Step 3: Build to confirm assets resolve** Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` Expected: build succeeds. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/Assets/Fonts/ src/ClaudeDo.Ui/ClaudeDo.Ui.csproj git commit -m "feat(ui): embed Inter Tight and JetBrains Mono fonts" ``` --- ### Task 5: Port `Tokens.axaml` into the Ui project **Files:** - Create: `src/ClaudeDo.Ui/Design/Tokens.axaml` - [ ] **Step 1: Copy and adapt** — open `docs/UI Rewrite/design_handoff_claudedo/Tokens.axaml` and copy its `` content into `src/ClaudeDo.Ui/Design/Tokens.axaml`. - [ ] **Step 2: Replace placeholder font URIs** — anywhere the file references `avares://YourApp/...`, replace with `avares://ClaudeDo.Ui/Assets/Fonts/#Inter Tight` and `avares://ClaudeDo.Ui/Assets/Fonts/#JetBrains Mono`. - [ ] **Step 3: Verify required keys present** — open the file and confirm these brushes/values exist (per README §"Design tokens"): - `VoidBrush #0A0E0C`, `DeepBrush #0D1311`, `SurfaceBrush #161D1A`, `Surface2Brush #1C2422`, `Surface3Brush #222B28`, `LineBrush #2A3330` - `TextBrush #E4EBE4`, `TextDimBrush #9AA8A0`, `TextMuteBrush #6B7973`, `TextFaintBrush #4A5550` - `MossBrush #7C9166`, `SageBrush #8B9D7A`, `PeatBrush #D4A574`, `BloodBrush #C87060` - `IslandRadius 14`, `ModalRadius 12`, `ChipRadius 10`, `RowRadius 8`, `ButtonRadius 6` - `MotionFast 0:0:0.12`, `MotionBase 0:0:0.18`, `MotionSlow 0:0:0.30` - `IslandShadow`, `ModalShadow` BoxShadow values - `SansFamily`, `MonoFamily` `FontFamily` values If any are missing, add them inline using the values from the README §"Design tokens (reference)". - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/Design/Tokens.axaml git commit -m "feat(ui): add design Tokens resource dictionary" ``` --- ### Task 6: Port `IslandStyles.axaml` into the Ui project **Files:** - Create: `src/ClaudeDo.Ui/Design/IslandStyles.axaml` - [ ] **Step 1: Copy** the full `` content from `docs/UI Rewrite/design_handoff_claudedo/IslandStyles.axaml` into `src/ClaudeDo.Ui/Design/IslandStyles.axaml`. - [ ] **Step 2: Confirm classed selectors present** — file must define styles for at least: - `Border.island`, `Border.island-header` - `Border.list-item`, `Border.list-item.active` - `TextBox.search` - `Border.task-row`, `Border.task-row.selected` - `Ellipse.task-check`, `Ellipse.task-check.done` - `Border.chip` and status variants `chip.running`, `chip.review`, `chip.error`, `chip.queued`, `chip.idle` - `Border.agent-strip` and status variants - `Border.terminal`, `TextBlock.log-sys`, `log-tool`, `log-claude`, `log-stdout`, `log-stderr`, `log-done`, `log-msg` - `Button.icon-btn` If any classed selector is missing, add a minimal one referencing the relevant token brush. - [ ] **Step 3: Commit** ```bash git add src/ClaudeDo.Ui/Design/IslandStyles.axaml git commit -m "feat(ui): add island control styles" ``` --- ### Task 7: Wire tokens and styles into `App.axaml` **Files:** - Modify: `src/ClaudeDo.Ui/App.axaml` - [ ] **Step 1: Read current App.axaml** to preserve any DI/region wiring: Run: open `src/ClaudeDo.Ui/App.axaml` and note its existing structure. - [ ] **Step 2: Edit** — ensure the `` root contains: ```xml ``` (Preserve any existing `RequestedThemeVariant`, converter resources, etc. — only add the includes if not already present.) - [ ] **Step 3: Build** Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` Expected: success. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/App.axaml git commit -m "feat(ui): merge Tokens and IslandStyles into App" ``` --- ## Phase 3 — Chromeless shell + three-island layout ### Task 8: Replace `MainWindowViewModel` with `IslandsShellViewModel` **Files:** - Create: `src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs` - [ ] **Step 1: Write the VM** with three child VMs and a width-driven boolean for collapsing the Details column: ```csharp using CommunityToolkit.Mvvm.ComponentModel; using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.ViewModels; public sealed partial class IslandsShellViewModel : ViewModelBase { public ListsIslandViewModel Lists { get; } public TasksIslandViewModel Tasks { get; } public DetailsIslandViewModel Details { get; } [ObservableProperty] private double _windowWidth = 1280; public bool ShowDetails => WindowWidth >= 1100; public bool ShowLists => WindowWidth >= 780; partial void OnWindowWidthChanged(double value) { OnPropertyChanged(nameof(ShowDetails)); OnPropertyChanged(nameof(ShowLists)); } public IslandsShellViewModel( ListsIslandViewModel lists, TasksIslandViewModel tasks, DetailsIslandViewModel details) { Lists = lists; Tasks = tasks; Details = details; Lists.SelectionChanged += (_, _) => Tasks.LoadForList(Lists.SelectedList); Tasks.SelectionChanged += (_, _) => Details.Bind(Tasks.SelectedTask); } } ``` - [ ] **Step 2: Register in DI** — modify `src/ClaudeDo.App/Program.cs` (or wherever `MainWindowViewModel` is registered) to register the new VMs: ```csharp services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); ``` (Adjust to scoped/transient if existing patterns demand.) - [ ] **Step 3: Build** — `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` — fails until child VM stubs exist; that's fine, next task creates them. - [ ] **Step 4: Defer commit** until child VMs compile. --- ### Task 9: Stub child island VMs (compile-only skeletons) **Files (all new):** - `src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs` - `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs` - `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` - [ ] **Step 1: Write minimal stubs** so `IslandsShellViewModel` compiles: ```csharp // ListsIslandViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class ListsIslandViewModel : ViewModelBase { public event EventHandler? SelectionChanged; [ObservableProperty] private ListNavItemViewModel? _selectedList; partial void OnSelectedListChanged(ListNavItemViewModel? value) => SelectionChanged?.Invoke(this, EventArgs.Empty); } // TasksIslandViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class TasksIslandViewModel : ViewModelBase { public event EventHandler? SelectionChanged; [ObservableProperty] private TaskRowViewModel? _selectedTask; public void LoadForList(ListNavItemViewModel? list) { /* Phase 5 */ } partial void OnSelectedTaskChanged(TaskRowViewModel? value) => SelectionChanged?.Invoke(this, EventArgs.Empty); } // DetailsIslandViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class DetailsIslandViewModel : ViewModelBase { [ObservableProperty] private TaskRowViewModel? _task; public void Bind(TaskRowViewModel? task) => Task = task; } ``` - [ ] **Step 2: Add minimal `ListNavItemViewModel` and `TaskRowViewModel`** so references resolve: ```csharp // ListNavItemViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class ListNavItemViewModel : ViewModelBase { public required string Id { get; init; } public required string Name { get; init; } [ObservableProperty] private int _count; [ObservableProperty] private bool _isActive; public string? IconKey { get; init; } } // TaskRowViewModel.cs (placeholder — fleshed out in Phase 5) using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class TaskRowViewModel : ViewModelBase { public required string Id { get; init; } [ObservableProperty] private string _title = ""; [ObservableProperty] private bool _done; } ``` - [ ] **Step 3: Build** — `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` should succeed. - [ ] **Step 4: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs src/ClaudeDo.Ui/ViewModels/Islands/ src/ClaudeDo.App git commit -m "feat(ui): scaffold islands shell and child VMs" ``` --- ### Task 10: Replace `MainWindow.axaml` with chromeless three-column shell **Files:** - Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml` - Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml.cs` - [ ] **Step 1: Rewrite `MainWindow.axaml`**: ```xml ``` - [ ] **Step 2: Add converters** — `NotNullToBool`, `StrikeIfTrue`, `EqStatusRunning` (and one per status). Place under `src/ClaudeDo.Ui/Converters/`. Register them as resources in `App.axaml`. (Each converter is ~10 lines; copy the pattern from `StatusColorConverter.cs`.) - [ ] **Step 3: Add `[RelayCommand]` ToggleDone / ToggleStar** to `TasksIslandViewModel`: ```csharp [RelayCommand] private async Task ToggleDoneAsync(TaskRowViewModel row) { row.Done = !row.Done; var entity = await _tasks.GetByIdAsync(row.Id); if (entity != null) { entity.Status = row.Done ? TaskStatus.Done : TaskStatus.Manual; await _tasks.UpdateAsync(entity); } UpdateSubtitle(); } [RelayCommand] private async Task ToggleStarAsync(TaskRowViewModel row) { row.IsStarred = !row.IsStarred; var entity = await _tasks.GetByIdAsync(row.Id); if (entity != null) { entity.IsStarred = row.IsStarred; await _tasks.UpdateAsync(entity); } } ``` - [ ] **Step 4: `TasksIslandView.axaml`** — header, add-task box, scrollable list: ```xml ``` - [ ] **Step 5: Selection handler** — add `Tapped` on `TaskRowView` setting `((TasksIslandViewModel)DataContext.parent).SelectedTask = vm`. Or use a `[RelayCommand] private void Select(TaskRowViewModel row) => SelectedTask = row;` and bind via `InputElement.Tapped` behavior. - [ ] **Step 6: Run app** — verify rows render with chips, add-task on Enter prepends a row, selection updates Details. - [ ] **Step 7: Commit** ```bash git add src/ClaudeDo.Ui/Views/Islands/ src/ClaudeDo.Ui/Converters/ src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs src/ClaudeDo.Ui/App.axaml git commit -m "feat(ui): tasks island with rows, chips, add-task, selection" ``` --- ## Phase 6 — Details Island ### Task 16: `DetailsIslandViewModel` — bind selected task + agent state **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` - Create: `src/ClaudeDo.Ui/ViewModels/Islands/LogLineViewModel.cs` - [ ] **Step 1: `LogLineViewModel`**: ```csharp namespace ClaudeDo.Ui.ViewModels.Islands; public enum LogKind { Sys, Tool, Claude, Stdout, Stderr, Done, Msg } public sealed class LogLineViewModel { public required LogKind Kind { get; init; } public required string Text { get; init; } public string ClassName => Kind switch { LogKind.Sys => "log-sys", LogKind.Tool => "log-tool", LogKind.Claude => "log-claude", LogKind.Stdout => "log-stdout", LogKind.Stderr => "log-stderr", LogKind.Done => "log-done", LogKind.Msg => "log-msg", }; } ``` - [ ] **Step 2: `DetailsIslandViewModel`** — bind everything the AgentStrip + Terminal + Subtasks + Notes need: ```csharp using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using ClaudeDo.Data.Repositories; using ClaudeDo.Ui.Services; namespace ClaudeDo.Ui.ViewModels.Islands; public sealed partial class DetailsIslandViewModel : ViewModelBase { private readonly TaskRepository _tasks; private readonly SubtaskRepository _subtasks; private readonly WorkerClient _worker; [ObservableProperty] private TaskRowViewModel? _task; [ObservableProperty] private string _editableTitle = ""; [ObservableProperty] private string _notes = ""; [ObservableProperty] private string _promptInput = ""; [ObservableProperty] private string _agentStatusLabel = "Idle"; [ObservableProperty] private string? _model; [ObservableProperty] private string? _worktreePath; [ObservableProperty] private string? _branchLine; [ObservableProperty] private int _turns; [ObservableProperty] private int _tokens; [ObservableProperty] private TimeSpan _elapsed; public ObservableCollection Log { get; } = new(); public ObservableCollection Subtasks { get; } = new(); public DetailsIslandViewModel(TaskRepository tasks, SubtaskRepository subtasks, WorkerClient worker) { _tasks = tasks; _subtasks = subtasks; _worker = worker; } public async void Bind(TaskRowViewModel? row) { Task = row; Log.Clear(); Subtasks.Clear(); if (row == null) return; var entity = await _tasks.GetByIdAsync(row.Id); if (entity == null) return; EditableTitle = entity.Title; Notes = entity.Notes ?? ""; Model = entity.Model; WorktreePath = entity.Worktree?.Path; BranchLine = entity.Worktree is { } w ? $"{w.BranchName} ← main" : null; AgentStatusLabel = entity.Status.ToString(); foreach (var s in await _subtasks.GetForTaskAsync(row.Id)) Subtasks.Add(new SubtaskRowViewModel { Id = s.Id, Title = s.Title, Done = s.Completed }); } [RelayCommand] private async Task SendPromptAsync() { if (string.IsNullOrWhiteSpace(PromptInput) || Task == null) return; Log.Add(new LogLineViewModel { Kind = LogKind.Msg, Text = $"[you] {PromptInput}" }); await _worker.SendPromptAsync(Task.Id, PromptInput); // adjust to actual API PromptInput = ""; } [RelayCommand] private async Task ApproveMergeAsync() { /* call worker merge */ await Task.CompletedTask; } [RelayCommand] private async Task StopAsync() { /* call worker stop */ await Task.CompletedTask; } } public sealed partial class SubtaskRowViewModel : ViewModelBase { public required string Id { get; init; } [ObservableProperty] private string _title = ""; [ObservableProperty] private bool _done; } ``` - [ ] **Step 3: Wire SignalR live log** — subscribe to `WorkerClient` log events in `Bind` and append to `Log`. Use the existing event pattern; verify the API surface in `WorkerClient.cs` first. - [ ] **Step 4: Build + commit** Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj` ```bash git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs src/ClaudeDo.Ui/ViewModels/Islands/LogLineViewModel.cs git commit -m "feat(ui): DetailsIslandViewModel with agent state and log" ``` --- ### Task 17: Build `DetailsIslandView` + `AgentStripView` + `SessionTerminalView` **Files:** - Modify: `src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml` - Create: `src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml(.cs)` - Create: `src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml(.cs)` - [ ] **Step 1: `AgentStripView.axaml`** — three rows per README §"Agent strip": ```xml