feat: support ON_COMPLETION handler sections in diagram

Add ON_COMPLETION to backend COMPOUND_TYPES and frontend rendering.
Completion handlers render as teal-tinted sections between the main
flow and error handlers, structurally parallel to onException.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 16:45:10 +01:00
parent 9b7626f6ff
commit f6220a9f89
5 changed files with 67 additions and 40 deletions

View File

@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useDiagramByRoute } from '../../api/queries/diagrams';
import type { DiagramNode, DiagramEdge } from '../../api/queries/diagrams';
import type { DiagramSection } from './types';
import { isErrorCompoundType } from './node-colors';
import { isErrorCompoundType, isCompletionCompoundType } from './node-colors';
const SECTION_GAP = 40;
@@ -20,12 +20,18 @@ export function useDiagramData(
const allEdges = layout.edges ?? [];
// Separate main nodes from error handler compound sections
// Separate main nodes from completion and error handler compound sections
const mainNodes: DiagramNode[] = [];
const completionSections: { label: string; nodes: DiagramNode[] }[] = [];
const errorSections: { label: string; nodes: DiagramNode[] }[] = [];
for (const node of layout.nodes) {
if (isErrorCompoundType(node.type) && node.children && node.children.length > 0) {
if (isCompletionCompoundType(node.type) && node.children && node.children.length > 0) {
completionSections.push({
label: node.label || 'onCompletion',
nodes: node.children,
});
} else if (isErrorCompoundType(node.type) && node.children && node.children.length > 0) {
errorSections.push({
label: node.label || 'Error Handler',
nodes: node.children,
@@ -58,39 +64,41 @@ export function useDiagramData(
let currentY = mainBounds.maxY + SECTION_GAP;
let maxWidth = mainBounds.maxX;
for (const es of errorSections) {
const errorBounds = computeBounds(es.nodes);
const offX = errorBounds.minX;
const offY = errorBounds.minY;
const addHandlerSections = (
handlers: { label: string; nodes: DiagramNode[] }[],
variant: 'completion' | 'error',
) => {
for (const hs of handlers) {
const bounds = computeBounds(hs.nodes);
const offX = bounds.minX;
const offY = bounds.minY;
const shiftedNodes = shiftNodes(hs.nodes, offX, offY);
const nodeIds = new Set<string>();
collectNodeIds(hs.nodes, nodeIds);
const edges = allEdges
.filter(e => nodeIds.has(e.sourceId) && nodeIds.has(e.targetId))
.map(e => ({
...e,
points: e.points.map(p => [p[0] - offX, p[1] - offY]),
}));
const sectionHeight = bounds.maxY - bounds.minY;
const sectionWidth = bounds.maxX - bounds.minX;
sections.push({
label: hs.label,
nodes: shiftedNodes,
edges,
offsetY: currentY,
variant,
});
currentY += sectionHeight + SECTION_GAP;
if (sectionWidth > maxWidth) maxWidth = sectionWidth;
}
};
// Normalize node coordinates relative to the section's own origin
const shiftedNodes = shiftNodes(es.nodes, offX, offY);
const errorNodeIds = new Set<string>();
collectNodeIds(es.nodes, errorNodeIds);
// Shift edge points too
const errorEdges = allEdges
.filter(e => errorNodeIds.has(e.sourceId) && errorNodeIds.has(e.targetId))
.map(e => ({
...e,
points: e.points.map(p => [p[0] - offX, p[1] - offY]),
}));
const sectionHeight = errorBounds.maxY - errorBounds.minY;
const sectionWidth = errorBounds.maxX - errorBounds.minX;
sections.push({
label: es.label,
nodes: shiftedNodes,
edges: errorEdges,
offsetY: currentY,
variant: 'error',
});
currentY += sectionHeight + SECTION_GAP;
if (sectionWidth > maxWidth) maxWidth = sectionWidth;
}
// Completion handlers first (above error handlers)
addHandlerSections(completionSections, 'completion');
// Then error handlers
addHandlerSections(errorSections, 'error');
const totalWidth = Math.max(layout.width ?? 0, mainBounds.maxX, maxWidth);
const totalHeight = currentY;