Reminders written down as todos had no home: every task looked like Claude work. A manual task now shows a MANUAL badge and hides send-to-queue, refine and the planning session; the queue picker, daily prep and the list handler all skip it, with a TaskStateService guard so the MCP surface and hub cannot start one either. Opening a hand-driven ConPTY session stays available on purpose. A list can be marked manual in its settings, which makes tasks created there (UI and MCP add_task) start out manual. Toggle per task from its context menu.
118 lines
3.9 KiB
C#
118 lines
3.9 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using ClaudeDo.Data;
|
|
using ClaudeDo.Ui.Localization;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
|
|
|
public sealed partial class MergeHelperTaskRowViewModel : ViewModelBase
|
|
{
|
|
public required string Id { get; init; }
|
|
public required string Title { get; init; }
|
|
public required string StatusText { get; init; }
|
|
|
|
[ObservableProperty] private bool _isSelected;
|
|
}
|
|
|
|
/// <summary>
|
|
/// "Let Claude handle it" task picker: lists the non-terminal tasks of a list (or all lists),
|
|
/// pre-ticks the actionable ones, and resolves <see cref="Result"/> with the ordered selected
|
|
/// task ids (null on cancel).
|
|
/// </summary>
|
|
public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
|
{
|
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
|
private string _listId = "";
|
|
|
|
public ObservableCollection<MergeHelperTaskRowViewModel> Tasks { get; } = new();
|
|
|
|
[ObservableProperty] private string _scopeLabel = "";
|
|
|
|
public bool HasTasks => Tasks.Count > 0;
|
|
public bool CanConfirm => Tasks.Any(t => t.IsSelected);
|
|
|
|
public TaskCompletionSource<IReadOnlyList<string>?> Result { get; } = new();
|
|
public Action? CloseAction { get; set; }
|
|
|
|
public MergeHelperSelectionModalViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
|
=> _dbFactory = dbFactory;
|
|
|
|
public void Configure(string listId, string listName)
|
|
{
|
|
_listId = listId;
|
|
ScopeLabel = Loc.T("modals.mergeHelper.scopeList", listName);
|
|
}
|
|
|
|
public async Task LoadAsync(CancellationToken ct = default)
|
|
{
|
|
foreach (var row in Tasks) row.PropertyChanged -= OnRowChanged;
|
|
Tasks.Clear();
|
|
|
|
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
|
var candidates = await ctx.Tasks.AsNoTracking()
|
|
.Where(t => t.Status != TaskStatus.Done && t.Status != TaskStatus.Cancelled)
|
|
.Where(t => t.ListId == _listId)
|
|
// Manual tasks are reminders the user owns — never offer them to the handler.
|
|
.Where(t => !t.IsManual)
|
|
.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
|
|
.Select(t => new { t.Id, t.Title, t.Status })
|
|
.ToListAsync(ct);
|
|
|
|
foreach (var c in candidates)
|
|
{
|
|
var row = new MergeHelperTaskRowViewModel
|
|
{
|
|
Id = c.Id,
|
|
Title = c.Title,
|
|
StatusText = c.Status.ToString(),
|
|
IsSelected = IsTickedByDefault(c.Status),
|
|
};
|
|
row.PropertyChanged += OnRowChanged;
|
|
Tasks.Add(row);
|
|
}
|
|
OnPropertyChanged(nameof(HasTasks));
|
|
OnPropertyChanged(nameof(CanConfirm));
|
|
}
|
|
|
|
// Actionable statuses are pre-ticked; Running/WaitingForChildren are listed but unticked
|
|
// (the helper would only poll them). Done/Cancelled never make it into the list.
|
|
internal static bool IsTickedByDefault(TaskStatus status) => status
|
|
is TaskStatus.Idle or TaskStatus.Queued or TaskStatus.WaitingForReview or TaskStatus.Failed;
|
|
|
|
private void OnRowChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(MergeHelperTaskRowViewModel.IsSelected))
|
|
OnPropertyChanged(nameof(CanConfirm));
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void SelectAll()
|
|
{
|
|
foreach (var t in Tasks) t.IsSelected = true;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void SelectNone()
|
|
{
|
|
foreach (var t in Tasks) t.IsSelected = false;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Confirm()
|
|
{
|
|
Result.TrySetResult(Tasks.Where(t => t.IsSelected).Select(t => t.Id).ToList());
|
|
CloseAction?.Invoke();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Cancel()
|
|
{
|
|
Result.TrySetResult(null);
|
|
CloseAction?.Invoke();
|
|
}
|
|
}
|