Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs
T
Mika Kuns 3a648b7d77 feat(tasks): mark tasks and lists as manual
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.
2026-07-27 15:02:51 +02:00

115 lines
4.1 KiB
C#

using System.Collections.ObjectModel;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Agent;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
namespace ClaudeDo.Ui.ViewModels.Modals;
public sealed partial class ListSettingsModalViewModel : ViewModelBase
{
private readonly IWorkerClient _worker;
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
public string ListId { get; set; } = "";
// True after the list was deleted, so the caller reloads the list nav instead of refreshing the row.
public bool Deleted { get; private set; }
// Wired by the view to prompt yes/no before deleting and to surface a blocking-FK error.
public Func<string, Task<bool>>? ConfirmAsync { get; set; }
public Func<string, Task>? ShowErrorAsync { get; set; }
[ObservableProperty] private string _name = "";
[ObservableProperty] private string _workingDir = "";
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
// A manual list holds reminders: tasks created here start out manual (TaskEntity.IsManual).
[ObservableProperty] private bool _isManual;
public ObservableCollection<string> CommitTypeOptions { get; } = new(CommitTypeRegistry.Types);
// The shared agent-config editor (Model / MaxTurns / SystemPrompt / AgentFile),
// scoped to this list (list → global inheritance).
public AgentConfigEditorViewModel Agent { get; }
public Action? CloseAction { get; set; }
public ListSettingsModalViewModel(IWorkerClient worker, IDbContextFactory<ClaudeDoDbContext> dbFactory)
{
_worker = worker;
_dbFactory = dbFactory;
Agent = new AgentConfigEditorViewModel(worker, AgentConfigScope.List);
}
public async Task LoadAsync(
string listId,
string name,
string? workingDir,
string defaultCommitType,
bool isManual = false,
CancellationToken ct = default)
{
ListId = listId;
Name = name;
IsManual = isManual;
WorkingDir = workingDir ?? "";
DefaultCommitType = string.IsNullOrWhiteSpace(defaultCommitType) ? CommitTypeRegistry.DefaultType : defaultCommitType;
await Agent.LoadForListAsync(listId, ct);
}
[RelayCommand]
private async Task SaveAsync()
{
await _worker.UpdateListAsync(new UpdateListDto(
ListId,
string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name,
string.IsNullOrWhiteSpace(WorkingDir) ? null : WorkingDir,
DefaultCommitType,
IsManual));
await Agent.SaveAsync();
CloseAction?.Invoke();
}
[RelayCommand]
private async Task DeleteAsync()
{
var displayName = string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name;
if (ConfirmAsync is not null)
{
var ok = await ConfirmAsync($"Delete list \"{displayName}\" and all its tasks? This cannot be undone.");
if (!ok) return;
}
try
{
await using var ctx = await _dbFactory.CreateDbContextAsync();
var lists = new ListRepository(ctx);
await lists.DeleteAsync(ListId);
}
catch (Exception ex) when (
(ex is Microsoft.Data.Sqlite.SqliteException
|| ex.InnerException is Microsoft.Data.Sqlite.SqliteException)
&& (ex.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase)
|| ex.InnerException?.Message.Contains("FOREIGN KEY", StringComparison.OrdinalIgnoreCase) == true))
{
if (ShowErrorAsync is not null)
await ShowErrorAsync("This list has planning sessions with child tasks. Discard those first, then delete the list.");
return;
}
Deleted = true;
CloseAction?.Invoke();
}
[RelayCommand]
private void Cancel() => CloseAction?.Invoke();
}