@synergenius/flow-weaver 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -492,10 +492,14 @@ export type TCICDSecret = {
492
492
  export type TCICDCache = {
493
493
  /** Cache strategy name (e.g., npm, pip, custom) */
494
494
  strategy: string;
495
- /** Cache path override */
495
+ /** Cache path override (comma-separated for multiple paths) */
496
496
  path?: string;
497
497
  /** Cache key file (e.g., package-lock.json) */
498
498
  key?: string;
499
+ /** Cache policy (pull, push, pull-push) */
500
+ policy?: string;
501
+ /** Files to use for cache key generation */
502
+ files?: string[];
499
503
  };
500
504
  /** Artifact declaration from @artifact annotation */
501
505
  export type TCICDArtifact = {
@@ -9671,7 +9671,7 @@ var VERSION;
9671
9671
  var init_generated_version = __esm({
9672
9672
  "src/generated-version.ts"() {
9673
9673
  "use strict";
9674
- VERSION = "0.19.0";
9674
+ VERSION = "0.19.2";
9675
9675
  }
9676
9676
  });
9677
9677
 
@@ -51065,6 +51065,13 @@ function buildDiagramGraph(ast, options = {}) {
51065
51065
  width: node.width,
51066
51066
  height: node.height
51067
51067
  }));
51068
+ for (const node of nodes) {
51069
+ if (node.scopeChildren) {
51070
+ for (const child of node.scopeChildren) {
51071
+ nodeBoxes.push({ id: child.id, x: child.x, y: child.y, width: child.width, height: child.height });
51072
+ }
51073
+ }
51074
+ }
51068
51075
  pendingConnections.sort((a, b) => {
51069
51076
  const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
51070
51077
  const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
@@ -51138,15 +51145,93 @@ function buildDiagramGraph(ast, options = {}) {
51138
51145
  });
51139
51146
  }
51140
51147
  for (const node of nodes) {
51141
- if (node.scopeConnections && node.scopePorts && node.scopeChildren) {
51142
- const childMap = /* @__PURE__ */ new Map();
51143
- for (const c of node.scopeChildren) childMap.set(c.id, c);
51144
- for (const conn of node.scopeConnections) {
51145
- const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, "output");
51146
- const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, "input");
51147
- if (sPort && tPort) {
51148
- conn.path = computeConnectionPath(sPort.cx, sPort.cy, tPort.cx, tPort.cy);
51149
- }
51148
+ if (!node.scopeConnections || !node.scopePorts || !node.scopeChildren) continue;
51149
+ const childMap = /* @__PURE__ */ new Map();
51150
+ for (const c of node.scopeChildren) childMap.set(c.id, c);
51151
+ const scopePending = [];
51152
+ for (const conn of node.scopeConnections) {
51153
+ const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, "output");
51154
+ const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, "input");
51155
+ if (!sPort || !tPort) continue;
51156
+ let fromPortIndex = 0;
51157
+ let toPortIndex = 0;
51158
+ if (conn.fromNode === node.id) {
51159
+ fromPortIndex = node.scopePorts.outputs.indexOf(sPort);
51160
+ } else {
51161
+ const fromChild = childMap.get(conn.fromNode);
51162
+ if (fromChild) fromPortIndex = fromChild.outputs.indexOf(sPort);
51163
+ }
51164
+ if (conn.toNode === node.id) {
51165
+ toPortIndex = node.scopePorts.inputs.indexOf(tPort);
51166
+ } else {
51167
+ const toChild = childMap.get(conn.toNode);
51168
+ if (toChild) toPortIndex = toChild.inputs.indexOf(tPort);
51169
+ }
51170
+ scopePending.push({
51171
+ conn,
51172
+ sourcePort: sPort,
51173
+ targetPort: tPort,
51174
+ fromNodeId: conn.fromNode,
51175
+ toNodeId: conn.toNode,
51176
+ fromPortIndex: Math.max(0, fromPortIndex),
51177
+ toPortIndex: Math.max(0, toPortIndex)
51178
+ });
51179
+ }
51180
+ scopePending.sort((a, b) => {
51181
+ const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
51182
+ const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
51183
+ if (Math.abs(aSpan - bSpan) > 1) return aSpan - bSpan;
51184
+ if (Math.abs(a.sourcePort.cx - b.sourcePort.cx) > 1) return a.sourcePort.cx - b.sourcePort.cx;
51185
+ return a.sourcePort.cy - b.sourcePort.cy;
51186
+ });
51187
+ const spSrcKey = (sp) => `s:${sp.fromNodeId}.${sp.conn.fromPort}`;
51188
+ const spTgtKey = (sp) => `t:${sp.toNodeId}.${sp.conn.toPort}`;
51189
+ const spTgtNodeKey = (sp) => `n:${sp.toNodeId}`;
51190
+ const spGroups = /* @__PURE__ */ new Map();
51191
+ for (const sp of scopePending) {
51192
+ for (const key of [spSrcKey(sp), spTgtKey(sp), spTgtNodeKey(sp)]) {
51193
+ if (!spGroups.has(key)) spGroups.set(key, []);
51194
+ spGroups.get(key).push(sp);
51195
+ }
51196
+ }
51197
+ const spForceCurveKeys = /* @__PURE__ */ new Set();
51198
+ for (const [key, group] of spGroups) {
51199
+ if (group.length < 2) continue;
51200
+ const anyShort = group.some((sp) => {
51201
+ const ddx = sp.targetPort.cx - sp.sourcePort.cx;
51202
+ const ddy = sp.targetPort.cy - sp.sourcePort.cy;
51203
+ return Math.sqrt(ddx * ddx + ddy * ddy) <= ORTHOGONAL_DISTANCE_THRESHOLD;
51204
+ });
51205
+ if (anyShort) spForceCurveKeys.add(key);
51206
+ }
51207
+ const spForceCurveSet = /* @__PURE__ */ new Set();
51208
+ for (const sp of scopePending) {
51209
+ if (spForceCurveKeys.has(spSrcKey(sp)) || spForceCurveKeys.has(spTgtKey(sp)) || spForceCurveKeys.has(spTgtNodeKey(sp))) {
51210
+ spForceCurveSet.add(sp);
51211
+ }
51212
+ }
51213
+ for (const sp of scopePending) {
51214
+ const sx = sp.sourcePort.cx;
51215
+ const sy = sp.sourcePort.cy;
51216
+ const tx = sp.targetPort.cx;
51217
+ const ty = sp.targetPort.cy;
51218
+ const ddx = tx - sx;
51219
+ const ddy = ty - sy;
51220
+ const dist = Math.sqrt(ddx * ddx + ddy * ddy);
51221
+ const useCurve = spForceCurveSet.has(sp);
51222
+ if (!useCurve && dist > ORTHOGONAL_DISTANCE_THRESHOLD) {
51223
+ const orthoPath = calculateOrthogonalPathSafe(
51224
+ [sx, sy],
51225
+ [tx, ty],
51226
+ nodeBoxes,
51227
+ sp.fromNodeId,
51228
+ sp.toNodeId,
51229
+ { fromPortIndex: sp.fromPortIndex, toPortIndex: sp.toPortIndex },
51230
+ allocator
51231
+ );
51232
+ sp.conn.path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
51233
+ } else {
51234
+ sp.conn.path = computeConnectionPath(sx, sy, tx, ty);
51150
51235
  }
51151
51236
  }
51152
51237
  }
@@ -52580,7 +52665,10 @@ path[data-source].port-hover { opacity: 1; }
52580
52665
  portConnections[tgt].push(src);
52581
52666
  });
52582
52667
 
52583
- // ---- Connection path computation (from geometry.ts) ----
52668
+ // ---- Connection path computation (bezier from geometry.ts + orthogonal router) ----
52669
+ var TRACK_SPACING = 15, EDGE_OFFSET = 5, MAX_CANDIDATES = 5;
52670
+ var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
52671
+
52584
52672
  function quadCurveControl(ax, ay, bx, by, ux, uy) {
52585
52673
  var dn = Math.abs(ay - by);
52586
52674
  return [bx + (ux * dn) / Math.abs(uy), ay];
@@ -52610,18 +52698,326 @@ path[data-source].port-hover { opacity: 1; }
52610
52698
  ' L ' + hx + ',' + hy;
52611
52699
  }
52612
52700
 
