@synergenius/flow-weaver 0.17.15 → 0.19.1

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.
@@ -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.17.15";
1
+ export declare const VERSION = "0.19.1";
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.17.15';
2
+ export const VERSION = '0.19.1';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -4,7 +4,7 @@
4
4
  * Flow Weaver Marketplace — discover, install, and publish reusable
5
5
  * node types, workflows, and patterns via npm.
6
6
  */
7
- export type { TMarketplaceManifest, TManifestNodeType, TManifestWorkflow, TManifestPattern, TManifestExportTarget, TManifestPort, TValidationIssue, TValidationSeverity, TPackageValidationResult, TMarketplacePackageInfo, TInstalledPackage, TMarketInitConfig, TManifestTagHandler, TManifestValidationRuleSet, TManifestDocTopic, TManifestInitContribution, } from './types.js';
7
+ export type { TMarketplaceManifest, TManifestNodeType, TManifestWorkflow, TManifestPattern, TManifestExportTarget, TManifestPort, TValidationIssue, TValidationSeverity, TPackageValidationResult, TMarketplacePackageInfo, TInstalledPackage, TMarketInitConfig, TManifestTagHandler, TManifestValidationRuleSet, TManifestDocTopic, TManifestInitContribution, TManifestCliCommand, TManifestMcpTool, } from './types.js';
8
8
  export { generateManifest, writeManifest, readManifest, type GenerateManifestOptions, type GenerateManifestResult, } from './manifest.js';
9
9
  export { validatePackage } from './validator.js';
10
10
  export { searchPackages, listInstalledPackages, getInstalledPackageManifest, discoverTagHandlers, discoverValidationRuleSets, discoverDocTopics, discoverInitContributions, type SearchOptions, type TDiscoveredTagHandler, type TDiscoveredValidationRuleSet, type TDiscoveredDocTopic, type TDiscoveredInitContribution, } from './registry.js';
@@ -36,6 +36,14 @@ export type TMarketplaceManifest = {
36
36
  docs?: TManifestDocTopic[];
37
37
  /** Init contributions: use cases and templates (v2) */
38
38
  initContributions?: TManifestInitContribution;
39
+ /** CLI entrypoint module for pack-contributed commands */
40
+ cliEntrypoint?: string;
41
+ /** CLI commands contributed by this pack */
42
+ cliCommands?: TManifestCliCommand[];
43
+ /** MCP entrypoint module for pack-contributed tools */
44
+ mcpEntrypoint?: string;
45
+ /** MCP tools contributed by this pack */
46
+ mcpTools?: TManifestMcpTool[];
39
47
  /** External dependency information */
40
48
  dependencies?: {
41
49
  /** Flow Weaver peer dependency constraints */
@@ -225,6 +233,28 @@ export type TManifestInitContribution = {
225
233
  /** Template IDs this pack provides (must match IDs in the template registry) */
226
234
  templates?: string[];
227
235
  };
236
+ /** A CLI command contributed by a pack. Registered under the pack namespace. */
237
+ export type TManifestCliCommand = {
238
+ /** Command name (e.g., "run", "history") */
239
+ name: string;
240
+ /** Human-readable description */
241
+ description: string;
242
+ /** Usage string for positional args (e.g., "<file>", "[id]") */
243
+ usage?: string;
244
+ /** Command options */
245
+ options?: Array<{
246
+ flags: string;
247
+ description: string;
248
+ default?: string | number | boolean;
249
+ }>;
250
+ };
251
+ /** An MCP tool contributed by a pack. */
252
+ export type TManifestMcpTool = {
253
+ /** Tool name (e.g., "fw_weaver_run") */
254
+ name: string;
255
+ /** Human-readable description */
256
+ description: string;
257
+ };
228
258
  export type TMarketInitConfig = {
229
259
  /** Package name (e.g., flowweaver-pack-openai) */
230
260
  name: string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Registers MCP tools contributed by installed marketplace packs.
3
+ *
4
+ * Scans for packs with mcpEntrypoint in their manifest. For each,
5
+ * imports the entrypoint and calls its registerMcpTools(mcp) function.
6
+ */
7
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ export declare function registerPackMcpTools(mcp: McpServer): Promise<void>;
9
+ //# sourceMappingURL=pack-tools.d.ts.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Registers MCP tools contributed by installed marketplace packs.
3
+ *
4
+ * Scans for packs with mcpEntrypoint in their manifest. For each,
5
+ * imports the entrypoint and calls its registerMcpTools(mcp) function.
6
+ */
7
+ import * as path from 'path';
8
+ import { listInstalledPackages } from '../marketplace/registry.js';
9
+ export async function registerPackMcpTools(mcp) {
10
+ const projectDir = process.cwd();
11
+ let packages;
12
+ try {
13
+ packages = await listInstalledPackages(projectDir);
14
+ }
15
+ catch {
16
+ return;
17
+ }
18
+ for (const pkg of packages) {
19
+ const manifest = pkg.manifest;
20
+ if (!manifest.mcpEntrypoint || !manifest.mcpTools?.length)
21
+ continue;
22
+ const entrypointPath = path.join(pkg.path, manifest.mcpEntrypoint);
23
+ try {
24
+ const mod = await import(entrypointPath);
25
+ if (typeof mod.registerMcpTools === 'function') {
26
+ await mod.registerMcpTools(mcp);
27
+ }
28
+ }
29
+ catch (err) {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ // Log to stderr so it doesn't interfere with MCP JSON-RPC on stdout
32
+ process.stderr.write(`[mcp] Failed to load pack tools from ${pkg.name}: ${msg}\n`);
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=pack-tools.js.map
@@ -18,6 +18,7 @@ import { registerDebugTools } from './tools-debug.js';
18
18
  import { registerContextTools } from './tools-context.js';
19
19
  import { registerResources } from './resources.js';
20
20
  import { registerPrompts } from './prompts.js';
21
+ import { registerPackMcpTools } from './pack-tools.js';
21
22
  function parseEventFilterFromEnv() {
22
23
  const filter = {};
23
24
  const include = process.env.FW_EVENT_INCLUDE;
@@ -82,6 +83,7 @@ export async function startMcpServer(options) {
82
83
  registerContextTools(mcp);
83
84
  registerResources(mcp, connection, buffer);
84
85
  registerPrompts(mcp);
86
+ await registerPackMcpTools(mcp);
85
87
  // Connect transport (only in stdio MCP mode)
86
88
  if (!options._testDeps && options.stdio) {
87
89
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.17.15",
3
+ "version": "0.19.1",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",