refactor: extract interfaces to Interfaces folders and consolidate filters

Move interface declarations into per-area Interfaces/ subfolders, merge the
small task-list filter classes into StatusFilter/SmartFlagFilter, and simplify
related services, converters and hub DTO handling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-05-30 15:41:10 +02:00
parent 77100b6b3b
commit 41da124a31
42 changed files with 306 additions and 532 deletions

View File

@@ -1,12 +0,0 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
public sealed class ImportantFilter : ITaskListFilter
{
public string Id => "smart:important";
public bool Matches(TaskEntity t) => t.IsStarred;
public bool ShouldCount(TaskEntity t) => t.IsStarred && t.Status != TaskStatus.Done;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) => false;
}

View File

@@ -1,12 +0,0 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
public sealed class MyDayFilter : ITaskListFilter
{
public string Id => "smart:my-day";
public bool Matches(TaskEntity t) => t.IsMyDay;
public bool ShouldCount(TaskEntity t) => t.IsMyDay && t.Status != TaskStatus.Done;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) => false;
}

View File

@@ -1,12 +0,0 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
public sealed class PlannedFilter : ITaskListFilter
{
public string Id => "smart:planned";
public bool Matches(TaskEntity t) => t.ScheduledFor != null;
public bool ShouldCount(TaskEntity t) => t.ScheduledFor != null && t.Status != TaskStatus.Done;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) => false;
}

View File

@@ -1,14 +0,0 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
public sealed class QueuedFilter : ITaskListFilter
{
public string Id => "virtual:queued";
public bool Matches(TaskEntity t) => t.Status == TaskStatus.Queued;
public bool ShouldCount(TaskEntity t) => t.Status == TaskStatus.Queued;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) =>
PlanningRules.IsPlanningParent(t) &&
PlanningRules.HasMatchingChild(t, all, c => c.Status == TaskStatus.Queued);
}

View File

@@ -1,14 +0,0 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
public sealed class RunningFilter : ITaskListFilter
{
public string Id => "virtual:running";
public bool Matches(TaskEntity t) => t.Status == TaskStatus.Running;
public bool ShouldCount(TaskEntity t) => t.Status == TaskStatus.Running;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) =>
PlanningRules.IsPlanningParent(t) &&
PlanningRules.HasMatchingChild(t, all, c => c.Status == TaskStatus.Running);
}

View File

@@ -0,0 +1,16 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
/// <summary>
/// Filter for a smart list keyed off a boolean/nullable task flag
/// (My Day, Important, Planned). Counts only non-done matches.
/// </summary>
public sealed class SmartFlagFilter(string id, Func<TaskEntity, bool> flag) : ITaskListFilter
{
public string Id => id;
public bool Matches(TaskEntity t) => flag(t);
public bool ShouldCount(TaskEntity t) => flag(t) && t.Status != TaskStatus.Done;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) => false;
}

View File

@@ -0,0 +1,18 @@
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering.Filters;
/// <summary>
/// Virtual list filter matching tasks by a single status (Queued, Running).
/// Planning parents appear contextually when they host a matching child.
/// </summary>
public sealed class StatusFilter(string id, TaskStatus status) : ITaskListFilter
{
public string Id => id;
public bool Matches(TaskEntity t) => t.Status == status;
public bool ShouldCount(TaskEntity t) => t.Status == status;
public bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) =>
PlanningRules.IsPlanningParent(t) &&
PlanningRules.HasMatchingChild(t, all, c => c.Status == status);
}

View File

@@ -1,4 +1,5 @@
using ClaudeDo.Data.Filtering.Filters;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Data.Filtering;
@@ -14,11 +15,11 @@ public sealed class TaskListFilterRegistry
private static readonly IReadOnlyDictionary<string, ITaskListFilter> BuiltIn =
new Dictionary<string, ITaskListFilter>(StringComparer.Ordinal)
{
["smart:my-day"] = new MyDayFilter(),
["smart:important"] = new ImportantFilter(),
["smart:planned"] = new PlannedFilter(),
["virtual:queued"] = new QueuedFilter(),
["virtual:running"] = new RunningFilter(),
["smart:my-day"] = new SmartFlagFilter("smart:my-day", t => t.IsMyDay),
["smart:important"] = new SmartFlagFilter("smart:important", t => t.IsStarred),
["smart:planned"] = new SmartFlagFilter("smart:planned", t => t.ScheduledFor != null),
["virtual:queued"] = new StatusFilter("virtual:queued", TaskStatus.Queued),
["virtual:running"] = new StatusFilter("virtual:running", TaskStatus.Running),
["virtual:review"] = new ReviewFilter(),
};

View File

@@ -23,7 +23,7 @@ public sealed class AppSettingsRepository
return row;
}
public async Task UpdateAsync(AppSettingsEntity updated, CancellationToken ct = default)
private async Task<AppSettingsEntity> GetOrCreateTrackedRowAsync(CancellationToken ct)
{
var row = await _context.AppSettings
.FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
@@ -32,6 +32,12 @@ public sealed class AppSettingsRepository
row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId };
_context.AppSettings.Add(row);
}
return row;
}
public async Task UpdateAsync(AppSettingsEntity updated, CancellationToken ct = default)
{
var row = await GetOrCreateTrackedRowAsync(ct);
row.DefaultClaudeInstructions = updated.DefaultClaudeInstructions ?? string.Empty;
row.DefaultModel = string.IsNullOrWhiteSpace(updated.DefaultModel) ? "sonnet" : updated.DefaultModel;
@@ -62,13 +68,7 @@ public sealed class AppSettingsRepository
public async Task SetRepoImportFoldersAsync(IEnumerable<string> folders, CancellationToken ct = default)
{
var list = folders.ToList();
var row = await _context.AppSettings
.FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
if (row is null)
{
row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId };
_context.AppSettings.Add(row);
}
var row = await GetOrCreateTrackedRowAsync(ct);
row.RepoImportFolders = list.Count == 0 ? null : JsonSerializer.Serialize(list);
await _context.SaveChangesAsync(ct);