fix(dashboard): route skip-forward edges to side (planner→end was hidden)

This commit is contained in:
2026-05-15 14:10:25 +08:00
parent 3431d3070b
commit f728b36e8d
@@ -62,7 +62,7 @@ function computeLayers(edges: readonly WorkflowGraphEdge[]): string[][] {
for (const id of ids) adj.set(id, []); for (const id of ids) adj.set(id, []);
for (const e of edges) { for (const e of edges) {
if (e.from !== e.to) { if (e.from !== e.to) {
adj.get(e.from)!.push(e.to); adj.get(e.from)?.push(e.to);
} }
} }
@@ -247,16 +247,17 @@ function computeLayout(input: LayoutInput): LayoutResult {
} }
// Build edges with label positions // Build edges with label positions
// For feedback edges (target rank < source rank), we'll compute label at midpoint // Feedback edges (target rank < source rank) and skip-forward edges (span > 1 layer)
// of the right-side arc. The actual SVG path is drawn by ConditionEdge component. // are routed to the side. Adjacent forward edges go straight down.
// Track feedback edge count per target node for alternating sides // Track routed edge count per side for alternating
const feedbackCountByTarget = new Map<string, number>(); const routedCountByTarget = new Map<string, number>();
const edges: Edge[] = input.edges.map((e) => { const edges: Edge[] = input.edges.map((e) => {
const isFallback = e.condition === "FALLBACK"; const isFallback = e.condition === "FALLBACK";
const isSelfLoop = e.from === e.to; const isSelfLoop = e.from === e.to;
const sourceRank = rank.get(e.from) ?? 0; const sourceRank = rank.get(e.from) ?? 0;
const targetRank = rank.get(e.to) ?? 0; const targetRank = rank.get(e.to) ?? 0;
const isFeedback = !isSelfLoop && targetRank <= sourceRank; const isFeedback = !isSelfLoop && targetRank <= sourceRank;
const isSkipForward = !isSelfLoop && !isFeedback && targetRank - sourceRank > 1;
const sourcePos = nodePositions.get(e.from); const sourcePos = nodePositions.get(e.from);
const targetPos = nodePositions.get(e.to); const targetPos = nodePositions.get(e.to);
@@ -266,10 +267,10 @@ function computeLayout(input: LayoutInput): LayoutResult {
let feedbackSide: "right" | "left" | null = null; let feedbackSide: "right" | "left" | null = null;
if (sourcePos !== undefined && targetPos !== undefined) { if (sourcePos !== undefined && targetPos !== undefined) {
if (isFeedback) { if (isFeedback || isSkipForward) {
// Alternate feedback edges left/right per target node // Route to side — alternate left/right per target node
const count = feedbackCountByTarget.get(e.to) ?? 0; const count = routedCountByTarget.get(e.to) ?? 0;
feedbackCountByTarget.set(e.to, count + 1); routedCountByTarget.set(e.to, count + 1);
feedbackSide = count % 2 === 0 ? "right" : "left"; feedbackSide = count % 2 === 0 ? "right" : "left";
const offsetX = const offsetX =
feedbackSide === "right" feedbackSide === "right"
@@ -292,18 +293,20 @@ function computeLayout(input: LayoutInput): LayoutResult {
id: edgeKey(e), id: edgeKey(e),
source: e.from, source: e.from,
target: e.to, target: e.to,
sourceHandle: isFeedback sourceHandle:
? feedbackSide === "left" isFeedback || isSkipForward
? "left-out" ? feedbackSide === "left"
: "right-out" ? "left-out"
: "bottom-out", : "right-out"
targetHandle: isFeedback ? (feedbackSide === "left" ? "left-in" : "right-in") : "top-in", : "bottom-out",
targetHandle:
isFeedback || isSkipForward ? (feedbackSide === "left" ? "left-in" : "right-in") : "top-in",
type: "condition", type: "condition",
data: { data: {
condition: e.condition, condition: e.condition,
conditionDescription: e.conditionDescription, conditionDescription: e.conditionDescription,
isFallback, isFallback,
isFeedback, isFeedback: isFeedback || isSkipForward,
isSelfLoop, isSelfLoop,
feedbackSide, feedbackSide,
labelX, labelX,