diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java index 4c6bd085..afb50a7c 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java @@ -356,6 +356,12 @@ public class ElkDiagramRenderer implements DiagramRenderer { allEdges.addAll(collectAllEdges(hr)); } for (ElkEdge elkEdge : allEdges) { + // Skip invisible ordering edges used only for DO_TRY section layout + String edgeId = elkEdge.getIdentifier(); + if (edgeId != null && edgeId.startsWith("__ordering_")) { + continue; + } + String sourceId = elkEdge.getSources().isEmpty() ? "" : elkEdge.getSources().get(0).getIdentifier(); String targetId = elkEdge.getTargets().isEmpty() ? "" : elkEdge.getTargets().get(0).getIdentifier(); @@ -568,6 +574,7 @@ public class ElkDiagramRenderer implements DiagramRenderer { } // Virtual _TRY_BODY wrapper with LR direction for horizontal try body chain + // Layer constraint FIRST ensures it stays at the top of the DO_TRY if (!tryBodyChildren.isEmpty()) { String wrapperId = rn.getId() + "._try_body"; ElkNode wrapper = factory.createElkNode(); @@ -591,8 +598,7 @@ public class ElkDiagramRenderer implements DiagramRenderer { } } - // Handler children stacked below try body: DO_FINALLY first, then DO_CATCH - // (ELK TB layout places children in insertion order) + // Handler children: DO_FINALLY in the middle, DO_CATCH at the bottom for (RouteNode child : handlerChildren) { if (child.getType() == NodeType.DO_FINALLY) { childNodeIds.add(child.getId()); @@ -607,6 +613,30 @@ public class ElkDiagramRenderer implements DiagramRenderer { compoundNodeIds, childNodeIds, doTryNodeIds); } } + + // Create invisible ordering edges to enforce top-to-bottom: + // try_body → doFinally → doCatch + List orderedSections = new ArrayList<>(); + if (!tryBodyChildren.isEmpty()) { + orderedSections.add(elkNodeMap.get(rn.getId() + "._try_body")); + } + for (RouteNode child : handlerChildren) { + if (child.getType() == NodeType.DO_FINALLY) { + orderedSections.add(elkNodeMap.get(child.getId())); + } + } + for (RouteNode child : handlerChildren) { + if (child.getType() == NodeType.DO_CATCH) { + orderedSections.add(elkNodeMap.get(child.getId())); + } + } + for (int i = 0; i < orderedSections.size() - 1; i++) { + ElkEdge orderEdge = factory.createElkEdge(); + orderEdge.setIdentifier("__ordering_" + rn.getId() + "_" + i); + orderEdge.setContainingNode(elkNode); + orderEdge.getSources().add(orderedSections.get(i)); + orderEdge.getTargets().add(orderedSections.get(i + 1)); + } } else if (isCompound) { compoundNodeIds.add(rn.getId()); elkNode.setWidth(200);