@burdenoff/fluidgrids-fe-sdk 2026.1105.4 → 2026.1106.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.
package/README.md CHANGED
@@ -256,6 +256,10 @@ import { WorkflowPreviewCanvas, previewCanvasFromDefinition, previewRevealOrder
256
256
  - `resolveNodeMeta(nodeType)` (optional, keep it stable) supplies registry names
257
257
  and icons; without it a card uses the node's saved manifest, then a name and
258
258
  icon derived from its type.
259
+ - Edges follow the builder's rule: an edge whose source or target handle the
260
+ builder's card for that node does not have (read from the node's stored data,
261
+ as the builder reads it) is not drawn, so the preview never shows a
262
+ connection the builder drops.
259
263
  - Nodes cannot be dragged, connected, selected or deleted; pan and zoom work.
260
264
  - `prefers-reduced-motion: reduce` turns every animation off.
261
265
 
@@ -93,9 +93,16 @@ declare function fallbackNodeIcon(nodeType: string): string;
93
93
  declare function previewCardModel(raw: Node, resolveMeta?: WorkflowPreviewNodeMetaResolver): PreviewCardModel;
94
94
  /**
95
95
  * The preview canvas of a stored definition, or `null` when it carries no
96
- * canvas. Sticky notes are annotations, not steps, so they are left out; an
97
- * edge to a node the preview does not draw is dropped, and a target handle
98
- * the target card does not have falls back to its first input.
96
+ * canvas. Sticky notes are annotations, not steps, so they are left out, with
97
+ * any edge to one.
98
+ *
99
+ * ★ An edge is drawn only when the builder draws it (`builderCardPorts`): one
100
+ * whose source or target handle the builder's card does not have (an
101
+ * assistant-written `handle('output')` on a node whose only output is
102
+ * `source`, or any edge into a trigger) is left out, never given a port of its
103
+ * own. The preview used to invent a labelled port for every unknown handle,
104
+ * so it showed connections the builder silently drops (BOFF-9806). The rule
105
+ * needs no node registry because the builder's does not use one either.
99
106
  */
100
107
  declare function previewCanvasFromDefinition(definitionJson: unknown, resolveMeta?: WorkflowPreviewNodeMetaResolver): PreviewCanvasModel | null;