52613
- // ---- Port position + connection path indexes ----
52701
+ // ---- Orthogonal router (ported from orthogonal-router.ts) ----
52702
+ function createAllocator() {
52703
+ var claims = [], vclaims = [];
52704
+ function isOcc(xn, xx, y) {
52705
+ for (var i = 0; i < claims.length; i++) {
52706
+ var c = claims[i];
52707
+ if (c.xn < xx && c.xx > xn && Math.abs(c.y - y) < TRACK_SPACING) return true;
52708
+ }
52709
+ return false;
52710
+ }
52711
+ function isOccV(yn, yx, x) {
52712
+ for (var i = 0; i < vclaims.length; i++) {
52713
+ var c = vclaims[i];
52714
+ if (c.yn < yx && c.yx > yn && Math.abs(c.x - x) < TRACK_SPACING) return true;
52715
+ }
52716
+ return false;
52717
+ }
52718
+ function blockedByNode(xn, xx, y, boxes) {
52719
+ for (var i = 0; i < boxes.length; i++) {
52720
+ var b = boxes[i]; if (xn < b.r && xx > b.l && y >= b.t && y <= b.b) return true;
52721
+ }
52722
+ return false;
52723
+ }
52724
+ function blockedByNodeV(yn, yx, x, boxes) {
52725
+ for (var i = 0; i < boxes.length; i++) {
52726
+ var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true;
52727
+ }
52728
+ return false;
52729
+ }
52730
+ function countHCross(xn, xx, y) {
52731
+ var n = 0;
52732
+ for (var i = 0; i < vclaims.length; i++) {
52733
+ var c = vclaims[i]; if (c.x > xn && c.x < xx && y >= c.yn && y <= c.yx) n++;
52734
+ }
52735
+ return n;
52736
+ }
52737
+ function countVCross(yn, yx, x) {
52738
+ var n = 0;
52739
+ for (var i = 0; i < claims.length; i++) {
52740
+ var c = claims[i]; if (c.y > yn && c.y < yx && x >= c.xn && x <= c.xx) n++;
52741
+ }
52742
+ return n;
52743
+ }
52744
+ return {
52745
+ findFreeY: function(xn, xx, cy, nb) {
52746
+ var free = function(y) { return !isOcc(xn, xx, y) && (!nb || !blockedByNode(xn, xx, y, nb)); };
52747
+ if (free(cy)) return cy;
52748
+ var cands = [];
52749
+ for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
52750
+ if (free(cy - off)) cands.push({ y: cy - off, d: off });
52751
+ if (free(cy + off)) cands.push({ y: cy + off, d: off });
52752
+ }
52753
+ if (!cands.length) return cy;
52754
+ var best = cands[0].y, bc = countHCross(xn, xx, cands[0].y), bd = cands[0].d;
52755
+ for (var i = 1; i < cands.length; i++) {
52756
+ var cr = countHCross(xn, xx, cands[i].y);
52757
+ if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].y; bc = cr; bd = cands[i].d; }
52758
+ }
52759
+ return best;
52760
+ },
52761
+ findFreeX: function(yn, yx, cx, nb) {
52762
+ var free = function(x) { return !isOccV(yn, yx, x) && (!nb || !blockedByNodeV(yn, yx, x, nb)); };
52763
+ if (free(cx)) return cx;
52764
+ var cands = [];
52765
+ for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
52766
+ if (free(cx - off)) cands.push({ x: cx - off, d: off });
52767
+ if (free(cx + off)) cands.push({ x: cx + off, d: off });
52768
+ }
52769
+ if (!cands.length) return cx;
52770
+ var best = cands[0].x, bc = countVCross(yn, yx, cands[0].x), bd = cands[0].d;
52771
+ for (var i = 1; i < cands.length; i++) {
52772
+ var cr = countVCross(yn, yx, cands[i].x);
52773
+ if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].x; bc = cr; bd = cands[i].d; }
52774
+ }
52775
+ return best;
52776
+ },
52777
+ claim: function(xn, xx, y) { claims.push({ xn: xn, xx: xx, y: y }); },
52778
+ claimV: function(yn, yx, x) { vclaims.push({ yn: yn, yx: yx, x: x }); }
52779
+ };
52780
+ }
52781
+
52782
+ function inflateBox(b, pad) { return { l: b.x - pad, r: b.x + b.w + pad, t: b.y - pad, b: b.y + b.h + pad }; }
52783
+ function segOvlp(xn, xx, y, bx) { return xn < bx.r && xx > bx.l && y >= bx.t && y <= bx.b; }
52784
+ function vSegClear(x, yn, yx, boxes) {
52785
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return false; }
52786
+ return true;
52787
+ }
52788
+
52789
+ function findClearY(xn, xx, cy, boxes) {
52790
+ var blocked = function(y) { for (var i = 0; i < boxes.length; i++) if (segOvlp(xn, xx, y, boxes[i])) return true; return false; };
52791
+ if (!blocked(cy)) return cy;
52792
+ var edges = [];
52793
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (xn < b.r && xx > b.l) { edges.push(b.t); edges.push(b.b); } }
52794
+ if (!edges.length) return cy;
52795
+ edges.sort(function(a, b) { return a - b; });
52796
+ var best = cy, bd = Infinity;
52797
+ for (var i = 0; i < edges.length; i++) {
52798
+ var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
52799
+ for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cy); if (d < bd) { bd = d; best = vals[j]; } } }
52800
+ }
52801
+ if (bd === Infinity) {
52802
+ var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
52803
+ best = Math.abs(mn - cy) <= Math.abs(mx - cy) ? mn : mx;
52804
+ if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
52805
+ }
52806
+ return best;
52807
+ }
52808
+
52809
+ function findClearX(yn, yx, cx, boxes) {
52810
+ var blocked = function(x) { for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true; } return false; };
52811
+ if (!blocked(cx)) return cx;
52812
+ var edges = [];
52813
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (yn < b.b && yx > b.t) { edges.push(b.l); edges.push(b.r); } }
52814
+ if (!edges.length) return cx;
52815
+ edges.sort(function(a, b) { return a - b; });
52816
+ var best = cx, bd = Infinity;
52817
+ for (var i = 0; i < edges.length; i++) {
52818
+ var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
52819
+ for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cx); if (d < bd) { bd = d; best = vals[j]; } } }
52820
+ }
52821
+ if (bd === Infinity) {
52822
+ var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
52823
+ best = Math.abs(mn - cx) <= Math.abs(mx - cx) ? mn : mx;
52824
+ if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
52825
+ }
52826
+ return best;
52827
+ }
52828
+
52829
+ function simplifyWaypoints(pts) {
52830
+ if (pts.length <= 2) return pts;
52831
+ var jogFound = true;
52832
+ while (jogFound) {
52833
+ jogFound = false;
52834
+ for (var i = 0; i < pts.length - 3; i++) {
52835
+ var a = pts[i], b = pts[i+1], c = pts[i+2], d = pts[i+3];
52836
+ var jogH = Math.abs(b[1] - c[1]);
52837
+ if (Math.abs(a[1] - b[1]) < 0.5 && Math.abs(b[0] - c[0]) < 0.5 && Math.abs(c[1] - d[1]) < 0.5 && jogH > 0.5 && jogH < JOG_THRESHOLD) {
52838
+ var mid = (b[1] + c[1]) / 2, snap = Math.abs(a[1] - mid) <= Math.abs(d[1] - mid) ? a[1] : d[1];
52839
+ pts = pts.slice(); pts[i+1] = [b[0], snap]; pts[i+2] = [c[0], snap]; jogFound = true; break;
52840
+ }
52841
+ var jogW = Math.abs(b[0] - c[0]);
52842
+ if (Math.abs(a[0] - b[0]) < 0.5 && Math.abs(b[1] - c[1]) < 0.5 && Math.abs(c[0] - d[0]) < 0.5 && jogW > 0.5 && jogW < JOG_THRESHOLD) {
52843
+ var mid = (b[0] + c[0]) / 2, snap = Math.abs(a[0] - mid) <= Math.abs(d[0] - mid) ? a[0] : d[0];
52844
+ pts = pts.slice(); pts[i+1] = [snap, b[1]]; pts[i+2] = [snap, c[1]]; jogFound = true; break;
52845
+ }
52846
+ }
52847
+ }
52848
+ var res = [pts[0]];
52849
+ for (var i = 1; i < pts.length - 1; i++) {
52850
+ var prev = res[res.length - 1], cur = pts[i], next = pts[i+1];
52851
+ if (Math.abs(prev[0] - cur[0]) + Math.abs(prev[1] - cur[1]) < MIN_SEG_LEN) continue;
52852
+ var sameX = Math.abs(prev[0] - cur[0]) < 0.01 && Math.abs(cur[0] - next[0]) < 0.01;
52853
+ var sameY = Math.abs(prev[1] - cur[1]) < 0.01 && Math.abs(cur[1] - next[1]) < 0.01;
52854
+ if (!sameX && !sameY) res.push(cur);
52855
+ }
52856
+ res.push(pts[pts.length - 1]);
52857
+ return res;
52858
+ }
52859
+
52860
+ function waypointsToPath(wp, cr) {
52861
+ if (wp.length < 2) return '';
52862
+ if (wp.length === 2) return 'M ' + wp[0][0] + ',' + wp[0][1] + ' L ' + wp[1][0] + ',' + wp[1][1];
52863
+ var radii = [];
52864
+ for (var i = 0; i < wp.length; i++) radii[i] = 0;
52865
+ for (var i = 1; i < wp.length - 1; i++) {
52866
+ var p = wp[i-1], c = wp[i], n = wp[i+1];
52867
+ var lp = Math.sqrt((p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]));
52868
+ var ln = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
52869
+ radii[i] = (lp < 0.01 || ln < 0.01) ? 0 : Math.min(cr, lp / 2, ln / 2);
52870
+ }
52871
+ for (var i = 1; i < wp.length - 2; i++) {
52872
+ var c = wp[i], n = wp[i+1];
52873
+ var sl = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
52874
+ var tot = radii[i] + radii[i+1];
52875
+ if (tot > sl && tot > 0) { var sc = sl / tot; radii[i] *= sc; radii[i+1] *= sc; }
52876
+ }
52877
+ var path = 'M ' + wp[0][0] + ',' + wp[0][1];
52878
+ for (var i = 1; i < wp.length - 1; i++) {
52879
+ var p = wp[i-1], c = wp[i], n = wp[i+1], r = radii[i];
52880
+ if (r < 2) { path += ' L ' + c[0] + ',' + c[1]; continue; }
52881
+ var dpx = p[0]-c[0], dpy = p[1]-c[1], dnx = n[0]-c[0], dny = n[1]-c[1];
52882
+ var lp = Math.sqrt(dpx*dpx + dpy*dpy), ln = Math.sqrt(dnx*dnx + dny*dny);
52883
+ var upx = dpx/lp, upy = dpy/lp, unx = dnx/ln, uny = dny/ln;
52884
+ var asx = c[0] + upx*r, asy = c[1] + upy*r, aex = c[0] + unx*r, aey = c[1] + uny*r;
52885
+ var cross = dpx*dny - dpy*dnx, sweep = cross > 0 ? 0 : 1;
52886
+ path += ' L ' + asx + ',' + asy + ' A ' + r + ' ' + r + ' 0 0 ' + sweep + ' ' + aex + ',' + aey;
52887
+ }
52888
+ path += ' L ' + wp[wp.length-1][0] + ',' + wp[wp.length-1][1];
52889
+ return path;
52890
+ }
52891
+
52892
+ function computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc) {
52893
+ var isSelf = srcId === tgtId;
52894
+ var iboxes = [];
52895
+ for (var i = 0; i < nboxes.length; i++) {
52896
+ var b = nboxes[i];
52897
+ if (isSelf || (b.id !== srcId && b.id !== tgtId)) iboxes.push(inflateBox(b, pad));
52898
+ }
52899
+ var se = [from[0] + exitStub, from[1]], sn = [to[0] - entryStub, to[1]];
52900
+ var xn = Math.min(se[0], sn[0]), xx = Math.max(se[0], sn[0]);
52901
+
52902
+ if (!isSelf && to[0] > from[0]) {
52903
+ var cy = (from[1] + to[1]) / 2;
52904
+ var intBoxes = [];
52905
+ for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) intBoxes.push(b); }
52906
+ if (intBoxes.length >= 2) {
52907
+ var ct = Infinity, cb = -Infinity;
52908
+ for (var i = 0; i < intBoxes.length; i++) { ct = Math.min(ct, intBoxes[i].t); cb = Math.max(cb, intBoxes[i].b); }
52909
+ if (cy > ct && cy < cb) { cy = (cy - ct <= cb - cy) ? ct - pad : cb + pad; }
52910
+ }
52911
+ var clearY = findClearY(xn, xx, cy, iboxes);
52912
+ if (Math.abs(from[1] - to[1]) < JOG_THRESHOLD && Math.abs(clearY - from[1]) < JOG_THRESHOLD) return null;
52913
+
52914
+ var midX = (se[0] + sn[0]) / 2, ymn = Math.min(from[1], to[1]), ymx = Math.max(from[1], to[1]);
52915
+ var cmx = findClearX(ymn, ymx, midX, iboxes);
52916
+ var fmx = alloc.findFreeX(ymn, ymx, cmx, iboxes);
52917
+ if (ymx - ymn >= JOG_THRESHOLD && fmx > se[0] && fmx < sn[0] &&
52918
+ vSegClear(fmx, ymn, ymx, iboxes) &&
52919
+ alloc.findFreeY(from[0], fmx, from[1], iboxes) === from[1] &&
52920
+ alloc.findFreeY(fmx, to[0], to[1], iboxes) === to[1]) {
52921
+ alloc.claim(from[0], fmx, from[1]); alloc.claim(fmx, to[0], to[1]); alloc.claimV(ymn, ymx, fmx);
52922
+ return simplifyWaypoints([from, [fmx, from[1]], [fmx, to[1]], to]);
52923
+ }
52924
+
52925
+ clearY = alloc.findFreeY(xn, xx, clearY, iboxes);
52926
+ if (Math.abs(clearY - from[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, from[1], b); })) clearY = from[1];
52927
+ else if (Math.abs(clearY - to[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, to[1], b); })) clearY = to[1];
52928
+ alloc.claim(xn, xx, clearY);
52929
+
52930
+ var eymn = Math.min(from[1], clearY), eymx = Math.max(from[1], clearY);
52931
+ var exX = findClearX(eymn, eymx, se[0], iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes);
52932
+ if (exX < from[0]) { exX = se[0]; if (!vSegClear(exX, eymn, eymx, iboxes)) { exX = findClearX(eymn, eymx, se[0] + TRACK_SPACING, iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes); } }
52933
+ alloc.claimV(eymn, eymx, exX);
52934
+
52935
+ var nymn = Math.min(to[1], clearY), nymx = Math.max(to[1], clearY);
52936
+ var nxX = findClearX(nymn, nymx, sn[0], iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes);
52937
+ if (nxX > to[0]) { nxX = sn[0]; if (!vSegClear(nxX, nymn, nymx, iboxes)) { nxX = findClearX(nymn, nymx, sn[0] - TRACK_SPACING, iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes); } }
52938
+ alloc.claimV(nymn, nymx, nxX);
52939
+
52940
+ return simplifyWaypoints([from, [exX, from[1]], [exX, clearY], [nxX, clearY], [nxX, to[1]], to]);
52941
+ } else {
52942
+ var srcBox = null, tgtBox = null;
52943
+ for (var i = 0; i < nboxes.length; i++) { if (nboxes[i].id === srcId) srcBox = nboxes[i]; if (nboxes[i].id === tgtId) tgtBox = nboxes[i]; }
52944
+ var corBoxes = []; for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) corBoxes.push(b); }
52945
+ var bots = corBoxes.map(function(b) { return b.b; }), tops = corBoxes.map(function(b) { return b.t; });
52946
+ if (srcBox) { bots.push(srcBox.y + srcBox.h + pad); tops.push(srcBox.y - pad); }
52947
+ if (tgtBox) { bots.push(tgtBox.y + tgtBox.h + pad); tops.push(tgtBox.y - pad); }
52948
+ var maxBot = Math.max.apply(null, bots.concat([from[1] + 50, to[1] + 50]));
52949
+ var minTop = Math.min.apply(null, tops.concat([from[1] - 50, to[1] - 50]));
52950
+ var avgY = (from[1] + to[1]) / 2;
52951
+ var escBelow = maxBot + pad, escAbove = minTop - pad;
52952
+ var escY = Math.abs(escAbove - avgY) <= Math.abs(escBelow - avgY) ? escAbove : escBelow;
52953
+ escY = findClearY(xn, xx, escY, iboxes); escY = alloc.findFreeY(xn, xx, escY, iboxes); alloc.claim(xn, xx, escY);
52954
+
52955
+ var bymn = Math.min(from[1], escY), bymx = Math.max(from[1], escY);
52956
+ var bexX = findClearX(bymn, bymx, se[0], iboxes); bexX = alloc.findFreeX(bymn, bymx, bexX, iboxes); alloc.claimV(bymn, bymx, bexX);
52957
+
52958
+ var bnmn = Math.min(to[1], escY), bnmx = Math.max(to[1], escY);
52959
+ var bnxX = findClearX(bnmn, bnmx, sn[0], iboxes); bnxX = alloc.findFreeX(bnmn, bnmx, bnxX, iboxes); alloc.claimV(bnmn, bnmx, bnxX);
52960
+
52961
+ return simplifyWaypoints([from, [bexX, from[1]], [bexX, escY], [bnxX, escY], [bnxX, to[1]], to]);
52962
+ }
52963
+ }
52964
+
52965
+ function calcOrthogonalPath(from, to, nboxes, srcId, tgtId, fromIdx, toIdx, alloc) {
52966
+ var pad = 15, stubLen = 20, stubSpc = 12, maxStub = 80, cr = 10;
52967
+ var exitStub = Math.min(stubLen + fromIdx * stubSpc, maxStub);
52968
+ var entryStub = Math.min(stubLen + toIdx * stubSpc, maxStub);
52969
+ try {
52970
+ var wp = computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc);
52971
+ if (!wp) return null;
52972
+ var p = waypointsToPath(wp, cr);
52973
+ return (p && p.length >= 5) ? p : null;
52974
+ } catch (e) { return null; }
52975
+ }
52976
+
52977
+ // ---- Port position + node box + connection path indexes ----
52614
52978
  var portPositions = {};
