diff --git a/src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.cs b/src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.cs
index 5d0f072..301b931 100644
--- a/src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.cs
+++ b/src/ClaudeDo.Data/Migrations/20260416064948_InitialCreate.cs
@@ -1,6 +1,4 @@
using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
@@ -10,8 +8,6 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace ClaudeDo.Data.Migrations
{
///
- [DbContext(typeof(ClaudeDoDbContext))]
- [Migration("20260416064948_InitialCreate")]
public partial class InitialCreate : Migration
{
///
diff --git a/src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.cs b/src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.cs
index 27b2fe8..7546767 100644
--- a/src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.cs
+++ b/src/ClaudeDo.Data/Migrations/20260420075929_AddTaskFlagsAndNotes.cs
@@ -1,5 +1,3 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
@@ -7,8 +5,6 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace ClaudeDo.Data.Migrations
{
///
- [DbContext(typeof(ClaudeDoDbContext))]
- [Migration("20260420075929_AddTaskFlagsAndNotes")]
public partial class AddTaskFlagsAndNotes : Migration
{
///
diff --git a/src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.cs b/src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.cs
index 99eef30..083c4cb 100644
--- a/src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.cs
+++ b/src/ClaudeDo.Data/Migrations/20260421113614_AddAppSettings.cs
@@ -1,14 +1,10 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ClaudeDo.Data.Migrations
{
///
- [DbContext(typeof(ClaudeDoDbContext))]
- [Migration("20260421113614_AddAppSettings")]
public partial class AddAppSettings : Migration
{
///
diff --git a/src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.cs b/src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.cs
index 3f275bd..6918635 100644
--- a/src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.cs
+++ b/src/ClaudeDo.Data/Migrations/20260422120000_AddTaskSortOrder.cs
@@ -1,5 +1,3 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
@@ -7,8 +5,6 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace ClaudeDo.Data.Migrations
{
///
- [DbContext(typeof(ClaudeDoDbContext))]
- [Migration("20260422120000_AddTaskSortOrder")]
public partial class AddTaskSortOrder : Migration
{
///
diff --git a/src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.cs b/src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.cs
index 9efb30a..56e0468 100644
--- a/src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.cs
+++ b/src/ClaudeDo.Data/Migrations/20260423154708_AddPlanningSupport.cs
@@ -1,15 +1,10 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ClaudeDo.Data.Migrations
{
///
- [DbContext(typeof(ClaudeDoDbContext))]
- [Migration("20260423154708_AddPlanningSupport")]
public partial class AddPlanningSupport : Migration
{
///
diff --git a/src/ClaudeDo.Ui/Services/ForegroundHelper.cs b/src/ClaudeDo.Ui/Services/ForegroundHelper.cs
new file mode 100644
index 0000000..240417c
--- /dev/null
+++ b/src/ClaudeDo.Ui/Services/ForegroundHelper.cs
@@ -0,0 +1,19 @@
+using System.Runtime.InteropServices;
+
+namespace ClaudeDo.Ui.Services;
+
+internal static class ForegroundHelper
+{
+ private const int ASFW_ANY = -1;
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool AllowSetForegroundWindow(int dwProcessId);
+
+ // Grants any process the right to take foreground on next SetForegroundWindow call.
+ // Used before RPCs that cause a helper process (e.g. wt.exe) to spawn a new window.
+ public static void AllowAny()
+ {
+ if (!OperatingSystem.IsWindows()) return;
+ try { AllowSetForegroundWindow(ASFW_ANY); } catch { }
+ }
+}
diff --git a/src/ClaudeDo.Ui/Services/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/IWorkerClient.cs
index 08cf2fc..b6b04c5 100644
--- a/src/ClaudeDo.Ui/Services/IWorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/IWorkerClient.cs
@@ -2,6 +2,10 @@ namespace ClaudeDo.Ui.Services;
public interface IWorkerClient
{
+ event Action? TaskUpdatedEvent;
+ event Action? WorktreeUpdatedEvent;
+ event Action? TaskMessageEvent;
+
Task WakeQueueAsync();
Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default);
Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default);
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs
index 082ec33..da4c1fd 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs
@@ -5,8 +5,8 @@ namespace ClaudeDo.Ui.ViewModels.Islands;
public sealed partial class ListNavItemViewModel : ViewModelBase
{
public required string Id { get; init; }
- public required string Name { get; init; }
public required ListKind Kind { get; init; }
+ [ObservableProperty] private string _name = "";
[ObservableProperty] private int _count;
[ObservableProperty] private bool _isActive;
[ObservableProperty] private string? _workingDir;
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
index 430e05a..a47e1e3 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs
@@ -40,8 +40,9 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
private async System.Threading.Tasks.Task OpenListSettingsAsync(ListNavItemViewModel? row)
{
if (row is null || ShowListSettingsModal is null || _services is null) return;
+ var rawId = row.Id.StartsWith("user:", StringComparison.Ordinal) ? row.Id["user:".Length..] : row.Id;
var vm = _services.GetRequiredService();
- await vm.LoadAsync(row.Id, row.Name, row.WorkingDir, row.DefaultCommitType);
+ await vm.LoadAsync(rawId, row.Name, row.WorkingDir, row.DefaultCommitType);
await ShowListSettingsModal(vm);
await RefreshRowAsync(row.Id);
}
@@ -169,6 +170,46 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
[RelayCommand]
private void Select(ListNavItemViewModel item) => SelectedList = item;
+ [RelayCommand]
+ private async Task CreateListAsync()
+ {
+ var entity = new ListEntity
+ {
+ Id = Guid.NewGuid().ToString("N"),
+ Name = "New list",
+ DefaultCommitType = "chore",
+ CreatedAt = DateTime.UtcNow,
+ };
+
+ await using (var ctx = await _dbFactory.CreateDbContextAsync())
+ {
+ var lists = new ListRepository(ctx);
+ await lists.AddAsync(entity);
+ }
+
+ var item = new ListNavItemViewModel
+ {
+ Id = $"user:{entity.Id}",
+ Name = entity.Name,
+ Kind = ListKind.User,
+ IconKey = "Folder",
+ DotColorKey = "Moss",
+ WorkingDir = entity.WorkingDir,
+ DefaultCommitType = entity.DefaultCommitType,
+ };
+ Items.Add(item);
+ UserLists.Add(item);
+ SelectedList = item;
+
+ if (ShowListSettingsModal is not null && _services is not null)
+ {
+ var vm = _services.GetRequiredService();
+ await vm.LoadAsync(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType);
+ await ShowListSettingsModal(vm);
+ await RefreshRowAsync(item.Id);
+ }
+ }
+
partial void OnSelectedListChanged(ListNavItemViewModel? value)
{
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);
@@ -188,6 +229,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
var entity = await lists.GetByIdAsync(rawId);
if (entity is null) return;
+ row.Name = entity.Name;
row.WorkingDir = entity.WorkingDir;
row.DefaultCommitType = entity.DefaultCommitType;
}
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
index 3d4a5bc..5f5142f 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
@@ -101,25 +101,27 @@ public sealed partial class TaskRowViewModel : ViewModelBase
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
public static TaskRowViewModel FromEntity(TaskEntity t)
+ {
+ var row = new TaskRowViewModel { Id = t.Id, CreatedAt = t.CreatedAt };
+ row.UpdateFromEntity(t);
+ return row;
+ }
+
+ public void UpdateFromEntity(TaskEntity t)
{
var (add, del) = ParseDiffStat(t.Worktree?.DiffStat);
- return new TaskRowViewModel
- {
- Id = t.Id,
- Title = t.Title,
- ListName = t.List?.Name ?? "",
- Done = t.Status == TaskStatus.Done,
- IsStarred = t.IsStarred,
- IsMyDay = t.IsMyDay,
- Status = t.Status,
- Branch = t.Worktree?.BranchName,
- DiffStat = t.Worktree?.DiffStat,
- ScheduledFor = t.ScheduledFor,
- DiffAdditions = add,
- DiffDeletions = del,
- CreatedAt = t.CreatedAt,
- ParentTaskId = t.ParentTaskId,
- };
+ Title = t.Title;
+ ListName = t.List?.Name ?? "";
+ Done = t.Status == TaskStatus.Done;
+ IsStarred = t.IsStarred;
+ IsMyDay = t.IsMyDay;
+ Status = t.Status;
+ Branch = t.Worktree?.BranchName;
+ DiffStat = t.Worktree?.DiffStat;
+ ScheduledFor = t.ScheduledFor;
+ DiffAdditions = add;
+ DiffDeletions = del;
+ ParentTaskId = t.ParentTaskId;
}
// Best-effort parse of diff stat strings like "+12 -3" or "12 additions, 3 deletions".
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
index baa02c9..af83223 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
@@ -49,6 +49,70 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
{
_dbFactory = dbFactory;
_worker = worker;
+ if (_worker is not null)
+ {
+ _worker.TaskUpdatedEvent += OnWorkerTaskUpdated;
+ _worker.WorktreeUpdatedEvent += OnWorkerTaskUpdated;
+ _worker.TaskMessageEvent += OnWorkerTaskMessage;
+ }
+ }
+
+ private void OnWorkerTaskMessage(string taskId, string line)
+ {
+ var row = Items.FirstOrDefault(r => r.Id == taskId);
+ if (row is not null) row.LiveTail = line;
+ }
+
+ private async void OnWorkerTaskUpdated(string taskId)
+ {
+ var list = _currentList;
+ if (list is null) return;
+
+ try
+ {
+ await using var db = await _dbFactory.CreateDbContextAsync();
+ var entity = await db.Tasks
+ .Include(t => t.List)
+ .Include(t => t.Worktree)
+ .FirstOrDefaultAsync(t => t.Id == taskId);
+
+ var existing = Items.FirstOrDefault(r => r.Id == taskId);
+
+ if (entity is null)
+ {
+ if (existing is not null) Items.Remove(existing);
+ }
+ else
+ {
+ var matches = TaskMatchesList(entity, list);
+ if (existing is not null && matches) existing.UpdateFromEntity(entity);
+ else if (existing is not null) Items.Remove(existing);
+ else if (matches) { LoadForList(list); return; }
+ else return;
+ }
+
+ Regroup();
+ UpdateSubtitle();
+ }
+ catch { }
+ }
+
+ private static bool TaskMatchesList(TaskEntity t, ListNavItemViewModel list) => list.Kind switch
+ {
+ ListKind.Smart when list.Id == "smart:my-day" => t.IsMyDay,
+ ListKind.Smart when list.Id == "smart:important" => t.IsStarred,
+ ListKind.Smart when list.Id == "smart:planned" => t.ScheduledFor != null,
+ ListKind.Virtual when list.Id == "virtual:queued" => t.Status == TaskStatus.Queued,
+ ListKind.Virtual when list.Id == "virtual:running" => t.Status == TaskStatus.Running,
+ ListKind.Virtual when list.Id == "virtual:review" => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active,
+ ListKind.User => $"user:{t.ListId}" == list.Id,
+ _ => false,
+ };
+
+ private void OnCurrentListPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(ListNavItemViewModel.Name) && sender is ListNavItemViewModel vm)
+ HeaderTitle = vm.Name;
}
public void LoadForList(ListNavItemViewModel? list)
@@ -58,7 +122,12 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
_loadCts = new CancellationTokenSource();
var ct = _loadCts.Token;
+ if (_currentList is not null)
+ _currentList.PropertyChanged -= OnCurrentListPropertyChanged;
_currentList = list;
+ if (_currentList is not null)
+ _currentList.PropertyChanged += OnCurrentListPropertyChanged;
+
Items.Clear();
OverdueItems.Clear();
OpenItems.Clear();
@@ -385,6 +454,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
private async Task OpenPlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null || row.Status != TaskStatus.Manual) return;
+ ForegroundHelper.AllowAny();
try { await _worker!.StartPlanningSessionAsync(row.Id); }
catch { }
}
@@ -412,6 +482,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
switch (choice)
{
case UnfinishedPlanningModalResult.Resume:
+ ForegroundHelper.AllowAny();
await _worker.ResumePlanningSessionAsync(row.Id);
break;
case UnfinishedPlanningModalResult.FinalizeNow:
diff --git a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml
index 89f09d5..7d34843 100644
--- a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml
+++ b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml
@@ -166,7 +166,8 @@
-