wiki/c-work/4-done/2026-04-generic-graph-editor.md
ValueOn AG 93edc94da3 upd
2026-04-10 22:44:27 +02:00

31 KiB
Raw Blame History

Typed Node Handover System — Spezifikation & Execution Plan

Ansatz: Greenfield. Keine Rückwärtskompatibilität, keine Migration, kein Deprecation.


1 Problemstellung

Der Graphical Editor hat keine formale Typisierung der Daten zwischen Nodes. Jede Node produziert ein freiformiges Dict; die Folge-Node extrahiert heuristisch. Kern-Symptome:

  • actionNodeExecutor.py (~860 Zeilen, >50 % handover-Heuristik pro Node-Paar)
  • outputPreviewRegistry.ts hartcodiert Beispiel-Schemas, die von Laufzeit-Outputs abweichen
  • Keine data.transform, data.filter, data.aggregate, flow.merge implementiert
  • Frontend baut pro Node-Typ eine eigene Config-Component (12+ Dateien)
  • Keine System-Variablen (Timestamp, Datum, User, etc.)

Die Method/Action-Schicht hat bereits WorkflowActionParameter mit FrontendType, _validateType, _validateParameters. Dieses System wird für den Graphen übernommen und erweitert.


2 Architektur

2.1 Port-Schema (Kern-Datenmodell)

Jede Node deklariert typisierte Ports für Ein- und Ausgänge sowie Parameter für die Konfiguration:

┌──────────────────────────────────────────────────────────────────────┐
│  Node Definition  (Beispiel: email.draftEmail)                       │
│                                                                      │
│  inputPorts:                                                         │
│    [0]: { accepts: [EmailDraft, AiResult] }                          │
│                                                                      │
│  outputPorts:                                                        │
│    [0]: { schema: ActionResult }                                     │
│                                                                      │
│  parameters:                                                         │
│    connectionId: { type: str, frontendType: USER_CONNECTION }        │
│    subject:      { type: str, frontendType: TEXT }                   │
│    body:         { type: str, frontendType: TEXTAREA }               │
│    to:           { type: List[str], frontendType: TEXT }             │
└──────────────────────────────────────────────────────────────────────┘

2.2 Port-Schema Pydantic-Modelle

Neue Datei: gateway/modules/features/graphicalEditor/portTypes.py

class PortField(BaseModel):
    name: str
    type: str                    # str, int, bool, List[str], List[Document], Dict[str,Any]
    description: Dict[str, str]  # {en, de, fr}
    required: bool = True

class PortSchema(BaseModel):
    name: str                    # z.B. "EmailDraft", "AiResult", "Transit"
    fields: List[PortField]

class InputPortDef(BaseModel):
    accepts: List[str]           # Liste akzeptierter Schema-Namen

class OutputPortDef(BaseModel):
    schema: str                  # Schema-Name aus dem Katalog
    dynamic: bool = False        # True = Schema wird aus Parametern abgeleitet
    deriveFrom: Optional[str] = None  # Parameter-Name für dynamische Ableitung

2.3 Port-Typen-Katalog

Port-Typ Felder Produziert von Konsumiert von
DocumentList documents: List[{name, data, mimeType, metadata}] sharepoint.readFile, sharepoint.downloadFile, ai.*, file.create, input.upload ai.*, file.create, email.draftEmail, sharepoint.uploadFile
FileList files: List[{url, name, path, size}] sharepoint.listFiles, sharepoint.findFile sharepoint.readFile, sharepoint.downloadFile, flow.loop
EmailDraft subject: str, body: str, to: List[str], cc?: List[str], attachments?: List[Document] ai.prompt (outputFormat=emailDraft) email.draftEmail
EmailList emails: List[{subject, from, to, body, date, attachments}] email.checkEmail, email.searchEmail ai.prompt, flow.loop
TaskList tasks: List[{id, name, status, url}] clickup.searchTasks, clickup.listTasks flow.loop, clickup.updateTask
TaskResult success: bool, taskId: str, task: {id, name, status} clickup.createTask, clickup.updateTask flow.ifElse
FormPayload payload: Dict[str, Any] (dynamisch) trigger.form, input.form Alle (via Feld-Referenz)
AiResult prompt: str, response: str, responseData?: Dict, context: str, documents: List[Document] ai.* email.draftEmail, file.create, sharepoint.uploadFile
BoolResult result: bool, reason?: str input.approval, input.confirmation flow.ifElse
TextResult text: str input.comment ai.*, file.create
LoopItem currentItem: Any, currentIndex: int, items: List[Any], count: int flow.loop (pro Iteration) Loop-Body Nodes
AggregateResult items: List[Any], count: int data.aggregate Alle
MergeResult inputs: Dict[int, Any], first: Any, merged: Dict flow.merge Alle

