Extend the dashboard aggregate workflow view across top-level workflow selectors without leaking its sentinel into task creation. - Add All workflows as an aggregate option for List, Header, Planning/Missions, and Graph selectors.\n- Preserve real workflow handoffs for quick create, planning, missions, and workflow editor entry points.\n- Include aggregate workflow counts and cross-surface regression coverage, plus docs and a changeset.\n\nFiles changed:\n .changeset/fn-7357-all-workflows-selectors.md | 7 +++\n docs/dashboard-guide.md | 9 ++--\n .../workflow-selection-cross-surface.test.tsx | 20 ++++---\n .../app/components/GraphWorkflowSwitcherSlot.tsx | 12 +++--\n .../app/components/HeaderWorkflowSwitcherSlot.tsx | 10 ++--\n packages/dashboard/app/components/ListView.tsx | 63 +++++++++++++++++-----\n .../components/PlanningWorkflowSwitcherSlot.tsx | 5 +-\n .../__tests__/GraphWorkflowSwitcherSlot.test.tsx | 34 ++++++++++++\n .../__tests__/HeaderWorkflowSwitcherSlot.test.tsx | 27 ++++++++++\n .../app/components/__tests__/ListView.test.tsx | 41 +++++++++++++-\n .../app/components/dashboard/MainContent.tsx | 17 ++++--\n .../app/components/workflowStatusCounts.ts | 5 ++\n packages/dashboard/app/hooks/useBoardWorkflows.ts | 20 +++++--\n .../dashboard/app/utils/boardWorkflowSelection.ts | 5 +-\n 14 files changed, 233 insertions(+), 42 deletions(-) Fusion-Task-Id: FN-7357 Fusion-Task-Lineage: b8658cad-7b12-4a06-b627-e2bde7de6951 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
113 lines
4.3 KiB
TypeScript
113 lines
4.3 KiB
TypeScript
import type { Task } from "@fusion/core";
|
|
import type { BoardWorkflowColumn, BoardWorkflowsPayload } from "../api";
|
|
import { ALL_WORKFLOWS_BOARD_VIEW_ID } from "../utils/boardWorkflowSelection";
|
|
|
|
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]))
|
|
);
|
|
}
|
|
|
|
const aggregateCounts = EMPTY_COUNTS();
|
|
countsByWorkflow.set(ALL_WORKFLOWS_BOARD_VIEW_ID, aggregateCounts);
|
|
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;
|
|
aggregateCounts[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;
|
|
aggregateCounts.merging += 1;
|
|
}
|
|
countsByWorkflow.set(workflow.id, counts);
|
|
}
|
|
|
|
return countsByWorkflow;
|
|
}
|