From 09abd7e3f6967562381ba20bf286d80bb4f6e013 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Wed, 30 Sep 2026 08:44:34 +0200 Subject: [PATCH] fix(editor): keep loop routes at a fixed height --- .../custom-connection.spec.ts | 12 +++++++++ .../custom-connection/custom-connection.ts | 2 +- src/app/utilities/rete-editor.ts | 25 ++++++------------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/app/shared/custom-connection/custom-connection.spec.ts b/src/app/shared/custom-connection/custom-connection.spec.ts index c0a2aeb..f559cec 100644 --- a/src/app/shared/custom-connection/custom-connection.spec.ts +++ b/src/app/shared/custom-connection/custom-connection.spec.ts @@ -65,4 +65,16 @@ describe('CustomConnectionComponent bias routing highlight', () => { comparisonViewState.show({ report: REPORT }); expect(component.strokeColor).toBe('#4682b4'); }); + + it('keeps a loop at its assigned height while a connected node moves above it', () => { + component.data = { + ...component.data, + __loopBack: true, + __loopTop: 120 + } as any; + component.start = { x: 0, y: 40 }; + component.end = { x: 100, y: 80 }; + + expect(component.drawnPath).toContain('L 100 120'); + }); }); diff --git a/src/app/shared/custom-connection/custom-connection.ts b/src/app/shared/custom-connection/custom-connection.ts index 214d4ce..20da801 100644 --- a/src/app/shared/custom-connection/custom-connection.ts +++ b/src/app/shared/custom-connection/custom-connection.ts @@ -267,7 +267,7 @@ export class CustomConnectionComponent { private get loopTop(): number { const ports = Math.min(this.start?.y ?? 0, this.end?.y ?? 0) - 90; const nodes = this.loopData?.__loopTop; - return typeof nodes === 'number' ? Math.min(nodes, ports) : ports; + return typeof nodes === 'number' ? nodes : ports; } get loopLabelX(): number { diff --git a/src/app/utilities/rete-editor.ts b/src/app/utilities/rete-editor.ts index 71ff6b8..648bce6 100644 --- a/src/app/utilities/rete-editor.ts +++ b/src/app/utilities/rete-editor.ts @@ -222,20 +222,6 @@ export async function createEditor( return context; }); - area.addPipe((context: any) => { - if (context?.type === 'nodetranslated') { - // Any node moved can be one the way back has to clear, not only one of its two ends. - for (const connection of editor.getConnections() as LoopAwareConnection[]) { - if (!connection.__loopBack) continue; - const top = loopTop(area, connection); - if (top === connection.__loopTop) continue; - connection.__loopTop = top; - void area.update('connection', connection.id); - } - } - return context; - }); - connection.addPreset(ConnectionPresets.classic.setup()); AreaExtensions.simpleNodesOrder(area); @@ -975,7 +961,13 @@ export async function refreshLoopMarkers( for (const connection of connections) { const loopBack = backEdges.has(connection.id); const readonly = resolvedRuntime?.readonly === true; - const top = loopBack ? loopTop(area, connection) : undefined; + // A loop gets its route when it becomes a loop. It then keeps that canvas height while cards + // move, so a person can drag a card across the wire instead of the wire perpetually fleeing up. + const top = loopBack + ? connection.__loopBack === true && typeof connection.__loopTop === 'number' + ? connection.__loopTop + : loopTop(area, connection) + : undefined; if (connection.__loopBack === loopBack && connection.__readonly === readonly && connection.__loopTop === top) continue; connection.__loopBack = loopBack; connection.__readonly = readonly; @@ -985,8 +977,7 @@ export async function refreshLoopMarkers( } /** - * Above the tops of the nodes at both ends. Their tops, not their bottoms: a node's height changes - * as it is expanded or edited, its position only when it is moved. + * The initial route of a loop: above the nodes it has to avoid when the loop is first established. */ function loopTop(area: AreaPlugin, connection: LoopAwareConnection): number | undefined { const ends = [connection.source, connection.target]