52615
52979
  content.querySelectorAll('[data-port-id]').forEach(function(el) {
52616
52980
  var id = el.getAttribute('data-port-id');
52617
52981
  portPositions[id] = { cx: parseFloat(el.getAttribute('cx')), cy: parseFloat(el.getAttribute('cy')) };
52618
52982
  });
52619
52983
 
52984
+ // Extract node bounding boxes from SVG rect elements
52985
+ var nodeBoxMap = {};
52986
+ content.querySelectorAll('.nodes [data-node-id]').forEach(function(g) {
52987
+ var nid = g.getAttribute('data-node-id');
52988
+ var rect = g.querySelector(':scope > rect');
52989
+ if (!rect) return;
52990
+ nodeBoxMap[nid] = {
52991
+ id: nid,
52992
+ x: parseFloat(rect.getAttribute('x')),
52993
+ y: parseFloat(rect.getAttribute('y')),
52994
+ w: parseFloat(rect.getAttribute('width')),
52995
+ h: parseFloat(rect.getAttribute('height'))
52996
+ };
52997
+ });
52998
+
52999
+ // Build port-to-node mapping and compute port indices within each node
53000
+ var portNodeMap = {};
53001
+ var portIndexMap = {};
53002
+ content.querySelectorAll('[data-port-id]').forEach(function(el) {
53003
+ var id = el.getAttribute('data-port-id');
53004
+ var dir = el.getAttribute('data-direction');
53005
+ var parts = id.split('.');
53006
+ var nodeId = parts[0];
53007
+ portNodeMap[id] = nodeId;
53008
+ if (!portIndexMap[nodeId]) portIndexMap[nodeId] = { input: [], output: [] };
53009
+ portIndexMap[nodeId][dir].push(id);
53010
+ });
53011
+
52620
53012
  var nodeOffsets = {};
