feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder pickbar). BlockedByTaskId bleibt unberuehrt. - Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird - QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten wird blockiert, alle Kanten muessen done sein, eine Kante auf einen verschwundenen Task blockiert weiter - SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine BFS ueber den Graphen, faengt also auch A->B->C->A - blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt) - add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig - UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label, die lineare Kette rendert unveraendert
This commit is contained in:
@@ -74,10 +74,15 @@ public class TasksIslandChainTests : IDisposable
|
||||
Title = t.Id,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Status = t.Status,
|
||||
DependsOnTaskId = t.DependsOnTaskId,
|
||||
ParentTaskId = t.ParentTaskId,
|
||||
SortOrder = t.SortOrder,
|
||||
});
|
||||
if (t.DependsOnTaskId is not null)
|
||||
db.TaskDependencies.Add(new TaskDependencyEntity
|
||||
{
|
||||
TaskId = t.Id,
|
||||
DependsOnTaskId = t.DependsOnTaskId,
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
return numbers;
|
||||
@@ -225,8 +230,7 @@ public class TasksIslandChainTests : IDisposable
|
||||
|
||||
await using (var db = NewContext())
|
||||
{
|
||||
var entity = await db.Tasks.FirstAsync(t => t.Id == "later");
|
||||
entity.DependsOnTaskId = "head";
|
||||
db.TaskDependencies.Add(new TaskDependencyEntity { TaskId = "later", DependsOnTaskId = "head" });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -236,4 +240,30 @@ public class TasksIslandChainTests : IDisposable
|
||||
Assert.True(updated.ShowAsChainMember);
|
||||
Assert.Equal(1, updated.ChainStep);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FanIn_RendersFlatWithEveryPredecessorInTheLabel()
|
||||
{
|
||||
// Two independent predecessors is not a chain -- the rail can only draw one line, so the
|
||||
// row stays flat and the label has to name both, or it misreports what it waits for.
|
||||
var numbers = await SeedAsync(
|
||||
("a", TaskStatus.Idle, null, null, 0),
|
||||
("b", TaskStatus.Idle, null, null, 1),
|
||||
("wiring", TaskStatus.Idle, "a", null, 2));
|
||||
await using (var db = NewContext())
|
||||
{
|
||||
db.TaskDependencies.Add(new TaskDependencyEntity { TaskId = "wiring", DependsOnTaskId = "b" });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var vm = BuildViewModel();
|
||||
await LoadAndWaitAsync(vm, UserList("list1", "Default"));
|
||||
|
||||
var wiring = vm.Items.First(r => r.Id == "wiring");
|
||||
Assert.False(wiring.ShowAsChainMember);
|
||||
Assert.Null(wiring.ChainStep);
|
||||
Assert.NotNull(wiring.ChainAfterLabel);
|
||||
Assert.Contains(numbers["a"].ToString(), wiring.ChainAfterLabel);
|
||||
Assert.Contains(numbers["b"].ToString(), wiring.ChainAfterLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,14 @@ public class TasksIslandClassifyItemsTests : IDisposable
|
||||
Title = t.Id,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Status = t.Status,
|
||||
DependsOnTaskId = t.DependsOnTaskId,
|
||||
SortOrder = t.SortOrder,
|
||||
});
|
||||
if (t.DependsOnTaskId is not null)
|
||||
db.TaskDependencies.Add(new TaskDependencyEntity
|
||||
{
|
||||
TaskId = t.Id,
|
||||
DependsOnTaskId = t.DependsOnTaskId,
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -266,8 +266,7 @@ public class TasksIslandReconcileTickTests : IDisposable
|
||||
|
||||
await using (var db = NewContext())
|
||||
{
|
||||
var t = await db.Tasks.FirstAsync(x => x.Id == "DEP");
|
||||
t.DependsOnTaskId = "HEAD";
|
||||
db.TaskDependencies.Add(new TaskDependencyEntity { TaskId = "DEP", DependsOnTaskId = "HEAD" });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user