FN-6806: show workflow counts only when expanded
Keep workflow dropdown triggers compact until the menu opens. - Hide Todo, In Progress, and Done count badges from the collapsed workflow switcher trigger. - Preserve active-workflow and option counts while the dropdown is expanded for comparison. - Update Board, List, switcher tests, and dashboard docs for the expanded-only count behavior. Files changed: docs/dashboard-guide.md | 2 +- .../dashboard/app/components/WorkflowSwitcher.tsx | 8 +++-- .../app/components/__tests__/Board.test.tsx | 3 +- .../app/components/__tests__/ListView.test.tsx | 5 ++- .../components/__tests__/WorkflowSwitcher.test.tsx | 36 +++++++++++++++++----- 5 files changed, 41 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-6806 Fusion-Task-Lineage: a3c2db33-fe08-4207-9aae-548b292d98bd
This commit is contained in:
@@ -77,7 +77,7 @@ Features:
|
||||
- Task card header meta badges group priority, fast mode, agent-created provenance, and elapsed/created-time chips into one wrapping row; agent labels prefer `sourceMetadata.agentName` over raw agent IDs
|
||||
- Column ordering semantics: `todo` mirrors scheduler pickup order (priority descending, then oldest `createdAt`, then task ID); `triage`, `in-progress`, `in-review`, and `archived` remain priority-first with task-ID tie-breaks; `done` is ordered by most recent completion first (`columnMovedAt`, then `updatedAt`, then `createdAt` fallback)
|
||||
- On mobile, both default and workflow-mode boards fill the project viewport while the column strip remains the internal horizontal scroller with contained edge overscroll.
|
||||
- Board and List workflow switchers use a themed dropdown instead of a native select. The closed trigger and each workflow option show compact Todo / In Progress / Done counts derived from workflow column flags, excluding archived columns.
|
||||
- Board and List workflow switchers use a themed dropdown instead of a native select. The closed trigger shows the workflow name and chevron only; compact Todo / In Progress / Done counts derived from workflow column flags (excluding archived columns) appear while the dropdown is expanded, including on each workflow option.
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
@@ -32,6 +32,10 @@ function getCounts(counts: Map<string, WorkflowStatusCounts>, workflowId: string
|
||||
* FNXC:WorkflowSwitcher 2026-06-20-00:09:
|
||||
* The board/list workflow switcher must be a fully rendered themed dropdown rather than a native select so each workflow option can include compact inline Todo, In Progress, and Done counts.
|
||||
* The component owns only presentation and accessible dropdown behavior; all status-bucket semantics stay in computeWorkflowStatusCounts so Board and ListView cannot drift.
|
||||
*
|
||||
* FNXC:WorkflowSwitcher 2026-06-20-00:31:
|
||||
* Counts are contextual detail, so the collapsed trigger must stay visually and accessibly scoped to the active workflow name plus chevron.
|
||||
* Render Todo, In Progress, and Done counts only while the dropdown is expanded; option rows keep their count text because the listbox is the comparison surface.
|
||||
*/
|
||||
export function WorkflowSwitcher({ workflows, value, onChange, counts, label: labelProp }: WorkflowSwitcherProps) {
|
||||
const { t } = useTranslation("app");
|
||||
@@ -259,8 +263,8 @@ export function WorkflowSwitcher({ workflows, value, onChange, counts, label: la
|
||||
>
|
||||
<span className="workflow-switcher-trigger-main">
|
||||
<span className="workflow-switcher-current-name">{selectedWorkflow.name}</span>
|
||||
{renderCountBadges(selectedCounts, "trigger")}
|
||||
{renderAccessibleCounts(selectedCounts)}
|
||||
{isOpen ? renderCountBadges(selectedCounts, "trigger") : null}
|
||||
{isOpen ? renderAccessibleCounts(selectedCounts) : null}
|
||||
</span>
|
||||
<ChevronDown className="workflow-switcher-chevron" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -1114,8 +1114,9 @@ describe("Board", () => {
|
||||
});
|
||||
const selector = await screen.findByTestId("workflow-switcher");
|
||||
expect(selector).toHaveTextContent("Coding");
|
||||
expect(selector).toHaveTextContent("1");
|
||||
expect(selector.querySelector(".workflow-switcher-counts")).toBeNull();
|
||||
await openWorkflowSwitcher();
|
||||
expect(selector).toHaveTextContent("1");
|
||||
expect(screen.getByTestId("workflow-switcher-option-wf-custom")).toHaveTextContent("2");
|
||||
fireEvent.keyDown(selector, { key: "Escape" });
|
||||
expect(workflowToolbarActionNames()).toEqual(["Edit workflows", "New workflow"]);
|
||||
|
||||
@@ -747,8 +747,9 @@ describe("ListView", () => {
|
||||
|
||||
const desktopTrigger = await screen.findByTestId("workflow-switcher");
|
||||
expect(desktopTrigger).toHaveTextContent("Coding");
|
||||
expect(desktopTrigger).toHaveTextContent("1");
|
||||
expect(desktopTrigger.querySelector(".workflow-switcher-counts")).toBeNull();
|
||||
await openWorkflowSwitcher();
|
||||
expect(desktopTrigger).toHaveTextContent("1");
|
||||
expect(screen.getByTestId("workflow-switcher-option-wf-custom")).toHaveTextContent("1");
|
||||
fireEvent.keyDown(desktopTrigger, { key: "Escape" });
|
||||
desktop.unmount();
|
||||
@@ -765,6 +766,8 @@ describe("ListView", () => {
|
||||
|
||||
const mobileTrigger = await screen.findByTestId("workflow-switcher");
|
||||
expect(mobileTrigger).toHaveTextContent("Coding");
|
||||
expect(mobileTrigger.querySelector(".workflow-switcher-counts")).toBeNull();
|
||||
await openWorkflowSwitcher();
|
||||
expect(mobileTrigger).toHaveTextContent("1");
|
||||
mobileSpy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ function countMap(entries: Array<[string, WorkflowStatusCounts]> = []) {
|
||||
}
|
||||
|
||||
describe("WorkflowSwitcher", () => {
|
||||
it("renders the active workflow with compact inline counts", () => {
|
||||
it("renders the active workflow without compact counts while collapsed", () => {
|
||||
render(
|
||||
<WorkflowSwitcher
|
||||
workflows={workflows}
|
||||
@@ -34,9 +34,10 @@ describe("WorkflowSwitcher", () => {
|
||||
|
||||
const trigger = screen.getByTestId("workflow-switcher");
|
||||
expect(trigger).toHaveTextContent("Coding");
|
||||
expect(trigger).toHaveTextContent("3");
|
||||
expect(trigger).toHaveTextContent("1");
|
||||
expect(trigger).toHaveTextContent("5");
|
||||
expect(within(trigger).queryByText("3", { selector: ".workflow-switcher-count--todo" })).not.toBeInTheDocument();
|
||||
expect(within(trigger).queryByText("1", { selector: ".workflow-switcher-count--in-progress" })).not.toBeInTheDocument();
|
||||
expect(within(trigger).queryByText("5", { selector: ".workflow-switcher-count--done" })).not.toBeInTheDocument();
|
||||
expect(trigger.querySelector(".workflow-switcher-counts--trigger")).toBeNull();
|
||||
expect(trigger).toHaveAccessibleName("Select workflow. Current workflow: Coding");
|
||||
});
|
||||
|
||||
@@ -79,12 +80,31 @@ describe("WorkflowSwitcher", () => {
|
||||
expect(screen.queryByRole("listbox", { name: "Workflow" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders zero counts for workflows absent from the counts map", () => {
|
||||
render(<WorkflowSwitcher workflows={workflows} value="coding" onChange={vi.fn()} counts={countMap()} />);
|
||||
it("renders populated and zero counts only after the dropdown expands", () => {
|
||||
render(
|
||||
<WorkflowSwitcher
|
||||
workflows={workflows}
|
||||
value="coding"
|
||||
onChange={vi.fn()}
|
||||
counts={countMap([["coding", { todo: 3, inProgress: 1, done: 5 }]])}
|
||||
/>,
|
||||
);
|
||||
|
||||
const trigger = screen.getByTestId("workflow-switcher");
|
||||
expect(trigger.querySelector(".workflow-switcher-counts")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(trigger);
|
||||
|
||||
expect(within(trigger).getByText("3", { selector: ".workflow-switcher-count--todo" })).toBeInTheDocument();
|
||||
expect(within(trigger).getByText("1", { selector: ".workflow-switcher-count--in-progress" })).toBeInTheDocument();
|
||||
expect(within(trigger).getByText("5", { selector: ".workflow-switcher-count--done" })).toBeInTheDocument();
|
||||
|
||||
const codingOption = screen.getByTestId("workflow-switcher-option-coding");
|
||||
expect(within(codingOption).getByText("3", { selector: ".workflow-switcher-count--todo" })).toBeInTheDocument();
|
||||
expect(within(codingOption).getByText("1", { selector: ".workflow-switcher-count--in-progress" })).toBeInTheDocument();
|
||||
expect(within(codingOption).getByText("5", { selector: ".workflow-switcher-count--done" })).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId("workflow-switcher"));
|
||||
const designOption = screen.getByTestId("workflow-switcher-option-design");
|
||||
|
||||
expect(within(designOption).getByText("0", { selector: ".workflow-switcher-count--todo" })).toBeInTheDocument();
|
||||
expect(within(designOption).getByText("0", { selector: ".workflow-switcher-count--in-progress" })).toBeInTheDocument();
|
||||
expect(within(designOption).getByText("0", { selector: ".workflow-switcher-count--done" })).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user