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.
This commit is contained in:
Mika Kuns
2026-07-27 15:02:51 +02:00
parent fde9615b34
commit 3a648b7d77
19 changed files with 168 additions and 11 deletions
@@ -427,6 +427,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
if (string.IsNullOrWhiteSpace(NewTaskTitle) || _currentList?.Kind != ListKind.User) return;
var listId = _currentList.Id["user:".Length..];
await using var db = await _dbFactory.CreateDbContextAsync();
// A manual list holds reminders, so tasks created in it start out manual.
var listIsManual = await db.Lists.Where(l => l.Id == listId).Select(l => l.IsManual).FirstOrDefaultAsync();
var entity = new TaskEntity
{
Id = Guid.NewGuid().ToString("N"),
@@ -434,6 +436,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
Title = NewTaskTitle.Trim(),
Status = TaskStatus.Idle,
CreatedAt = DateTime.UtcNow,
IsManual = listIsManual,
};
await new TaskRepository(db).AddAsync(entity);
var row = TaskRowViewModel.FromEntity(entity);
@@ -622,6 +625,23 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
TasksChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Flips a task between "Claude can run this" and "manual reminder". A manual task
/// hides every hand-off affordance and is skipped by the queue picker and daily prep.</summary>
[RelayCommand]
private async Task ToggleManualAsync(TaskRowViewModel? row)
{
if (row is null) return;
row.IsManual = !row.IsManual;
await using var db = await _dbFactory.CreateDbContextAsync();
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
if (entity != null)
{
entity.IsManual = row.IsManual;
await db.SaveChangesAsync();
}
TasksChanged?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private async Task AddToMyDayAsync(TaskRowViewModel? row)
{