52621
53013
  var connIndex = [];
52622
53014
  content.querySelectorAll('path[data-source]').forEach(function(p) {
52623
53015
  var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
52624
- connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0], scopeOf: p.getAttribute('data-scope') || null });
53016
+ var srcNode = src.split('.')[0], tgtNode = tgt.split('.')[0];
53017
+ var srcIdx = portIndexMap[srcNode] ? portIndexMap[srcNode].output.indexOf(src) : 0;
53018
+ var tgtIdx = portIndexMap[tgtNode] ? portIndexMap[tgtNode].input.indexOf(tgt) : 0;
53019
+ connIndex.push({ el: p, src: src, tgt: tgt, srcNode: srcNode, tgtNode: tgtNode,
53020
+ scopeOf: p.getAttribute('data-scope') || null, srcIdx: Math.max(0, srcIdx), tgtIdx: Math.max(0, tgtIdx) });
52625
53021
  });
52626
53022
 
52627
53023
  // Snapshot of original port positions for reset
@@ -52629,6 +53025,47 @@ path[data-source].port-hover { opacity: 1; }
52629
53025
  for (var pid in portPositions) {
52630
53026
  origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
52631
53027
  }
53028
+ var origNodeBoxMap = {};
53029
+ for (var nid in nodeBoxMap) {
53030
+ var b = nodeBoxMap[nid];
53031
+ origNodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
53032
+ }
53033
+
53034
+ // ---- Recalculate all connection paths using orthogonal + bezier routing ----
53035
+ function recalcAllPaths() {
53036
+ var boxes = [];
53037
+ for (var nid in nodeBoxMap) boxes.push(nodeBoxMap[nid]);
53038
+ var sorted = connIndex.slice().sort(function(a, b) {
53039
+ var spa = portPositions[a.src] && portPositions[a.tgt] ? Math.abs(portPositions[a.tgt].cx - portPositions[a.src].cx) : 0;
53040
+ var spb = portPositions[b.src] && portPositions[b.tgt] ? Math.abs(portPositions[b.tgt].cx - portPositions[b.src].cx) : 0;
53041
+ if (Math.abs(spa - spb) > 1) return spa - spb;
53042
+ var sxa = portPositions[a.src] ? portPositions[a.src].cx : 0, sxb = portPositions[b.src] ? portPositions[b.src].cx : 0;
53043
+ if (Math.abs(sxa - sxb) > 1) return sxa - sxb;
53044
+ var sya = portPositions[a.src] ? portPositions[a.src].cy : 0, syb = portPositions[b.src] ? portPositions[b.src].cy : 0;
53045
+ return sya - syb;
53046
+ });
53047
+ var alloc = createAllocator();
53048
+ for (var i = 0; i < sorted.length; i++) {
53049
+ var c = sorted[i];
53050
+ var sp = portPositions[c.src], tp = portPositions[c.tgt];
53051
+ if (!sp || !tp) continue;
53052
+ var sx = sp.cx, sy = sp.cy, tx = tp.cx, ty = tp.cy;
53053
+ // For scope connections, use parent-local coords
53054
+ if (c.scopeOf) {
53055
+ var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
53056
+ sx -= pOff.dx; sy -= pOff.dy; tx -= pOff.dx; ty -= pOff.dy;
53057
+ }
53058
+ var ddx = tx - sx, ddy = ty - sy, dist = Math.sqrt(ddx * ddx + ddy * ddy);
53059
+ var path;
53060
+ if (dist > ORTHO_THRESHOLD) {
53061
+ path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
53062
+ if (!path) path = computeConnectionPath(sx, sy, tx, ty);
53063
+ } else {
53064
+ path = computeConnectionPath(sx, sy, tx, ty);
53065
+ }
53066
+ c.el.setAttribute('d', path);
53067
+ }
53068
+ }
52632
53069
 
52633
53070
  function resetLayout() {
52634
53071
  for (var nid in nodeOffsets) {
@@ -52648,10 +53085,11 @@ path[data-source].port-hover { opacity: 1; }
52648
53085
  for (var pid in origPortPositions) {
52649
53086
  portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
52650
53087
  }
52651
- connIndex.forEach(function(c) {
52652
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
52653
- if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
52654
- });
53088
+ for (var nid in origNodeBoxMap) {
53089
+ var b = origNodeBoxMap[nid];
53090
+ nodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
53091
+ }
53092
+ recalcAllPaths();
52655
53093
  fitToView();
52656
53094
  }
52657
53095
  document.getElementById('btn-reset').addEventListener('click', resetLayout);
@@ -52721,39 +53159,24 @@ path[data-source].port-hover { opacity: 1; }
52721
53159
  });
52722
53160
  }
52723
53161
 
52724
- // Recalculate affected connection paths (skip scope connections when parent is dragged \u2014 they move with the group transform)
52725
- connIndex.forEach(function(c) {
52726
- if (c.scopeOf === nodeId) return;
52727
- if (c.srcNode === nodeId || c.tgtNode === nodeId) {
52728
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
52729
- if (sp && tp) {
52730
- if (c.scopeOf) {
52731
- // Scope connection paths live inside the parent group; use parent-local coords
52732
- var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
52733
- c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
52734
- } else {
52735
- c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
52736
- }
53162
+ // Update node box positions for orthogonal routing
53163
+ if (nodeBoxMap[nodeId]) {
53164
+ var nb = origNodeBoxMap[nodeId];
53165
+ if (nb) nodeBoxMap[nodeId] = { id: nb.id, x: nb.x + off.dx, y: nb.y + off.dy, w: nb.w, h: nb.h };
53166
+ }
53167
+ if (nodeG) {
53168
+ var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
53169
+ children.forEach(function(childG) {
53170
+ var childId = childG.getAttribute('data-node-id');
53171
+ var cnb = origNodeBoxMap[childId];
53172
+ if (cnb && nodeOffsets[childId]) {
53173
+ nodeBoxMap[childId] = { id: cnb.id, x: cnb.x + nodeOffsets[childId].dx, y: cnb.y + nodeOffsets[childId].dy, w: cnb.w, h: cnb.h };
52737
53174
  }
52738
- }
52739
- if (nodeG) {
52740
- var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
52741
- children.forEach(function(childG) {
52742
- var childId = childG.getAttribute('data-node-id');
52743
- if (c.srcNode === childId || c.tgtNode === childId) {
52744
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
52745
- if (sp && tp) {
52746
- if (c.scopeOf) {
52747
- var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
52748
- c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
52749
- } else {
52750
- c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
52751
- }
52752
- }
52753
- }
52754
- });
52755
- }
52756
- });
53175
+ });
53176
+ }
53177
+
53178
+ // Recalculate all connection paths with orthogonal routing
53179
+ recalcAllPaths();
52757
53180
  }
