diff --git a/packages/dashboard/app/utils/__tests__/taskProgress.test.ts b/packages/dashboard/app/utils/__tests__/taskProgress.test.ts index 938e87f3de..10a5a6b201 100644 --- a/packages/dashboard/app/utils/__tests__/taskProgress.test.ts +++ b/packages/dashboard/app/utils/__tests__/taskProgress.test.ts @@ -5,7 +5,7 @@ import { getUnifiedTaskProgress } from "../taskProgress"; /* FNXC:WorkflowSteps 2026-06-25-00:00 — graph-native progress model (plan U3). These tests pin the render-state contract that the progress bar / Workflow tab rely on: -- names resolve from result.workflowStepName (no DB-row name lookup), with a raw-id fallback; +- names resolve from result.workflowStepName (no DB-row name lookup), with a humanized node-id fallback; - a "pending" result with a startedAt and no completedAt is the `running` state, vs bare `pending`; - advisory_failure (non-blocking) is distinct from failed (blocking) and counts as completed; - disabled optional steps (absent from enabledWorkflowSteps) never appear in the counter/bar. @@ -49,11 +49,25 @@ describe("getUnifiedTaskProgress", () => { ); const item = progress.items.find((i) => i.id === "workflow-code-review"); - expect(item?.name).toBe("code-review"); + // Enabled-but-not-run has no recorded name → humanize the node id to proper casing, + // never render the raw lowercase id. + expect(item?.name).toBe("Code Review"); // Enabled but never run → pending. expect(item?.status).toBe("pending"); }); + it("humanizes the node id to proper casing for an enabled-but-not-run step", () => { + const progress = getUnifiedTaskProgress( + makeTask({ + enabledWorkflowSteps: ["browser-verification", "frontend-ux-design", "code-review"], + workflowStepResults: [], + }), + ); + expect(progress.items.find((i) => i.id === "workflow-browser-verification")?.name).toBe("Browser Verification"); + expect(progress.items.find((i) => i.id === "workflow-frontend-ux-design")?.name).toBe("Frontend UX Design"); + expect(progress.items.find((i) => i.id === "workflow-code-review")?.name).toBe("Code Review"); + }); + it("distinguishes running (started, not completed) from pending (not started)", () => { const progress = getUnifiedTaskProgress( makeTask({ diff --git a/packages/dashboard/app/utils/taskProgress.ts b/packages/dashboard/app/utils/taskProgress.ts index 173c00410f..71f249c30c 100644 --- a/packages/dashboard/app/utils/taskProgress.ts +++ b/packages/dashboard/app/utils/taskProgress.ts @@ -59,12 +59,32 @@ function isCompleted(status: UnifiedTaskProgressStatus): boolean { return status === "done" || status === "skipped" || status === "advisory_failure"; } +/* +FNXC:WorkflowStepResults 2026-06-26-16:30: +An enabled-but-not-yet-run workflow step has no recorded result yet, so there is no +`workflowStepName` to show. Rather than render the raw graph node id (e.g. `code-review`, +`browser-verification`), humanize it into a Title Case label ("Code Review", +"Browser Verification"). Once the graph records the step it carries the workflow's exact +`config.name`, which always wins; humanization is only the pre-run fallback. The UI must +show proper casing for workflow steps (e.g. "Code Review"), never the lowercase hyphenated id. +*/ +function humanizeWorkflowStepId(workflowStepId: string): string { + const words = workflowStepId + .replace(/^plugin:/, "") + .split(/[-_:\s]+/) + .filter(Boolean); + if (words.length === 0) return workflowStepId; + return words + .map((w) => (/^(ux|ui|qa|ai|api|pr|id)$/i.test(w) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1))) + .join(" "); +} + function resolveWorkflowStepName(workflowStepId: string, result: WorkflowStepResult | undefined): string { const resultName = result?.workflowStepName?.trim(); if (resultName) { return resultName; } - return workflowStepId; + return humanizeWorkflowStepId(workflowStepId); } export function getUnifiedTaskProgress(