feat(FN-3365): pass workspace verification gates in board component

Fix FN-3365 completes Step 3 of workspace verification gates by making a minor adjustment to the Board component in the dashboard, likely correcting a validation or boundary condition that was previously failing.

Fusion-Task-Id: FN-3365
This commit is contained in:
Fusion
2026-05-04 13:27:24 -07:00
committed by gsxdsm
parent 89da1de311
commit e1ac4b6e59
5 changed files with 50 additions and 36 deletions

View File

@@ -1,5 +1,6 @@
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput, TaskPriority } from "@fusion/core";
import { COLUMNS, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY } from "@fusion/core";
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput } from "@fusion/core";
import { COLUMNS } from "@fusion/core";
import { compareTaskPriority, sortTasksByPriorityThenAgeAndId } from "../../../core/src/task-priority";
import { Column } from "./Column";
import type { ToastType } from "../hooks/useToast";
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
@@ -53,24 +54,6 @@ interface BoardProps {
lastFetchTimeMs?: number;
}
const PRIORITY_RANK: Record<TaskPriority, number> = {
low: 0,
normal: 1,
high: 2,
urgent: 3,
};
function normalizePriority(priority: unknown): TaskPriority {
if (typeof priority === "string" && (TASK_PRIORITIES as readonly string[]).includes(priority)) {
return priority as TaskPriority;
}
return DEFAULT_TASK_PRIORITY;
}
function compareTaskPriority(a: unknown, b: unknown): number {
return PRIORITY_RANK[normalizePriority(b)] - PRIORITY_RANK[normalizePriority(a)];
}
function compareTaskIdNumeric(a: string, b: string): number {
const aNum = Number.parseInt(a.slice(a.lastIndexOf("-") + 1), 10);
const bNum = Number.parseInt(b.slice(b.lastIndexOf("-") + 1), 10);
@@ -89,6 +72,11 @@ function getDoneSortTimestamp(task: Task): number {
}
function sortTasksForColumn(tasks: Task[], column: ColumnType): Task[] {
if (column === "todo") {
// Match scheduler pickup order: priority DESC, createdAt ASC, id ASC.
return sortTasksByPriorityThenAgeAndId(tasks);
}
return [...tasks].sort((a, b) => {
if (column === "done") {
const timestampCmp = getDoneSortTimestamp(b) - getDoneSortTimestamp(a);
@@ -109,8 +97,7 @@ function sortTasksForColumn(tasks: Task[], column: ColumnType): Task[] {
}
}
// Primary sort for non-done columns: priority descending (urgent → high → normal → low).
// compareTaskPriority normalizes missing/invalid values to `normal`.
// Primary sort for non-done/non-todo columns: priority descending.
const priorityCmp = compareTaskPriority(a.priority, b.priority);
if (priorityCmp !== 0) {
return priorityCmp;

View File

@@ -355,35 +355,35 @@ describe("Board", () => {
expect(doneTasks.map((t: Task) => t.id)).toEqual(["FN-012", "FN-011", "FN-010"]);
});
it("keeps non-done columns priority-ordered even when recency differs", () => {
it("orders todo by priority before age", () => {
const tasks: Task[] = [
createTask({
id: "FN-003",
description: "Low but newest",
description: "Low but oldest",
column: "todo",
priority: "low",
columnMovedAt: "2024-01-01T12:00:00.000Z",
createdAt: "2024-01-01T08:00:00.000Z",
}),
createTask({
id: "FN-001",
description: "Urgent but older",
description: "Urgent but newer",
column: "todo",
priority: "urgent",
columnMovedAt: "2024-01-01T10:00:00.000Z",
createdAt: "2024-01-01T10:00:00.000Z",
}),
createTask({
id: "FN-004",
description: "Normal",
column: "todo",
priority: "normal",
columnMovedAt: "2024-01-01T11:00:00.000Z",
createdAt: "2024-01-01T09:00:00.000Z",
}),
createTask({
id: "FN-002",
description: "High",
column: "todo",
priority: "high",
columnMovedAt: "2024-01-01T09:00:00.000Z",
createdAt: "2024-01-01T07:00:00.000Z",
}),
];
@@ -394,11 +394,37 @@ describe("Board", () => {
expect(todoTasks.map((t: Task) => t.id)).toEqual(["FN-001", "FN-002", "FN-004", "FN-003"]);
});
it("orders same-priority tasks by numeric task ID ascending", () => {
it("orders same-priority todo tasks by oldest createdAt first", () => {
const tasks: Task[] = [
createTask({ id: "FN-050", description: "Fifty", column: "in-progress", priority: "normal" }),
createTask({ id: "FN-010", description: "Ten", column: "in-progress", priority: "normal" }),
createTask({ id: "FN-030", description: "Thirty", column: "in-progress", priority: "normal" }),
createTask({ id: "FN-020", description: "Newest", column: "todo", priority: "high", createdAt: "2024-01-01T12:00:00.000Z" }),
createTask({ id: "FN-021", description: "Oldest", column: "todo", priority: "high", createdAt: "2024-01-01T09:00:00.000Z" }),
createTask({ id: "FN-022", description: "Middle", column: "todo", priority: "high", createdAt: "2024-01-01T10:00:00.000Z" }),
];
renderBoard({ tasks });
const todoTasks = JSON.parse(screen.getByTestId("column-todo").getAttribute("data-tasks") || "[]") as Task[];
expect(todoTasks.map((t: Task) => t.id)).toEqual(["FN-021", "FN-022", "FN-020"]);
});
it("uses task ID as deterministic tie-breaker when todo createdAt matches", () => {
const tasks: Task[] = [
createTask({ id: "FN-050", description: "Fifty", column: "todo", priority: "normal", createdAt: "2024-01-01T10:00:00.000Z" }),
createTask({ id: "FN-010", description: "Ten", column: "todo", priority: "normal", createdAt: "2024-01-01T10:00:00.000Z" }),
createTask({ id: "FN-030", description: "Thirty", column: "todo", priority: "normal", createdAt: "2024-01-01T10:00:00.000Z" }),
];
renderBoard({ tasks });
const todoTasks = JSON.parse(screen.getByTestId("column-todo").getAttribute("data-tasks") || "[]") as Task[];
expect(todoTasks.map((t: Task) => t.id)).toEqual(["FN-010", "FN-030", "FN-050"]);
});
it("keeps non-todo columns on priority then task ID ordering", () => {
const tasks: Task[] = [
createTask({ id: "FN-050", description: "Fifty", column: "in-progress", priority: "normal", createdAt: "2024-01-01T12:00:00.000Z" }),
createTask({ id: "FN-010", description: "Ten", column: "in-progress", priority: "normal", createdAt: "2024-01-01T09:00:00.000Z" }),
createTask({ id: "FN-030", description: "Thirty", column: "in-progress", priority: "normal", createdAt: "2024-01-01T10:00:00.000Z" }),
];
renderBoard({ tasks });