52758
53181
 
52759
53182
  var allLabelIds = Object.keys(labelMap);
@@ -106002,7 +106425,7 @@ function displayInstalledPackage(pkg) {
106002
106425
  // src/cli/index.ts
106003
106426
  init_logger();
106004
106427
  init_error_utils();
106005
- var version2 = true ? "0.19.0" : "0.0.0-dev";
106428
+ var version2 = true ? "0.19.2" : "0.0.0-dev";
106006
106429
  var program2 = new Command();
106007
106430
  program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
106008
106431
  logger.banner(version2);
@@ -926,6 +926,7 @@ export function buildDiagramGraph(ast, options = {}) {
926
926
  }
927
927
  }
928
928
  // Build NodeBox array for orthogonal routing (after coordinate normalization)
929
+ // Include scope children so the router can route around them
929
930
  const nodeBoxes = nodes.map(node => ({
930
931
  id: node.id,
931
932
  x: node.x,
@@ -933,6 +934,13 @@ export function buildDiagramGraph(ast, options = {}) {
933
934
  width: node.width,
934
935
  height: node.height,
935
936
  }));
937
+ for (const node of nodes) {
938
+ if (node.scopeChildren) {
939
+ for (const child of node.scopeChildren) {
940
+ nodeBoxes.push({ id: child.id, x: child.x, y: child.y, width: child.width, height: child.height });
941
+ }
942
+ }
943
+ }
936
944
  // Sort connections: short spans first, then by source X, then by source Y
937
945
  // (matching original editor for deterministic track allocation)
938
946
  pendingConnections.sort((a, b) => {
@@ -1010,18 +1018,100 @@ export function buildDiagramGraph(ast, options = {}) {
1010
1018
  path,
1011
1019
  });
1012
1020
  }
1013
- // Recompute scope connection paths after normalization
1021
+ // Recompute scope connection paths with the same routing logic as external connections
1014
1022
  for (const node of nodes) {
1015
- if (node.scopeConnections && node.scopePorts && node.scopeChildren) {
1016
- const childMap = new Map();
1017
- for (const c of node.scopeChildren)
1018
- childMap.set(c.id, c);
1019
- for (const conn of node.scopeConnections) {
1020
- const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, 'output');
1021
- const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, 'input');
1022
- if (sPort && tPort) {
1023
- conn.path = computeConnectionPath(sPort.cx, sPort.cy, tPort.cx, tPort.cy);
1024
- }
1023
+ if (!node.scopeConnections || !node.scopePorts || !node.scopeChildren)
1024
+ continue;
1025
+ const childMap = new Map();
1026
+ for (const c of node.scopeChildren)
1027
+ childMap.set(c.id, c);
1028
+ const scopePending = [];
1029
+ for (const conn of node.scopeConnections) {
1030
+ const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, 'output');
1031
+ const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, 'input');
1032
+ if (!sPort || !tPort)
1033
+ continue;
1034
+ let fromPortIndex = 0;
1035
+ let toPortIndex = 0;
1036
+ if (conn.fromNode === node.id) {
1037
+ fromPortIndex = node.scopePorts.outputs.indexOf(sPort);
1038
+ }
1039
+ else {
1040
+ const fromChild = childMap.get(conn.fromNode);
1041
+ if (fromChild)
1042
+ fromPortIndex = fromChild.outputs.indexOf(sPort);
1043
+ }
1044
+ if (conn.toNode === node.id) {
1045
+ toPortIndex = node.scopePorts.inputs.indexOf(tPort);
1046
+ }
1047
+ else {
1048
+ const toChild = childMap.get(conn.toNode);
1049
+ if (toChild)
1050
+ toPortIndex = toChild.inputs.indexOf(tPort);
1051
+ }
1052
+ scopePending.push({
1053
+ conn, sourcePort: sPort, targetPort: tPort,
1054
+ fromNodeId: conn.fromNode, toNodeId: conn.toNode,
1055
+ fromPortIndex: Math.max(0, fromPortIndex),
1056
+ toPortIndex: Math.max(0, toPortIndex),
1057
+ });
1058
+ }
1059
+ // Sort: short spans first, then by source X, then source Y
1060
+ scopePending.sort((a, b) => {
1061
+ const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
1062
+ const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
1063
+ if (Math.abs(aSpan - bSpan) > 1)
1064
+ return aSpan - bSpan;
1065
+ if (Math.abs(a.sourcePort.cx - b.sourcePort.cx) > 1)
1066
+ return a.sourcePort.cx - b.sourcePort.cx;
1067
+ return a.sourcePort.cy - b.sourcePort.cy;
1068
+ });
1069
+ // Fan-out/fan-in consistency: force all to curves if any in the group is short
1070
+ const spSrcKey = (sp) => `s:${sp.fromNodeId}.${sp.conn.fromPort}`;
1071
+ const spTgtKey = (sp) => `t:${sp.toNodeId}.${sp.conn.toPort}`;
1072
+ const spTgtNodeKey = (sp) => `n:${sp.toNodeId}`;
1073
+ const spGroups = new Map();
1074
+ for (const sp of scopePending) {
1075
+ for (const key of [spSrcKey(sp), spTgtKey(sp), spTgtNodeKey(sp)]) {
1076
+ if (!spGroups.has(key))
1077
+ spGroups.set(key, []);
1078
+ spGroups.get(key).push(sp);
1079
+ }
1080
+ }
1081
+ const spForceCurveKeys = new Set();
1082
+ for (const [key, group] of spGroups) {
1083
+ if (group.length < 2)
1084
+ continue;
1085
+ const anyShort = group.some(sp => {
1086
+ const ddx = sp.targetPort.cx - sp.sourcePort.cx;
1087
+ const ddy = sp.targetPort.cy - sp.sourcePort.cy;
1088
+ return Math.sqrt(ddx * ddx + ddy * ddy) <= ORTHOGONAL_DISTANCE_THRESHOLD;
1089
+ });
1090
+ if (anyShort)
1091
+ spForceCurveKeys.add(key);
1092
+ }
1093
+ const spForceCurveSet = new Set();
1094
+ for (const sp of scopePending) {
1095
+ if (spForceCurveKeys.has(spSrcKey(sp)) || spForceCurveKeys.has(spTgtKey(sp)) || spForceCurveKeys.has(spTgtNodeKey(sp))) {
1096
+ spForceCurveSet.add(sp);
1097
+ }
1098
+ }
1099
+ // Route each scope connection
1100
+ for (const sp of scopePending) {
1101
+ const sx = sp.sourcePort.cx;
1102
+ const sy = sp.sourcePort.cy;
1103
+ const tx = sp.targetPort.cx;
1104
+ const ty = sp.targetPort.cy;
1105
+ const ddx = tx - sx;
1106
+ const ddy = ty - sy;
1107
+ const dist = Math.sqrt(ddx * ddx + ddy * ddy);
1108
+ const useCurve = spForceCurveSet.has(sp);
1109
+ if (!useCurve && dist > ORTHOGONAL_DISTANCE_THRESHOLD) {
1110
+ const orthoPath = calculateOrthogonalPathSafe([sx, sy], [tx, ty], nodeBoxes, sp.fromNodeId, sp.toNodeId, { fromPortIndex: sp.fromPortIndex, toPortIndex: sp.toPortIndex }, allocator);
1111
+ sp.conn.path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
1112
+ }
1113
+ else {
1114
+ sp.conn.path = computeConnectionPath(sx, sy, tx, ty);
1025
1115
  }
1026
1116
  }
1027
1117
  }
@@ -553,7 +553,10 @@ path[data-source].port-hover { opacity: 1; }
553
553
  portConnections[tgt].push(src);
554
554
  });
555
555
 
556
- // ---- Connection path computation (from geometry.ts) ----
556
+ // ---- Connection path computation (bezier from geometry.ts + orthogonal router) ----
557
+ var TRACK_SPACING = 15, EDGE_OFFSET = 5, MAX_CANDIDATES = 5;
558
+ var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
559
+
557
560
  function quadCurveControl(ax, ay, bx, by, ux, uy) {
558
561
  var dn = Math.abs(ay - by);
559
562
  return [bx + (ux * dn) / Math.abs(uy), ay];
@@ -583,18 +586,326 @@ path[data-source].port-hover { opacity: 1; }
583
586
  ' L ' + hx + ',' + hy;
584
587
  }
585
588
 
