@synergenius/flow-weaver 0.10.0 → 0.10.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.
- package/README.md +16 -6
- package/dist/cli/flow-weaver.mjs +229 -11
- package/dist/diagram/html-viewer.js +109 -6
- package/dist/diagram/renderer.js +3 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# @synergenius/flow-weaver
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@synergenius/flow-weaver)
|
|
4
|
+
[](https://www.npmjs.com/package/@synergenius/flow-weaver)
|
|
5
|
+
[](https://github.com/synergenius-fw/flow-weaver/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/synergenius-fw/flow-weaver/actions/workflows/ci.yml)
|
|
7
|
+
[](https://codecov.io/gh/synergenius-fw/flow-weaver)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
[](https://nodejs.org)
|
|
6
10
|
|
|
7
11
|
**Build AI agent workflows visually. Ship them as your own code.**
|
|
8
12
|
|
|
@@ -10,11 +14,17 @@ Design agent workflows in the Studio, in TypeScript, or let AI build them for yo
|
|
|
10
14
|
|
|
11
15
|
## Three Ways to Build
|
|
12
16
|
|
|
13
|
-
**Studio.**
|
|
17
|
+
**Studio.**
|
|
18
|
+
A unified environment that combines a full code editor with a visual graph builder. You can write and refactor workflows directly in code or compose them visually on the canvas. Both representations stay synchronized at all times. Editing either one updates the other instantly, so you can move between abstraction levels without friction.
|
|
14
19
|
|
|
15
|
-
**TypeScript.**
|
|
20
|
+
**TypeScript.**
|
|
21
|
+
Define workflows in plain TypeScript by annotating functions with JSDoc. The compiler derives an executable workflow graph with static typing and compile-time validation.
|
|
22
|
+
There is no YAML, no JSON configuration, and no runtime layer.
|
|
23
|
+
No lock-in. Remove the annotations and you keep a clean, readable TypeScript file with zero dependencies.
|
|
16
24
|
|
|
17
|
-
**AI Agents.**
|
|
25
|
+
**AI Agents.**
|
|
26
|
+
Connect Claude Code, Cursor, or OpenClaw to design and ship workflows. Agents scaffold implementations, run the compiler, interpret validation errors, apply corrections, and repeat the process until the workflow compiles successfully.
|
|
27
|
+
With more than 30 MCP tools available, the entire build loop can run autonomously.
|
|
18
28
|
|
|
19
29
|
1. **Agent creates**: scaffolds from templates, builds from a model, or writes from scratch
|
|
20
30
|
2. **Compiler validates**: 15+ validation passes catch missing connections, type mismatches, unreachable paths
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -16881,6 +16881,121 @@ var require_browser = __commonJS({
|
|
|
16881
16881
|
}
|
|
16882
16882
|
});
|
|
16883
16883
|
|
|
16884
|
+
// node_modules/has-flag/index.js
|
|
16885
|
+
var require_has_flag = __commonJS({
|
|
16886
|
+
"node_modules/has-flag/index.js"(exports2, module2) {
|
|
16887
|
+
"use strict";
|
|
16888
|
+
module2.exports = (flag, argv = process.argv) => {
|
|
16889
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
16890
|
+
const position = argv.indexOf(prefix + flag);
|
|
16891
|
+
const terminatorPosition = argv.indexOf("--");
|
|
16892
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
16893
|
+
};
|
|
16894
|
+
}
|
|
16895
|
+
});
|
|
16896
|
+
|
|
16897
|
+
// node_modules/supports-color/index.js
|
|
16898
|
+
var require_supports_color = __commonJS({
|
|
16899
|
+
"node_modules/supports-color/index.js"(exports2, module2) {
|
|
16900
|
+
"use strict";
|
|
16901
|
+
var os3 = __require("os");
|
|
16902
|
+
var tty = __require("tty");
|
|
16903
|
+
var hasFlag = require_has_flag();
|
|
16904
|
+
var { env } = process;
|
|
16905
|
+
var forceColor;
|
|
16906
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
16907
|
+
forceColor = 0;
|
|
16908
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
16909
|
+
forceColor = 1;
|
|
16910
|
+
}
|
|
16911
|
+
if ("FORCE_COLOR" in env) {
|
|
16912
|
+
if (env.FORCE_COLOR === "true") {
|
|
16913
|
+
forceColor = 1;
|
|
16914
|
+
} else if (env.FORCE_COLOR === "false") {
|
|
16915
|
+
forceColor = 0;
|
|
16916
|
+
} else {
|
|
16917
|
+
forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
|
|
16918
|
+
}
|
|
16919
|
+
}
|
|
16920
|
+
function translateLevel(level) {
|
|
16921
|
+
if (level === 0) {
|
|
16922
|
+
return false;
|
|
16923
|
+
}
|
|
16924
|
+
return {
|
|
16925
|
+
level,
|
|
16926
|
+
hasBasic: true,
|
|
16927
|
+
has256: level >= 2,
|
|
16928
|
+
has16m: level >= 3
|
|
16929
|
+
};
|
|
16930
|
+
}
|
|
16931
|
+
function supportsColor(haveStream, streamIsTTY) {
|
|
16932
|
+
if (forceColor === 0) {
|
|
16933
|
+
return 0;
|
|
16934
|
+
}
|
|
16935
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
16936
|
+
return 3;
|
|
16937
|
+
}
|
|
16938
|
+
if (hasFlag("color=256")) {
|
|
16939
|
+
return 2;
|
|
16940
|
+
}
|
|
16941
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
16942
|
+
return 0;
|
|
16943
|
+
}
|
|
16944
|
+
const min = forceColor || 0;
|
|
16945
|
+
if (env.TERM === "dumb") {
|
|
16946
|
+
return min;
|
|
16947
|
+
}
|
|
16948
|
+
if (process.platform === "win32") {
|
|
16949
|
+
const osRelease = os3.release().split(".");
|
|
16950
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
16951
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
16952
|
+
}
|
|
16953
|
+
return 1;
|
|
16954
|
+
}
|
|
16955
|
+
if ("CI" in env) {
|
|
16956
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
16957
|
+
return 1;
|
|
16958
|
+
}
|
|
16959
|
+
return min;
|
|
16960
|
+
}
|
|
16961
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
16962
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
16963
|
+
}
|
|
16964
|
+
if (env.COLORTERM === "truecolor") {
|
|
16965
|
+
return 3;
|
|
16966
|
+
}
|
|
16967
|
+
if ("TERM_PROGRAM" in env) {
|
|
16968
|
+
const version3 = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
16969
|
+
switch (env.TERM_PROGRAM) {
|
|
16970
|
+
case "iTerm.app":
|
|
16971
|
+
return version3 >= 3 ? 3 : 2;
|
|
16972
|
+
case "Apple_Terminal":
|
|
16973
|
+
return 2;
|
|
16974
|
+
}
|
|
16975
|
+
}
|
|
16976
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
16977
|
+
return 2;
|
|
16978
|
+
}
|
|
16979
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
16980
|
+
return 1;
|
|
16981
|
+
}
|
|
16982
|
+
if ("COLORTERM" in env) {
|
|
16983
|
+
return 1;
|
|
16984
|
+
}
|
|
16985
|
+
return min;
|
|
16986
|
+
}
|
|
16987
|
+
function getSupportLevel(stream2) {
|
|
16988
|
+
const level = supportsColor(stream2, stream2 && stream2.isTTY);
|
|
16989
|
+
return translateLevel(level);
|
|
16990
|
+
}
|
|
16991
|
+
module2.exports = {
|
|
16992
|
+
supportsColor: getSupportLevel,
|
|
16993
|
+
stdout: translateLevel(supportsColor(true, tty.isatty(1))),
|
|
16994
|
+
stderr: translateLevel(supportsColor(true, tty.isatty(2)))
|
|
16995
|
+
};
|
|
16996
|
+
}
|
|
16997
|
+
});
|
|
16998
|
+
|
|
16884
16999
|
// node_modules/debug/src/node.js
|
|
16885
17000
|
var require_node = __commonJS({
|
|
16886
17001
|
"node_modules/debug/src/node.js"(exports2, module2) {
|
|
@@ -16899,7 +17014,7 @@ var require_node = __commonJS({
|
|
|
16899
17014
|
);
|
|
16900
17015
|
exports2.colors = [6, 2, 3, 4, 5, 1];
|
|
16901
17016
|
try {
|
|
16902
|
-
const supportsColor =
|
|
17017
|
+
const supportsColor = require_supports_color();
|
|
16903
17018
|
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
16904
17019
|
exports2.colors = [
|
|
16905
17020
|
20,
|
|
@@ -61065,12 +61180,12 @@ function renderConnection(parts2, conn, gradIndex) {
|
|
|
61065
61180
|
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
61066
61181
|
);
|
|
61067
61182
|
}
|
|
61068
|
-
function renderScopeConnection(parts2, conn, allConnections) {
|
|
61183
|
+
function renderScopeConnection(parts2, conn, allConnections, parentNodeId) {
|
|
61069
61184
|
const gradIndex = allConnections.indexOf(conn);
|
|
61070
61185
|
if (gradIndex < 0) return;
|
|
61071
61186
|
const dashAttr = conn.isStepConnection ? "" : ' stroke-dasharray="8 4"';
|
|
61072
61187
|
parts2.push(
|
|
61073
|
-
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
61188
|
+
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input" data-scope="${escapeXml(parentNodeId)}"/>`
|
|
61074
61189
|
);
|
|
61075
61190
|
}
|
|
61076
61191
|
function renderNodeBody(parts2, node, theme, indent) {
|
|
@@ -61114,7 +61229,7 @@ function renderScopedContent(parts2, node, theme, themeName, allConnections) {
|
|
|
61114
61229
|
` <rect x="${scopeX}" y="${scopeY}" width="${scopeW}" height="${scopeH}" rx="4" fill="none" stroke="${theme.scopeAreaStroke}" stroke-width="1" stroke-dasharray="4 2" opacity="0.5"/>`
|
|
61115
61230
|
);
|
|
61116
61231
|
for (const conn of node.scopeConnections ?? []) {
|
|
61117
|
-
renderScopeConnection(parts2, conn, allConnections);
|
|
61232
|
+
renderScopeConnection(parts2, conn, allConnections, node.id);
|
|
61118
61233
|
}
|
|
61119
61234
|
if (scopePorts) {
|
|
61120
61235
|
renderPortDots(parts2, node.id, scopePorts.inputs, scopePorts.outputs, themeName);
|
|
@@ -61351,6 +61466,21 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61351
61466
|
border-radius: 3px; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
61352
61467
|
font-size: 12px; background: rgba(255,255,255,0.1);
|
|
61353
61468
|
}
|
|
61469
|
+
|
|
61470
|
+
/* Studio nudge toast */
|
|
61471
|
+
#studio-hint {
|
|
61472
|
+
position: fixed; bottom: 60px; left: 50%;
|
|
61473
|
+
transform: translateX(-50%);
|
|
61474
|
+
background: ${surfaceMain}; border: 1px solid ${borderSubtle};
|
|
61475
|
+
padding: 8px 16px; border-radius: 8px;
|
|
61476
|
+
font-size: 13px; color: ${textMed};
|
|
61477
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
61478
|
+
z-index: 20; opacity: 0; transition: opacity 0.4s;
|
|
61479
|
+
pointer-events: none;
|
|
61480
|
+
}
|
|
61481
|
+
#studio-hint.visible { opacity: 1; pointer-events: auto; }
|
|
61482
|
+
#studio-hint a { color: ${brandAccent}; text-decoration: none; font-weight: 600; }
|
|
61483
|
+
#studio-hint a:hover { text-decoration: underline; }
|
|
61354
61484
|
</style>
|
|
61355
61485
|
</head>
|
|
61356
61486
|
<body>
|
|
@@ -61374,6 +61504,12 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61374
61504
|
<path d="M1 5h12M1 9h12M5 1v12M9 1v12" opacity="0.4"/>
|
|
61375
61505
|
</svg>
|
|
61376
61506
|
</button>
|
|
61507
|
+
<button class="ctrl-btn" id="btn-reset" title="Reset layout" aria-label="Reset layout">
|
|
61508
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
61509
|
+
<path d="M1.5 2v3.5h3.5"/>
|
|
61510
|
+
<path d="M2.1 8.5a5 5 0 1 0 .9-4L1.5 5.5"/>
|
|
61511
|
+
</svg>
|
|
61512
|
+
</button>
|
|
61377
61513
|
</div>
|
|
61378
61514
|
<div id="info-panel">
|
|
61379
61515
|
<h3 id="info-title"></h3>
|
|
@@ -61384,6 +61520,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61384
61520
|
<span>Flow Weaver</span>
|
|
61385
61521
|
</a>
|
|
61386
61522
|
<div id="scroll-hint">Use <kbd id="mod-key">Ctrl</kbd> + scroll to zoom</div>
|
|
61523
|
+
<div id="studio-hint">Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.</div>
|
|
61387
61524
|
<script>
|
|
61388
61525
|
(function() {
|
|
61389
61526
|
'use strict';
|
|
@@ -61396,6 +61533,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61396
61533
|
var infoTitle = document.getElementById('info-title');
|
|
61397
61534
|
var infoBody = document.getElementById('info-body');
|
|
61398
61535
|
var scrollHint = document.getElementById('scroll-hint');
|
|
61536
|
+
var studioHint = document.getElementById('studio-hint');
|
|
61399
61537
|
|
|
61400
61538
|
// Parse the original viewBox (diagram bounding box)
|
|
61401
61539
|
var vbParts = '${viewBox}'.split(/\\s+/).map(Number);
|
|
@@ -61467,6 +61605,17 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61467
61605
|
|
|
61468
61606
|
// ---- Pan (drag) + Node drag ----
|
|
61469
61607
|
var draggedNodeId = null, dragNodeStart = null, didDragNode = false;
|
|
61608
|
+
var dragCount = 0, nudgeIndex = 0, nudgeTimer = null;
|
|
61609
|
+
var nudgeMessages = [
|
|
61610
|
+
'Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.',
|
|
61611
|
+
'Changes here are temporary. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Try the Studio</a> to keep them.',
|
|
61612
|
+
'Want to collaborate? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> has real-time sharing.',
|
|
61613
|
+
'Build and deploy from the cloud with <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a>.',
|
|
61614
|
+
'This viewer is read-only. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Build workflows</a> in the Studio.',
|
|
61615
|
+
'Version history, diff viewer, rollbacks. All in <a href="https://flowweaver.ai" target="_blank" rel="noopener">the Studio</a>.',
|
|
61616
|
+
'Debug workflows step by step in <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a>.',
|
|
61617
|
+
'Ship faster. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> runs your workflows in the cloud.'
|
|
61618
|
+
];
|
|
61470
61619
|
|
|
61471
61620
|
canvas.addEventListener('pointerdown', function(e) {
|
|
61472
61621
|
if (e.button !== 0) return;
|
|
@@ -61525,6 +61674,18 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61525
61674
|
});
|
|
61526
61675
|
|
|
61527
61676
|
function endDrag() {
|
|
61677
|
+
if (didDragNode) {
|
|
61678
|
+
dragCount++;
|
|
61679
|
+
var threshold = nudgeIndex === 0 ? 3 : 5;
|
|
61680
|
+
if (dragCount >= threshold) {
|
|
61681
|
+
dragCount = 0;
|
|
61682
|
+
studioHint.innerHTML = nudgeMessages[nudgeIndex % nudgeMessages.length];
|
|
61683
|
+
nudgeIndex++;
|
|
61684
|
+
studioHint.classList.add('visible');
|
|
61685
|
+
clearTimeout(nudgeTimer);
|
|
61686
|
+
nudgeTimer = setTimeout(function() { studioHint.classList.remove('visible'); }, 5000);
|
|
61687
|
+
}
|
|
61688
|
+
}
|
|
61528
61689
|
pointerDown = false;
|
|
61529
61690
|
draggedNodeId = null;
|
|
61530
61691
|
canvas.classList.remove('dragging');
|
|
@@ -61616,9 +61777,41 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61616
61777
|
var connIndex = [];
|
|
61617
61778
|
content.querySelectorAll('path[data-source]').forEach(function(p) {
|
|
61618
61779
|
var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
|
|
61619
|
-
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0] });
|
|
61780
|
+
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0], scopeOf: p.getAttribute('data-scope') || null });
|
|
61620
61781
|
});
|
|
61621
61782
|
|
|
61783
|
+
// Snapshot of original port positions for reset
|
|
61784
|
+
var origPortPositions = {};
|
|
61785
|
+
for (var pid in portPositions) {
|
|
61786
|
+
origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
|
|
61787
|
+
}
|
|
61788
|
+
|
|
61789
|
+
function resetLayout() {
|
|
61790
|
+
for (var nid in nodeOffsets) {
|
|
61791
|
+
var esc = CSS.escape(nid);
|
|
61792
|
+
var nodeG = content.querySelector('.nodes [data-node-id="' + esc + '"]');
|
|
61793
|
+
if (nodeG) nodeG.removeAttribute('transform');
|
|
61794
|
+
var labelG = content.querySelector('[data-label-for="' + esc + '"]');
|
|
61795
|
+
if (labelG) labelG.removeAttribute('transform');
|
|
61796
|
+
allLabelIds.forEach(function(id) {
|
|
61797
|
+
if (id.indexOf(nid + '.') === 0) {
|
|
61798
|
+
var el = labelMap[id];
|
|
61799
|
+
if (el) el.removeAttribute('transform');
|
|
61800
|
+
}
|
|
61801
|
+
});
|
|
61802
|
+
}
|
|
61803
|
+
nodeOffsets = {};
|
|
61804
|
+
for (var pid in origPortPositions) {
|
|
61805
|
+
portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
|
|
61806
|
+
}
|
|
61807
|
+
connIndex.forEach(function(c) {
|
|
61808
|
+
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
61809
|
+
if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
61810
|
+
});
|
|
61811
|
+
fitToView();
|
|
61812
|
+
}
|
|
61813
|
+
document.getElementById('btn-reset').addEventListener('click', resetLayout);
|
|
61814
|
+
|
|
61622
61815
|
// ---- Node drag: moveNode ----
|
|
61623
61816
|
function moveNode(nodeId, dx, dy) {
|
|
61624
61817
|
if (!nodeOffsets[nodeId]) nodeOffsets[nodeId] = { dx: 0, dy: 0 };
|
|
@@ -61626,9 +61819,18 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61626
61819
|
off.dx += dx; off.dy += dy;
|
|
61627
61820
|
var tr = 'translate(' + off.dx + ',' + off.dy + ')';
|
|
61628
61821
|
|
|
61629
|
-
// Move node group
|
|
61822
|
+
// Move node group (if nested inside a scoped parent, subtract parent offset)
|
|
61630
61823
|
var nodeG = content.querySelector('.nodes [data-node-id="' + CSS.escape(nodeId) + '"]');
|
|
61631
|
-
if (nodeG)
|
|
61824
|
+
if (nodeG) {
|
|
61825
|
+
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('[data-node-id]') : null;
|
|
61826
|
+
if (parentNodeG) {
|
|
61827
|
+
var parentId = parentNodeG.getAttribute('data-node-id');
|
|
61828
|
+
var parentOff = nodeOffsets[parentId] || { dx: 0, dy: 0 };
|
|
61829
|
+
nodeG.setAttribute('transform', 'translate(' + (off.dx - parentOff.dx) + ',' + (off.dy - parentOff.dy) + ')');
|
|
61830
|
+
} else {
|
|
61831
|
+
nodeG.setAttribute('transform', tr);
|
|
61832
|
+
}
|
|
61833
|
+
}
|
|
61632
61834
|
|
|
61633
61835
|
// Move label
|
|
61634
61836
|
var labelG = content.querySelector('[data-label-for="' + CSS.escape(nodeId) + '"]');
|
|
@@ -61675,11 +61877,20 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61675
61877
|
});
|
|
61676
61878
|
}
|
|
61677
61879
|
|
|
61678
|
-
// Recalculate affected connection paths
|
|
61880
|
+
// Recalculate affected connection paths (skip scope connections when parent is dragged \u2014 they move with the group transform)
|
|
61679
61881
|
connIndex.forEach(function(c) {
|
|
61882
|
+
if (c.scopeOf === nodeId) return;
|
|
61680
61883
|
if (c.srcNode === nodeId || c.tgtNode === nodeId) {
|
|
61681
61884
|
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
61682
|
-
if (sp && tp)
|
|
61885
|
+
if (sp && tp) {
|
|
61886
|
+
if (c.scopeOf) {
|
|
61887
|
+
// Scope connection paths live inside the parent group; use parent-local coords
|
|
61888
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
61889
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
61890
|
+
} else {
|
|
61891
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
61892
|
+
}
|
|
61893
|
+
}
|
|
61683
61894
|
}
|
|
61684
61895
|
if (nodeG) {
|
|
61685
61896
|
var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
|
|
@@ -61687,7 +61898,14 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61687
61898
|
var childId = childG.getAttribute('data-node-id');
|
|
61688
61899
|
if (c.srcNode === childId || c.tgtNode === childId) {
|
|
61689
61900
|
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
61690
|
-
if (sp && tp)
|
|
61901
|
+
if (sp && tp) {
|
|
61902
|
+
if (c.scopeOf) {
|
|
61903
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
61904
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
61905
|
+
} else {
|
|
61906
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
61907
|
+
}
|
|
61908
|
+
}
|
|
61691
61909
|
}
|
|
61692
61910
|
});
|
|
61693
61911
|
}
|
|
@@ -96512,7 +96730,7 @@ function displayInstalledPackage(pkg) {
|
|
|
96512
96730
|
}
|
|
96513
96731
|
|
|
96514
96732
|
// src/cli/index.ts
|
|
96515
|
-
var version2 = true ? "0.10.
|
|
96733
|
+
var version2 = true ? "0.10.2" : "0.0.0-dev";
|
|
96516
96734
|
var program2 = new Command();
|
|
96517
96735
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
96518
96736
|
program2.configureOutput({
|
|
@@ -170,6 +170,21 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
170
170
|
border-radius: 3px; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
171
171
|
font-size: 12px; background: rgba(255,255,255,0.1);
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
/* Studio nudge toast */
|
|
175
|
+
#studio-hint {
|
|
176
|
+
position: fixed; bottom: 60px; left: 50%;
|
|
177
|
+
transform: translateX(-50%);
|
|
178
|
+
background: ${surfaceMain}; border: 1px solid ${borderSubtle};
|
|
179
|
+
padding: 8px 16px; border-radius: 8px;
|
|
180
|
+
font-size: 13px; color: ${textMed};
|
|
181
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
182
|
+
z-index: 20; opacity: 0; transition: opacity 0.4s;
|
|
183
|
+
pointer-events: none;
|
|
184
|
+
}
|
|
185
|
+
#studio-hint.visible { opacity: 1; pointer-events: auto; }
|
|
186
|
+
#studio-hint a { color: ${brandAccent}; text-decoration: none; font-weight: 600; }
|
|
187
|
+
#studio-hint a:hover { text-decoration: underline; }
|
|
173
188
|
</style>
|
|
174
189
|
</head>
|
|
175
190
|
<body>
|
|
@@ -193,6 +208,12 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
193
208
|
<path d="M1 5h12M1 9h12M5 1v12M9 1v12" opacity="0.4"/>
|
|
194
209
|
</svg>
|
|
195
210
|
</button>
|
|
211
|
+
<button class="ctrl-btn" id="btn-reset" title="Reset layout" aria-label="Reset layout">
|
|
212
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
213
|
+
<path d="M1.5 2v3.5h3.5"/>
|
|
214
|
+
<path d="M2.1 8.5a5 5 0 1 0 .9-4L1.5 5.5"/>
|
|
215
|
+
</svg>
|
|
216
|
+
</button>
|
|
196
217
|
</div>
|
|
197
218
|
<div id="info-panel">
|
|
198
219
|
<h3 id="info-title"></h3>
|
|
@@ -203,6 +224,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
203
224
|
<span>Flow Weaver</span>
|
|
204
225
|
</a>
|
|
205
226
|
<div id="scroll-hint">Use <kbd id="mod-key">Ctrl</kbd> + scroll to zoom</div>
|
|
227
|
+
<div id="studio-hint">Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.</div>
|
|
206
228
|
<script>
|
|
207
229
|
(function() {
|
|
208
230
|
'use strict';
|
|
@@ -215,6 +237,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
215
237
|
var infoTitle = document.getElementById('info-title');
|
|
216
238
|
var infoBody = document.getElementById('info-body');
|
|
217
239
|
var scrollHint = document.getElementById('scroll-hint');
|
|
240
|
+
var studioHint = document.getElementById('studio-hint');
|
|
218
241
|
|
|
219
242
|
// Parse the original viewBox (diagram bounding box)
|
|
220
243
|
var vbParts = '${viewBox}'.split(/\\s+/).map(Number);
|
|
@@ -286,6 +309,17 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
286
309
|
|
|
287
310
|
// ---- Pan (drag) + Node drag ----
|
|
288
311
|
var draggedNodeId = null, dragNodeStart = null, didDragNode = false;
|
|
312
|
+
var dragCount = 0, nudgeIndex = 0, nudgeTimer = null;
|
|
313
|
+
var nudgeMessages = [
|
|
314
|
+
'Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.',
|
|
315
|
+
'Changes here are temporary. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Try the Studio</a> to keep them.',
|
|
316
|
+
'Want to collaborate? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> has real-time sharing.',
|
|
317
|
+
'Build and deploy from the cloud with <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a>.',
|
|
318
|
+
'This viewer is read-only. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Build workflows</a> in the Studio.',
|
|
319
|
+
'Version history, diff viewer, rollbacks. All in <a href="https://flowweaver.ai" target="_blank" rel="noopener">the Studio</a>.',
|
|
320
|
+
'Debug workflows step by step in <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a>.',
|
|
321
|
+
'Ship faster. <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> runs your workflows in the cloud.'
|
|
322
|
+
];
|
|
289
323
|
|
|
290
324
|
canvas.addEventListener('pointerdown', function(e) {
|
|
291
325
|
if (e.button !== 0) return;
|
|
@@ -344,6 +378,18 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
344
378
|
});
|
|
345
379
|
|
|
346
380
|
function endDrag() {
|
|
381
|
+
if (didDragNode) {
|
|
382
|
+
dragCount++;
|
|
383
|
+
var threshold = nudgeIndex === 0 ? 3 : 5;
|
|
384
|
+
if (dragCount >= threshold) {
|
|
385
|
+
dragCount = 0;
|
|
386
|
+
studioHint.innerHTML = nudgeMessages[nudgeIndex % nudgeMessages.length];
|
|
387
|
+
nudgeIndex++;
|
|
388
|
+
studioHint.classList.add('visible');
|
|
389
|
+
clearTimeout(nudgeTimer);
|
|
390
|
+
nudgeTimer = setTimeout(function() { studioHint.classList.remove('visible'); }, 5000);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
347
393
|
pointerDown = false;
|
|
348
394
|
draggedNodeId = null;
|
|
349
395
|
canvas.classList.remove('dragging');
|
|
@@ -435,9 +481,41 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
435
481
|
var connIndex = [];
|
|
436
482
|
content.querySelectorAll('path[data-source]').forEach(function(p) {
|
|
437
483
|
var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
|
|
438
|
-
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0] });
|
|
484
|
+
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0], scopeOf: p.getAttribute('data-scope') || null });
|
|
439
485
|
});
|
|
440
486
|
|
|
487
|
+
// Snapshot of original port positions for reset
|
|
488
|
+
var origPortPositions = {};
|
|
489
|
+
for (var pid in portPositions) {
|
|
490
|
+
origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function resetLayout() {
|
|
494
|
+
for (var nid in nodeOffsets) {
|
|
495
|
+
var esc = CSS.escape(nid);
|
|
496
|
+
var nodeG = content.querySelector('.nodes [data-node-id="' + esc + '"]');
|
|
497
|
+
if (nodeG) nodeG.removeAttribute('transform');
|
|
498
|
+
var labelG = content.querySelector('[data-label-for="' + esc + '"]');
|
|
499
|
+
if (labelG) labelG.removeAttribute('transform');
|
|
500
|
+
allLabelIds.forEach(function(id) {
|
|
501
|
+
if (id.indexOf(nid + '.') === 0) {
|
|
502
|
+
var el = labelMap[id];
|
|
503
|
+
if (el) el.removeAttribute('transform');
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
nodeOffsets = {};
|
|
508
|
+
for (var pid in origPortPositions) {
|
|
509
|
+
portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
|
|
510
|
+
}
|
|
511
|
+
connIndex.forEach(function(c) {
|
|
512
|
+
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
513
|
+
if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
514
|
+
});
|
|
515
|
+
fitToView();
|
|
516
|
+
}
|
|
517
|
+
document.getElementById('btn-reset').addEventListener('click', resetLayout);
|
|
518
|
+
|
|
441
519
|
// ---- Node drag: moveNode ----
|
|
442
520
|
function moveNode(nodeId, dx, dy) {
|
|
443
521
|
if (!nodeOffsets[nodeId]) nodeOffsets[nodeId] = { dx: 0, dy: 0 };
|
|
@@ -445,9 +523,18 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
445
523
|
off.dx += dx; off.dy += dy;
|
|
446
524
|
var tr = 'translate(' + off.dx + ',' + off.dy + ')';
|
|
447
525
|
|
|
448
|
-
// Move node group
|
|
526
|
+
// Move node group (if nested inside a scoped parent, subtract parent offset)
|
|
449
527
|
var nodeG = content.querySelector('.nodes [data-node-id="' + CSS.escape(nodeId) + '"]');
|
|
450
|
-
if (nodeG)
|
|
528
|
+
if (nodeG) {
|
|
529
|
+
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('[data-node-id]') : null;
|
|
530
|
+
if (parentNodeG) {
|
|
531
|
+
var parentId = parentNodeG.getAttribute('data-node-id');
|
|
532
|
+
var parentOff = nodeOffsets[parentId] || { dx: 0, dy: 0 };
|
|
533
|
+
nodeG.setAttribute('transform', 'translate(' + (off.dx - parentOff.dx) + ',' + (off.dy - parentOff.dy) + ')');
|
|
534
|
+
} else {
|
|
535
|
+
nodeG.setAttribute('transform', tr);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
451
538
|
|
|
452
539
|
// Move label
|
|
453
540
|
var labelG = content.querySelector('[data-label-for="' + CSS.escape(nodeId) + '"]');
|
|
@@ -494,11 +581,20 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
494
581
|
});
|
|
495
582
|
}
|
|
496
583
|
|
|
497
|
-
// Recalculate affected connection paths
|
|
584
|
+
// Recalculate affected connection paths (skip scope connections when parent is dragged — they move with the group transform)
|
|
498
585
|
connIndex.forEach(function(c) {
|
|
586
|
+
if (c.scopeOf === nodeId) return;
|
|
499
587
|
if (c.srcNode === nodeId || c.tgtNode === nodeId) {
|
|
500
588
|
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
501
|
-
if (sp && tp)
|
|
589
|
+
if (sp && tp) {
|
|
590
|
+
if (c.scopeOf) {
|
|
591
|
+
// Scope connection paths live inside the parent group; use parent-local coords
|
|
592
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
593
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
594
|
+
} else {
|
|
595
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
502
598
|
}
|
|
503
599
|
if (nodeG) {
|
|
504
600
|
var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
|
|
@@ -506,7 +602,14 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
506
602
|
var childId = childG.getAttribute('data-node-id');
|
|
507
603
|
if (c.srcNode === childId || c.tgtNode === childId) {
|
|
508
604
|
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
509
|
-
if (sp && tp)
|
|
605
|
+
if (sp && tp) {
|
|
606
|
+
if (c.scopeOf) {
|
|
607
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
608
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
609
|
+
} else {
|
|
610
|
+
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
510
613
|
}
|
|
511
614
|
});
|
|
512
615
|
}
|
package/dist/diagram/renderer.js
CHANGED
|
@@ -100,12 +100,12 @@ function renderConnection(parts, conn, gradIndex) {
|
|
|
100
100
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
101
101
|
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
102
102
|
}
|
|
103
|
-
function renderScopeConnection(parts, conn, allConnections) {
|
|
103
|
+
function renderScopeConnection(parts, conn, allConnections, parentNodeId) {
|
|
104
104
|
const gradIndex = allConnections.indexOf(conn);
|
|
105
105
|
if (gradIndex < 0)
|
|
106
106
|
return;
|
|
107
107
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
108
|
-
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
108
|
+
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input" data-scope="${escapeXml(parentNodeId)}"/>`);
|
|
109
109
|
}
|
|
110
110
|
// ---- Node rendering ----
|
|
111
111
|
/** Render node body rect + icon */
|
|
@@ -147,7 +147,7 @@ function renderScopedContent(parts, node, theme, themeName, allConnections) {
|
|
|
147
147
|
parts.push(` <rect x="${scopeX}" y="${scopeY}" width="${scopeW}" height="${scopeH}" rx="4" fill="none" stroke="${theme.scopeAreaStroke}" stroke-width="1" stroke-dasharray="4 2" opacity="0.5"/>`);
|
|
148
148
|
// Scope connections (before ports so ports appear on top)
|
|
149
149
|
for (const conn of node.scopeConnections ?? []) {
|
|
150
|
-
renderScopeConnection(parts, conn, allConnections);
|
|
150
|
+
renderScopeConnection(parts, conn, allConnections, node.id);
|
|
151
151
|
}
|
|
152
152
|
// Scope port dots (before children so dots sit on top of connections)
|
|
153
153
|
if (scopePorts) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flow-weaver",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -145,6 +145,7 @@
|
|
|
145
145
|
"@types/js-yaml": "^4.0.9",
|
|
146
146
|
"@types/node": "^20.11.0",
|
|
147
147
|
"@types/react": "^19.0.0",
|
|
148
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
148
149
|
"esbuild": "^0.27.2",
|
|
149
150
|
"prettier": "^3.1.1",
|
|
150
151
|
"rimraf": "6.1.2",
|