@synergenius/flow-weaver 0.33.1 → 0.33.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.
@@ -1,8 +1,19 @@
1
- import { getTheme, getPortColor, getPortRingColor, TYPE_ABBREVIATIONS, NODE_ICON_PATHS, NODE_DEFAULT_COLOR } from './theme.js';
2
- import { PORT_RADIUS, BORDER_RADIUS, LABEL_HEIGHT, LABEL_GAP, SCOPE_PORT_COLUMN, measureText } from './geometry.js';
1
+ import { getTheme, getPortColor, TYPE_ABBREVIATIONS, NODE_ICON_PATHS, NODE_DEFAULT_COLOR, NODE_VARIANT_COLORS } from './theme.js';
2
+ import { PORT_RADIUS, BORDER_RADIUS, LABEL_GAP, SCOPE_PORT_COLUMN, measureText } from './geometry.js';
3
3
  function escapeXml(str) {
4
4
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
5
5
  }
6
+ /** Resolve icon color from a node's resolved border hex back to the variant icon color */
7
+ function resolveIconColor(nodeColor, themeName, theme) {
8
+ if (nodeColor === NODE_DEFAULT_COLOR)
9
+ return theme.nodeIconColor;
10
+ for (const v of Object.values(NODE_VARIANT_COLORS)) {
11
+ if (v.darkBorder === nodeColor || v.border === nodeColor) {
12
+ return themeName === 'dark' ? v.darkIcon : v.icon;
13
+ }
14
+ }
15
+ return nodeColor; // custom hex — use as-is
16
+ }
6
17
  /** Collect all connections (main + scope) for gradient def generation */