586
- // ---- Port position + connection path indexes ----
589
+ // ---- Orthogonal router (ported from orthogonal-router.ts) ----
590
+ function createAllocator() {
591
+ var claims = [], vclaims = [];
592
+ function isOcc(xn, xx, y) {
593
+ for (var i = 0; i < claims.length; i++) {
594
+ var c = claims[i];
595
+ if (c.xn < xx && c.xx > xn && Math.abs(c.y - y) < TRACK_SPACING) return true;
596
+ }
597
+ return false;
598
+ }
599
+ function isOccV(yn, yx, x) {
600
+ for (var i = 0; i < vclaims.length; i++) {
601
+ var c = vclaims[i];
602
+ if (c.yn < yx && c.yx > yn && Math.abs(c.x - x) < TRACK_SPACING) return true;
603
+ }
604
+ return false;
605
+ }
606
+ function blockedByNode(xn, xx, y, boxes) {
607
+ for (var i = 0; i < boxes.length; i++) {
608
+ var b = boxes[i]; if (xn < b.r && xx > b.l && y >= b.t && y <= b.b) return true;
609
+ }
610
+ return false;
611
+ }
612
+ function blockedByNodeV(yn, yx, x, boxes) {
613
+ for (var i = 0; i < boxes.length; i++) {
614
+ var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true;
615
+ }
616
+ return false;
617
+ }
618
+ function countHCross(xn, xx, y) {
619
+ var n = 0;
620
+ for (var i = 0; i < vclaims.length; i++) {
621
+ var c = vclaims[i]; if (c.x > xn && c.x < xx && y >= c.yn && y <= c.yx) n++;
622
+ }
623
+ return n;
624
+ }
625
+ function countVCross(yn, yx, x) {
626
+ var n = 0;
627
+ for (var i = 0; i < claims.length; i++) {
628
+ var c = claims[i]; if (c.y > yn && c.y < yx && x >= c.xn && x <= c.xx) n++;
629
+ }
630
+ return n;
631
+ }
632
+ return {
633
+ findFreeY: function(xn, xx, cy, nb) {
634
+ var free = function(y) { return !isOcc(xn, xx, y) && (!nb || !blockedByNode(xn, xx, y, nb)); };
635
+ if (free(cy)) return cy;
636
+ var cands = [];
637
+ for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
638
+ if (free(cy - off)) cands.push({ y: cy - off, d: off });
639
+ if (free(cy + off)) cands.push({ y: cy + off, d: off });
640
+ }
641
+ if (!cands.length) return cy;
642
+ var best = cands[0].y, bc = countHCross(xn, xx, cands[0].y), bd = cands[0].d;
643
+ for (var i = 1; i < cands.length; i++) {
644
+ var cr = countHCross(xn, xx, cands[i].y);
645
+ if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].y; bc = cr; bd = cands[i].d; }
646
+ }
647
+ return best;
648
+ },
649
+ findFreeX: function(yn, yx, cx, nb) {
650
+ var free = function(x) { return !isOccV(yn, yx, x) && (!nb || !blockedByNodeV(yn, yx, x, nb)); };
651
+ if (free(cx)) return cx;
652
+ var cands = [];
653
+ for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
654
+ if (free(cx - off)) cands.push({ x: cx - off, d: off });
655
+ if (free(cx + off)) cands.push({ x: cx + off, d: off });
656
+ }
657
+ if (!cands.length) return cx;
658
+ var best = cands[0].x, bc = countVCross(yn, yx, cands[0].x), bd = cands[0].d;
659
+ for (var i = 1; i < cands.length; i++) {
660
+ var cr = countVCross(yn, yx, cands[i].x);
661
+ if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].x; bc = cr; bd = cands[i].d; }
662
+ }
663
+ return best;
664
+ },
665
+ claim: function(xn, xx, y) { claims.push({ xn: xn, xx: xx, y: y }); },
666
+ claimV: function(yn, yx, x) { vclaims.push({ yn: yn, yx: yx, x: x }); }
667
+ };
668
+ }
669
+
670
+ function inflateBox(b, pad) { return { l: b.x - pad, r: b.x + b.w + pad, t: b.y - pad, b: b.y + b.h + pad }; }
671
+ function segOvlp(xn, xx, y, bx) { return xn < bx.r && xx > bx.l && y >= bx.t && y <= bx.b; }
672
+ function vSegClear(x, yn, yx, boxes) {
673
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return false; }
674
+ return true;
675
+ }
676
+
677
+ function findClearY(xn, xx, cy, boxes) {
678
+ var blocked = function(y) { for (var i = 0; i < boxes.length; i++) if (segOvlp(xn, xx, y, boxes[i])) return true; return false; };
679
+ if (!blocked(cy)) return cy;
680
+ var edges = [];
681
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (xn < b.r && xx > b.l) { edges.push(b.t); edges.push(b.b); } }
682
+ if (!edges.length) return cy;
683
+ edges.sort(function(a, b) { return a - b; });
684
+ var best = cy, bd = Infinity;
685
+ for (var i = 0; i < edges.length; i++) {
686
+ var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
687
+ for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cy); if (d < bd) { bd = d; best = vals[j]; } } }
688
+ }
689
+ if (bd === Infinity) {
690
+ var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
691
+ best = Math.abs(mn - cy) <= Math.abs(mx - cy) ? mn : mx;
692
+ if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
693
+ }
694
+ return best;
695
+ }
696
+
697
+ function findClearX(yn, yx, cx, boxes) {
698
+ var blocked = function(x) { for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true; } return false; };
699
+ if (!blocked(cx)) return cx;
700
+ var edges = [];
701
+ for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (yn < b.b && yx > b.t) { edges.push(b.l); edges.push(b.r); } }
702
+ if (!edges.length) return cx;
703
+ edges.sort(function(a, b) { return a - b; });
704
+ var best = cx, bd = Infinity;
705
+ for (var i = 0; i < edges.length; i++) {
706
+ var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
707
+ for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cx); if (d < bd) { bd = d; best = vals[j]; } } }
708
+ }
709
+ if (bd === Infinity) {
710
+ var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
711
+ best = Math.abs(mn - cx) <= Math.abs(mx - cx) ? mn : mx;
712
+ if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
713
+ }
714
+ return best;
715
+ }
716
+
717
+ function simplifyWaypoints(pts) {
718
+ if (pts.length <= 2) return pts;
719
+ var jogFound = true;
720
+ while (jogFound) {
721
+ jogFound = false;
722
+ for (var i = 0; i < pts.length - 3; i++) {
723
+ var a = pts[i], b = pts[i+1], c = pts[i+2], d = pts[i+3];
724
+ var jogH = Math.abs(b[1] - c[1]);
725
+ if (Math.abs(a[1] - b[1]) < 0.5 && Math.abs(b[0] - c[0]) < 0.5 && Math.abs(c[1] - d[1]) < 0.5 && jogH > 0.5 && jogH < JOG_THRESHOLD) {
726
+ var mid = (b[1] + c[1]) / 2, snap = Math.abs(a[1] - mid) <= Math.abs(d[1] - mid) ? a[1] : d[1];
727
+ pts = pts.slice(); pts[i+1] = [b[0], snap]; pts[i+2] = [c[0], snap]; jogFound = true; break;
728
+ }
729
+ var jogW = Math.abs(b[0] - c[0]);
730
+ if (Math.abs(a[0] - b[0]) < 0.5 && Math.abs(b[1] - c[1]) < 0.5 && Math.abs(c[0] - d[0]) < 0.5 && jogW > 0.5 && jogW < JOG_THRESHOLD) {
731
+ var mid = (b[0] + c[0]) / 2, snap = Math.abs(a[0] - mid) <= Math.abs(d[0] - mid) ? a[0] : d[0];
732
+ pts = pts.slice(); pts[i+1] = [snap, b[1]]; pts[i+2] = [snap, c[1]]; jogFound = true; break;
733
+ }
734
+ }
735
+ }
736
+ var res = [pts[0]];
737
+ for (var i = 1; i < pts.length - 1; i++) {
738
+ var prev = res[res.length - 1], cur = pts[i], next = pts[i+1];
739
+ if (Math.abs(prev[0] - cur[0]) + Math.abs(prev[1] - cur[1]) < MIN_SEG_LEN) continue;
740
+ var sameX = Math.abs(prev[0] - cur[0]) < 0.01 && Math.abs(cur[0] - next[0]) < 0.01;
741
+ var sameY = Math.abs(prev[1] - cur[1]) < 0.01 && Math.abs(cur[1] - next[1]) < 0.01;
742
+ if (!sameX && !sameY) res.push(cur);
743
+ }
744
+ res.push(pts[pts.length - 1]);
745
+ return res;
746
+ }
747
+
748
+ function waypointsToPath(wp, cr) {
749
+ if (wp.length < 2) return '';
750
+ if (wp.length === 2) return 'M ' + wp[0][0] + ',' + wp[0][1] + ' L ' + wp[1][0] + ',' + wp[1][1];
751
+ var radii = [];
752
+ for (var i = 0; i < wp.length; i++) radii[i] = 0;
753
+ for (var i = 1; i < wp.length - 1; i++) {
754
+ var p = wp[i-1], c = wp[i], n = wp[i+1];
755
+ var lp = Math.sqrt((p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]));
756
+ var ln = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
757
+ radii[i] = (lp < 0.01 || ln < 0.01) ? 0 : Math.min(cr, lp / 2, ln / 2);
758
+ }
759
+ for (var i = 1; i < wp.length - 2; i++) {
760
+ var c = wp[i], n = wp[i+1];
761
+ var sl = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
762
+ var tot = radii[i] + radii[i+1];
763
+ if (tot > sl && tot > 0) { var sc = sl / tot; radii[i] *= sc; radii[i+1] *= sc; }
764
+ }
765
+ var path = 'M ' + wp[0][0] + ',' + wp[0][1];
766
+ for (var i = 1; i < wp.length - 1; i++) {
767
+ var p = wp[i-1], c = wp[i], n = wp[i+1], r = radii[i];
768
+ if (r < 2) { path += ' L ' + c[0] + ',' + c[1]; continue; }
769
+ var dpx = p[0]-c[0], dpy = p[1]-c[1], dnx = n[0]-c[0], dny = n[1]-c[1];
770
+ var lp = Math.sqrt(dpx*dpx + dpy*dpy), ln = Math.sqrt(dnx*dnx + dny*dny);
771
+ var upx = dpx/lp, upy = dpy/lp, unx = dnx/ln, uny = dny/ln;
772
+ var asx = c[0] + upx*r, asy = c[1] + upy*r, aex = c[0] + unx*r, aey = c[1] + uny*r;
773
+ var cross = dpx*dny - dpy*dnx, sweep = cross > 0 ? 0 : 1;
774
+ path += ' L ' + asx + ',' + asy + ' A ' + r + ' ' + r + ' 0 0 ' + sweep + ' ' + aex + ',' + aey;
775
+ }
776
+ path += ' L ' + wp[wp.length-1][0] + ',' + wp[wp.length-1][1];
777
+ return path;
778
+ }
779
+
780
+ function computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc) {
781
+ var isSelf = srcId === tgtId;
782
+ var iboxes = [];
783
+ for (var i = 0; i < nboxes.length; i++) {
784
+ var b = nboxes[i];
785
+ if (isSelf || (b.id !== srcId && b.id !== tgtId)) iboxes.push(inflateBox(b, pad));
786
+ }
787
+ var se = [from[0] + exitStub, from[1]], sn = [to[0] - entryStub, to[1]];
788
+ var xn = Math.min(se[0], sn[0]), xx = Math.max(se[0], sn[0]);
789
+
790
+ if (!isSelf && to[0] > from[0]) {
791
+ var cy = (from[1] + to[1]) / 2;
792
+ var intBoxes = [];
793
+ for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) intBoxes.push(b); }
794
+ if (intBoxes.length >= 2) {
795
+ var ct = Infinity, cb = -Infinity;
796
+ for (var i = 0; i < intBoxes.length; i++) { ct = Math.min(ct, intBoxes[i].t); cb = Math.max(cb, intBoxes[i].b); }
797
+ if (cy > ct && cy < cb) { cy = (cy - ct <= cb - cy) ? ct - pad : cb + pad; }
798
+ }
799
+ var clearY = findClearY(xn, xx, cy, iboxes);
800
+ if (Math.abs(from[1] - to[1]) < JOG_THRESHOLD && Math.abs(clearY - from[1]) < JOG_THRESHOLD) return null;
801
+
802
+ var midX = (se[0] + sn[0]) / 2, ymn = Math.min(from[1], to[1]), ymx = Math.max(from[1], to[1]);
803
+ var cmx = findClearX(ymn, ymx, midX, iboxes);
804
+ var fmx = alloc.findFreeX(ymn, ymx, cmx, iboxes);
805
+ if (ymx - ymn >= JOG_THRESHOLD && fmx > se[0] && fmx < sn[0] &&
806
+ vSegClear(fmx, ymn, ymx, iboxes) &&
807
+ alloc.findFreeY(from[0], fmx, from[1], iboxes) === from[1] &&
808
+ alloc.findFreeY(fmx, to[0], to[1], iboxes) === to[1]) {
809
+ alloc.claim(from[0], fmx, from[1]); alloc.claim(fmx, to[0], to[1]); alloc.claimV(ymn, ymx, fmx);
810
+ return simplifyWaypoints([from, [fmx, from[1]], [fmx, to[1]], to]);
811
+ }
812
+
813
+ clearY = alloc.findFreeY(xn, xx, clearY, iboxes);
814
+ if (Math.abs(clearY - from[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, from[1], b); })) clearY = from[1];
815
+ else if (Math.abs(clearY - to[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, to[1], b); })) clearY = to[1];
816
+ alloc.claim(xn, xx, clearY);
817
+
818
+ var eymn = Math.min(from[1], clearY), eymx = Math.max(from[1], clearY);
819
+ var exX = findClearX(eymn, eymx, se[0], iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes);
820
+ if (exX < from[0]) { exX = se[0]; if (!vSegClear(exX, eymn, eymx, iboxes)) { exX = findClearX(eymn, eymx, se[0] + TRACK_SPACING, iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes); } }
821
+ alloc.claimV(eymn, eymx, exX);
822
+
823
+ var nymn = Math.min(to[1], clearY), nymx = Math.max(to[1], clearY);
824
+ var nxX = findClearX(nymn, nymx, sn[0], iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes);
825
+ if (nxX > to[0]) { nxX = sn[0]; if (!vSegClear(nxX, nymn, nymx, iboxes)) { nxX = findClearX(nymn, nymx, sn[0] - TRACK_SPACING, iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes); } }
826
+ alloc.claimV(nymn, nymx, nxX);
827
+
828
+ return simplifyWaypoints([from, [exX, from[1]], [exX, clearY], [nxX, clearY], [nxX, to[1]], to]);
829
+ } else {
830
+ var srcBox = null, tgtBox = null;
831
+ for (var i = 0; i < nboxes.length; i++) { if (nboxes[i].id === srcId) srcBox = nboxes[i]; if (nboxes[i].id === tgtId) tgtBox = nboxes[i]; }
832
+ var corBoxes = []; for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) corBoxes.push(b); }
833
+ var bots = corBoxes.map(function(b) { return b.b; }), tops = corBoxes.map(function(b) { return b.t; });
834
+ if (srcBox) { bots.push(srcBox.y + srcBox.h + pad); tops.push(srcBox.y - pad); }
835
+ if (tgtBox) { bots.push(tgtBox.y + tgtBox.h + pad); tops.push(tgtBox.y - pad); }
836
+ var maxBot = Math.max.apply(null, bots.concat([from[1] + 50, to[1] + 50]));
837
+ var minTop = Math.min.apply(null, tops.concat([from[1] - 50, to[1] - 50]));
838
+ var avgY = (from[1] + to[1]) / 2;
839
+ var escBelow = maxBot + pad, escAbove = minTop - pad;
840
+ var escY = Math.abs(escAbove - avgY) <= Math.abs(escBelow - avgY) ? escAbove : escBelow;
841
+ escY = findClearY(xn, xx, escY, iboxes); escY = alloc.findFreeY(xn, xx, escY, iboxes); alloc.claim(xn, xx, escY);
842
+
843
+ var bymn = Math.min(from[1], escY), bymx = Math.max(from[1], escY);
844
+ var bexX = findClearX(bymn, bymx, se[0], iboxes); bexX = alloc.findFreeX(bymn, bymx, bexX, iboxes); alloc.claimV(bymn, bymx, bexX);
845
+
846
+ var bnmn = Math.min(to[1], escY), bnmx = Math.max(to[1], escY);
847
+ var bnxX = findClearX(bnmn, bnmx, sn[0], iboxes); bnxX = alloc.findFreeX(bnmn, bnmx, bnxX, iboxes); alloc.claimV(bnmn, bnmx, bnxX);
848
+
849
+ return simplifyWaypoints([from, [bexX, from[1]], [bexX, escY], [bnxX, escY], [bnxX, to[1]], to]);
850
+ }
851
+ }
852
+
853
+ function calcOrthogonalPath(from, to, nboxes, srcId, tgtId, fromIdx, toIdx, alloc) {
854
+ var pad = 15, stubLen = 20, stubSpc = 12, maxStub = 80, cr = 10;
855
+ var exitStub = Math.min(stubLen + fromIdx * stubSpc, maxStub);
856
+ var entryStub = Math.min(stubLen + toIdx * stubSpc, maxStub);
857
+ try {
858
+ var wp = computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc);
859
+ if (!wp) return null;
860
+ var p = waypointsToPath(wp, cr);
861
+ return (p && p.length >= 5) ? p : null;
862
+ } catch (e) { return null; }
863
+ }
864
+
865
+ // ---- Port position + node box + connection path indexes ----
587
866
  var portPositions = {};