Jeder Port-Typ bekommt implizit zwei Meta-Felder: _success: bool und _error: str?. Wenn ein Node fehlschlägt, liefert der Normalizer { _success: false, _error: "...", ...restliche Felder leer/default }.

2.4 Transit-Typ

Flow-Control-Nodes transformieren keine Daten — sie routen. Transit bedeutet: Output-Schema = Input-Schema des Upstream-Produzenten.

Transit-Nodes produzieren ein Envelope:

{
    "_transit": True,
    "_meta": { "branch": 0, "conditionResult": True },  # Routing-Metadaten
    "data": <upstream-output-unverändert>,
}
Node Output-Typ _meta-Felder
flow.ifElse Transit branch: int, conditionResult: bool
flow.switch Transit match: int, value: str
data.filter Transit originalCount: int, filteredCount: int

Transit-Auflösung (rekursiv): DataPicker und Engine folgen _transit-Kette bis zum echten Produzenten. Multi-Output-Ports (ifElse hat 2): Transit-Auflösung berücksichtigt sourceOutput-Index, beide Ports zeigen dasselbe Upstream-Schema.

flow.loop ist kein Transit — er transformiert eine Liste in LoopItem pro Iteration.

2.5 Dynamische Schemas

Nodes mit konfigurationsabhängigem Output (Formulare, data.transform):

{
    "id": "input.form",
    "outputPorts": {
        0: {"schema": "FormPayload", "dynamic": True, "deriveFrom": "fields"},
    },
}

Schema-Ableitung: die Engine und das Frontend leiten aus dem Parameter fields die konkreten Output-Felder ab. Jedes Formular-Feld erscheint als wählbares Attribut im DataPicker.


3 Handover-Mechanismen

3.1 Prioritätsreihenfolge

Wenn ein Parameter-Wert aus mehreren Quellen kommen kann:

DataRef/SystemVar/Static > Wire-Handover > Default

  1. Wire-Handover: Extraktor füllt Input-Felder aus dem Upstream-Output.
  2. DataRef/System/Static: überschreiben Wire-Werte (= explizite User-Wahl gewinnt).
  3. Defaults aus Parameter-Definition, falls noch leer.

3.2 Mechanismus 1: DataRef (Hauptmechanismus, ~80 %)

User wählt im DataPicker, welches Feld eines Vorgängers in welchen Parameter fließt:

email.draftEmail.parameters:
   subject = DataRef { nodeId: "ai_1", path: ["responseData", "subject"] }
   body    = DataRef { nodeId: "ai_1", path: ["responseData", "body"] }
   to      = DataRef { nodeId: "form_1", path: ["payload", "recipient"] }

3.3 Mechanismus 2: Wire-Handover (Extraktoren)

Pro Input-Port-Typ ein Extraktor (~12 Funktionen, nicht N×M):

INPUT_EXTRACTORS: Dict[str, Callable] = {
    "EmailDraft": _extractEmailDraft,
    "DocumentList": _extractDocuments,
    "TextResult": _extractText,
    ...
}

Extraktor kennt das Upstream-Schema (aus outputPorts) und greift Felder direkt ab — kein Raten mit Fallbacks.

3.4 Mechanismus 3: data.transform

Expliziter Konverter-Node für Fälle, wo DataRef und Extraktor nicht reichen.

3.5 Mechanismus 4: System-Variablen

Dritter DynamicValue-Typ neben ref und value:

