fix: drop dependency-graph plugin's use of removed stuck-task tagging

The dependency-graph plugin imported the deleted taskStuck util through its
dashboard interop shim, breaking the dashboard vite build. Remove the isStuck
gate from graph nodes, the taskStuckTimeoutMs prop threading, the taskStuck
module declaration from dashboard-interop.d.ts, and the stalled-card-as-stuck
test coverage. Covered by the existing remove-stuck-task-tagging changeset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-08-17 15:58:58 -07:00
parent 2eae0b2507
commit 29d94e0fa3
6 changed files with 10 additions and 66 deletions

View File

@@ -31,7 +31,6 @@ export interface DependencyGraphProps {
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
onRetryTask?: (id: string) => Promise<Task>;
onOpenDetailWithTab?: (task: Task, initialTab: "changes") => void;
taskStuckTimeoutMs?: number;
onOpenMission?: (missionId: string) => void;
onMoveTask?: (id: string, column: Task["column"], optionsOrPosition?: { preserveProgress?: boolean } | number) => Promise<Task>;
lastFetchTimeMs?: number;
@@ -53,7 +52,6 @@ export function DependencyGraph({
onDeleteTask,
onRetryTask,
onOpenDetailWithTab,
taskStuckTimeoutMs,
onOpenMission,
onMoveTask,
lastFetchTimeMs,
@@ -500,7 +498,6 @@ export function DependencyGraph({
onDeleteTask={onDeleteTask}
onRetryTask={onRetryTask}
onOpenDetailWithTab={onOpenDetailWithTab}
taskStuckTimeoutMs={taskStuckTimeoutMs}
onOpenMission={onOpenMission}
onMoveTask={onMoveTask}
lastFetchTimeMs={lastFetchTimeMs}

View File

@@ -3,7 +3,6 @@ import type { TraitFlags } from "@fusion/core";
import type { GraphPosition } from "./types.js";
import { useNodeDrag } from "./hooks/useNodeDrag.js";
import { TaskCard } from "@fusion/dashboard/app/components/TaskCard";
import { isTaskStuck } from "@fusion/dashboard/app/utils/taskStuck";
import "./GraphTaskNode.css";
import "./GraphHighlight.css";
import "./styles/drag.css";
@@ -23,7 +22,6 @@ type TaskCardBridgeProps = Pick<
| "onDeleteTask"
| "onRetryTask"
| "onOpenDetailWithTab"
| "taskStuckTimeoutMs"
| "onOpenMission"
| "onMoveTask"
| "lastFetchTimeMs"
@@ -34,10 +32,8 @@ export interface GraphTaskNodeProps extends TaskCardBridgeProps, Pick<HTMLAttrib
FNXC:WorkflowLifecycleColumns 2026-07-31-15:30:
This card's OWN resolved column traits, threaded from the host's plugin view context.
Two defects close here, both from this component having no access to the board's vocabulary:
- `isTaskStuck` was called without its `columnFlags` argument, so `isWipColumnRole` fell back to
the literal and NO card in the graph was ever shown stuck on a renamed board — while the same
card showed stuck correctly on the main board. That asymmetry was the tell.
Defect closed here, from this component having no access to the board's vocabulary
(a second consumer, isTaskStuck, was deleted with the stuck-tag removal):
- The `TaskCard` rendered below is a THIRD producer of unflagged cards, after the two #3025 fixed.
It bypasses `renderTaskCard` entirely by importing the component directly, so a host-side fix
could not reach it; every role helper inside it read the legacy ids.
@@ -83,10 +79,10 @@ export function GraphTaskNode({
onNodeDragEnd,
...taskCardProps
}: GraphTaskNodeProps) {
const { task, globalPaused, taskStuckTimeoutMs, lastFetchTimeMs, onOpenDetail } = taskCardProps;
const { task, globalPaused, onOpenDetail } = taskCardProps;
const isFailed = task.status === "failed";
const isPaused = task.paused === true;
const isStuck = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs, taskColumnFlags);
// FNXC:StuckTagRemoval 2026-08-17-22:30: stuck-task tagging was removed from the dashboard; graph nodes no longer derive or suppress on a stuck state.
/*
FNXC:PluginLifecycleColumns 2026-07-30-03:40 (U11 #2515 audit):
Keyed on `column === "triage"`, this went permanently FALSE for default-lineage
@@ -122,7 +118,6 @@ export function GraphTaskNode({
!globalPaused &&
!isFailed &&
!isPaused &&
!isStuck &&
!isAwaitingApproval &&
hasExecutionSignal;

View File

@@ -42,7 +42,6 @@ function props(overrides: Partial<React.ComponentProps<typeof GraphTaskNode>> =
onOpenDetailWithTab: vi.fn(),
onMoveTask: vi.fn(),
onOpenMission: vi.fn(),
taskStuckTimeoutMs: 1000,
lastFetchTimeMs: Date.now(),
workflowStepNameLookup: new Map<string, string>(),
...overrides,

View File

@@ -48,7 +48,6 @@ function createProps(task: Task) {
onOpenDetailWithTab: vi.fn(),
onMoveTask: vi.fn(),
onOpenMission: vi.fn(),
taskStuckTimeoutMs: 60_000,
lastFetchTimeMs: Date.now(),
workflowStepNameLookup: new Map<string, string>(),
};
@@ -384,38 +383,8 @@ describe("GraphTaskNode", () => {
});
});
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-15:30:
THE INVARIANT: a stalled card in the board's OWN wip lane reads as stuck, not as healthily running.
`isTaskStuck` was called without its `columnFlags` argument, so `isWipColumnRole` fell back to the
literal `in-progress`. On a renamed board no graph card could ever be stuck — and because `isStuck`
gates `isActive`, a wedged card rendered with the ACTIVE styling instead: the graph said "running"
about a task that had not moved in hours, while the main board showed the same card as stuck.
That asymmetry between two views of one task is the whole defect, and it is what this pins.
Reverted (the 4th argument dropped, or the flags not threaded from the host context), the first case
gets the `--active` class back and fails.
*/
describe("stuck detection on a renamed board", () => {
const STALE_MS = 120_000;
function stalledCard() {
return createTask({
column: "building",
status: "executing",
updatedAt: new Date(Date.now() - STALE_MS).toISOString(),
} as Partial<Task>);
}
it("treats a stalled card in a RENAMED wip lane as stuck, not active", () => {
const props = createProps(stalledCard());
render(<GraphTaskNode {...props} taskColumnFlags={{ countsTowardWip: true }} />);
expect(screen.getByTestId("graph-task-node-FN-TEST").className).not.toContain("graph-task-node--active");
});
// FNXC:StuckTagRemoval 2026-08-17-22:30: stuck-task tagging removed from the dashboard; the stalled-card-as-stuck coverage went with it.
describe("active styling", () => {
it("still reads a legacy in-progress card as active when it is fresh", () => {
const props = createProps(createTask({ column: "in-progress", status: "executing", updatedAt: new Date().toISOString() } as Partial<Task>));
render(<GraphTaskNode {...props} />);

View File

@@ -157,7 +157,6 @@ describe("dependency graph interactions", () => {
onOpenDetailWithTab={vi.fn()}
onMoveTask={vi.fn()}
onOpenMission={vi.fn()}
taskStuckTimeoutMs={1_000}
lastFetchTimeMs={Date.now()}
workflowStepNameLookup={new Map()}
/>,

View File

@@ -1,21 +1,7 @@
declare module "@fusion/dashboard/app/utils/taskStuck" {
import type { Task, TraitFlags } from "@fusion/core";
/* FNXC:WorkflowLifecycleColumns 2026-07-31-15:30: the 4th parameter existed upstream and this shim
did not declare it, so the plugin could not pass resolved traits even once it had them — and
`isWipColumnRole` fell back to the literal, meaning NO card in the graph was ever shown stuck on a
renamed board while the same card showed stuck correctly on the main board. */
export function isTaskStuck(
task: Task,
/* FNXC:PluginInteropDrift 2026-07-31-07:50: positionally REQUIRED in the real signature
(`number | undefined`), not optional — a mirror that is merely approximate is the drift this
file already caused once. Found by check-plugin-interop-drift. */
taskStuckTimeoutMs: number | undefined,
lastFetchTimeMs?: number,
columnFlags?: Partial<TraitFlags>,
): boolean;
}
/*
FNXC:StuckTagRemoval 2026-08-17-22:30:
The taskStuck shim was deleted with the dashboard's stuck-task tagging; the host module no longer exists.
*/
declare module "@fusion/dashboard/app/plugins/types" {
import type { ReactNode } from "react";
import type { Task, TaskDetail, TraitFlags, WorkflowStep } from "@fusion/core";
@@ -57,7 +43,6 @@ declare module "@fusion/dashboard/app/components/TaskCard" {
onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
onRetryTask?: (id: string) => Promise<Task>;
onOpenDetailWithTab?: (task: Task | TaskDetail, initialTab: "changes") => void;
taskStuckTimeoutMs?: number;
onOpenMission?: (missionId: string) => void;
onMoveTask?: (id: string, column: Column, optionsOrPosition?: { preserveProgress?: boolean } | number) => Promise<Task>;
lastFetchTimeMs?: number;