588
867
  content.querySelectorAll('[data-port-id]').forEach(function(el) {
589
868
  var id = el.getAttribute('data-port-id');
590
869
  portPositions[id] = { cx: parseFloat(el.getAttribute('cx')), cy: parseFloat(el.getAttribute('cy')) };
591
870
  });
592
871
 
872
+ // Extract node bounding boxes from SVG rect elements
873
+ var nodeBoxMap = {};
874
+ content.querySelectorAll('.nodes [data-node-id]').forEach(function(g) {
875
+ var nid = g.getAttribute('data-node-id');
876
+ var rect = g.querySelector(':scope > rect');
877
+ if (!rect) return;
878
+ nodeBoxMap[nid] = {
879
+ id: nid,
880
+ x: parseFloat(rect.getAttribute('x')),
881
+ y: parseFloat(rect.getAttribute('y')),
882
+ w: parseFloat(rect.getAttribute('width')),
883
+ h: parseFloat(rect.getAttribute('height'))
884
+ };
885
+ });
886
+
887
+ // Build port-to-node mapping and compute port indices within each node
888
+ var portNodeMap = {};
889
+ var portIndexMap = {};
890
+ content.querySelectorAll('[data-port-id]').forEach(function(el) {
891
+ var id = el.getAttribute('data-port-id');
892
+ var dir = el.getAttribute('data-direction');
893
+ var parts = id.split('.');
894
+ var nodeId = parts[0];
895
+ portNodeMap[id] = nodeId;
896
+ if (!portIndexMap[nodeId]) portIndexMap[nodeId] = { input: [], output: [] };
897
+ portIndexMap[nodeId][dir].push(id);
898
+ });
899
+
593
900
  var nodeOffsets = {};
