From e172b136126a7b5f8a5267ab444013e06d47b1c6 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 15 Jul 2026 10:39:57 -0700 Subject: [PATCH] fix(dashboard): never show raw landing/reviewing on task status badges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Share one badge label mapper for board and list so AI-merge pipeline statuses always display as Merging… instead of engine strings. --- .../dashboard/app/components/ListView.tsx | 8 +++++-- .../dashboard/app/components/TaskCard.tsx | 11 ++------- .../__tests__/taskStatusBadgeLabel.test.ts | 23 +++++++++++++++++++ .../app/utils/taskStatusBadgeLabel.ts | 20 ++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 packages/dashboard/app/utils/__tests__/taskStatusBadgeLabel.test.ts create mode 100644 packages/dashboard/app/utils/taskStatusBadgeLabel.ts diff --git a/packages/dashboard/app/components/ListView.tsx b/packages/dashboard/app/components/ListView.tsx index 1cb76240e8..ed1c06dc77 100644 --- a/packages/dashboard/app/components/ListView.tsx +++ b/packages/dashboard/app/components/ListView.tsx @@ -22,6 +22,7 @@ import { useViewportMode } from "../hooks/useViewportMode"; import { getScopedItem, removeScopedItem, setScopedItem } from "../utils/projectStorage"; import { ALL_WORKFLOWS_BOARD_VIEW_ID } from "../utils/boardWorkflowSelection"; import { getUnifiedTaskProgress, isPlanReviewRunning } from "../utils/taskProgress"; +import { getTaskStatusBadgeLabel } from "../utils/taskStatusBadgeLabel"; import { useConfirm } from "../hooks/useConfirm"; import { extractDependencyDeleteConflict, extractLineageDeleteConflict } from "../utils/taskDelete"; import { WorkflowSwitcher } from "./WorkflowSwitcher"; @@ -76,9 +77,12 @@ function isListContextInteractiveTarget(target: EventTarget | null): boolean { type SortField = "title" | "status" | "column" | "retries"; +/* +FNXC:MergeQueue 2026-07-15-10:45: +List status column used to print raw engine statuses (landing/reviewing). Share the board badge mapper so list and card never diverge. +*/ function getTaskStatusLabel(status: string, t: TFunction<"app">): string { - if (status === "merging-fix") return t("listView.statusMergingFix", "Merging fixes…"); - return status; + return getTaskStatusBadgeLabel(status, t); } type SortDirection = "asc" | "desc"; diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx index 93d871ab2c..3ad6c508d2 100644 --- a/packages/dashboard/app/components/TaskCard.tsx +++ b/packages/dashboard/app/components/TaskCard.tsx @@ -42,6 +42,7 @@ import { getTaskAgeStalenessCopy, shouldShowTaskAgeStalenessBadge } from "../uti import { getUnifiedTaskProgress, isPlanReviewRunning } from "../utils/taskProgress"; import { getPrBadgeModifierClass } from "../utils/prBadgeClass"; import { getActiveRuntimeMs, getEndToEndDurationMs, getTimedDurationMs, getWorkflowRuntimeMs, parseTimestampToMs } from "../utils/taskTiming"; +import { getTaskStatusBadgeLabel } from "../utils/taskStatusBadgeLabel"; import { canStartPrFeedbackAddressing, getTaskPrimaryPrInfo } from "../utils/prFeedback"; import type { ToastType } from "../hooks/useToast"; import { useConfirm } from "../hooks/useConfirm"; @@ -295,15 +296,7 @@ const TIME_INDICATOR_COLUMNS = new Set([ const LIVE_TIME_INDICATOR_POLL_MS = 30_000; function getTaskStatusLabel(status: string, t: TFunction<"app">): string { - if (status === "merging-fix") return t("tasks.statusMergingFix", "Merging fixes…"); - /* - FNXC:MergeQueue 2026-07-15-10:40: - Operators expect a "merging" badge while AI merge owns the pump. Map reviewing/landing to the same Merging… label so the board does not look idle during the long review and land phases. - */ - if (status === "reviewing" || status === "landing" || status === "merging" || status === "merging-pr") { - return t("tasks.statusMerging", "Merging…"); - } - return status; + return getTaskStatusBadgeLabel(status, t); } function getDoneCompletionMs(task: Task): number | null { diff --git a/packages/dashboard/app/utils/__tests__/taskStatusBadgeLabel.test.ts b/packages/dashboard/app/utils/__tests__/taskStatusBadgeLabel.test.ts new file mode 100644 index 0000000000..c76ef655c6 --- /dev/null +++ b/packages/dashboard/app/utils/__tests__/taskStatusBadgeLabel.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import type { TFunction } from "i18next"; +import { getTaskStatusBadgeLabel } from "../taskStatusBadgeLabel"; + +const t = ((key: string, fallback?: string) => fallback ?? key) as TFunction<"app">; + +describe("getTaskStatusBadgeLabel", () => { + it("maps the full AI merge pipeline to Merging…", () => { + for (const status of ["merging", "merging-pr", "reviewing", "landing"]) { + expect(getTaskStatusBadgeLabel(status, t)).toBe("Merging…"); + } + }); + + it("keeps merging-fix distinct", () => { + expect(getTaskStatusBadgeLabel("merging-fix", t)).toBe("Merging fixes…"); + }); + + it("passes through non-merge statuses", () => { + expect(getTaskStatusBadgeLabel("planning", t)).toBe("planning"); + expect(getTaskStatusBadgeLabel("failed", t)).toBe("failed"); + expect(getTaskStatusBadgeLabel(null, t)).toBe(""); + }); +}); diff --git a/packages/dashboard/app/utils/taskStatusBadgeLabel.ts b/packages/dashboard/app/utils/taskStatusBadgeLabel.ts new file mode 100644 index 0000000000..5ed384b16a --- /dev/null +++ b/packages/dashboard/app/utils/taskStatusBadgeLabel.ts @@ -0,0 +1,20 @@ +/* +FNXC:MergeQueue 2026-07-15-10:45: +AI merge sets task.status to reviewing/landing for most of the live merge window. Board/list badges must never show those raw engine strings; map the full active-merge pipeline to operator-facing Merging… (and Merging fixes… for merging-fix). +*/ +import type { TFunction } from "i18next"; +import { isActiveMergeStatus } from "../../../core/src/active-merge-status"; + +export function getTaskStatusBadgeLabel( + status: string | null | undefined, + t: TFunction<"app">, +): string { + if (!status) return ""; + if (status === "merging-fix") { + return t("tasks.statusMergingFix", "Merging fixes…"); + } + if (isActiveMergeStatus(status)) { + return t("tasks.statusMerging", "Merging…"); + } + return status; +}