SYSTEM_VARIABLES = {
    "system.timestamp":   { "type": "int",    "description": "Unix timestamp (ms)" },
    "system.date":        { "type": "str",    "description": "ISO date (YYYY-MM-DD)" },
    "system.datetime":    { "type": "str",    "description": "ISO datetime" },
    "system.time":        { "type": "str",    "description": "HH:MM:SS" },
    "system.userId":      { "type": "str",    "description": "Aktueller User-ID" },
    "system.userName":    { "type": "str",    "description": "Aktueller User-Name" },
    "system.userEmail":   { "type": "str",    "description": "Aktueller User-Email" },
    "system.workflowId":  { "type": "str",    "description": "Workflow-ID" },
    "system.runId":       { "type": "str",    "description": "Run-ID" },
    "system.instanceId":  { "type": "str",    "description": "Feature-Instanz-ID" },
    "system.mandateId":   { "type": "str",    "description": "Mandant-ID" },
    "system.loopIndex":   { "type": "int",    "description": "Aktueller Loop-Index (nur in Loop)" },
    "system.loopCount":   { "type": "int",    "description": "Anzahl Loop-Items (nur in Loop)" },
    "system.uuid":        { "type": "str",    "description": "Neue zufällige UUID" },
}

4 Generisches Frontend-Rendering

4.1 Prinzip: Jede UI-Darstellung ist ein FrontendType

Es gibt keine Node-spezifischen Config-Components. NODE_CONFIG_REGISTRY entfällt komplett.

4.2 FrontendType-Katalog

class FrontendType(str, Enum):
    # Standard Types
    TEXT = "text"
    TEXTAREA = "textarea"
    NUMBER = "number"
    CHECKBOX = "checkbox"
    DATE = "date"
    DATETIME = "datetime"
    EMAIL = "email"
    SELECT = "select"
    MULTISELECT = "multiselect"
    JSON = "json"
    FILE = "file"
    HIDDEN = "hidden"

    # Picker Types (API-backed)
    USER_CONNECTION = "userConnection"
    SHAREPOINT_FOLDER = "sharepointFolder"
    SHAREPOINT_FILE = "sharepointFile"
    CLICKUP_LIST = "clickupList"
    CLICKUP_TASK = "clickupTask"

    # Complex Structure Types
    CASE_LIST = "caseList"
    FIELD_BUILDER = "fieldBuilder"
    KEY_VALUE_ROWS = "keyValueRows"
    CRON = "cron"
    CONDITION = "condition"
    MAPPING_TABLE = "mappingTable"
    FILTER_EXPRESSION = "filterExpression"

4.3 Parameter-Abhängigkeiten

Picker-Parameter die von anderen Parametern desselben Nodes abhängen:

{
    "name": "path",
    "type": "str",
    "frontendType": "sharepointFolder",
    "frontendOptions": {"dependsOn": "connectionId"},
}

Der generische Renderer übergibt den Wert des abhängigen Parameters. Der Picker rendert sich disabled, solange die Abhängigkeit nicht erfüllt ist.

4.4 Generischer Renderer

const FRONTEND_TYPE_RENDERERS: Record<string, ComponentType<FieldRendererProps>> = {
    text:              TextInput,
    textarea:          TextareaInput,
    number:            NumberInput,
    checkbox:          CheckboxInput,
    date:              DatePicker,
    select:            SelectInput,
    multiselect:       MultiSelectInput,
    json:              JsonEditor,
    userConnection:    ConnectionPicker,
    sharepointFolder:  FolderPicker,
    clickupList:       ClickUpListPicker,
    caseList:          CaseListEditor,
    fieldBuilder:      FieldBuilderEditor,
    keyValueRows:      KeyValueRowsEditor,
    cron:              CronBuilder,
    condition:         ConditionBuilder,
    mappingTable:      MappingTableEditor,
    filterExpression:  FilterExpressionEditor,
};

function GenericNodeConfig({ node, nodeType }) {
    return nodeType.parameters.map(param => {
        const Renderer = FRONTEND_TYPE_RENDERERS[param.frontendType] ?? TextInput;
        return <Renderer key={param.name} param={param} value={...} />;
    });
}

5 Neue Nodes

5.1 data.aggregate

{
    "id": "data.aggregate",
    "category": "data",
    "label": {"en": "Aggregate", "de": "Sammeln", "fr": "Agréger"},
    "parameters": [
        {"name": "mode", "type": "str", "frontendType": "select",
         "options": ["collect", "concat", "sum", "count"], "default": "collect"},
    ],
    "inputPorts": {0: {"accepts": ["Transit"]}},
    "outputPorts": {0: {"schema": "AggregateResult"}},
    "executor": "data",
}