7
18
  function collectAllConnections(graph) {
8
19
  const all = [...graph.connections];
@@ -32,22 +43,22 @@ export function renderSVG(graph, options = {}) {
32
43
  parts.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vbX} ${vbY} ${vbWidth} ${vbHeight}" width="${svgWidth}" height="${svgHeight}">`);
33
44
  // Styles
34
45
  parts.push(`<style>`);
35
- parts.push(` text { font-family: Montserrat, 'Segoe UI', Roboto, sans-serif; }`);
36
- parts.push(` .node-label { font-size: 13px; font-weight: 700; fill: ${theme.labelColor}; }`);
46
+ parts.push(` text { font-family: Montserrat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }`);
47
+ parts.push(` .node-label { font-size: 18px; font-weight: 600; fill: ${theme.labelColor}; }`);
37
48
  parts.push(` .port-label { font-size: 10px; font-weight: 600; fill: ${theme.labelColor}; }`);
38
49
  parts.push(` .port-type-label { font-size: 10px; font-weight: 600; }`);
39
50
  parts.push(`</style>`);
40
51
  // Defs (dot grid pattern + node shadow filter + connection gradients)
41
52
  parts.push(`<defs>`);
42
53
  parts.push(` <pattern id="dot-grid" width="20" height="20" patternUnits="userSpaceOnUse">`);
43
- parts.push(` <circle cx="10" cy="10" r="1.5" fill="${theme.dotColor}" opacity="${theme.dotOpacity}"/>`);
54
+ parts.push(` <circle cx="10" cy="10" r="0.75" fill="${theme.dotColor}" opacity="${theme.dotOpacity}"/>`);
44
55
  parts.push(` </pattern>`);
45
- parts.push(` <filter id="node-shadow" x="-10%" y="-10%" width="130%" height="140%">`);
46
- parts.push(` <feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="${theme.nodeShadowOpacity}" flood-color="#000"/>`);
47
- parts.push(` </filter>`);
56
+ // No drop shadow matches platform (nodes use outline glow, not shadow)
48
57
  for (let i = 0; i < allConnections.length; i++) {
49
58
  const conn = allConnections[i];
50
- parts.push(` <linearGradient id="conn-grad-${i}" x1="0%" y1="0%" x2="100%" y2="0%">`);
59
+ // Use userSpaceOnUse so gradients work on flat horizontal paths
60
+ // (objectBoundingBox fails when bounding box height is zero)
61
+ parts.push(` <linearGradient id="conn-grad-${i}" gradientUnits="userSpaceOnUse" x1="${vbX}" y1="0" x2="${vbX + vbWidth}" y2="0">`);
51
62
  parts.push(` <stop offset="0%" stop-color="${conn.sourceColor}"/>`);
52
63
  parts.push(` <stop offset="100%" stop-color="${conn.targetColor}"/>`);
53
64
  parts.push(` </linearGradient>`);
@@ -56,12 +67,11 @@ export function renderSVG(graph, options = {}) {
56
67
  // Background
57
68
  parts.push(`<rect x="${vbX}" y="${vbY}" width="${vbWidth}" height="${vbHeight}" fill="${theme.background}"/>`);
58
69
  parts.push(`<rect x="${vbX}" y="${vbY}" width="${vbWidth}" height="${vbHeight}" fill="url(#dot-grid)"/>`);
59
- // Connections + stubs (same layer, below nodes and labels)
70
+ // Connections + stubs (below nodes and labels)
60
71
  parts.push(`<g class="connections">`);
61
72
  for (let i = 0; i < graph.connections.length; i++) {
62
73
  renderConnection(parts, graph.connections[i], i, !graph.connections[i].path);
63
74
  }
64
- // Stubs sit alongside connection paths; short-distance ones start hidden for HTML viewer toggling
65
75
  parts.push(` <g class="stubs">`);
66
76
  for (const conn of graph.connections) {
67
77
  const hideStubs = !!conn.path;
@@ -72,13 +82,13 @@ export function renderSVG(graph, options = {}) {
72
82
  }
73
83
  parts.push(` </g>`);
74
84
  parts.push(`</g>`);
75
- // Nodes (bodies, icons, port dots — no labels)
85
+ // Nodes (bodies, icons, port dots)
76
86
  parts.push(`<g class="nodes">`);
77
87
  for (const node of graph.nodes) {
78
88
  parts.push(renderNode(node, theme, themeName, allConnections));
79
89
  }
80
90
  parts.push(`</g>`);
81
- // Labels rendered last so they appear on top of everything (single pass)
91
+ // Labels rendered last so they appear on top of everything
82
92
  parts.push(`<g class="labels">`);
83
93
  for (const node of graph.nodes) {
84
94
  renderNodeLabel(parts, node, theme);
@@ -87,7 +97,6 @@ export function renderSVG(graph, options = {}) {
87
97
  for (const child of node.scopeChildren) {
88
98
  renderNodeLabel(parts, child, theme);
89
99
  }
90
- // Scope port labels + child port labels
91
100
  if (showPortLabels && node.scopePorts) {
92
101
  renderPortLabels(parts, node.id, node.scopePorts.inputs, node.scopePorts.outputs, theme, themeName);
93
102
  }
@@ -97,14 +106,6 @@ export function renderSVG(graph, options = {}) {
97
106
  }
98
107
  }
99
108
  parts.push(`</g>`);
100
- // Watermark (logo + text)
101
- const wmX = vbX + vbWidth - 16;
102
- const wmY = vbY + vbHeight - 14;
103
- const wmBrand = themeName === 'dark' ? '#8e9eff' : '#5468ff';
104
- parts.push(`<g opacity="0.5">`);
105
- parts.push(` <svg x="${wmX - 118}" y="${wmY - 18}" width="22" height="22" viewBox="0 0 256 256" fill="none"><path d="M80 128C134 128 122 49 176 49" stroke="${wmBrand}" stroke-width="14" stroke-linecap="round"/><path d="M80 128C134 128 122 207 176 207" stroke="${wmBrand}" stroke-width="14" stroke-linecap="round"/><rect x="28" y="102" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/><rect x="176" y="23" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/><rect x="176" y="181" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/></svg>`);
106
- parts.push(` <text x="${wmX}" y="${wmY}" text-anchor="end" font-size="14" font-weight="700" fill="${wmBrand}" font-family="Montserrat, 'Segoe UI', Roboto, sans-serif">Flow Weaver</text>`);
107
- parts.push(`</g>`);
108
109
  parts.push(`</svg>`);
109
110
  return parts.join('\n');
110
111
  }
@@ -113,7 +114,7 @@ function renderConnection(parts, conn, gradIndex, hidden = false) {
113
114
  const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
114
115
  const displayAttr = hidden ? ' display="none"' : '';
115
116
  const pathD = conn.path || 'M0,0';
116
- parts.push(` <path d="${pathD}" 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"${displayAttr}/>`);
117
+ parts.push(` <path d="${pathD}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="1"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"${displayAttr}/>`);
117
118
  }
118
119
  function renderStub(parts, stub, conn, hidden = false) {
119
120
  const dashAttr = stub.dashed ? ' stroke-dasharray="6 3"' : '';
@@ -132,16 +133,17 @@ function renderScopeConnection(parts, conn, allConnections, parentNodeId) {
132
133
  if (gradIndex < 0)
133
134
  return;
134
135
  const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
135
- 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)}"/>`);
136
+ parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="1"${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)}"/>`);
136
137
  }
137
138
  // ---- Node rendering ----
138
139
  /** Render node body rect + icon */
139
- function renderNodeBody(parts, node, theme, indent) {
140
+ function renderNodeBody(parts, node, theme, themeName, indent) {
140
141
  const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
141
- parts.push(`${indent}<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`);
142
+ parts.push(`${indent}<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.background}" stroke="${strokeColor}" stroke-width="2"/>`);
142
143
  const iconPath = NODE_ICON_PATHS[node.icon] ?? NODE_ICON_PATHS.code;
143
- const iconColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
144
- const iconSize = 40;
144
+ // node.color is a resolved hex (e.g. "#5e9eff"), find matching variant by border value
145
+ const iconColor = resolveIconColor(node.color, themeName, theme);
146
+ const iconSize = 50;
145
147
  const iconX = node.x + (node.width - iconSize) / 2;
146
148
  const iconY = node.y + (node.height - iconSize) / 2;
147
149
  parts.push(`${indent}<svg x="${iconX}" y="${iconY}" width="${iconSize}" height="${iconSize}" viewBox="0 -960 960 960"><path d="${iconPath}" fill="${iconColor}"/></svg>`);
@@ -152,57 +154,61 @@ function renderNode(node, theme, themeName, allConnections) {
152
154
  if (node.scopeChildren && node.scopeChildren.length > 0) {
153
155
  // Scoped node: body rect only (icon omitted — children occupy the inner area)
154
156
  const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
155
- parts.push(` <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`);
157
+ parts.push(` <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.background}" stroke="${strokeColor}" stroke-width="2"/>`);
156
158
  renderScopedContent(parts, node, theme, themeName, allConnections);
157
159
  }
158
160
  else {
159
- renderNodeBody(parts, node, theme, ' ');
161
+ renderNodeBody(parts, node, theme, themeName, ' ');
160
162
  }
161
163
  // External port dots (labels rendered in top-level labels pass)
162
- renderPortDots(parts, node.id, node.inputs, node.outputs, themeName);
164
+ renderPortDots(parts, node.id, node.inputs, node.outputs, themeName, theme);
163
165
  parts.push(` </g>`);
164
166
  return parts.join('\n');
165
167
  }
166
168
  function renderScopedContent(parts, node, theme, themeName, allConnections) {
167
169
  const children = node.scopeChildren;
168
170
  const scopePorts = node.scopePorts;
169
- // Scope area inner rectangle
171
+ // Scope area dividers — matches platform scopeContainerStyle (2px solid outline columns)
172
+ // Vertical lines at left/right scope port columns + horizontal top/bottom dividers
170
173
  const scopeX = node.x + SCOPE_PORT_COLUMN;
171
- const scopeY = node.y + 4;
172
174
  const scopeW = node.width - SCOPE_PORT_COLUMN * 2;
173
- const scopeH = node.height - 8;
174
- 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"/>`);
175
+ const scopeRightX = node.x + node.width - SCOPE_PORT_COLUMN;
176
+ const lineY1 = node.y;
177
+ const lineY2 = node.y + node.height;
178
+ const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
179
+ // Vertical column dividers (left and right scope port columns)
180
+ parts.push(` <line x1="${scopeX}" y1="${lineY1}" x2="${scopeX}" y2="${lineY2}" stroke="${strokeColor}" stroke-width="2" opacity="0.5"/>`);
181
+ parts.push(` <line x1="${scopeRightX}" y1="${lineY1}" x2="${scopeRightX}" y2="${lineY2}" stroke="${strokeColor}" stroke-width="2" opacity="0.5"/>`);
182
+ // Horizontal top/bottom area dividers
183
+ parts.push(` <line x1="${scopeX}" y1="${lineY1 + 2}" x2="${scopeRightX}" y2="${lineY1 + 2}" stroke="${theme.scopeAreaStroke}" stroke-width="1" opacity="0.3"/>`);
184
+ parts.push(` <line x1="${scopeX}" y1="${lineY2 - 2}" x2="${scopeRightX}" y2="${lineY2 - 2}" stroke="${theme.scopeAreaStroke}" stroke-width="1" opacity="0.3"/>`);
175
185
  // Scope connections (before ports so ports appear on top)
176
186
  for (const conn of node.scopeConnections ?? []) {
177
187
  renderScopeConnection(parts, conn, allConnections, node.id);
178
188
  }
179
189
  // Scope port dots (before children so dots sit on top of connections)
180
190
  if (scopePorts) {
181
- renderPortDots(parts, node.id, scopePorts.inputs, scopePorts.outputs, themeName);
191
+ renderPortDots(parts, node.id, scopePorts.inputs, scopePorts.outputs, themeName, theme);
182
192
  }
183
193
  // Child nodes (all labels handled in top-level labels pass)
184
194
  for (const child of children) {
185
195
  parts.push(` <g data-node-id="${escapeXml(child.id)}">`);
186
- renderNodeBody(parts, child, theme, ' ');
187
- renderPortDots(parts, child.id, child.inputs, child.outputs, themeName);
196
+ renderNodeBody(parts, child, theme, themeName, ' ');
197
+ renderPortDots(parts, child.id, child.inputs, child.outputs, themeName, theme);
188
198
  parts.push(` </g>`);
189
199
  }
190
200
  }
191
201
  // ---- Label rendering ----
192
- /** Render a node name label badge */
202
+ /** Render a node name label (plain text, no badge background — matches platform) */
193
203
  function renderNodeLabel(parts, node, theme) {
194
204
  const isScoped = !!(node.scopeChildren && node.scopeChildren.length > 0);
195
205
  const labelText = escapeXml(node.label);
196
- const textWidth = labelText.length * 7;
197
- const labelBgWidth = textWidth + 16;
198
- const labelBgHeight = LABEL_HEIGHT;
199
- const labelBgX = isScoped ? node.x : node.x + node.width / 2 - labelBgWidth / 2;
200
- const labelBgY = node.y - LABEL_GAP - labelBgHeight;
201
- const labelTextX = isScoped ? node.x + 8 : node.x + node.width / 2;
206
+ const labelTextX = isScoped ? node.x + 6 : node.x + node.width / 2;
207
+ const labelTextY = node.y - LABEL_GAP;
202
208
  const labelAnchor = isScoped ? 'start' : 'middle';
209
+ const labelColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.labelColor;
203
210
  parts.push(` <g data-label-for="${escapeXml(node.id)}">`);
204
- parts.push(` <rect x="${labelBgX}" y="${labelBgY}" width="${labelBgWidth}" height="${labelBgHeight}" rx="6" fill="${theme.labelBadgeFill}" opacity="0.95"/>`);
205
- parts.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== NODE_DEFAULT_COLOR ? node.color : theme.labelColor}">${labelText}</text>`);
211
+ parts.push(` <text class="node-label" x="${labelTextX}" y="${labelTextY}" text-anchor="${labelAnchor}" fill="${labelColor}">${labelText}</text>`);
206
212
  parts.push(` </g>`);
207
213
  }
208
214
  /** Render port labels for a node if showPortLabels is enabled */
@@ -212,51 +218,59 @@ function renderPortLabelsForNode(parts, node, theme, themeName, showPortLabels)
212
218
  }
213
219
  }
214
220
  // ---- Port rendering ----
215
- /** Render only port dot circles (no labels) */
216
- function renderPortDots(parts, nodeId, inputs, outputs, themeName) {
221
+ /** Render port indicators: outer ring (port color) + inner bar (bg) matching platform portStyle */
222
+ function renderPortDots(parts, nodeId, inputs, outputs, themeName, theme) {
223
+ // 2px bar with 2px colored ring (boxShadow) — matches platform portStyle
224
+ // SVG equivalent: inner rect (subtle bg) + outer rect (port color, slightly larger)
225
+ const barWidth = 2;
226
+ const barHeight = 14;
227
+ const ringWidth = 2; // boxShadow spread
228
+ const outerW = barWidth + ringWidth * 2; // 6px total
229
+ const outerH = barHeight + ringWidth * 2; // 18px total
217
230
  for (const port of [...inputs, ...outputs]) {
218
231
  const color = getPortColor(port.dataType, port.isFailure, themeName);
219
- const ringColor = getPortRingColor(port.dataType, port.isFailure, themeName);
220
232
  const dir = port.direction === 'INPUT' ? 'input' : 'output';
221
- parts.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
233
+ const ox = port.cx - outerW / 2;
234
+ const oy = port.cy - outerH / 2;
235
+ const ix = port.cx - barWidth / 2;
236
+ const iy = port.cy - barHeight / 2;
237
+ // Outer ring (port-type color)
238
+ parts.push(` <rect x="${ox}" y="${oy}" width="${outerW}" height="${outerH}" rx="4" fill="${color}" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
239
+ // Inner bar (subtle bg)
240
+ parts.push(` <rect x="${ix}" y="${iy}" width="${barWidth}" height="${barHeight}" rx="2" fill="${theme.background}" pointer-events="none"/>`);
222
241
  }
223
242
  }
224
- /** Render only port label badges (no dots) */
243
+ /** Render only port label badges (no dots) — rectangular with port-type border */
225
244
  function renderPortLabels(parts, nodeId, inputs, outputs, theme, themeName) {
226
245
  for (const port of [...inputs, ...outputs]) {
227
246
  const color = getPortColor(port.dataType, port.isFailure, themeName);
228
247
  const isInput = port.direction === 'INPUT';
229
- const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
230
248
  const dir = isInput ? 'input' : 'output';
231
249
  const portId = `${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}`;
232
250
  const portLabel = port.label;
233
- const typeWidth = measureText(abbrev);
234
251
  const labelWidth = measureText(portLabel);
235
- const pad = 7;
236
- const divGap = 4;
237
- const badgeWidth = pad + typeWidth + divGap + 1 + divGap + labelWidth + pad;
252
+ const pad = 6;
253
+ const gap = 4;
254
+ const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
255
+ const typeWidth = measureText(abbrev);
256
+ const badgeWidth = pad + typeWidth + gap + labelWidth + pad;
238
257
  const badgeHeight = 16;
239
258
  const badgeGap = 5;
240
- const rr = badgeHeight / 2;
241
259
  const badgeX = isInput
242
260
  ? port.cx - PORT_RADIUS - badgeGap - badgeWidth
243
261
  : port.cx + PORT_RADIUS + badgeGap;
244
262
  const badgeY = port.cy - badgeHeight / 2;
245
263
  parts.push(` <g data-port-label="${portId}">`);
246
- parts.push(` <rect x="${badgeX}" y="${badgeY}" width="${badgeWidth}" height="${badgeHeight}" rx="${rr}" fill="${theme.nodeFill}" stroke="${theme.labelBadgeBorder}" stroke-width="1"/>`);
264
+ parts.push(` <rect x="${badgeX}" y="${badgeY}" width="${badgeWidth}" height="${badgeHeight}" rx="3" fill="${theme.background}" stroke="${color}" stroke-width="1"/>`);
247
265
  if (isInput) {
248
266
  const typeX = badgeX + badgeWidth - pad - typeWidth / 2;
249
- const divX = typeX - typeWidth / 2 - divGap;
250
- const nameX = divX - divGap;
251
- parts.push(` <line x1="${divX}" y1="${badgeY + 3}" x2="${divX}" y2="${badgeY + badgeHeight - 3}" stroke="${theme.labelBadgeBorder}" stroke-width="1"/>`);
267
+ const nameX = typeX - typeWidth / 2 - gap;
252
268
  parts.push(` <text class="port-label" x="${nameX}" y="${port.cy + 3.5}" text-anchor="end">${escapeXml(portLabel)}</text>`);
253
269
  parts.push(` <text class="port-type-label" x="${typeX}" y="${port.cy + 3.5}" text-anchor="middle" fill="${color}">${escapeXml(abbrev)}</text>`);
254
270
  }
255
271
  else {
256
272
  const typeX = badgeX + pad + typeWidth / 2;
257
- const divX = badgeX + pad + typeWidth + divGap;
258
- const nameX = divX + 1 + divGap;
259
- parts.push(` <line x1="${divX}" y1="${badgeY + 3}" x2="${divX}" y2="${badgeY + badgeHeight - 3}" stroke="${theme.labelBadgeBorder}" stroke-width="1"/>`);
273
+ const nameX = typeX + typeWidth / 2 + gap;
260
274
  parts.push(` <text class="port-type-label" x="${typeX}" y="${port.cy + 3.5}" text-anchor="middle" fill="${color}">${escapeXml(abbrev)}</text>`);
261
275
  parts.push(` <text class="port-label" x="${nameX}" y="${port.cy + 3.5}" text-anchor="start">${escapeXml(portLabel)}</text>`);
262
276
  }
@@ -4,6 +4,8 @@ export declare const NODE_DEFAULT_COLOR = "#334155";
4
4
  export declare const NODE_VARIANT_COLORS: Record<string, {
5
5
  border: string;
6
6
  darkBorder: string;
7
+ icon: string;
8
+ darkIcon: string;
7
9
  }>;
8
10
  export declare function getTheme(name: 'dark' | 'light'): ThemePalette;
9
11
  export declare function getPortColor(dataType: TDataType, isFailure: boolean, theme?: 'dark' | 'light'): string;
@@ -28,15 +28,15 @@ const LIGHT_FAILURE_COLOR = '#e34646'; // red-shade-2
28
28
  // Light: border = X-shade-2
29
29
  export const NODE_DEFAULT_COLOR = '#334155';
30
30
  export const NODE_VARIANT_COLORS = {
31
- blue: { border: '#548ce3', darkBorder: '#5e9eff' }, // blue-shade-2 / blue-dark-shade-1
32
- purple: { border: '#9f5fe3', darkBorder: '#b36bff' }, // purple-shade-2 / purple-dark-shade-1
33
- cyan: { border: '#63ccc4', darkBorder: '#6fe5dc' }, // cyan-shade-2 / cyan-dark-shade-1
34
- teal: { border: '#63ccc4', darkBorder: '#6fe5dc' }, // alias for cyan
35
- orange: { border: '#e3732d', darkBorder: '#ff8133' }, // orange-shade-2 / orange-dark-shade-1
36
- pink: { border: '#e349c2', darkBorder: '#ff52da' }, // pink-shade-2 / pink-dark-shade-1
37
- green: { border: '#0ec850', darkBorder: '#10e15a' }, // green-shade-2 / green-dark-shade-1
38
- red: { border: '#e34646', darkBorder: '#ff4f4f' }, // red-shade-2 / red-dark-shade-1
39
- yellow: { border: '#e3a82b', darkBorder: '#ffbd30' }, // yellow-shade-2 / yellow-dark-shade-1
31
+ blue: { border: '#548ce3', darkBorder: '#5e9eff', icon: '#3a6bbf', darkIcon: '#4a7ad4' }, // blue-shade-2 / blue-dark-shade-1 / blue-shade-3 / blue-dark-shade-3
32
+ purple: { border: '#9f5fe3', darkBorder: '#b36bff', icon: '#7d44bf', darkIcon: '#9050d4' }, // purple-shade-2 / purple-dark-shade-1
33
+ cyan: { border: '#63ccc4', darkBorder: '#6fe5dc', icon: '#4aada6', darkIcon: '#56c4bb' }, // cyan-shade-2 / cyan-dark-shade-1
34
+ teal: { border: '#63ccc4', darkBorder: '#6fe5dc', icon: '#4aada6', darkIcon: '#56c4bb' }, // alias for cyan
35
+ orange: { border: '#e3732d', darkBorder: '#ff8133', icon: '#bf5a1e', darkIcon: '#d46a28' }, // orange-shade-2 / orange-dark-shade-1
36
+ pink: { border: '#e349c2', darkBorder: '#ff52da', icon: '#bf349f', darkIcon: '#d43fb5' }, // pink-shade-2 / pink-dark-shade-1
37
+ green: { border: '#0ec850', darkBorder: '#10e15a', icon: '#0aa53f', darkIcon: '#0dbd4a' }, // green-shade-2 / green-dark-shade-1
38
+ red: { border: '#e34646', darkBorder: '#ff4f4f', icon: '#bf3333', darkIcon: '#d43b3b' }, // red-shade-2 / red-dark-shade-1
39
+ yellow: { border: '#e3a82b', darkBorder: '#ffbd30', icon: '#bf8c21', darkIcon: '#d49e28' }, // yellow-shade-2 / yellow-dark-shade-1
40
40
  };
41
41
  // ---- Theme palettes (exact values from token system) ----
42
42
  const DARK_PALETTE = {
@@ -46,13 +46,13 @@ const DARK_PALETTE = {
46
46
  labelColor: '#a4beff', // secondary-dark-base (node label text)
47
47
  sublabelColor: '#babac0', // color-text-subtle = dark-shade-30
48
48
  connectionColor: '#5f5f6d', // color-border-subtle = dark-shade-70
49
- dotColor: '#8e9eff', // color-background-dots = primary-dark-tint-1
49
+ dotColor: '#7b8cd9', // color-background-dots-secondary = primary-dark-tint-2
50
50
  labelBadgeFill: '#252538', // color-surface-low = dark-shade-95
51
51
  labelBadgeBorder: '#313143', // color-surface-lowest = dark-shade-90
52
- nodeIconColor: '#a4beff', // secondary-dark-base (matches label color)
52
+ nodeIconColor: '#8e9eff', // color-brand-main = primary-dark-tint-1
53
53
  scopeAreaStroke: '#5f5f6d', // color-border-subtle = dark-shade-70
54
- nodeShadowOpacity: 0.15,
55
- dotOpacity: 0.5,
54
+ nodeShadowOpacity: 0,
55
+ dotOpacity: 0.4,
56
56
  };
57
57
  const LIGHT_PALETTE = {
58
58
  background: '#f6f7ff', // color-brand-subtle-bg = primary-extended-tint-95
@@ -61,13 +61,13 @@ const LIGHT_PALETTE = {
61
61
  labelColor: '#223354', // secondary-base (light)
62
62
  sublabelColor: '#808080', // shade-50
63
63
  connectionColor: '#b3b3b3', // shade-30
64
- dotColor: '#5468ff', // color-background-dots = primary-shade-1
64
+ dotColor: '#4a5ce0', // color-background-dots-secondary = primary-shade-2
65
65
  labelBadgeFill: '#ffffff', // surface-main — 80% opacity applied in renderer
66
66
  labelBadgeBorder: '#e6e6e6', // shade-10
67
- nodeIconColor: '#5468ff', // primary-base (light)
67
+ nodeIconColor: '#5468ff', // color-brand-main = primary-base
68
68
  scopeAreaStroke: '#cccccc', // color-border-default
69
- nodeShadowOpacity: 0.08,
70
- dotOpacity: 0.45,
69
+ nodeShadowOpacity: 0,
70
+ dotOpacity: 0.4,
71
71
  };
72
72
  export function getTheme(name) {
73
73
  return name === 'light' ? LIGHT_PALETTE : DARK_PALETTE;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.33.1";
1
+ export declare const VERSION = "0.33.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.33.1';
2
+ export const VERSION = '0.33.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.33.1",
3
+ "version": "0.33.2",
4
4
  "description": "Flow Weaver: deterministic TypeScript workflow compiler. Define workflows with JSDoc annotations, compile to standalone functions with zero runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",