feat(ui): Review-Merge-Button — Claude nimmt Waiting-for-Review pro Liste ab
Neuer Button in der virtuellen Review-Liste: öffnet das Merge-Helper-Auswahlmodal im Review-Modus (alle WaitingForReview-Tasks repo-verknüpfter Listen, gruppiert unter Listen-Headern, alle vorgetickt) und startet pro gewählter Liste eine ConPTY-Handler-Session direkt in der Merge-Phase — via bestehendem Handoff-Spec (nextPhase "merge"), kein Triage/Wait davor. Handler-Host-Task wie beim vollen "Let Claude handle it"-Lauf (HandlerBaseCommit, IsManual).
This commit is contained in:
@@ -16,6 +16,12 @@ public sealed partial class MergeHelperTaskRowViewModel : ViewModelBase
|
||||
public required string StatusText { get; init; }
|
||||
public string? ParentTaskId { get; init; }
|
||||
|
||||
/// <summary>The owning list — only set in review mode, where rows span lists.</summary>
|
||||
public string ListId { get; init; } = "";
|
||||
|
||||
/// <summary>A non-selectable list-name section header (review mode groups rows by list).</summary>
|
||||
public bool IsListHeader { get; init; }
|
||||
|
||||
/// <summary>True when the parent is also a candidate and rendered directly above this row.</summary>
|
||||
public bool IsChild { get; init; }
|
||||
|
||||
@@ -37,6 +43,7 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private string _listId = "";
|
||||
private bool _reviewMode;
|
||||
|
||||
// Parent id -> its child rows, built after load; drives the tick-parent-ticks-children cascade.
|
||||
private Dictionary<string, List<MergeHelperTaskRowViewModel>> _childrenByParentId = new();
|
||||
@@ -84,6 +91,25 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
ScopeLabel = Loc.T("modals.mergeHelper.scopeList", listName);
|
||||
}
|
||||
|
||||
/// <summary>Review mode: offers every WaitingForReview task across all repo-linked lists,
|
||||
/// grouped under list-name header rows, all pre-ticked — the "merge the review pile" picker.</summary>
|
||||
public void ConfigureForReview()
|
||||
{
|
||||
_reviewMode = true;
|
||||
ScopeLabel = Loc.T("modals.mergeHelper.scopeReview");
|
||||
}
|
||||
|
||||
/// <summary>Groups a confirmed review-mode selection by owning list, preserving row order —
|
||||
/// the caller starts one merge-phase handler session per group.</summary>
|
||||
public IReadOnlyList<(string ListId, IReadOnlyList<string> TaskIds)> GroupSelectionByList(IReadOnlyList<string> ids)
|
||||
{
|
||||
var listByTaskId = Tasks.Where(t => !t.IsListHeader).ToDictionary(t => t.Id, t => t.ListId);
|
||||
return ids.Where(listByTaskId.ContainsKey)
|
||||
.GroupBy(id => listByTaskId[id])
|
||||
.Select(g => (g.Key, (IReadOnlyList<string>)g.ToList()))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// LoadAsync rebuilds every row from scratch and re-applies IsTickedByDefault, which would wipe
|
||||
// the user's ticks out from under them on every tick — in BOTH directions. Remembering only
|
||||
// the ticked ids would silently re-tick a row the user deliberately unticked (Idle/Queued/
|
||||
@@ -91,7 +117,7 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
// already existed across the reload; only genuinely new rows get the default.
|
||||
internal async Task ReconcileTickAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_listId) || _tickInFlight) return;
|
||||
if ((string.IsNullOrEmpty(_listId) && !_reviewMode) || _tickInFlight) return;
|
||||
_tickInFlight = true;
|
||||
try
|
||||
{
|
||||
@@ -137,6 +163,51 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
_childrenByParentId = new();
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
|
||||
if (_reviewMode)
|
||||
{
|
||||
// Every WaitingForReview task of every repo-linked list, grouped under a header row per
|
||||
// list. IsManual excludes reminders AND prior handler host tasks. Flat on purpose: a
|
||||
// review parent's children are already terminal, so the hierarchy walk below has
|
||||
// nothing to nest here.
|
||||
var reviewCandidates = await ctx.Tasks.AsNoTracking()
|
||||
.Where(t => t.Status == TaskStatus.WaitingForReview && !t.IsManual)
|
||||
.Join(ctx.Lists.Where(l => l.WorkingDir != null && l.WorkingDir != ""),
|
||||
t => t.ListId, l => l.Id,
|
||||
(t, l) => new { t.Id, t.Title, t.Status, t.ParentTaskId, t.ListId, ListName = l.Name, t.SortOrder, t.CreatedAt })
|
||||
.OrderBy(x => x.ListName).ThenBy(x => x.SortOrder).ThenBy(x => x.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
foreach (var group in reviewCandidates.GroupBy(x => x.ListId))
|
||||
{
|
||||
Tasks.Add(new MergeHelperTaskRowViewModel
|
||||
{
|
||||
Id = $"header:{group.Key}",
|
||||
Title = group.First().ListName,
|
||||
StatusText = "",
|
||||
ListId = group.Key,
|
||||
IsListHeader = true,
|
||||
});
|
||||
foreach (var c in group)
|
||||
{
|
||||
var reviewRow = new MergeHelperTaskRowViewModel
|
||||
{
|
||||
Id = c.Id,
|
||||
Title = c.Title,
|
||||
StatusText = c.Status.ToString(),
|
||||
ParentTaskId = c.ParentTaskId,
|
||||
ListId = c.ListId,
|
||||
IsSelected = true,
|
||||
};
|
||||
reviewRow.PropertyChanged += OnRowChanged;
|
||||
Tasks.Add(reviewRow);
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(HasTasks));
|
||||
OnPropertyChanged(nameof(CanConfirm));
|
||||
return;
|
||||
}
|
||||
var candidates = await ctx.Tasks.AsNoTracking()
|
||||
.Where(t => t.Status != TaskStatus.Done && t.Status != TaskStatus.Cancelled)
|
||||
.Where(t => t.ListId == _listId)
|
||||
@@ -222,7 +293,8 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
[RelayCommand]
|
||||
private void SelectAll()
|
||||
{
|
||||
foreach (var t in Tasks) t.IsSelected = true;
|
||||
foreach (var t in Tasks)
|
||||
if (!t.IsListHeader) t.IsSelected = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -235,7 +307,7 @@ public sealed partial class MergeHelperSelectionModalViewModel : ViewModelBase
|
||||
private void Confirm()
|
||||
{
|
||||
StopReconcileTick();
|
||||
Result.TrySetResult(Tasks.Where(t => t.IsSelected).Select(t => t.Id).ToList());
|
||||
Result.TrySetResult(Tasks.Where(t => t.IsSelected && !t.IsListHeader).Select(t => t.Id).ToList());
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user