Engine-Semantik: Wenn data.aggregate im Loop-Body steht, wird ihr Output nicht pro Iteration überschrieben, sondern in _aggregateAccumulators[nodeId] gesammelt. Nach Loop-Ende: nodeOutputs[nodeId] = { items: akkumulator, count: len }.

5.2 data.transform

{
    "id": "data.transform",
    "category": "data",
    "label": {"en": "Transform", "de": "Umwandeln", "fr": "Transformer"},
    "parameters": [
        {"name": "mappings", "type": "json", "frontendType": "mappingTable"},
    ],
    "inputPorts": {0: {"accepts": ["Transit"]}},
    "outputPorts": {0: {"schema": "Dict", "dynamic": True, "deriveFrom": "mappings"}},
    "executor": "data",
}

5.3 data.filter

{
    "id": "data.filter",
    "category": "data",
    "label": {"en": "Filter", "de": "Filtern", "fr": "Filtrer"},
    "parameters": [
        {"name": "condition", "type": "str", "frontendType": "filterExpression"},
    ],
    "inputPorts": {0: {"accepts": ["AggregateResult", "FileList", "TaskList", "EmailList", "DocumentList"]}},
    "outputPorts": {0: {"schema": "Transit"}},
    "executor": "data",
}

Filter-Expression-Sprache: Vergleiche (==, !=, <, >, <=, >=), Logik (and, or, not), String (contains, startsWith), Null (isEmpty, isNotEmpty). Der FILTER_EXPRESSION-Renderer baut einen visuellen Condition-Builder (Dropdown Feld → Operator → Wert).

5.4 flow.merge

{
    "id": "flow.merge",
    "category": "flow",
    "label": {"en": "Merge", "de": "Zusammenführen", "fr": "Fusionner"},
    "parameters": [
        {"name": "mode", "type": "str", "frontendType": "select",
         "options": ["first", "all", "append"], "default": "first"},
    ],
    "inputs": 2,
    "outputs": 1,
    "inputPorts": {0: {"accepts": ["Transit"]}, 1: {"accepts": ["Transit"]}},
    "outputPorts": {0: {"schema": "MergeResult"}},
    "executor": "flow",
    "meta": {"icon": "mdi-call-merge", "color": "#FF9800"},
}

Engine-Semantik: flow.merge wird erst ausgeführt, wenn alle verbundenen Vorgänger verarbeitet sind. Für inaktive Branches (nach ifElse): der übersprungene Vorgänger ist nicht in nodeOutputs → wird als „nicht verfügbar" markiert. mode=first nimmt den ersten verfügbaren, mode=all merged nur die verfügbaren.


6 Entscheidungen

# Frage Entscheidung
1 Typ-Mismatch bei Kante? Soft — Warnung + visuelle Markierung, kein Hard-Block.
2 Transit statt Any für Flow-Nodes? Ja. Transit-Envelope mit _meta und data. DataPicker resolved rekursiv.
3 Dynamische Schemas? Ja. deriveFrom-Parameter → Schema-Ableitung.
4 Migration? Nein. Greenfield.
5 data.aggregate separat? Ja. Separater Node.
6 flow.merge? Ja, sofort. Komplettes Set.
7 _paramMap Zukunft? Entfällt. Node-Parameter = Action-Parameter-Namen. UI-Labels separat in description.
8 Parameter vs Wire Priorität? DataRef > Wire > Default.
9 Fehler-Output? Jeder Port-Typ hat _success/_error Meta-Felder.
10 AI Structured Output? AiResult.responseData: Dict + Parameter outputFormat (text/json).
11 Pause/Resume? Resume-Handler validiert User-Eingabe gegen Output-Schema der pausierten Node.

7 Execution Plan — Backend

7.1 NEU: gateway/modules/features/graphicalEditor/portTypes.py

