From 9464f31cafa0e3a8b7bf4a51b1bff2b66acc02df Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 1 Jul 2026 23:15:12 -0700 Subject: [PATCH] FN-7406: fix All workflows dropdown counts Ensure All workflows dropdown counters report the shared aggregate without double-counting tasks. - Reuse computeWorkflowStatusCounts directly in Board instead of re-summing a map that already includes the All workflows sentinel. - Document the aggregate count invariant and add a published patch changeset. - Extend Board, List, header, graph, and workflow count tests to cover exact All workflows totals. Files changed: .changeset/fn-7406-workflow-dropdown-counts.md | 7 +++ docs/dashboard-guide.md | 5 +- packages/dashboard/app/components/Board.tsx | 17 +++--- .../app/components/__tests__/Board.test.tsx | 56 ++++++++++++++++++- .../__tests__/GraphWorkflowSwitcherSlot.test.tsx | 8 ++- .../__tests__/HeaderWorkflowSwitcherSlot.test.tsx | 8 ++- .../app/components/__tests__/ListView.test.tsx | 62 ++++++++++++++++++++++ .../__tests__/workflowStatusCounts.test.ts | 48 +++++++++++++++-- 8 files changed, 188 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-7406 Fusion-Task-Lineage: bb344c03-c349-47ea-9e8c-c445975f8e02 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7406-workflow-dropdown-counts.md | 7 +++ docs/dashboard-guide.md | 5 +- packages/dashboard/app/components/Board.tsx | 17 ++--- .../app/components/__tests__/Board.test.tsx | 56 ++++++++++++++++- .../GraphWorkflowSwitcherSlot.test.tsx | 8 ++- .../HeaderWorkflowSwitcherSlot.test.tsx | 8 ++- .../components/__tests__/ListView.test.tsx | 62 +++++++++++++++++++ .../__tests__/workflowStatusCounts.test.ts | 48 ++++++++++++-- 8 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 .changeset/fn-7406-workflow-dropdown-counts.md diff --git a/.changeset/fn-7406-workflow-dropdown-counts.md b/.changeset/fn-7406-workflow-dropdown-counts.md new file mode 100644 index 0000000000..00978869ba --- /dev/null +++ b/.changeset/fn-7406-workflow-dropdown-counts.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix the All workflows dropdown counter so it no longer double-counts tasks. +category: fix +dev: Board now uses the shared workflow status count aggregate directly, with regression coverage for Board/List and header/graph selector count behavior. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 3969f410bb..661bcaa97a 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -159,9 +159,10 @@ FNXC:TaskCardMobileSelection 2026-07-01-00:00: Mobile Board long-press is a task FNXC:WorkflowSelection 2026-06-30-00:00: The view preference persists either a real workflow id or the All workflows sentinel so refresh/remount restores the operator's last top-level workflow context without treating the sentinel as a backend workflow id. FNXC:WorkflowSelection 2026-07-01-00:00: All workflows is available on Board, List, Planning, Missions, and Graph top-level selectors; Planning/Missions task creation receives default/no-specific-workflow behavior instead of the sentinel. --> - + -- Board and List workflow switchers use a themed dropdown instead of a native select. The closed trigger shows the workflow identity (Fusion icon for built-ins, optional custom icon for custom workflows), name, and chevron only; compact Todo / In Progress / Done counts derived from workflow column flags (excluding archived and board-hidden columns) refresh each time the dropdown opens and appear while the dropdown is expanded, including on each workflow option. Built-in lanes with synthesized trait-less lifecycle columns fall back to canonical column ids (`todo`, `in-progress`, `done`, and `archived`) for those counts. Board and List also show **All workflows** before real workflows as a dashboard-only aggregate view with combined counts and a deterministic union of visible workflow columns; shared column ids use the default workflow label/flags when available, otherwise the first workflow definition that declares the column. Hidden columns stay workflow-scoped in the aggregate: a task whose effective workflow hides a shared column is omitted from that aggregate column even if another workflow exposes the same column id. That option is not editable, persists as top-level workflow view state, and quick-create/Plan/Subtask/Mission handoffs translate it to a real default workflow id or no-specific-workflow behavior so task creation never sends the sentinel. Each real workflow option row also exposes an inline edit action, and a persistent **New workflow** footer stays visible below the scrollable option list. The open listbox grows from the longest workflow name plus its count/edit decorations while remaining viewport-bounded; the closed trigger stays narrow and ellipsized. Those inline count badges intentionally use the same board column color tokens as cards: `--todo`, `--in-progress`, and `--done`. +- Board and List workflow switchers use a themed dropdown instead of a native select. The closed trigger shows the workflow identity (Fusion icon for built-ins, optional custom icon for custom workflows), name, and chevron only; compact Todo / In Progress / Done counts derived from workflow column flags (excluding archived and board-hidden columns) refresh each time the dropdown opens and appear while the dropdown is expanded, including on each workflow option. Built-in lanes with synthesized trait-less lifecycle columns fall back to canonical column ids (`todo`, `in-progress`, `done`, and `archived`) for those counts. Board and List also show **All workflows** before real workflows as a dashboard-only aggregate view with combined counts that sum only the real visible workflow rows exactly once and a deterministic union of visible workflow columns; shared column ids use the default workflow label/flags when available, otherwise the first workflow definition that declares the column. Hidden columns stay workflow-scoped in the aggregate: a task whose effective workflow hides a shared column is omitted from that aggregate column even if another workflow exposes the same column id. That option is not editable, persists as top-level workflow view state, and quick-create/Plan/Subtask/Mission handoffs translate it to a real default workflow id or no-specific-workflow behavior so task creation never sends the sentinel. Each real workflow option row also exposes an inline edit action, and a persistent **New workflow** footer stays visible below the scrollable option list. The open listbox grows from the longest workflow name plus its count/edit decorations while remaining viewport-bounded; the closed trigger stays narrow and ellipsized. Those inline count badges intentionally use the same board column color tokens as cards: `--todo`, `--in-progress`, and `--done`. - When workflow columns are enabled, Board and List hydrate the last successful workflow-lane payload from a per-project session cache; cold loads show a neutral skeleton until settings and workflow metadata are known, avoiding a legacy single-lane flash. The selected workflow is remembered per project in durable browser storage and restored when returning to Board/List after task refreshes, route changes, respecification flows, or refinement creation from Task Detail and done-task chat; Board, List, Planning/Missions header selectors, and Graph also restore the dashboard-only **All workflows** aggregate view when that was the last selected top-level workflow context. If a saved real workflow is later deleted, Fusion falls back to a valid default/first workflow so tasks remain visible. - Briefly leaving Board/List for a task detail or another non-task-SSE view preserves the current in-memory task snapshot. Returning to Board/List reuses that fresh snapshot immediately and restores live SSE updates without an extra all-task fetch; Fusion still runs one catch-up fetch when task data is missing, stale, or from a failed refresh. diff --git a/packages/dashboard/app/components/Board.tsx b/packages/dashboard/app/components/Board.tsx index 9c5dcdaba6..93171dd429 100644 --- a/packages/dashboard/app/components/Board.tsx +++ b/packages/dashboard/app/components/Board.tsx @@ -13,7 +13,7 @@ import { MOBILE_MEDIA_QUERY, useViewportMode } from "../hooks/useViewportMode"; import { recordResumeEvent } from "../utils/resumeInstrumentation"; import { getBoardCanDropTaskRejection } from "./boardCanDropTask"; import { WorkflowSwitcher } from "./WorkflowSwitcher"; -import { computeWorkflowStatusCounts, type WorkflowStatusCounts } from "./workflowStatusCounts"; +import { computeWorkflowStatusCounts } from "./workflowStatusCounts"; import { writeBoardWorkflowsCache } from "../utils/boardWorkflowsCache"; import { useBoardWorkflows } from "../hooks/useBoardWorkflows"; import { @@ -400,16 +400,11 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o const getDraggingTaskId = useCallback(() => draggingTaskIdRef.current, []); const workflowStatusCounts = useMemo(() => { - const counts = computeWorkflowStatusCounts(tasks, boardWorkflows); - const aggregateCounts: WorkflowStatusCounts = { todo: 0, inProgress: 0, done: 0, merging: 0 }; - for (const workflowCounts of counts.values()) { - aggregateCounts.todo += workflowCounts.todo; - aggregateCounts.inProgress += workflowCounts.inProgress; - aggregateCounts.done += workflowCounts.done; - aggregateCounts.merging += workflowCounts.merging; - } - counts.set(ALL_WORKFLOWS_BOARD_VIEW_ID, aggregateCounts); - return counts; + /* + FNXC:WorkflowSwitcher 2026-07-01-23:04: + computeWorkflowStatusCounts already owns the dashboard-only All workflows aggregate sentinel. Board must pass the helper result through directly instead of re-summing every map entry, because the map includes the sentinel and summing it again doubles the dropdown aggregate row. + */ + return computeWorkflowStatusCounts(tasks, boardWorkflows); }, [boardWorkflows, tasks]); useEffect(() => { diff --git a/packages/dashboard/app/components/__tests__/Board.test.tsx b/packages/dashboard/app/components/__tests__/Board.test.tsx index 1cee9c8ec8..80096fca84 100644 --- a/packages/dashboard/app/components/__tests__/Board.test.tsx +++ b/packages/dashboard/app/components/__tests__/Board.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; +import { render, screen, fireEvent, waitFor, act, within } from "@testing-library/react"; import { Board } from "../Board"; import { COLUMNS } from "@fusion/core"; import { ALL_WORKFLOWS_BOARD_VIEW_ID, BOARD_WORKFLOW_SELECTION_STORAGE_KEY } from "../../utils/boardWorkflowSelection"; @@ -1472,6 +1472,60 @@ describe("Board", () => { } }); + it("renders the all-workflows dropdown count as a single aggregate of real workflow rows", async () => { + enableFlag( + { + "FN-default-todo": "builtin:coding", + "FN-default-active": "builtin:coding", + "FN-default-done": "builtin:coding", + "FN-custom-todo": "wf-custom", + "FN-custom-done": "wf-custom", + "FN-stale": "wf-deleted", + "FN-hidden": "builtin:coding", + "FN-archived": "builtin:coding", + }, + [ + { + ...DEFAULT_WORKFLOW, + columns: [ + ...DEFAULT_WORKFLOW.columns, + { id: "quiet", name: "Quiet", flags: { hiddenFromBoard: true } }, + ], + }, + { + ...CUSTOM_WORKFLOW, + name: "Coding", + }, + ], + ); + renderBoard({ + tasks: [ + mkTask({ id: "FN-default-todo", column: "todo" }), + mkTask({ id: "FN-default-active", column: "in-progress", status: "merging" }), + mkTask({ id: "FN-default-done", column: "done" }), + mkTask({ id: "FN-custom-todo", column: "intake" }), + mkTask({ id: "FN-custom-done", column: "done" }), + mkTask({ id: "FN-stale", column: "todo" }), + mkTask({ id: "FN-hidden", column: "quiet" }), + mkTask({ id: "FN-archived", column: "archived" }), + ], + }); + + await openWorkflowSwitcher(); + const aggregateOption = screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`); + expect(aggregateOption).toHaveTextContent("All workflows"); + expect(within(aggregateOption).getByTitle("Todo: 3")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("In Progress: 1")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("Done: 2")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("1 merging")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("Todo: 2")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("In Progress: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("Done: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("Todo: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("In Progress: 0")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("Done: 1")).toBeInTheDocument(); + }); + it("renders one selected workflow at a time and switches workflows from the dropdown", async () => { const onCreateWorkflow = vi.fn(); const onOpenWorkflowEditor = vi.fn(); diff --git a/packages/dashboard/app/components/__tests__/GraphWorkflowSwitcherSlot.test.tsx b/packages/dashboard/app/components/__tests__/GraphWorkflowSwitcherSlot.test.tsx index fcfdb2ab8f..ab1492dc91 100644 --- a/packages/dashboard/app/components/__tests__/GraphWorkflowSwitcherSlot.test.tsx +++ b/packages/dashboard/app/components/__tests__/GraphWorkflowSwitcherSlot.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { BoardWorkflowDefinition, BoardWorkflowsPayload } from "../../api"; import { ALL_WORKFLOWS_BOARD_VIEW_ID } from "../../utils/boardWorkflowSelection"; @@ -220,7 +220,11 @@ describe("GraphWorkflowSwitcherSlot", () => { render(); fireEvent.click(await screen.findByTestId("workflow-switcher")); - expect(screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)).toHaveTextContent("All workflows"); + const aggregateOption = screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`); + expect(aggregateOption).toHaveTextContent("All workflows"); + expect(within(aggregateOption).getByTitle("Todo: 0")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("In Progress: 0")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("Done: 0")).toBeInTheDocument(); expect(screen.queryByTestId(`workflow-switcher-edit-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)).toBeNull(); fireEvent.click(screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)); diff --git a/packages/dashboard/app/components/__tests__/HeaderWorkflowSwitcherSlot.test.tsx b/packages/dashboard/app/components/__tests__/HeaderWorkflowSwitcherSlot.test.tsx index f994d0bf67..dd3ed3bb28 100644 --- a/packages/dashboard/app/components/__tests__/HeaderWorkflowSwitcherSlot.test.tsx +++ b/packages/dashboard/app/components/__tests__/HeaderWorkflowSwitcherSlot.test.tsx @@ -4,7 +4,7 @@ The shared header workflow slot is the canonical desktop workflow selector for P */ import type { ReactNode } from "react"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { BoardWorkflowDefinition, BoardWorkflowsPayload } from "../../api"; import { HeaderWorkflowSwitcherSlot, type HeaderWorkflowSelection } from "../HeaderWorkflowSwitcherSlot"; @@ -144,7 +144,11 @@ describe("HeaderWorkflowSwitcherSlot", () => { ); fireEvent.click(await screen.findByTestId("workflow-switcher")); - expect(screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)).toHaveTextContent("All workflows"); + const aggregateOption = screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`); + expect(aggregateOption).toHaveTextContent("All workflows"); + expect(within(aggregateOption).getByTitle("Todo: 0")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("In Progress: 0")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("Done: 0")).toBeInTheDocument(); expect(screen.queryByTestId(`workflow-switcher-edit-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)).toBeNull(); fireEvent.click(screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`)); diff --git a/packages/dashboard/app/components/__tests__/ListView.test.tsx b/packages/dashboard/app/components/__tests__/ListView.test.tsx index d6406df390..3273157ca1 100644 --- a/packages/dashboard/app/components/__tests__/ListView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ListView.test.tsx @@ -1201,6 +1201,68 @@ describe("ListView", () => { mobileSpy.mockRestore(); }); + it("shows the same single aggregate all-workflows dropdown count in ListView", async () => { + vi.mocked(fetchBoardWorkflows).mockResolvedValue({ + flagEnabled: true, + defaultWorkflowId: "builtin:coding", + workflows: [ + { + id: "builtin:coding", + name: "Coding", + columns: [ + { id: "triage", name: "Triage", flags: { intake: true } }, + { id: "in-progress", name: "In Progress", flags: { countsTowardWip: true } }, + { id: "done", name: "Done", flags: { complete: true } }, + { id: "archived", name: "Archived", flags: { archived: true } }, + ], + }, + { + id: "wf-custom", + name: "Coding", + columns: [ + { id: "backlog", name: "Backlog", flags: { intake: true } }, + { id: "complete", name: "Complete", flags: { complete: true } }, + { id: "hidden", name: "Hidden", flags: { hiddenFromBoard: true } }, + ], + }, + ], + taskWorkflowIds: { + "FN-001": "builtin:coding", + "FN-002": "builtin:coding", + "FN-003": "wf-custom", + "FN-004": "wf-custom", + "FN-005": "missing-workflow", + "FN-006": "wf-custom", + "FN-007": "builtin:coding", + }, + }); + + renderListView({ + tasks: [ + createMockTask({ id: "FN-001", column: "triage", title: "Coding todo" }), + createMockTask({ id: "FN-002", column: "in-progress", title: "Coding active", status: "merging" }), + createMockTask({ id: "FN-003", column: "backlog", title: "Custom todo" }), + createMockTask({ id: "FN-004", column: "complete", title: "Custom done" }), + createMockTask({ id: "FN-005", column: "done", title: "Stale done" }), + createMockTask({ id: "FN-006", column: "hidden", title: "Hidden custom" }), + createMockTask({ id: "FN-007", column: "archived", title: "Archived coding" }), + ], + }); + + await openWorkflowSwitcher(); + const aggregateOption = screen.getByTestId(`workflow-switcher-option-${ALL_WORKFLOWS_BOARD_VIEW_ID}`); + expect(within(aggregateOption).getByTitle("Todo: 2")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("In Progress: 1")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("Done: 2")).toBeInTheDocument(); + expect(within(aggregateOption).getByTitle("1 merging")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("Todo: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("In Progress: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-builtin:coding")).getByTitle("Done: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("Todo: 1")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("In Progress: 0")).toBeInTheDocument(); + expect(within(screen.getByTestId("workflow-switcher-option-wf-custom")).getByTitle("Done: 1")).toBeInTheDocument(); + }); + it("shows all workflows in ListView without submitting the aggregate sentinel", async () => { const mockOnQuickCreate = vi.fn().mockResolvedValue({ id: "FN-new" }); vi.mocked(fetchBoardWorkflows).mockResolvedValue({ diff --git a/packages/dashboard/app/components/__tests__/workflowStatusCounts.test.ts b/packages/dashboard/app/components/__tests__/workflowStatusCounts.test.ts index a7c929db73..c807f1ea79 100644 --- a/packages/dashboard/app/components/__tests__/workflowStatusCounts.test.ts +++ b/packages/dashboard/app/components/__tests__/workflowStatusCounts.test.ts @@ -5,6 +5,7 @@ import { type Task, } from "@fusion/core"; import type { BoardWorkflowColumn, BoardWorkflowsPayload } from "../../api"; +import { ALL_WORKFLOWS_BOARD_VIEW_ID } from "../../utils/boardWorkflowSelection"; import { computeWorkflowStatusCounts } from "../workflowStatusCounts"; const boardWorkflows: BoardWorkflowsPayload = { @@ -110,12 +111,13 @@ describe("computeWorkflowStatusCounts", () => { expect(computeWorkflowStatusCounts(undefined, undefined).size).toBe(0); }); - it("initializes every workflow with zero counts for empty and duplicate/populated states", () => { + it("initializes every workflow and the aggregate sentinel with zero counts for empty and duplicate/populated states", () => { const counts = computeWorkflowStatusCounts([], boardWorkflows); expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); expect(counts.get("design")).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); expect(counts.get("empty")).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); + expect(counts.get(ALL_WORKFLOWS_BOARD_VIEW_ID)).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); }); it("classifies todo, in-progress, and done buckets from workflow column flags", () => { @@ -176,25 +178,61 @@ describe("computeWorkflowStatusCounts", () => { expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 1, merging: 0 }); }); - it("counts tasks independently for their assigned workflow", () => { + it("counts tasks independently for their assigned workflow and aggregates real rows exactly once", () => { + const duplicateNameWorkflow = { + id: "design-copy", + name: "Design", + columns: [ + { id: "copy-todo", name: "Todo", flags: { intake: true } }, + { id: "copy-active", name: "Active", flags: { countsTowardWip: true } }, + { id: "copy-done", name: "Done", flags: { complete: true } }, + ], + }; const counts = computeWorkflowStatusCounts( [ + task("FN-default-todo", "todo"), + task("FN-missing-assignment", "ready"), + task("FN-stale-assignment", "done"), + task("FN-hidden", "quiet"), + task("FN-archived", "archived"), + task("FN-unknown-column", "missing"), task("FN-design-todo", "design-todo"), - task("FN-design-active", "design-active"), + taskWithStatus("FN-design-active", "design-active", "merging"), task("FN-design-done", "design-done"), + taskWithStatus("FN-copy-active", "copy-active", "merging-fix"), ], { ...boardWorkflows, + workflows: [ + { + ...boardWorkflows.workflows[0], + columns: [ + ...boardWorkflows.workflows[0].columns, + { id: "quiet", name: "Quiet", flags: { hiddenFromBoard: true } }, + ], + }, + boardWorkflows.workflows[1], + duplicateNameWorkflow, + boardWorkflows.workflows[2], + ], taskWorkflowIds: { + "FN-stale-assignment": "deleted-workflow", + "FN-hidden": "default", + "FN-archived": "default", + "FN-unknown-column": "design", "FN-design-todo": "design", "FN-design-active": "design", "FN-design-done": "design", + "FN-copy-active": "design-copy", }, } ); - expect(counts.get("design")).toEqual({ todo: 1, inProgress: 1, done: 1, merging: 0 }); - expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); + expect(counts.get("default")).toEqual({ todo: 2, inProgress: 0, done: 1, merging: 0 }); + expect(counts.get("design")).toEqual({ todo: 1, inProgress: 1, done: 1, merging: 1 }); + expect(counts.get("design-copy")).toEqual({ todo: 0, inProgress: 1, done: 0, merging: 1 }); + expect(counts.get("empty")).toEqual({ todo: 0, inProgress: 0, done: 0, merging: 0 }); + expect(counts.get(ALL_WORKFLOWS_BOARD_VIEW_ID)).toEqual({ todo: 3, inProgress: 2, done: 2, merging: 2 }); }); it("tracks actively merging tasks per workflow separately from bucket counts", () => {