From f2ade91000bd578c99ee2366e6c8ee28c606ca83 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Fri, 25 Sep 2026 14:57:56 +0200 Subject: [PATCH] Draw a loop's way back above every node between its ends It cleared the two nodes it connects and nothing else, so when the loop's own steps sat higher - the usual case - its top ran hidden behind them, limit label included. It now clears every node between its ends, and follows any of them being moved. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/app/utilities/rete-editor.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/app/utilities/rete-editor.ts b/src/app/utilities/rete-editor.ts index b5b255b..ede9e3a 100644 --- a/src/app/utilities/rete-editor.ts +++ b/src/app/utilities/rete-editor.ts @@ -184,10 +184,12 @@ export async function createEditor( area.addPipe((context: any) => { if (context?.type === 'nodetranslated') { - const nodeId = String(context.data?.id ?? ''); + // 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 || (connection.source !== nodeId && connection.target !== nodeId)) continue; - connection.__loopTop = loopTop(area, connection); + if (!connection.__loopBack) continue; + const top = loopTop(area, connection); + if (top === connection.__loopTop) continue; + connection.__loopTop = top; void area.update('connection', connection.id); } } @@ -813,10 +815,18 @@ export async function refreshLoopMarkers( * as it is expanded or edited, its position only when it is moved. */ function loopTop(area: AreaPlugin, connection: LoopAwareConnection): number | undefined { - const tops = [connection.source, connection.target] - .map((id) => area.nodeViews.get(id)?.position.y) - .filter((y): y is number => typeof y === 'number'); - return tops.length ? Math.min(...tops) - 48 : undefined; + const ends = [connection.source, connection.target] + .map((id) => area.nodeViews.get(id)) + .filter((view): view is NonNullable => view != null); + if (!ends.length) return undefined; + // The way back runs across everything between its two ends, so it has to clear the tallest of + // those nodes too - the loop's own steps usually sit there - not only the two it connects. + const left = Math.min(...ends.map((view) => view.position.x)); + const right = Math.max(...ends.map((view) => view.position.x + view.element.offsetWidth)); + const tops = [...area.nodeViews.values()] + .filter((view) => view.position.x + view.element.offsetWidth >= left && view.position.x <= right) + .map((view) => view.position.y); + return Math.min(...tops, ...ends.map((view) => view.position.y)) - 48; } /**