Erstellen. Inhalt:

  • PortField, PortSchema, InputPortDef, OutputPortDef (Pydantic-Modelle, Abschnitt 2.2)
  • PORT_TYPE_CATALOG: Dict aller PortSchemas (Abschnitt 2.3)
  • SYSTEM_VARIABLES: Dict (Abschnitt 3.5)
  • Output-Normalizer: _normalizeToSchema(raw: Dict, schemaName: str) -> Dict + pro Port-Typ eine Funktion
  • Input-Extraktoren: INPUT_EXTRACTORS: Dict[str, Callable] (Abschnitt 3.3)
  • Transit-Envelope-Helpers: _wrapTransit(data, meta), _unwrapTransit(output), _resolveTransitChain(nodeId, nodeOutputs, connectionMap)
  • Schema-Ableitungsfunktionen: _deriveFormPayloadSchema(node), _deriveTransformSchema(node) (Abschnitt 2.5)

7.2 ÄNDERN: gateway/modules/shared/frontendTypes.py

Aktuell: FrontendType Enum mit 14 Werten (Zeilen 1663).

Änderung: Erweitern um Complex Structure Types:

Hinzufügen:
    SHAREPOINT_FILE = "sharepointFile"
    CLICKUP_LIST = "clickupList"
    CLICKUP_TASK = "clickupTask"
    CASE_LIST = "caseList"
    FIELD_BUILDER = "fieldBuilder"
    KEY_VALUE_ROWS = "keyValueRows"
    CRON = "cron"
    CONDITION = "condition"
    MAPPING_TABLE = "mappingTable"
    FILTER_EXPRESSION = "filterExpression"

CUSTOM_TYPE_OPTIONS_API und CUSTOM_TYPE_DESCRIPTIONS entsprechend erweitern.

