From 8f37165f96a52767fd6f67e5163610c5ad0ae0a4 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 23 Aug 2026 23:27:36 +0200 Subject: [PATCH 01/17] feat: add DataTypeListSubFlowInputComponent for handling list of sub-flows in DataTypeInputComponent --- .../inputs/DataTypeInputComponent.tsx | 8 + .../DataTypeListSubFlowInputComponent.tsx | 169 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/packages/ce/src/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent.tsx diff --git a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx index 989964a0..2727aa89 100644 --- a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx @@ -32,6 +32,9 @@ import {DataTypeGenericInputComponent} from "@edition/datatype/components/inputs import { DataTypeSubFlowInputComponent } from "@edition/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent"; +import { + DataTypeListSubFlowInputComponent +} from "@edition/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent"; export interface DataTypeInputComponentProps extends Omit, "onChange"> { schema: (NodeSchema | Schema) @@ -123,6 +126,11 @@ export const DataTypeInputComponent: React.FC = (pr schema={schema} suggestions={suggestions} {...rest}/> + case "list-sub-flow": + return default: return = (props) => { + + const {schema, formValidation, title, initialValue, description, suggestions, onChange} = props + + const params = useParams() + const flowService = useService(FlowService) + + const flowIndex = Number(params.flowId) || 1 + const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}` + + const initialSubFlows: SubFlowValue[] = ((initialValue as LiteralValue)?.__typename === "LiteralValue" + ? ((initialValue as LiteralValue).references ?? []) + : []) + .map(reference => reference.value) + .filter((value): value is SubFlowValue => value?.__typename === "SubFlowValue") + + const [subFlows, setSubFlows] = React.useState(initialSubFlows) + const [dialogOpen, setDialogOpen] = React.useState(false) + const functionSuggestions = useFunctionSuggestions() + + const directMappingSuggestions = React.useMemo(() => { + const inner = schema && "schema" in schema ? (schema as NodeSchema).schema : (schema as Schema | undefined) + const items = (inner as { items?: Schema[] })?.items ?? [] + const seen = new Set() + return items.flatMap(item => item.suggestions ?? []) + .filter((suggest): suggest is SubFlowValue => suggest.__typename === "SubFlowValue") + .filter(suggest => { + const key = suggest.functionDefinition?.id ?? suggest.signature ?? "" + if (seen.has(key)) return false + seen.add(key) + return true + }) + }, [schema]) + + const referenceSuggestions = React.useMemo( + () => (suggestions ?? []).filter(suggest => suggest.__typename !== "LiteralValue"), + [suggestions] + ) + + const onChangeDebounced = useDebouncedCallback((value: LiteralValue | SubFlowValue | NodeFunction | ReferenceValue | null) => { + onChange?.(value) + }, 200) + + const keyOf = (value: SubFlowValue) => value.startingNodeId ?? value.functionDefinition?.id ?? "" + const byKey = new Map(subFlows.map(value => [keyOf(value), value])) + const tags: TagValue[] = subFlows.map(value => ({value: keyOf(value)})) + + const commit = (next: SubFlowValue[]) => { + setSubFlows(next) + + if (next.length === 0) { + formValidation?.setValue?.(null) + onChangeDebounced(null) + return + } + + const references: InlineReferenceValue[] = next.map((value, index) => ({ + __typename: "InlineReferenceValue", + signature: `sub_flow_${index}`, + value + })) + const literal: LiteralValue = { + __typename: "LiteralValue", + value: next.map((_, index) => `\${sub_flow_${index}}`), + references + } + formValidation?.setValue?.(literal) + onChangeDebounced(literal) + } + + return <> + { + if (value?.__typename === "NodeFunction") { + const nodeId = flowService.addNodeById(flowId, value) + value = {__typename: "SubFlowValue", startingNodeId: nodeId} + } + if (value?.__typename !== "SubFlowValue") return + commit([...subFlows, value]) + }}/> + {title} + {description} + { + formValidation?.setValue?.(value) + onChangeDebounced(value) + }} + suggestions={suggestions} + formValidation={formValidation}> + { + const value = byKey.get(matchedText) + return value ? : null + } + } + ]} + formValidation={{...formValidation, setValue: undefined}} + onChange={changed => { + const remaining = changed.map(tag => String(tag.value)) + commit(subFlows.filter(value => remaining.includes(keyOf(value)))) + }} + right={ + { + if (value?.__typename === "SubFlowValue") { + commit([...subFlows, value]) + return + } + if (!value) { + commit([]) + return + } + setSubFlows([]) + formValidation?.setValue?.(value) + onChangeDebounced(value) + }}> + + + } + rightType={"action"}> + + + + + +} From 3bebf0ce2c2985a7c735ea26cfcffa8cf9115329 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 23 Aug 2026 23:27:44 +0200 Subject: [PATCH 02/17] feat: update flowIndex parsing in NodeBadgeComponent for improved type safety --- .../ce/src/datatype/components/badges/NodeBadgeComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx b/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx index 480d3edf..11415eb8 100644 --- a/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx +++ b/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx @@ -16,7 +16,7 @@ export interface NodeBadgeComponentProps extends Omit = (props) => { const params = useParams() - const flowIndex = params.flowId as any as number + const flowIndex = Number(params.flowId) || 1 const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}` const {value, definition, ...rest} = props From f8889ff3e4527c88fabe21063193a04727267d04 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 23 Aug 2026 23:27:57 +0200 Subject: [PATCH 03/17] feat: enhance keyword filtering in SuggestionDialogComponent for improved type safety --- .../components/suggestion/SuggestionDialogComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx b/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx index 86a15b04..0317c0e9 100644 --- a/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx +++ b/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx @@ -114,7 +114,7 @@ export const SuggestionDialogComponent: React.FC const DisplayIcon = icon(suggestion.icon as IconString) return <> - typeof keyword === "string")} display={"block"} my={0.7} style={{boxSizing: "border-box", overflow: "hidden"}} From ac14266c8ece395463f3de71d05c659dc249367f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 00:00:06 +0200 Subject: [PATCH 04/17] feat: enhance mapNodeValue and mapNodeParameter functions to support sub-flow handling and improved type safety --- src/packages/core/src/util/playground-mock.ts | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/packages/core/src/util/playground-mock.ts b/src/packages/core/src/util/playground-mock.ts index 7e72399b..afec1812 100644 --- a/src/packages/core/src/util/playground-mock.ts +++ b/src/packages/core/src/util/playground-mock.ts @@ -167,13 +167,40 @@ const mapFlowType = (flowType: FlowType, id: string, runtimeFlowTypeId: string, } }) -const mapNodeValue = (value?: NodeValue) => { +const mapNodeValue = (value?: NodeValue, functionId: Map = new Map()): Record | null => { const inner = value?.value - if (inner?.oneofKind === "literalValue") return literalValue(inner.literalValue.value) + if (inner?.oneofKind === "literalValue") return { + __typename: "LiteralValue", + value: plainValue(inner.literalValue.value), + references: inner.literalValue.references.map(reference => ({ + __typename: "InlineReferenceValue", + signature: reference.signature, + value: mapNodeValue(reference.value, functionId) + })) + } + if (inner?.oneofKind === "subFlow") { + const executionReference = inner.subFlow.executionReference + if (executionReference.oneofKind === "startingNodeId") return { + __typename: "SubFlowValue", + startingNodeId: gid("NodeFunction", executionReference.startingNodeId) + } + if (executionReference.oneofKind === "function") { + const identifier = executionReference.function.functionIdentifier + return { + __typename: "SubFlowValue", + functionDefinition: { + __typename: "FunctionDefinition", + id: functionId.get(identifier) ?? null, + identifier + } + } + } + return {__typename: "SubFlowValue"} + } return null } -const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, parameterId: Map) => ({ +const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, parameterId: Map, functionId: Map) => ({ __typename: "NodeParameter", id: gid("NodeParameter", parameter.databaseId), createdAt: TIMESTAMP, @@ -185,11 +212,11 @@ const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, p createdAt: TIMESTAMP, updatedAt: TIMESTAMP }, - value: mapNodeValue(parameter.value) + value: mapNodeValue(parameter.value, functionId) }) const mapNodeFunction = (node: NodeFunction, functionId: Map, parameterId: Map) => { - const parameterNodes = node.parameters.map(parameter => mapNodeParameter(parameter, node.runtimeFunctionId, parameterId)) + const parameterNodes = node.parameters.map(parameter => mapNodeParameter(parameter, node.runtimeFunctionId, parameterId, functionId)) return { __typename: "NodeFunction", id: gid("NodeFunction", node.databaseId ?? 0), From 2e3e21a0072524b601d47ce97f0b2c972ab69e23 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 00:00:14 +0200 Subject: [PATCH 05/17] feat: improve sub-flow handling in DataTypeListSubFlowInputComponent for enhanced value mapping --- .../list-sub-flow/DataTypeListSubFlowInputComponent.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent.tsx index 89f83303..6428766e 100644 --- a/src/packages/ce/src/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent.tsx @@ -138,8 +138,9 @@ export const DataTypeListSubFlowInputComponent: React.FC { - const remaining = changed.map(tag => String(tag.value)) - commit(subFlows.filter(value => remaining.includes(keyOf(value)))) + commit(changed + .map(tag => byKey.get(String(tag.value))) + .filter((value): value is SubFlowValue => value !== undefined)) }} right={ { From d2e18e3be7dd1d6011d0977b109301dd8e6b6cd6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 00:00:27 +0200 Subject: [PATCH 06/17] feat: enhance sub-flow handling in Flow components for improved parameter mapping and layout --- .../builder/FlowBuilderComponent.tsx | 42 ++----- .../ce/src/flow/hooks/Flow.edges.hook.ts | 36 ++++-- .../ce/src/flow/hooks/Flow.nodes.hook.ts | 113 ++++++++++-------- 3 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx index 7acb112c..61a57bf7 100644 --- a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx +++ b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx @@ -58,7 +58,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const paramIds = new Map() for (const n of nodes) { - const link = (n.data as any)?.parentNodeId + const link = (n.data as any)?.parentNodeId ?? (n.type === "group" ? (n.data as any)?.nodeId : undefined) if (link) { const arr = paramIds.get(link) ?? [] arr.push(n.id) @@ -167,10 +167,6 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { // relatives Layout (Center in globalen Koordinaten) const relCenter = new Map() - // Unterkante je rechter Spalten-"Band", damit Parameter nicht kollidieren - const columnBottom = new Map() - const colKey = (x: number) => Math.round(x / 10) - const layoutIter = (root: Node, cx: number, cy: number): number => { type Frame = { node: Node @@ -181,9 +177,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { h?: number right?: Node[] rightIndex?: number - py?: number + rightX?: number rightBottom?: number - childKey?: number childPs?: Size lastChildBottom?: number @@ -225,11 +220,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { f.right = right f.gParams = gParams - let total = 0 - for (const p of right) total += size(p).h - total += V * Math.max(0, right.length - 1) - - f.py = f.cy - total / 2 + f.rightX = f.cx + f.w! / 2 + H f.rightBottom = f.cy + h / 2 f.rightIndex = 0 f.phase = 1 @@ -240,22 +231,13 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { if (f.rightIndex! < f.right!.length) { const p = f.right![f.rightIndex!] const ps = size(p) - const px = f.cx + f.w! / 2 + H + ps.w / 2 - let pcy = f.py! + ps.h / 2 - - const key = colKey(px) - const occ = columnBottom.get(key) ?? Number.NEGATIVE_INFINITY - const minTop = occ + V - const desiredTop = pcy - ps.h / 2 - - if (desiredTop < minTop) { - pcy = minTop + ps.h / 2 - f.py = pcy - ps.h / 2 - } + const pcx = f.rightX! + ps.w / 2 + const pcy = f.cy - f.childKey = key + f.rightX = f.rightX! + ps.w + H + f.rightBottom = Math.max(f.rightBottom!, pcy + ps.h / 2) f.childPs = ps - stack.push({node: p, cx: px, cy: pcy, phase: 0}) + stack.push({node: p, cx: pcx, cy: pcy, phase: 0}) f.phase = 10 } else { f.bottom = Math.max(f.cy + f.h! / 2, f.rightBottom!) @@ -266,12 +248,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { case 10: { const subBottom = f.lastChildBottom! - columnBottom.set( - f.childKey!, - Math.max(columnBottom.get(f.childKey!) ?? Number.NEGATIVE_INFINITY, subBottom) - ) f.rightBottom = Math.max(f.rightBottom!, subBottom) - f.py = Math.max(f.py! + f.childPs!.h + V, subBottom + V) f.rightIndex!++ f.phase = 1 break @@ -386,7 +363,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { // Root-Nodes stapeln let yCursor = 0 for (const r of nodes) { - if (!(r.data as any)?.parentNodeId && !r.parentId) { + const link = (r.data as any)?.parentNodeId ?? (r.type === "group" ? (r.data as any)?.nodeId : undefined) + if (!link && !r.parentId) { const b = layoutIter(r, 0, yCursor + size(r).h / 2) yCursor = b + V } diff --git a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts index deac3544..b11c94f6 100644 --- a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts @@ -1,6 +1,6 @@ import {Edge} from "@xyflow/react"; import React from "react"; -import type {Flow, Namespace, NamespaceProject, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import type {Flow, Namespace, NamespaceProject, NodeFunction, SubFlowValue} from "@code0-tech/sagittarius-graphql-types"; import {hashToColor, useService, useStore} from "@code0-tech/pictor"; import {FlowService} from "@edition/flow/services/Flow.service"; import {FunctionService} from "@edition/function/services/Function.service"; @@ -95,19 +95,31 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj const parameterDefinition = functionService.getById(node.functionDefinition?.id!!)?.parameterDefinitions?.nodes?.[index]; if (!parameterValue) return - if (parameterValue && parameterValue.__typename === "SubFlowValue") { - - if (parameterValue.functionDefinition?.id) { + const subFlowValues: { subFlow: SubFlowValue, key: string }[] = + parameterValue.__typename === "SubFlowValue" + ? [{subFlow: parameterValue, key: `${param.id}`}] + : parameterValue.__typename === "LiteralValue" + ? (parameterValue.references ?? []) + .filter(reference => reference?.value?.__typename === "SubFlowValue") + .map((reference, referenceIndex) => ({ + subFlow: reference!.value as SubFlowValue, + key: `${param.id}-${reference?.signature ?? referenceIndex}` + })) + : [] + + subFlowValues.forEach(({subFlow, key}) => { + + if (!subFlow.startingNodeId && subFlow.functionDefinition?.id) { edges.push({ - id: `${node.id}-${param.id}-next`, - source: `${node.id}-${param.id}`, + id: `${node.id}-${key}-next`, + source: `${node.id}-${key}`, target: node.id!, targetHandle: `param`, deletable: false, selectable: false, animated: true, data: { - color: hashToColor(parameterValue?.startingNodeId || parameterValue.functionDefinition?.id || ""), + color: hashToColor(subFlow?.startingNodeId || subFlow.functionDefinition?.id || ""), type: 'parameter', flowId: flowId } @@ -126,7 +138,7 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj animated: true, label: parameterDefinition?.names!![0]?.content ?? FALLBACK_FUNCTION_PARAMETER_NAME, data: { - color: hashToColor(parameterValue?.startingNodeId || parameterValue.functionDefinition?.id || ""), + color: hashToColor(subFlow?.startingNodeId || subFlow.functionDefinition?.id || ""), type: 'group', flowId: flowId, parentNodeId: parentNode?.id @@ -135,16 +147,14 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj (groupsWithValue.get(node.id!) ?? (groupsWithValue.set(node.id!, []), groupsWithValue.get(node.id!)!)).push(groupId); - if (parameterValue.startingNodeId) { + if (subFlow.startingNodeId) { traverse( - flowService.getNodeById(flowId, parameterValue.startingNodeId)!, + flowService.getNodeById(flowId, subFlow.startingNodeId)!, node, true ); } - - - } + }) }); if (node.nextNodeId) { diff --git a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts index e9ac0b5e..3cc86585 100644 --- a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts @@ -1,5 +1,5 @@ import {Node} from "@xyflow/react"; -import type {Flow, Namespace, NamespaceProject, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import type {Flow, Namespace, NamespaceProject, NodeFunction, SubFlowValue} from "@code0-tech/sagittarius-graphql-types"; import React from "react"; import {hashToColor, useService, useStore} from "@code0-tech/pictor"; import {FlowService} from "@edition/flow/services/Flow.service"; @@ -80,54 +80,69 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], node.parameters?.nodes?.forEach((param) => { const value = param?.value - if (!value || value.__typename !== "SubFlowValue") return - - if (value.functionDefinition?.id) { - nodes.push({ - id: `${nodeId}-${param.id}`, - type: functionDefinition && "design" in functionDefinition ? functionDefinition?.design as string : "square", - position: {x: 0, y: 0}, - draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, - data: { - isParameter: true, - parameterId: param?.id, - parentNodeId: nodeId, - index: globalIndex, - functionId: value.functionDefinition?.id, - flowId: flowId, - color: hashToColor(value?.startingNodeId ?? value?.functionDefinition?.id ?? ""), - schema: [] - }, - }) - return - } - - const groupId = `${nodeId}-group-${groupCounter++}` - - if (!visited.has(groupId)) { - visited.add(groupId) - - nodes.push({ - id: groupId, - type: "group", - position: {x: 0, y: 0}, - draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, - data: { - isParameter: true, - nodeId: nodeId, - flowId: flowId, - color: hashToColor(value?.startingNodeId ?? ""), - schema: [] - }, - }) - } - - const child = flowService.getNodeById(flowId, value.startingNodeId) - if (child) traverse(child, groupId) + if (!value) return + + const subFlowValues: { subFlow: SubFlowValue, key: string }[] = + value.__typename === "SubFlowValue" + ? [{subFlow: value, key: `${param?.id}`}] + : value.__typename === "LiteralValue" + ? (value.references ?? []) + .filter(reference => reference?.value?.__typename === "SubFlowValue") + .map((reference, index) => ({ + subFlow: reference!.value as SubFlowValue, + key: `${param?.id}-${reference?.signature ?? index}` + })) + : [] + + subFlowValues.forEach(({subFlow, key}) => { + if (!subFlow.startingNodeId && subFlow.functionDefinition?.id) { + nodes.push({ + id: `${nodeId}-${key}`, + type: functionDefinition && "design" in functionDefinition ? functionDefinition?.design as string : "square", + position: {x: 0, y: 0}, + draggable: false, + parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, + data: { + isParameter: true, + parameterId: param?.id, + parentNodeId: nodeId, + index: globalIndex, + functionId: subFlow.functionDefinition?.id, + flowId: flowId, + color: hashToColor(subFlow?.startingNodeId ?? subFlow?.functionDefinition?.id ?? ""), + schema: [] + }, + }) + return + } + + const groupId = `${nodeId}-group-${groupCounter++}` + + if (!visited.has(groupId)) { + visited.add(groupId) + + nodes.push({ + id: groupId, + type: "group", + position: {x: 0, y: 0}, + draggable: false, + selectable: false, + parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, + data: { + isParameter: true, + nodeId: nodeId, + flowId: flowId, + color: hashToColor(subFlow?.startingNodeId ?? ""), + schema: [] + }, + }) + } + + const child = flowService.getNodeById(flowId, subFlow.startingNodeId) + if (child) traverse(child, groupId) + }) }) if (node.nextNodeId) { From dde2f2885f411c05666f729f96e56379574c265d Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 09:58:16 +0200 Subject: [PATCH 07/17] feat: enhance sub-flow value handling in Flow.nodes.hook.ts to include signature support --- src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts index 3cc86585..6253a56c 100644 --- a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts @@ -82,7 +82,7 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], const value = param?.value if (!value) return - const subFlowValues: { subFlow: SubFlowValue, key: string }[] = + const subFlowValues: { subFlow: SubFlowValue, key: string, signature?: string }[] = value.__typename === "SubFlowValue" ? [{subFlow: value, key: `${param?.id}`}] : value.__typename === "LiteralValue" @@ -90,11 +90,12 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], .filter(reference => reference?.value?.__typename === "SubFlowValue") .map((reference, index) => ({ subFlow: reference!.value as SubFlowValue, - key: `${param?.id}-${reference?.signature ?? index}` + key: `${param?.id}-${reference?.signature ?? index}`, + signature: reference?.signature ?? undefined })) : [] - subFlowValues.forEach(({subFlow, key}) => { + subFlowValues.forEach(({subFlow, key, signature}) => { if (!subFlow.startingNodeId && subFlow.functionDefinition?.id) { nodes.push({ id: `${nodeId}-${key}`, @@ -106,6 +107,7 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], data: { isParameter: true, parameterId: param?.id, + referenceSignature: signature, parentNodeId: nodeId, index: globalIndex, functionId: subFlow.functionDefinition?.id, From 59b8d17d8ae18dc1c5d79cfc7b700791a9423e16 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 09:58:58 +0200 Subject: [PATCH 08/17] feat: enhance parameter node removal logic to support sub-flow references and improve flow integrity --- .../ce/src/flow/services/Flow.service.ts | 139 ++++++++++++++---- 1 file changed, 114 insertions(+), 25 deletions(-) diff --git a/src/packages/ce/src/flow/services/Flow.service.ts b/src/packages/ce/src/flow/services/Flow.service.ts index 3eeded25..bdb9bea8 100644 --- a/src/packages/ce/src/flow/services/Flow.service.ts +++ b/src/packages/ce/src/flow/services/Flow.service.ts @@ -5,6 +5,7 @@ import { FlowSetting, FlowType, FunctionDefinition, + InlineReferenceValue, LiteralValue, Maybe, Mutation, @@ -107,26 +108,54 @@ export class FlowService extends ReactiveArrayService value.id === id); } - protected removeParameterNode(flow: FlowView, node: NodeParameter): void { - if (node?.value?.__typename === "SubFlowValue") { - const parameterNode = flow?.nodes?.nodes?.find(n => n?.id === (node.value as SubFlowValue)?.startingNodeId) - if (parameterNode) { - flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== (node.value as SubFlowValue)?.startingNodeId) - let nextNodeId = parameterNode.nextNodeId - while (nextNodeId) { - const nextNode = flow!.nodes!.nodes!.find(n => n?.id === nextNodeId) - if (nextNode) { - flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== nextNodeId) - nextNodeId = nextNode.nextNodeId - } else { - nextNodeId = null - } + protected removeParameterNode(flow: FlowView, node: NodeParameter, keep?: Set): void { + const value = node?.value + if (value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, value, keep) + } else if (value?.__typename === "LiteralValue") { + (value.references ?? []).forEach(reference => { + if (reference?.value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, reference.value as SubFlowValue, keep) } - parameterNode.parameters?.nodes?.forEach(p => { - this.removeParameterNode(flow, p!!) - }) + }) + } + } + + private removeSubFlowNodes(flow: FlowView, subFlow: SubFlowValue, keep?: Set): void { + if (subFlow?.startingNodeId && keep?.has(subFlow.startingNodeId)) return + + const parameterNode = flow?.nodes?.nodes?.find(n => n?.id === subFlow?.startingNodeId) + if (!parameterNode) return + + flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== subFlow?.startingNodeId) + let nextNodeId = parameterNode.nextNodeId + while (nextNodeId) { + const nextNode = flow!.nodes!.nodes!.find(n => n?.id === nextNodeId) + if (nextNode) { + flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== nextNodeId) + nextNodeId = nextNode.nextNodeId + } else { + nextNodeId = null } } + parameterNode.parameters?.nodes?.forEach(p => { + this.removeParameterNode(flow, p!!, keep) + }) + } + + private collectStartingNodeIds(value?: Maybe): Set { + const ids = new Set() + if (!value) return ids + if (value.__typename === "SubFlowValue" && value.startingNodeId) { + ids.add(value.startingNodeId) + } else if (value.__typename === "LiteralValue") { + (value.references ?? []).forEach(reference => { + if (reference?.value?.__typename === "SubFlowValue" && (reference.value as SubFlowValue).startingNodeId) { + ids.add((reference.value as SubFlowValue).startingNodeId!) + } + }) + } + return ids } getNodeById(flowId: FlowView['id'], nodeId: NodeFunction['id']): NodeFunction | undefined { @@ -232,11 +261,40 @@ export class FlowService extends ReactiveArrayService { const flow = this.getById(flowId) const node = this.getNodeById(flowId, nodeId) - const parentNode = flow?.nodes?.nodes?.find(node => node?.parameters?.nodes?.find(p => p?.value?.__typename === "SubFlowValue" && (p.value as SubFlowValue)?.startingNodeId === nodeId)) const previousNodes = flow?.nodes?.nodes?.find(n => n?.nextNodeId === nodeId) const index = this.values().findIndex(f => f.id === flowId) if (!flow || !node) return + let parentNode: Maybe | undefined + let parentParameter: Maybe | undefined + let parentSubFlow: SubFlowValue | undefined + let parentLiteral: LiteralValue | undefined + let parentReference: InlineReferenceValue | undefined + + for (const candidate of flow.nodes?.nodes ?? []) { + for (const parameter of candidate?.parameters?.nodes ?? []) { + const value = parameter?.value + if (value?.__typename === "SubFlowValue" && value.startingNodeId === nodeId) { + parentNode = candidate + parentParameter = parameter + parentSubFlow = value + break + } + if (value?.__typename === "LiteralValue") { + const reference = value.references?.find(r => r?.value?.__typename === "SubFlowValue" && (r.value as SubFlowValue).startingNodeId === nodeId) + if (reference) { + parentNode = candidate + parentParameter = parameter + parentLiteral = value + parentReference = reference + parentSubFlow = reference.value as SubFlowValue + break + } + } + } + if (parentNode) break + } + flow.nodes!.nodes = flow.nodes!.nodes!.filter(n => n?.id !== nodeId) node.parameters?.nodes?.forEach(p => this.removeParameterNode(flow, p!!)) @@ -247,13 +305,44 @@ export class FlowService extends ReactiveArrayService p?.value?.__typename === "SubFlowValue" && (p.value as SubFlowValue)?.startingNodeId === nodeId) - if (parameter && parameter.value?.__typename === "SubFlowValue" && node.nextNodeId) { - parameter.value.startingNodeId = node.nextNodeId - } else if (parameter) { - parameter.value = undefined + if (parentSubFlow) { + if (node.nextNodeId) { + parentSubFlow.startingNodeId = node.nextNodeId + } else if (parentLiteral && parentReference) { + parentLiteral.references = (parentLiteral.references ?? []).filter(r => r !== parentReference) + parentLiteral.value = (parentLiteral.value ?? []).filter((v: unknown) => v !== `\${${parentReference!.signature}}`) + } else if (parentParameter) { + parentParameter.value = undefined + } + } + + flow.editedAt = new Date().toISOString() + + this.set(index, new View(flow)) + await this.syncFlow(flowId) + } + + async removeParameterMapping(flowId: FlowView['id'], parentNodeId: NodeFunction['id'], parameterId: NodeParameter['id'], referenceSignature?: string): Promise { + const flow = this.getById(flowId) + const index = this.values().findIndex(f => f.id === flowId) + if (!flow) return + + const node = flow.nodes?.nodes?.find(n => n?.id === parentNodeId) + const parameter = node?.parameters?.nodes?.find(p => p?.id === parameterId) + if (!parameter) return + + const value = parameter.value + if (value?.__typename === "LiteralValue" && referenceSignature) { + const reference = value.references?.find(r => r?.signature === referenceSignature) + if (reference?.value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, reference.value as SubFlowValue) } + value.references = (value.references ?? []).filter(r => r?.signature !== referenceSignature) + value.value = (value.value ?? []).filter((v: unknown) => v !== `\${${referenceSignature}}`) + if ((value.references?.length ?? 0) === 0) parameter.value = undefined + } else if (value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, value) + parameter.value = undefined } flow.editedAt = new Date().toISOString() @@ -407,7 +496,7 @@ export class FlowService extends ReactiveArrayService Date: Mon, 24 Aug 2026 09:59:05 +0200 Subject: [PATCH 09/17] feat: adjust vertical positioning in FlowBuilderComponent for improved layout consistency --- .../ce/src/flow/components/builder/FlowBuilderComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx index 61a57bf7..04049808 100644 --- a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx +++ b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx @@ -267,7 +267,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { f.gSizes = gSizes f.gx = f.cx - rowW / 2 - f.gy = f.bottom! + V + f.gy = f.bottom! + 2 * V f.rowBottom = f.bottom f.gIndex = 0 f.phase = 3 From 218ff97136a93f008d9d5132b28aeb71f04c4ff1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 09:59:30 +0200 Subject: [PATCH 10/17] feat: enhance node deletion logic in FlowPanelControlComponent to support parameter mapping removal --- .../components/panels/FlowPanelControlComponent.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx index a3fc0931..ac3c38ac 100644 --- a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx +++ b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx @@ -35,6 +35,7 @@ import {AIChatComponent} from "@edition/ai/components/AIChatComponent"; import {mapAiGenerationFlowToFlowInput} from "@edition/ai/util/AI.flow.mapper"; import {toast} from "@code0-tech/pictor/dist/components/toast/Toast"; import {useFlowCompareStore} from "@edition/flow/hooks/Flow.compare.hook"; +import {FunctionNodeComponentProps} from "@edition/function/components/nodes/FunctionNodeComponent"; import {FlowView} from "@edition/flow/services/Flow.view"; import {FlowExecuteDialogComponent} from "@edition/flow/components/FlowExecuteDialogComponent"; @@ -67,11 +68,16 @@ export const FlowPanelControlComponent: React.FC //callbacks const deleteActiveNode = React.useCallback(() => { if (!selectedNode) return + const data = selectedNode.data as FunctionNodeComponentProps // @ts-ignore startTransition(async () => { - await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id']) + if (data?.isParameter && data?.parentNodeId && data?.parameterId) { + await flowService.removeParameterMapping(flowId, data.parentNodeId, data.parameterId, data.referenceSignature) + } else { + await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id']) + } }) - }, [selectedNode, flowService, flowStore]) + }, [selectedNode, flowService, flowStore, flowId]) const onAIData = React.useCallback((payload: AiGenerateFlowSubscriptionPayload) => { const aiFlow = payload?.flow From 44e77e33f873501e2eddd2606d95b34de4400fe6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 09:59:51 +0200 Subject: [PATCH 11/17] feat: extend FunctionNodeComponent to include parameterId and referenceSignature for enhanced node functionality --- .../ce/src/function/components/nodes/FunctionNodeComponent.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts b/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts index c3dfc95e..db891504 100644 --- a/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts +++ b/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts @@ -1,4 +1,4 @@ -import {Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import {Flow, FunctionDefinition, NodeFunction, NodeParameter} from "@code0-tech/sagittarius-graphql-types"; import {Component} from "@code0-tech/pictor"; import {NodeSchema} from "@code0-tech/triangulum"; @@ -10,6 +10,8 @@ export interface FunctionNodeComponentProps extends Record, Com compareType?: 'added' | 'removed' | 'changed' color: string parentNodeId?: NodeFunction['id'] + parameterId?: NodeParameter['id'] + referenceSignature?: string isParameter?: boolean index?: number } \ No newline at end of file From 88986feb92233e5337de467b95fecd4955fafa16 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:00:01 +0200 Subject: [PATCH 12/17] feat: improve text positioning in FunctionNodeSquareComponent for better visibility --- .../components/nodes/FunctionNodeSquareComponent.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx b/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx index f325f99a..89df41c2 100644 --- a/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx +++ b/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx @@ -2,7 +2,7 @@ import {Handle, Node, NodeProps, Position, useStore} from "@xyflow/react"; import React, {CSSProperties, memo} from "react"; import "./FunctionNodeComponent.style.scss"; import {FunctionNodeComponentProps} from "./FunctionNodeComponent"; -import {Card, Flex, Text, useService, useStore as usePictorStore} from "@code0-tech/pictor"; +import {Card, Flex, getSize, Text, useService, useStore as usePictorStore} from "@code0-tech/pictor"; import {useFlowValidation} from "@edition/flow/hooks/Flow.validation.hook"; import {IconVariable} from "@tabler/icons-react"; import {FlowService} from "@edition/flow/services/Flow.service"; @@ -135,7 +135,14 @@ export const FunctionNodeSquareComponent: React.FC - {definition?.names?.[0]?.content ?? FALLBACK_FUNCTION_NAME} + {definition?.names?.[0]?.content ?? FALLBACK_FUNCTION_NAME} ); }) From a440b48df265880931cf4eeea59effcfe2514123 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:13:34 +0200 Subject: [PATCH 13/17] feat: update flowIndex parsing in DataTypeSubFlowInputComponent for improved robustness --- .../inputs/sub-flow/DataTypeSubFlowInputComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx index 39819ba7..999eebf6 100644 --- a/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx @@ -21,7 +21,7 @@ export const DataTypeSubFlowInputComponent: React.FC suggestions?.findIndex(suggest => { From c388e64ec6cf60de87a83ff4e01711a635bb4cdc Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:30:46 +0200 Subject: [PATCH 14/17] feat: update parameter handling in Flow.nodes.hook.ts to use parameterIndex for improved clarity --- src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts index 6253a56c..7e6e6f88 100644 --- a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts @@ -78,7 +78,7 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], }) } - node.parameters?.nodes?.forEach((param) => { + node.parameters?.nodes?.forEach((param, parameterIndex) => { const value = param?.value if (!value) return @@ -106,7 +106,7 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], extent: parentGroup ? "parent" : undefined, data: { isParameter: true, - parameterId: param?.id, + parameterIndex: parameterIndex, referenceSignature: signature, parentNodeId: nodeId, index: globalIndex, From fc0f49dbfa05f3da07969d8673a0b039315662f1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:30:54 +0200 Subject: [PATCH 15/17] feat: update removeParameterMapping to use parameterIndex for improved parameter access --- src/packages/ce/src/flow/services/Flow.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/flow/services/Flow.service.ts b/src/packages/ce/src/flow/services/Flow.service.ts index bdb9bea8..6413a27d 100644 --- a/src/packages/ce/src/flow/services/Flow.service.ts +++ b/src/packages/ce/src/flow/services/Flow.service.ts @@ -322,13 +322,13 @@ export class FlowService extends ReactiveArrayService { + async removeParameterMapping(flowId: FlowView['id'], parentNodeId: NodeFunction['id'], parameterIndex: number, referenceSignature?: string): Promise { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) if (!flow) return const node = flow.nodes?.nodes?.find(n => n?.id === parentNodeId) - const parameter = node?.parameters?.nodes?.find(p => p?.id === parameterId) + const parameter = node?.parameters?.nodes?.[parameterIndex] if (!parameter) return const value = parameter.value From 8f8223921c86840b7ebab4a4aadc94bb7557deb5 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:31:08 +0200 Subject: [PATCH 16/17] feat: update FlowPanelControlComponent to use parameterIndex for improved parameter mapping --- .../src/flow/components/panels/FlowPanelControlComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx index ac3c38ac..bc592407 100644 --- a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx +++ b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx @@ -71,8 +71,8 @@ export const FlowPanelControlComponent: React.FC const data = selectedNode.data as FunctionNodeComponentProps // @ts-ignore startTransition(async () => { - if (data?.isParameter && data?.parentNodeId && data?.parameterId) { - await flowService.removeParameterMapping(flowId, data.parentNodeId, data.parameterId, data.referenceSignature) + if (data?.isParameter && data?.parentNodeId && data?.parameterIndex != null) { + await flowService.removeParameterMapping(flowId, data.parentNodeId, data.parameterIndex, data.referenceSignature) } else { await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id']) } From 2ba82aa55b4d968830b801ad08f6b31c5a17ab33 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 24 Aug 2026 10:31:14 +0200 Subject: [PATCH 17/17] feat: update FunctionNodeComponent to use parameterIndex for improved parameter handling --- .../ce/src/function/components/nodes/FunctionNodeComponent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts b/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts index db891504..9c865f14 100644 --- a/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts +++ b/src/packages/ce/src/function/components/nodes/FunctionNodeComponent.ts @@ -1,4 +1,4 @@ -import {Flow, FunctionDefinition, NodeFunction, NodeParameter} from "@code0-tech/sagittarius-graphql-types"; +import {Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; import {Component} from "@code0-tech/pictor"; import {NodeSchema} from "@code0-tech/triangulum"; @@ -10,7 +10,7 @@ export interface FunctionNodeComponentProps extends Record, Com compareType?: 'added' | 'removed' | 'changed' color: string parentNodeId?: NodeFunction['id'] - parameterId?: NodeParameter['id'] + parameterIndex?: number referenceSignature?: string isParameter?: boolean index?: number