@particle-academy/fancy-flow 0.63.0 → 0.65.0

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/dist/index.cjs CHANGED
@@ -13960,6 +13960,7 @@ function NodeConfigPanel({
13960
13960
  renderCredentialField,
13961
13961
  renderDocumentField,
13962
13962
  fieldRenderers,
13963
+ fieldFilter,
13963
13964
  graph,
13964
13965
  className,
13965
13966
  style: style2
@@ -14006,6 +14007,9 @@ function NodeConfigPanel({
14006
14007
  const setStatusMsg = (key, value) => onChange({ ...node, data: { ...node.data, [key]: value } });
14007
14008
  const setConfigValue = (key, value) => onChange({ ...node, data: { ...node.data, config: { ...config, [key]: value } } });
14008
14009
  const issues = validateConfig(kind, config);
14010
+ const configFields = (kind.configSchema ?? []).filter(
14011
+ (field) => fieldFilter ? fieldFilter({ node, kind, field }) : true
14012
+ );
14009
14013
  const adapter2 = getRichInputAdapter();
14010
14014
  const documentField = renderDocumentField ?? (adapter2?.renderEditor ? ({ value, onChange: set3 }) => adapter2.renderEditor({ value, onChange: set3 }) : void 0);
14011
14015
  return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: ["ff-panel", className ?? ""].filter(Boolean).join(" "), style: style2, children: [
@@ -14081,8 +14085,8 @@ function NodeConfigPanel({
14081
14085
  onChange: (next) => onChange({ ...node, data: { ...node.data, config: next } }),
14082
14086
  nodeId: node.id
14083
14087
  }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14084
- (kind.configSchema ?? []).length > 0 && /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "ff-panel__divider" }),
14085
- (kind.configSchema ?? []).map((field) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-panel__field", children: [
14088
+ configFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "ff-panel__divider" }),
14089
+ configFields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-panel__field", children: [
14086
14090
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-panel__label", htmlFor: fieldId(field.key), children: [
14087
14091
  field.label,
14088
14092
  field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-panel__required", "aria-hidden": true, children: " *" })
@@ -14390,6 +14394,55 @@ function preview(v2) {
14390
14394
  }
14391
14395
  }
14392
14396
 
14397
+ // src/analysis/graph-connectivity.ts
14398
+ function checkGraphConnectivity(graph) {
14399
+ const { nodes, edges } = graph;
14400
+ const issues = [];
14401
+ const hasIncoming = /* @__PURE__ */ new Set();
14402
+ const hasOutgoing = /* @__PURE__ */ new Set();
14403
+ for (const edge of edges) {
14404
+ hasIncoming.add(edge.target);
14405
+ hasOutgoing.add(edge.source);
14406
+ }
14407
+ const single = nodes.length === 1;
14408
+ for (const node of nodes) {
14409
+ if (single || mayFloat(node)) continue;
14410
+ if (!hasIncoming.has(node.id) && !hasOutgoing.has(node.id)) {
14411
+ issues.push({
14412
+ level: "error",
14413
+ nodeId: node.id,
14414
+ message: `Node "${node.id}" is connected to nothing \u2014 no inbound edge and no outbound edge. It still RUNS (a node with no inbound edge is a root), but it receives nothing from the graph and reaches nobody in it, so it is either unwired or left behind by a deletion. Only a note, an annotation or a lane may float.`
14415
+ });
14416
+ }
14417
+ }
14418
+ const byId = new Map(nodes.map((n) => [n.id, n]));
14419
+ for (const edge of edges) {
14420
+ const source = byId.get(edge.source);
14421
+ if (!source || !isTerminator(source)) continue;
14422
+ issues.push({
14423
+ level: "error",
14424
+ edgeId: edge.id,
14425
+ message: `Edge "${edge.id}" reads from "${edge.source}", which is a TERMINAL node and publishes no output ports at all. Nothing can ever travel this edge: it does not fail at run time, it delivers nothing, and "${edge.target}" runs anyway with an empty input.`
14426
+ });
14427
+ }
14428
+ return issues;
14429
+ }
14430
+ function mayFloat(node) {
14431
+ const type = node.type;
14432
+ if (!type) return false;
14433
+ if (type === "note") return true;
14434
+ const kind = getNodeKind(type);
14435
+ if (!kind) return true;
14436
+ return kindIds(kind).includes("note") || kind.category === "annotation" || kind.category === "layout";
14437
+ }
14438
+ function isTerminator(node) {
14439
+ const own = node.data?.outputs;
14440
+ if (Array.isArray(own)) return own.length === 0;
14441
+ const kind = node.type ? getNodeKind(node.type) : null;
14442
+ if (!kind) return false;
14443
+ return Array.isArray(kind.outputs) && kind.outputs.length === 0;
14444
+ }
14445
+
14393
14446
  // src/schema/workflow-schema.ts
14394
14447
  var WORKFLOW_SCHEMA_VERSION = 1;
14395
14448
  var WORKFLOW_SCHEMA_URL = "https://particle.academy/schemas/workflow/v1.json";
@@ -14537,6 +14590,7 @@ function importWorkflow(schema, options = {}) {
14537
14590
  label: e.label
14538
14591
  };
14539
14592
  }).filter((e) => e !== null);
14593
+ issues.push(...checkGraphConnectivity({ nodes, edges }));
14540
14594
  const ok = issues.every((i) => i.level !== "error");
14541
14595
  const declaredInputs = Array.isArray(s.inputs) ? s.inputs.filter(
14542
14596
  (input) => input !== null && typeof input === "object" && typeof input.name === "string"
@@ -14940,6 +14994,7 @@ function FlowEditorInner({
14940
14994
  showPalette = true,
14941
14995
  showPanel = true,
14942
14996
  fieldRenderers,
14997
+ fieldFilter,
14943
14998
  showFeed = true,
14944
14999
  extraToolbar,
14945
15000
  actions = [],
@@ -15508,6 +15563,7 @@ function FlowEditorInner({
15508
15563
  node: api.selected,
15509
15564
  onChange: api.updateNode,
15510
15565
  fieldRenderers,
15566
+ fieldFilter,
15511
15567
  graph: flow,
15512
15568
  onDelete: builtins.delete === false ? void 0 : (n) => api.deleteNodes([n.id])
15513
15569
  }