diff --git a/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx b/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx
index cb164b21df..472f8fe265 100644
--- a/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx
+++ b/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx
@@ -41,8 +41,22 @@ function createTask(id: string, dependencies: string[] = []): Task {
} as Task;
}
+function setViewportDimensions(width: number, height: number) {
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: width });
+ Object.defineProperty(window, "innerHeight", { configurable: true, value: height });
+}
+
+function expectEdgePaintsViaResolvableStyle(edge: SVGElement, expectedStroke = "var(--border)", expectedStrokeWidth = "var(--btn-border-width)") {
+ expect(edge.getAttribute("stroke")).not.toBe(expectedStroke);
+ expect(edge.getAttribute("stroke-width")).not.toBe(expectedStrokeWidth);
+ expect(edge.getAttribute("strokeWidth")).not.toBe(expectedStrokeWidth);
+ expect(edge.style.stroke).toBe(expectedStroke);
+ expect(edge.style.strokeWidth).toBe(expectedStrokeWidth);
+}
+
afterEach(() => {
cleanup();
+ setViewportDimensions(1024, 768);
});
describe("DependencyGraph highlighting", () => {
@@ -97,14 +111,39 @@ describe("DependencyGraph highlighting", () => {
const edgeAB = edges.find((edge) => edge.getAttribute("data-edge-id") === "B->A");
const edgeCB = edges.find((edge) => edge.getAttribute("data-edge-id") === "C->B");
+ expect(edgeAB).toBeDefined();
expect(edgeAB?.className.baseVal || edgeAB?.className).toContain("graph-edge--highlighted");
+ expectEdgePaintsViaResolvableStyle(edgeAB as SVGElement, "var(--todo)", "var(--space-xs)");
+ expect(edgeCB).toBeDefined();
expect(edgeCB?.className.baseVal || edgeCB?.className).toContain("graph-edge--highlighted");
+ expectEdgePaintsViaResolvableStyle(edgeCB as SVGElement, "var(--todo)", "var(--space-xs)");
fireEvent.doubleClick(screen.getByTestId("graph-task-node-C"));
expect(onOpenDetail).toHaveBeenCalledTimes(1);
expect(onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "C" }));
});
+ it.each([
+ ["vertical desktop", 1200, 800],
+ ["horizontal mobile", 390, 844],
+ ] as const)("renders dependency chain edges with resolvable stroke in %s orientation", (_label, width, height) => {
+ setViewportDimensions(width, height);
+ render();
+
+ const edges = screen.getAllByTestId("dependency-edge");
+ expect(edges.map((edge) => edge.getAttribute("data-edge-id")).sort()).toEqual(["B->A", "C->B"]);
+ for (const edge of edges) {
+ expectEdgePaintsViaResolvableStyle(edge);
+ }
+ });
+
+ it("does not render an edge when a dependency target is filtered out", () => {
+ const doneDependency = { ...createTask("Done"), column: "done" as const };
+ render();
+
+ expect(screen.queryAllByTestId("dependency-edge")).toHaveLength(0);
+ });
+
it("highlights only isolated node with no dependencies", () => {
render();
fireEvent.mouseEnter(screen.getByTestId("graph-task-node-X"));
diff --git a/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx b/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx
index b2e655e16e..d8a1eccfb9 100644
--- a/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx
+++ b/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx
@@ -19,6 +19,14 @@ function renderEdges(edges: GraphEdge[], highlightedEdgeIds?: Set) {
);
}
+function expectEdgePaintsViaResolvableStyle(edge: SVGElement, expectedStroke: string, expectedStrokeWidth: string) {
+ expect(edge.getAttribute("stroke")).not.toBe(expectedStroke);
+ expect(edge.getAttribute("stroke-width")).not.toBe(expectedStrokeWidth);
+ expect(edge.getAttribute("strokeWidth")).not.toBe(expectedStrokeWidth);
+ expect(edge.style.stroke).toBe(expectedStroke);
+ expect(edge.style.strokeWidth).toBe(expectedStrokeWidth);
+}
+
describe("GraphEdges", () => {
afterEach(() => {
cleanup();
@@ -26,8 +34,22 @@ describe("GraphEdges", () => {
it("renders single edge", () => {
renderEdges([{ source: "A", target: "B" }]);
const edge = screen.getAllByTestId("dependency-edge")[0];
- expect(edge.getAttribute("opacity")).toBe("1");
- expect(edge.getAttribute("stroke")).toBe("var(--border)");
+ expect(edge.getAttribute("opacity")).toBeNull();
+ expectEdgePaintsViaResolvableStyle(edge, "var(--border)", "var(--btn-border-width)");
+ });
+
+ it("renders no edge paths when there are zero edges", () => {
+ renderEdges([]);
+ expect(screen.queryAllByTestId("dependency-edge")).toHaveLength(0);
+ });
+
+ it("renders arrowhead fill through resolvable style instead of a presentation attribute", () => {
+ const { container } = renderEdges([{ source: "A", target: "B" }]);
+ const arrowheadPath = container.querySelector("#dependency-graph-arrowhead path") as SVGPathElement | null;
+
+ expect(arrowheadPath).not.toBeNull();
+ expect(arrowheadPath?.getAttribute("fill")).not.toBe("var(--border)");
+ expect(arrowheadPath?.style.fill).toBe("var(--border)");
});
it("renders multiple edges", () => {
@@ -67,9 +89,13 @@ describe("GraphEdges", () => {
const highlighted = all.find((edge) => edge.getAttribute("data-edge-id") === "A->B");
const dimmed = all.find((edge) => edge.getAttribute("data-edge-id") === "A->C");
- expect(highlighted?.getAttribute("opacity")).toBe("1");
+ expect(highlighted).toBeDefined();
+ expect(highlighted?.style.opacity).toBe("1");
+ expectEdgePaintsViaResolvableStyle(highlighted as SVGElement, "var(--todo)", "var(--space-xs)");
expect(highlighted?.getAttribute("class") ?? "").toContain("graph-edge--highlighted");
- expect(dimmed?.getAttribute("opacity")).toBe("0.15");
+ expect(dimmed).toBeDefined();
+ expect(dimmed?.style.opacity).toBe("0.15");
+ expectEdgePaintsViaResolvableStyle(dimmed as SVGElement, "var(--border)", "var(--btn-border-width)");
expect(dimmed?.getAttribute("class") ?? "").toContain("graph-edge--dimmed");
});
});
diff --git a/plugins/fusion-plugin-dependency-graph/src/edges.tsx b/plugins/fusion-plugin-dependency-graph/src/edges.tsx
index fafaa97551..c260abb9dc 100644
--- a/plugins/fusion-plugin-dependency-graph/src/edges.tsx
+++ b/plugins/fusion-plugin-dependency-graph/src/edges.tsx
@@ -13,6 +13,11 @@ interface GraphEdgesProps {
const DEFAULT_NODE_WIDTH = 280;
const DEFAULT_NODE_HEIGHT = 100;
+/**
+ * FNXC:DependencyGraphEdges 2026-06-19-08:59:
+ * Browser SVG presentation attributes do not resolve CSS custom properties, so dependency edge theme paint and widths must travel through real CSS via inline style or classes.
+ * Keep only literal SVG-safe values such as fill="none" as presentation attributes so dependency connector strokes and arrowheads remain visible across default, highlighted, and dimmed states.
+ */
export function GraphEdges({
edges,
positions,
@@ -35,7 +40,7 @@ export function GraphEdges({
orient="auto"
markerUnits="strokeWidth"
>
-
+
{edges.map((edge) => {
@@ -59,11 +64,13 @@ export function GraphEdges({
className={`dependency-graph-edge${isActiveHighlight ? " graph-edge--highlighted" : ""}${hasHighlights && !isActiveHighlight ? " graph-edge--dimmed" : ""}`}
d={`M ${x1} ${y1} C ${x1} ${controlY}, ${x2} ${controlY}, ${x2} ${y2}`}
fill="none"
- stroke={isActiveHighlight ? "var(--todo)" : "var(--border)"}
- strokeWidth={isActiveHighlight ? "var(--space-xs)" : "var(--btn-border-width)"}
- opacity={hasHighlights && !isActiveHighlight ? 0.15 : 1}
markerEnd="url(#dependency-graph-arrowhead)"
- style={{ transition: "opacity var(--transition-fast), stroke var(--transition-fast), stroke-width var(--transition-fast)" }}
+ style={{
+ opacity: hasHighlights && !isActiveHighlight ? 0.15 : 1,
+ stroke: isActiveHighlight ? "var(--todo)" : "var(--border)",
+ strokeWidth: isActiveHighlight ? "var(--space-xs)" : "var(--btn-border-width)",
+ transition: "opacity var(--transition-fast), stroke var(--transition-fast), stroke-width var(--transition-fast)",
+ }}
/>
);
})}