594
901
  var connIndex = [];
595
902
  content.querySelectorAll('path[data-source]').forEach(function(p) {
596
903
  var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
597
- connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0], scopeOf: p.getAttribute('data-scope') || null });
904
+ var srcNode = src.split('.')[0], tgtNode = tgt.split('.')[0];
905
+ var srcIdx = portIndexMap[srcNode] ? portIndexMap[srcNode].output.indexOf(src) : 0;
906
+ var tgtIdx = portIndexMap[tgtNode] ? portIndexMap[tgtNode].input.indexOf(tgt) : 0;
907
+ connIndex.push({ el: p, src: src, tgt: tgt, srcNode: srcNode, tgtNode: tgtNode,
908
+ scopeOf: p.getAttribute('data-scope') || null, srcIdx: Math.max(0, srcIdx), tgtIdx: Math.max(0, tgtIdx) });
598
909
  });
599
910
 
600
911
  // Snapshot of original port positions for reset
@@ -602,6 +913,47 @@ path[data-source].port-hover { opacity: 1; }
602
913
  for (var pid in portPositions) {
603
914
  origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
604
915
  }
916
+ var origNodeBoxMap = {};
917
+ for (var nid in nodeBoxMap) {
918
+ var b = nodeBoxMap[nid];
919
+ origNodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
920
+ }
921
+
922
+ // ---- Recalculate all connection paths using orthogonal + bezier routing ----
923
+ function recalcAllPaths() {
924
+ var boxes = [];
925
+ for (var nid in nodeBoxMap) boxes.push(nodeBoxMap[nid]);
926
+ var sorted = connIndex.slice().sort(function(a, b) {
927
+ var spa = portPositions[a.src] && portPositions[a.tgt] ? Math.abs(portPositions[a.tgt].cx - portPositions[a.src].cx) : 0;
928
+ var spb = portPositions[b.src] && portPositions[b.tgt] ? Math.abs(portPositions[b.tgt].cx - portPositions[b.src].cx) : 0;
929
+ if (Math.abs(spa - spb) > 1) return spa - spb;
930
+ var sxa = portPositions[a.src] ? portPositions[a.src].cx : 0, sxb = portPositions[b.src] ? portPositions[b.src].cx : 0;
931
+ if (Math.abs(sxa - sxb) > 1) return sxa - sxb;
932
+ var sya = portPositions[a.src] ? portPositions[a.src].cy : 0, syb = portPositions[b.src] ? portPositions[b.src].cy : 0;
933
+ return sya - syb;
934
+ });
935
+ var alloc = createAllocator();
936
+ for (var i = 0; i < sorted.length; i++) {
937
+ var c = sorted[i];
938
+ var sp = portPositions[c.src], tp = portPositions[c.tgt];
939
+ if (!sp || !tp) continue;
940
+ var sx = sp.cx, sy = sp.cy, tx = tp.cx, ty = tp.cy;
941
+ // For scope connections, use parent-local coords
942
+ if (c.scopeOf) {
943
+ var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
944
+ sx -= pOff.dx; sy -= pOff.dy; tx -= pOff.dx; ty -= pOff.dy;
945
+ }
946
+ var ddx = tx - sx, ddy = ty - sy, dist = Math.sqrt(ddx * ddx + ddy * ddy);
947
+ var path;
948
+ if (dist > ORTHO_THRESHOLD) {
949
+ path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
950
+ if (!path) path = computeConnectionPath(sx, sy, tx, ty);
951
+ } else {
952
+ path = computeConnectionPath(sx, sy, tx, ty);
953
+ }
954
+ c.el.setAttribute('d', path);
955
+ }
956
+ }
605
957
 
606
958
  function resetLayout() {
607
959
  for (var nid in nodeOffsets) {
@@ -621,10 +973,11 @@ path[data-source].port-hover { opacity: 1; }
621
973
  for (var pid in origPortPositions) {
622
974
  portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
623
975
  }
624
- connIndex.forEach(function(c) {
625
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
626
- if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
627
- });
976
+ for (var nid in origNodeBoxMap) {
977
+ var b = origNodeBoxMap[nid];
978
+ nodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
979
+ }
980
+ recalcAllPaths();
628
981
  fitToView();
629
982
  }
630
983
  document.getElementById('btn-reset').addEventListener('click', resetLayout);
@@ -694,39 +1047,24 @@ path[data-source].port-hover { opacity: 1; }
694
1047
  });
695
1048
  }
696
1049
 
697
- // Recalculate affected connection paths (skip scope connections when parent is dragged — they move with the group transform)
698
- connIndex.forEach(function(c) {
699
- if (c.scopeOf === nodeId) return;
700
- if (c.srcNode === nodeId || c.tgtNode === nodeId) {
701
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
702
- if (sp && tp) {
703
- if (c.scopeOf) {
704
- // Scope connection paths live inside the parent group; use parent-local coords
705
- var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
706
- c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
707
- } else {
708
- c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
709
- }
1050
+ // Update node box positions for orthogonal routing
1051
+ if (nodeBoxMap[nodeId]) {
1052
+ var nb = origNodeBoxMap[nodeId];
1053
+ if (nb) nodeBoxMap[nodeId] = { id: nb.id, x: nb.x + off.dx, y: nb.y + off.dy, w: nb.w, h: nb.h };
1054
+ }
1055
+ if (nodeG) {
1056
+ var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
1057
+ children.forEach(function(childG) {
1058
+ var childId = childG.getAttribute('data-node-id');
1059
+ var cnb = origNodeBoxMap[childId];
1060
+ if (cnb && nodeOffsets[childId]) {
1061
+ nodeBoxMap[childId] = { id: cnb.id, x: cnb.x + nodeOffsets[childId].dx, y: cnb.y + nodeOffsets[childId].dy, w: cnb.w, h: cnb.h };
710
1062
  }
711
- }
712
- if (nodeG) {
713
- var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
714
- children.forEach(function(childG) {
715
- var childId = childG.getAttribute('data-node-id');
716
- if (c.srcNode === childId || c.tgtNode === childId) {
717
- var sp = portPositions[c.src], tp = portPositions[c.tgt];
718
- if (sp && tp) {
719
- if (c.scopeOf) {
720
- var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
721
- c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
722
- } else {
723
- c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
724
- }
725
- }
726
- }
727
- });
728
- }
729
- });
1063
+ });
1064
+ }
1065
+
1066
+ // Recalculate all connection paths with orthogonal routing
1067
+ recalcAllPaths();
730
1068
  }
731
1069
 
732
1070
  var allLabelIds = Object.keys(labelMap);
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.19.0";
1
+ export declare const VERSION = "0.19.2";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.19.0';
2
+ export const VERSION = '0.19.2';
3
3
  //# sourceMappingURL=generated-version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",