101
108
  /**
@@ -1,5 +1,5 @@
1
1
  import { canvasFromDefinition } from './chunk-6VIG44VF.js';
2
- import { LogicNodeCard, NodeCard, NodeCardHeader, NodeCardBody, InputHandles, OutputHandles, STICKY_NOTE_TYPE, outputHandlesSpacerHeight, isNodeTrigger, resolveOutputHandles, resolveInputHandles, resolveOperationName, WAIT_FOR_SIGNAL_NODE_TYPE, CODE_WORKFLOW_NODE_TYPE, readSignalName } from './chunk-TGWDFLEX.js';
2
+ import { LogicNodeCard, NodeCard, NodeCardHeader, NodeCardBody, InputHandles, OutputHandles, STICKY_NOTE_TYPE, isNodeTrigger, resolveOutputHandles, resolveInputHandles, resolveOperationName, outputHandlesSpacerHeight, WAIT_FOR_SIGNAL_NODE_TYPE, CODE_WORKFLOW_NODE_TYPE, readSignalName } from './chunk-TGWDFLEX.js';
3
3
  import './chunk-Y7FKJJMF.js';
4
4
  import { useTr } from './chunk-UULVIT25.js';
5
5
  import { memo, useMemo, useState, useCallback, useSyncExternalStore, useRef, useEffect } from 'react';
@@ -119,27 +119,14 @@ function previewCardModel(raw, resolveMeta) {
119
119
  )
120
120
  };
121
121
  }
122
- function branchTone(id) {
123
- const key = id.toLowerCase();
124
- if (key === "true" || key === "success" || key === "approved")
125
- return "success";
126
- if (key === "false" || key === "done") return "neutral";
127
- if (key === "error" || key === "failed" || key === "declined") return "error";
128
- return void 0;
129
- }
130
- function withEdgeSourceHandles(card, edgeHandleIds) {
122
+ function withAdapterPorts(card, edgeHandleIds) {
131
123
  const known = new Set(card.outputHandles.map((handle) => handle.id));
132
124
  const missing = [...new Set(edgeHandleIds)].filter((id) => !known.has(id));
133
125
  if (missing.length === 0) return card;
134
- const isDefault = card.outputHandles.length === 1 && card.outputHandles[0]?.label === "";
135
- const named = missing.map((id) => ({
136
- id,
137
- label: id,
138
- tone: branchTone(id)
139
- }));
140
- if (isDefault && named.length === 1)
141
- return { ...card, outputHandles: [{ id: named[0].id, label: "" }] };
142
- const outputHandles = isDefault ? named : [...card.outputHandles, ...named];
126
+ const outputHandles = [
127
+ ...card.outputHandles,
128
+ ...missing.map((id) => ({ id, label: id }))
129
+ ];
143
130
  return {
144
131
  ...card,
145
132
  outputHandles,
@@ -148,28 +135,82 @@ function withEdgeSourceHandles(card, edgeHandleIds) {
148
135
  )
149
136
  };
150
137
  }
138
+ function adapterPortNames(data) {
139
+ const manifest = isRecord(data.nodeData) ? data.nodeData : {};
140
+ const adapters = Array.isArray(manifest.nodeAdapters) ? manifest.nodeAdapters : [];
141
+ return adapters.map((adapter) => isRecord(adapter) ? adapter.name : void 0).filter((name) => typeof name === "string" && !!name);
142
+ }
143
+ function builderCardPorts(nodes, edges, cards) {
144
+ const adapters = /* @__PURE__ */ new Map();
145
+ for (const node of nodes)
146
+ adapters.set(node.id, adapterPortNames(cardData(node)));
147
+ const adapterSets = new Map(
148
+ [...adapters].map(([id, names]) => [id, new Set(names)])
149
+ );
150
+ const subNodes = /* @__PURE__ */ new Set();
151
+ for (const node of nodes) {
152
+ if (cardData(node).isAdapterInstance) subNodes.add(node.id);
153
+ }
154
+ for (const edge of edges) {
155
+ const handle = edge.sourceHandle;
156
+ if (!handle || handle === "source") continue;
157
+ if (adapterSets.get(edge.source)?.has(handle)) subNodes.add(edge.target);
158
+ }
159
+ const ports = /* @__PURE__ */ new Map();
160
+ for (const node of nodes) {
161
+ const card = cards.get(node.id);
162
+ if (!card) continue;
163
+ const own = adapters.get(node.id) ?? [];
164
+ if (subNodes.has(node.id)) {
165
+ ports.set(node.id, { sources: own, targets: ["target"] });
166
+ continue;
167
+ }
168
+ const hasInputs = card.variant === "logic" || !card.trigger;
169
+ ports.set(node.id, {
170
+ sources: [...card.outputHandles.map((handle) => handle.id), ...own],
171
+ targets: hasInputs ? card.inputHandles.map((handle) => handle.id) : []
172
+ });
173
+ }
174
+ return ports;
175
+ }
176
+ function builderHandle(ports, handle) {
177
+ if (!handle) return ports[0] ?? null;
178
+ return ports.includes(handle) ? handle : null;
179
+ }
151
180
  function previewCanvasFromDefinition(definitionJson, resolveMeta) {
152
181
  const canvas = canvasFromDefinition(definitionJson);
153
182
  if (!canvas) return null;
154
183
  const steps = canvas.nodes.filter((node) => node.type !== STICKY_NOTE_TYPE);
155
184
  const ids = new Set(steps.map((node) => node.id));
156
- const rawEdges = canvas.edges.filter(
185
+ const betweenSteps = canvas.edges.filter(
157
186
  (edge) => ids.has(edge.source) && ids.has(edge.target)
158
187
  );
188
+ const resolved = /* @__PURE__ */ new Map();
189
+ for (const raw of steps)
190
+ resolved.set(raw.id, previewCardModel(raw, resolveMeta));
191
+ const ports = builderCardPorts(steps, betweenSteps, resolved);
192
+ const rawEdges = betweenSteps.flatMap((edge) => {
193
+ const sourceHandle = builderHandle(
194
+ ports.get(edge.source)?.sources ?? [],
195
+ edge.sourceHandle
196
+ );
197
+ const targetHandle = builderHandle(
198
+ ports.get(edge.target)?.targets ?? [],
199
+ edge.targetHandle
200
+ );
201
+ return sourceHandle && targetHandle ? [{ ...edge, sourceHandle, targetHandle }] : [];
202
+ });
159
203
  const sourceHandles = /* @__PURE__ */ new Map();
160
204
  for (const edge of rawEdges) {
161
- if (!edge.sourceHandle) continue;
162
205
  const list = sourceHandles.get(edge.source) ?? [];
163
206
  list.push(edge.sourceHandle);
164
207
  sourceHandles.set(edge.source, list);
165
208
  }
166
- const cards = /* @__PURE__ */ new Map();
167
209
  const nodes = steps.map((raw) => {
168
- const card = withEdgeSourceHandles(
169
- previewCardModel(raw, resolveMeta),
210
+ const card = withAdapterPorts(
211
+ resolved.get(raw.id) ?? previewCardModel(raw, resolveMeta),
170
212
  sourceHandles.get(raw.id) ?? []
171
213
  );
172
- cards.set(raw.id, card);
173
214
  const position = isRecord(raw.position) ? raw.position : {};
174
215
  return {
175
216
  id: raw.id,
@@ -185,23 +226,17 @@ function previewCanvasFromDefinition(definitionJson, resolveMeta) {
185
226
  deletable: false
186
227
  };
187
228
  });
188
- const edges = rawEdges.map((edge) => {
189
- const sourceCard = cards.get(edge.source);
190
- const targetCard = cards.get(edge.target);
191
- const targetHandle = edge.targetHandle && targetCard?.inputHandles.some((h) => h.id === edge.targetHandle) ? edge.targetHandle : targetCard?.inputHandles[0]?.id ?? null;
192
- const sourceHandle = edge.sourceHandle ?? sourceCard?.outputHandles[0]?.id ?? null;
193
- return {
194
- id: edge.id,
195
- source: edge.source,
196
- target: edge.target,
197
- sourceHandle,
198
- targetHandle,
199
- type: PREVIEW_EDGE_TYPE,
200
- selectable: false,
201
- deletable: false,
202
- focusable: false
203
- };
204
- });
229
+ const edges = rawEdges.map((edge) => ({
230
+ id: edge.id,
231
+ source: edge.source,
232
+ target: edge.target,
233
+ sourceHandle: edge.sourceHandle,
234
+ targetHandle: edge.targetHandle,
235
+ type: PREVIEW_EDGE_TYPE,
236
+ selectable: false,
237
+ deletable: false,
238
+ focusable: false
239
+ }));
205
240
  return { nodes, edges };
206
241
  }
207
242
  function previewRevealOrder(nodes, shown = /* @__PURE__ */ new Set()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fluidgrids-fe-sdk",
3
- "version": "2026.1105.4",
3
+ "version": "2026.1106.1",
4
4
  "description": "FluidGrids Workflow Builder SDK - Embeddable visual workflow automation",
5
5
  "type": "module",
6
6
  "imports": {