feat(FN-4538): complete Step 5 — show overlap blockers in dashboard

Fusion-Task-Id: FN-4538
Fusion-Task-Lineage: 332b4c78-b67f-45d0-9181-0bb539b662c1
This commit is contained in:
Fusion
2026-05-14 22:09:34 -07:00
committed by gsxdsm
parent 3f651fe2ba
commit dd934208c8
4 changed files with 62 additions and 4 deletions

View File

@@ -452,6 +452,7 @@ function areTaskCardPropsEqual(previous: TaskCardProps, next: TaskCardProps): bo
previousTask.error === nextTask.error &&
previousTask.size === nextTask.size &&
previousTask.blockedBy === nextTask.blockedBy &&
previousTask.overlapBlockedBy === nextTask.overlapBlockedBy &&
previousTask.worktree === nextTask.worktree &&
previousTask.branch === nextTask.branch &&
previousTask.baseBranch === nextTask.baseBranch &&
@@ -1760,7 +1761,7 @@ function TaskCardComponent({
)}
</div>
)}
{(((task.retrySummary?.total ?? 0) > 0) || (task.dependencies && task.dependencies.length > 0) || queued || task.status === "queued" || task.blockedBy || (fanout && fanout.totalCount > 0)) && (
{(((task.retrySummary?.total ?? 0) > 0) || (task.dependencies && task.dependencies.length > 0) || queued || task.status === "queued" || task.blockedBy || task.overlapBlockedBy || (fanout && fanout.totalCount > 0)) && (
<div className="card-meta">
{task.dependencies && task.dependencies.length > 0 && (
<div className="card-dep-list">
@@ -1776,9 +1777,9 @@ function TaskCardComponent({
))}
</div>
)}
{task.blockedBy && (
<span className="card-scope-badge" data-tooltip={`Blocked by ${task.blockedBy} (file overlap)`}>
<Layers size={12} style={{ verticalAlign: "middle" }} /> {task.blockedBy}
{(task.overlapBlockedBy || task.blockedBy) && (
<span className="card-scope-badge" data-tooltip={`Blocked by ${task.overlapBlockedBy || task.blockedBy} (file overlap)`}>
<Layers size={12} style={{ verticalAlign: "middle" }} /> {task.overlapBlockedBy || task.blockedBy}
</span>
)}
{fanout && fanout.totalCount > 0 && (

View File

@@ -1899,6 +1899,12 @@ export function TaskDetailContent({
const overlapBlockingSummary = blockingEntry
? `${task.id} is blocking ${blockingEntry.overlapBlockedTodoCount} todo task(s) via blockedBy overlap`
: null;
const overlapBlockerTask = workingTask.overlapBlockedBy
? tasks.find((candidate) => candidate.id === workingTask.overlapBlockedBy)
: undefined;
const overlapBlockerActive = Boolean(
overlapBlockerTask && (overlapBlockerTask.column === "in-progress" || overlapBlockerTask.column === "in-review"),
);
const assignedAgentLabel = assignedAgent?.name ?? task.assignedAgentId ?? null;
const detailProviders = useMemo(() => {
@@ -3047,6 +3053,12 @@ export function TaskDetailContent({
) : (
<div className="detail-empty-inline">(no dependencies)</div>
)}
{workingTask.overlapBlockedBy && (
<div className="detail-empty-inline">
File scope overlap blocker: {workingTask.overlapBlockedBy}
{!overlapBlockerActive && " (stale)"}
</div>
)}
<div className="dep-trigger-wrap">
<button
type="button"

View File

@@ -661,6 +661,30 @@ describe("TaskCard", () => {
expect(container.querySelector(".card-fanout-badge")).toBeNull();
});
it("renders overlap scope badge when overlapBlockedBy is set without blockedBy", () => {
render(
<TaskCard
task={makeTask({ column: "todo", blockedBy: undefined, overlapBlockedBy: "FN-OVER" })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.getByText("FN-OVER")).toBeInTheDocument();
});
it("does not render overlap scope badge when blockedBy and overlapBlockedBy are absent", () => {
const { container } = render(
<TaskCard
task={makeTask({ column: "todo", blockedBy: undefined, overlapBlockedBy: undefined })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(container.querySelector(".card-scope-badge")).toBeNull();
});
it("renders fan-out badge with downstream count and tooltip", () => {
render(
<TaskCard

View File

@@ -312,6 +312,27 @@ describe("TaskDetailModal", () => {
});
});
it("shows active file scope overlap blocker in Dependencies section", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-T", column: "todo", overlapBlockedBy: "FN-OVER" })}
tasks={[
makeTask({ id: "FN-T", column: "todo", overlapBlockedBy: "FN-OVER" }),
makeTask({ id: "FN-OVER", column: "in-progress" }),
]}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByText("File scope overlap blocker: FN-OVER")).toBeInTheDocument();
expect(screen.queryByText("File scope overlap blocker: FN-OVER (stale)")).toBeNull();
});
it("shows overlap blockedBy summary in Blocking section", () => {
render(
<TaskDetailModal