Files
fusion/packages/dashboard/app/components/workflowStatusCounts.ts
gsxdsm 797b30ce5d FN-7242: add all-workflows board view and workflow badges
Adds an aggregate workflow board mode with workflow-name context on mixed-workflow task surfaces.

- Add a dashboard-only All workflows switcher option that unions visible workflow columns without persisting the sentinel as a real workflow selection.
- Thread workflow metadata into board cards, worktree groups, and task detail headers so mixed workflow views show the task workflow name.
- Update workflow status counts, quick-create targeting, docs, changesets, and regression coverage for aggregate board and badge behavior.

Files changed:
 .changeset/fn-7242-all-workflows-board.md          |   7 +
 .changeset/fn-7242-task-detail-workflow-badge.md   |   7 +
 .changeset/fn-7242-workflow-badges.md              |   7 +
 docs/dashboard-guide.md                            |   8 +-
 .../workflow-selection-cross-surface.test.tsx      |   6 +-
 packages/dashboard/app/components/Board.tsx        | 295 +++++++++++++++++++--
 packages/dashboard/app/components/Column.tsx       |   6 +-
 packages/dashboard/app/components/TaskCard.css     |  16 +-
 packages/dashboard/app/components/TaskCard.tsx     |  23 +-
 .../dashboard/app/components/TaskDetailModal.css   |  14 +-
 .../dashboard/app/components/TaskDetailModal.tsx   |  43 ++-
 .../dashboard/app/components/WorkflowSwitcher.tsx  |  38 ++-
 .../dashboard/app/components/WorktreeGroup.tsx     |   5 +
 .../app/components/__tests__/Board.test.tsx        | 192 +++++++++++++-
 .../__tests__/TaskCard.badge-wrap.test.tsx         |   2 +
 .../app/components/__tests__/TaskCard.test.tsx     |  38 +++
 .../__tests__/TaskDetailModal.rendering.test.tsx   | 161 ++++++++++-
 .../components/__tests__/WorkflowSwitcher.test.tsx |  50 ++++
 .../__tests__/workflowStatusCounts.test.ts         |  26 +-
 .../app/components/workflowStatusCounts.ts         |  16 +-
 20 files changed, 887 insertions(+), 73 deletions(-)

Fusion-Task-Id: FN-7242
Fusion-Task-Lineage: 7ff9d35a-83ef-406c-88f4-a8846edad381
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-06-29 18:54:28 -07:00

108 lines
4.0 KiB
TypeScript

import type { Task } from "@fusion/core";
import type { BoardWorkflowColumn, BoardWorkflowsPayload } from "../api";
export interface WorkflowStatusCounts {
todo: number;
inProgress: number;
done: number;
merging: number;
}
const EMPTY_COUNTS = (): WorkflowStatusCounts => ({
todo: 0,
inProgress: 0,
done: 0,
merging: 0,
});
type WorkflowStatusBucket = keyof WorkflowStatusCounts | "excluded";
const MERGING_STATUSES = new Set(["merging", "merging-pr", "merging-fix"]);
/**
* FNXC:WorkflowSwitcher 2026-06-20-00:09:
* The board/list workflow dropdown must show compact Todo, In Progress, and Done task counts for every selectable workflow without duplicating logic across render surfaces.
* Use workflow column flags as the source of truth: archived or board-hidden columns are excluded, complete columns count as Done, active non-intake WIP columns count as In Progress, and all remaining visible work counts as Todo/not-yet-started.
*
* FNXC:WorkflowSwitcher 2026-06-21-00:00:
* Built-in linear workflows synthesize canonical lifecycle columns with empty traits, so their resolved flags cannot identify Done, In Progress, or Archived buckets.
* Fall back to canonical lifecycle column ids only after flag-based classification fails, keeping trait-bearing workflows authoritative while preventing Done tasks in Quick fix-style lanes from being miscounted.
*/
function classifyWorkflowStatusColumn(
column: BoardWorkflowColumn
): WorkflowStatusBucket {
if (column.flags.archived || column.flags.hiddenFromBoard) return "excluded";
if (column.flags.complete) return "done";
if (column.flags.countsTowardWip && !column.flags.intake) return "inProgress";
switch (column.id) {
case "archived":
return "excluded";
case "done":
return "done";
case "in-progress":
return "inProgress";
default:
return "todo";
}
}
export function computeWorkflowStatusCounts(
tasks: readonly Task[] | null | undefined,
boardWorkflows: BoardWorkflowsPayload | null | undefined
): Map<string, WorkflowStatusCounts> {
const countsByWorkflow = new Map<string, WorkflowStatusCounts>();
if (!boardWorkflows) return countsByWorkflow;
const workflowsById = new Map(
boardWorkflows.workflows.map((workflow) => [workflow.id, workflow])
);
const knownWorkflowIds = new Set(workflowsById.keys());
const columnsByWorkflowId = new Map<
string,
Map<string, BoardWorkflowColumn>
>();
for (const workflow of boardWorkflows.workflows) {
countsByWorkflow.set(workflow.id, EMPTY_COUNTS());
columnsByWorkflowId.set(
workflow.id,
new Map(workflow.columns.map((column) => [column.id, column]))
);
}
if (!tasks?.length) return countsByWorkflow;
for (const task of tasks) {
const assignedWorkflowId = boardWorkflows.taskWorkflowIds[task.id];
/*
FNXC:WorkflowSwitcher 2026-06-29-18:37:
Workflow counts must follow the same stale-assignment repair semantics as Board rendering: missing or unknown task-workflow ids fall back to the default workflow so cards and selector counts do not diverge while taskWorkflowIds is stale.
*/
const workflowId = assignedWorkflowId && knownWorkflowIds.has(assignedWorkflowId)
? assignedWorkflowId
: boardWorkflows.defaultWorkflowId;
const workflow = workflowsById.get(workflowId);
if (!workflow) continue;
const column = columnsByWorkflowId.get(workflow.id)?.get(task.column);
if (!column) continue;
const bucket = classifyWorkflowStatusColumn(column);
if (bucket === "excluded") continue;
const counts = countsByWorkflow.get(workflow.id) ?? EMPTY_COUNTS();
counts[bucket] += 1;
if (MERGING_STATUSES.has(task.status ?? "")) {
/*
FNXC:WorkflowSwitcher 2026-06-22-20:30:
Workflow boards need a visible flashing indicator in the workflow dropdown when any task assigned to that workflow is actively merging, independent of whether the workflow's review/merge column buckets as Todo or In Progress.
*/
counts.merging += 1;
}
countsByWorkflow.set(workflow.id, counts);
}
return countsByWorkflow;
}