7.3 NEU SCHREIBEN: gateway/modules/features/graphicalEditor/nodeDefinitions/*.py

Alle 10 Dateien (triggers.py, flow.py, input.py, ai.py, email.py, sharepoint.py, clickup.py, file.py, trustee.py, __init__.py) komplett neu.

Jede Node-Definition bekommt:

  • inputPorts: Dict mit InputPortDef pro Port-Index
  • outputPorts: Dict mit OutputPortDef pro Port-Index
  • parameters: Liste von Dicts im WorkflowActionParameter-Format (mit frontendType, frontendOptions inkl. dependsOn)
  • Kein _paramMap mehr — Parameter-Namen = Action-Parameter-Namen
  • _method und _action bleiben (für ActionExecutor-Mapping)

NEU: data.py mit data.aggregate, data.transform, data.filter.

__init__.py: STATIC_NODE_TYPES um DATA_NODES erweitern; flow.py um flow.merge erweitern.

7.4 ÄNDERN: gateway/modules/features/graphicalEditor/nodeRegistry.py

Funktion getNodeTypesForApi (Zeile 5475):

  • _localizeNode anpassen: inputPorts und outputPorts in API-Response einschließen (nicht als _-Prefix, also nicht wegstrippen)
  • Response erweitern um systemVariables aus portTypes.SYSTEM_VARIABLES
  • Response erweitern um portTypeCatalog aus portTypes.PORT_TYPE_CATALOG

Funktion getNodeTypeToMethodAction (Zeile 7889): bleibt, _method/_action weiterhin verwendet.

7.5 ÄNDERN: gateway/modules/workflows/automation2/graphUtils.py

Funktion resolveParameterReferences (Zeile 185243):

  • Neuen Case hinzufügen für type: "system":
if isinstance(value, dict) and value.get("type") == "system":
    return _resolveSystemVariable(value["variable"], context)
  • _resolveSystemVariable(variable, context) als neue Funktion (Abschnitt 3.5).

Funktion validateGraph (Zeile 86120): erweitern um Port-Kompatibilitäts-Check (soft — Warnings sammeln, nicht hart ablehnen).

7.6 NEU SCHREIBEN: gateway/modules/workflows/automation2/executors/actionNodeExecutor.py

Komplett neu. Die ~860 Zeilen mit heuristischen Merge-Funktionen (_extractEmailContentFromUpstream, _getContextFromUpstream, _gatherAttachmentDocumentsFromUpstream, _formatEmailOutputAsContext, _unpackIncomingEmail, _getIncomingEmailFromUpstream, _buildActionParams, _paramMap-Logik, etc.) werden ersetzt durch:

  1. Node-Definition laden (_getNodeDefinition)
  2. Parameter per resolveParameterReferences auflösen (DataRef, SystemVar, Static)
  3. Wire-Handover: wenn inputSources[nodeId][0] existiert, Extraktor aus INPUT_EXTRACTORS aufrufen
  4. Priorität anwenden (DataRef > Wire > Default)
  5. ActionExecutor.executeAction(method, action, params) aufrufen
  6. Ergebnis durch _normalizeToSchema(result, outputSchema) normalisieren
  7. Fehler-Envelope: bei Exception { _success: false, _error: str, ...defaults } zurückgeben

Keine node-type-spezifische Logik mehr im Executor.

7.7 NEU: gateway/modules/workflows/automation2/executors/dataExecutor.py

Neuer Executor für data.aggregate, data.transform, data.filter:

  • data.aggregate: im Loop-Kontext akkumuliert Ergebnisse (Modus: collect, concat, sum, count)
  • data.transform: wendet mappings an (jedes Mapping löst eine DataRef/Static-Referenz auf)
  • data.filter: evaluiert condition pro Item der Input-Liste, gibt gefilterte Liste zurück

7.8 ÄNDERN: gateway/modules/workflows/automation2/executors/flowExecutor.py

Funktion _ifElse (Zeile 5565): Output ändern zu Transit-Envelope:

# Heute:
return {"branch": 0 if ok else 1, "conditionResult": ok, "input": inp}

# Neu:
return {"_transit": True, "_meta": {"branch": 0 if ok else 1, "conditionResult": ok}, "data": inp}

Funktion _switch (Zeile 199207): analog Transit-Envelope.

Funktion _loop (Zeile 261272): bleibt wie bisher (kein Transit, produziert LoopItem).

NEU: _merge Funktion: sammelt Outputs aller verbundenen Input-Ports aus nodeOutputs, wendet Modus an (first/all/append), gibt MergeResult zurück.

7.9 ÄNDERN: gateway/modules/workflows/automation2/executors/__init__.py

DataExecutor importieren und exportieren.

7.10 ÄNDERN: gateway/modules/workflows/automation2/executionEngine.py

Funktion _getExecutor (Zeile 7185): Case für data.* hinzufügen → DataExecutor.

Funktion _is_node_on_active_path (Zeile 3968): Transit-Envelope berücksichtigen — branch/match jetzt in out["_meta"] statt direkt in out.

Loop-Body-Verarbeitung (Zeile 400460): Spezialbehandlung für data.aggregate Nodes: statt nodeOutputs[bnid] = result ein _aggregateAccumulators[bnid].append(result). Nach Loop-Ende: nodeOutputs[aggregateNodeId] = { items: akkumulator, count: len }.

Nach jedem Execute (Zeile 390+): _normalizeToSchema(result, outputSchema) aufrufen bevor in nodeOutputs gespeichert.

flow.merge Wartelogik: merge-Nodes werden von topoSort automatisch nach ihren Vorgängern eingeordnet (BFS). Vor Ausführung: prüfe ob alle verbundenen Vorgänger in nodeOutputs sind oder übersprungen wurden (inaktiver Branch → not in nodeOutputs und Node wurde als skipped geloggt).

Pause/Resume: Resume-Handler (initialNodeOutputs) — Validierung der User-Eingabe gegen Output-Schema der pausierten Node (in executeGraph nach Empfang von initialNodeOutputs).

7.11 ÄNDERN: gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py

Funktion get_node_types (Zeile 146168): Response-Struktur erweitern:

# Heute:
return {"nodeTypes": localized, "categories": categories}

# Neu:
return {
    "nodeTypes": localized,           # mit inputPorts, outputPorts
    "categories": categories,
    "portTypeCatalog": PORT_TYPE_CATALOG,  # alle Schemas
    "systemVariables": SYSTEM_VARIABLES,
}

8 Execution Plan — Frontend

8.1 ÄNDERN: frontend_nyla/src/api/workflowApi.ts

Interface NodeTypeParameter (Zeile 1420): erweitern um frontendType, frontendOptions, options, validation.

Interface NodeType (Zeile 2239): erweitern um inputPorts, outputPorts.

Interface NodeTypesResponse (Zeile 4649): erweitern um portTypeCatalog, systemVariables.

8.2 LÖSCHEN + NEU: frontend_nyla/src/components/FlowEditor/nodes/configs/

Löschen: index.ts, AiNodeConfig.tsx, EmailNodeConfig.tsx, SharePointNodeConfig.tsx, ClickUpNodeConfig.tsx, ApprovalNodeConfig.tsx, UploadNodeConfig.tsx, CommentNodeConfig.tsx, ReviewNodeConfig.tsx, SelectionNodeConfig.tsx, ConfirmationNodeConfig.tsx, FileCreateNodeConfig.tsx, TrusteeNodeConfig.tsx, types.ts.

Neu: frontendTypeRenderers/ Ordner mit einem Renderer pro FrontendType (TextInput, TextareaInput, NumberInput, CheckboxInput, DatePicker, SelectInput, MultiSelectInput, JsonEditor, ConnectionPicker, FolderPicker, ClickUpListPicker, CaseListEditor, FieldBuilderEditor, KeyValueRowsEditor, CronBuilder, ConditionBuilder, MappingTableEditor, FilterExpressionEditor).

Neu: frontendTypeRenderers/index.ts mit FRONTEND_TYPE_RENDERERS Registry.

Bestehende Logik aus den gelöschten Config-Components (z.B. ConnectionPicker aus EmailNodeConfig, CaseEditor aus SwitchNodeConfig, FieldBuilder aus FormNodeConfig) wird in die entsprechenden FrontendType-Renderer extrahiert.

8.3 ÄNDERN: frontend_nyla/src/components/FlowEditor/editor/NodeConfigPanel.tsx

Komplett neu. Statt NODE_CONFIG_REGISTRY-Lookup:

import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';

// Iteriere nodeType.parameters, rendere pro Parameter den passenden Renderer
nodeType.parameters.map(param => {
    const Renderer = FRONTEND_TYPE_RENDERERS[param.frontendType] ?? TextInput;
    return <Renderer param={param} value={...} onChange={...} />;
});

8.4 LÖSCHEN: frontend_nyla/src/components/FlowEditor/nodes/shared/outputPreviewRegistry.ts

Komplett entfernen. Ersetzen durch generische Funktion die aus dem portTypeCatalog + outputPorts Schema den Preview-Baum baut:

function buildPreviewFromSchema(
    node: CanvasNode,
    nodeTypes: NodeType[],
    portTypeCatalog: Record<string, PortSchema>
): Record<string, unknown> {
    const nt = nodeTypes.find(t => t.id === node.type);
    const outputSchema = nt?.outputPorts?.[0]?.schema;
    if (outputSchema === 'Transit') return {}; // resolved dynamisch
    const schema = portTypeCatalog[outputSchema];
    // Generiere Beispielwerte pro Feld aus schema.fields
}

8.5 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/dataRef.ts

DynamicValue Union (Zeile 20): erweitern um SystemVarRef:

interface SystemVarRef {
    type: 'system';
    variable: string;
}

type DynamicValue = DataRef | DataValue | SystemVarRef;

isRef, isValue etc. ergänzen um isSystemVar Type Guard.

8.6 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx

  • buildPickablePaths (Zeile 2040): Schema-basiert statt aus Preview-Daten. Erhält portTypeCatalog und baut Baum aus Schema-Feldern.
  • Transit-Auflösung: wenn Output-Schema = Transit, folge connectionMap rückwärts bis zum echten Produzenten.
  • Neue Sektion „System" mit allen Variablen aus systemVariables, gruppiert (Datum/Zeit, User, Workflow).
  • Bei Pick eines System-Werts: onPick liefert SystemVarRef statt DataRef.

8.7 ÄNDERN: frontend_nyla/src/components/FlowEditor/context/Automation2DataFlowContext.tsx

  • Automation2DataFlowContextValue (Zeile 1019): erweitern um portTypeCatalog, systemVariables.
  • nodeOutputsPreview Berechnung: aus Schema statt aus outputPreviewRegistry.

8.8 ÄNDERN: frontend_nyla/src/components/FlowEditor/nodes/shared/graphUtils.ts

fromApiGraph / toApiGraph: inputPorts und outputPorts mitserialisieren in CanvasNode.

8.9 ÄNDERN: frontend_nyla/src/components/FlowEditor/editor/FlowCanvas (Verbindungs-Validierung)

Beim Verbinden zweier Ports: prüfe sourceNode.outputPorts[outputIdx].schema gegen targetNode.inputPorts[inputIdx].accepts. Bei Mismatch: Kante gelb/orange markieren (Soft-Warnung).


9 Dateien-Übersicht

Neue Dateien

Datei Inhalt
gateway/.../graphicalEditor/portTypes.py PortSchema, Katalog, Normalizer, Extraktoren, System-Variablen
gateway/.../automation2/executors/dataExecutor.py Executor für data.aggregate, data.transform, data.filter
gateway/.../nodeDefinitions/data.py Node-Definitionen: data.aggregate, data.transform, data.filter
frontend_nyla/.../nodes/frontendTypeRenderers/index.ts FRONTEND_TYPE_RENDERERS Registry
frontend_nyla/.../nodes/frontendTypeRenderers/*.tsx Ein Renderer pro FrontendType

Komplett neu geschriebene Dateien

Datei Grund
gateway/.../automation2/executors/actionNodeExecutor.py Heuristische Merge-Logik → Normalizer + Extraktor
gateway/.../nodeDefinitions/triggers.py + inputPorts, outputPorts, frontendType
gateway/.../nodeDefinitions/flow.py + flow.merge, Transit-Ports
gateway/.../nodeDefinitions/input.py + dynamische FormPayload-Schemas
gateway/.../nodeDefinitions/ai.py + AiResult-Ports, outputFormat-Param
gateway/.../nodeDefinitions/email.py + EmailDraft/EmailList-Ports
gateway/.../nodeDefinitions/sharepoint.py + FileList/DocumentList-Ports
gateway/.../nodeDefinitions/clickup.py + TaskList/TaskResult-Ports
gateway/.../nodeDefinitions/file.py + DocumentList-Ports
gateway/.../nodeDefinitions/trustee.py + Ports
gateway/.../nodeDefinitions/__init__.py + DATA_NODES Import
frontend_nyla/.../editor/NodeConfigPanel.tsx Generischer Renderer statt NODE_CONFIG_REGISTRY

Geänderte Dateien

Datei Änderung
gateway/.../shared/frontendTypes.py FrontendType Enum erweitern (+10 Werte)
gateway/.../automation2/graphUtils.py resolveParameterReferences + SystemVar, validateGraph + Port-Check
gateway/.../automation2/executors/flowExecutor.py ifElse/switch → Transit-Envelope, + _merge
gateway/.../automation2/executors/__init__.py + DataExecutor Export
gateway/.../automation2/executionEngine.py + DataExecutor, Transit-_meta, aggregate-Akkumulator, merge-Wartelogik
gateway/.../graphicalEditor/nodeRegistry.py API-Response + portTypeCatalog, systemVariables
gateway/.../graphicalEditor/routeFeatureGraphicalEditor.py Response-Struktur erweitern
frontend_nyla/.../api/workflowApi.ts Interfaces erweitern (inputPorts, outputPorts, frontendType)
frontend_nyla/.../nodes/shared/dataRef.ts + SystemVarRef
frontend_nyla/.../nodes/shared/DataPicker.tsx Schema-basiert, Transit-Auflösung, System-Sektion
frontend_nyla/.../nodes/shared/graphUtils.ts inputPorts/outputPorts serialisieren
frontend_nyla/.../context/Automation2DataFlowContext.tsx + portTypeCatalog, systemVariables
frontend_nyla/.../editor/FlowCanvas Verbindungs-Validierung

Gelöschte Dateien

Datei Grund
frontend_nyla/.../nodes/configs/index.ts Ersetzt durch FRONTEND_TYPE_RENDERERS
frontend_nyla/.../nodes/configs/AiNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/EmailNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/SharePointNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/ClickUpNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/ApprovalNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/UploadNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/CommentNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/ReviewNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/SelectionNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/ConfirmationNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/FileCreateNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/configs/TrusteeNodeConfig.tsx Generischer Renderer
frontend_nyla/.../nodes/shared/outputPreviewRegistry.ts Schema-basierte Preview