diff --git a/TOPICS.md b/TOPICS.md
index 751a12c..847ca9f 100644
--- a/TOPICS.md
+++ b/TOPICS.md
@@ -42,6 +42,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
|-------|-------|------------|
| Automation Unification | c-work/1-plan/2026-04-automation-unification.md | Refactoring v1/v2/Workspace |
| Web Image Search | c-work/1-plan/2026-03-web-image-search.md | WEB_SEARCH_MEDIA Feature |
+| UI i18n / Sprachsets (done) | c-work/3-validate/2026-04-ui-i18n-dynamic-language-sets.md | Mehrsprachigkeit, `t()`, Sprachset-API, Admin-UI, AI-Übersetzung |
## Prozess & Betrieb
diff --git a/b-reference/frontend-nyla/architecture.md b/b-reference/frontend-nyla/architecture.md
index 2e0f45e..36e98ca 100644
--- a/b-reference/frontend-nyla/architecture.md
+++ b/b-reference/frontend-nyla/architecture.md
@@ -1,5 +1,5 @@
-
+
# Frontend Nyla -- Architektur
@@ -21,7 +21,7 @@ Technologie-Stack (Stand UI-Doku): React 19.x, Vite 5.x, TypeScript 5.8.x, React
| `api/` | API-Client (`api.ts`) und Feature-spezifische API-Module |
| `core/` | PageManager |
| `layouts/` | Layout-Komponenten |
-| `locales/` | i18n |
+| `locales/` | i18n: API-basiertes Language-Loading (`index.ts`, `types.ts`), keine statischen Locale-Files |
| `types/` | TypeScript-Typen |
| `utils/` | Utility-Funktionen |
@@ -49,11 +49,13 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
- **Geschützte Bereiche:** Route-Guards (z. B. `ProtectedRoute`) prüfen Authentifizierung; Redirect bei fehlender Session.
- **Haupt-App:** Nach Login **`MainLayout.tsx`** mit **`MandateNavigation`** (Sidebar) und `` (React Router).
- **Seiten-Mapping:** `pageRegistry.tsx` definiert `PAGE_REGISTRY` und `FEATURE_REGISTRY`; Seiten werden lazy geladen. `core/PageManager/` enthält ergänzende Infrastruktur (State Preservation, Lifecycle-Hooks).
-- **i18n / Theme:** global über Context; API-Requests mit Auth-Header (Interceptor in zentralem API-Client).
+- **i18n:** DB-backed via `LanguageContext` (`t()`-Hook). Sprachsets werden dynamisch via public API geladen (`GET /api/i18n/sets/{code}`). Key-Konvention: **Deutscher Klartext = Key**. Jeder neue/geänderte UI-Text MUSS mit `t('Deutscher Klartext')` getaggt werden. Variable Interpolation: `t('Text {var}', {var: 'Wert'})`. Fallback: Ziel-Set → `de`-Set → Key selbst. Keine statischen Locale-Files.
+- **Theme:** global über Context; API-Requests mit Auth-Header (Interceptor in zentralem API-Client).
## UI-Regeln
- **Keine Browser-Dialoge** (`alert` / `confirm` / `prompt`) — stattdessen `useConfirm()` und `usePrompt()` Hooks
+- **i18n-Pflicht:** Jeder UI-Text (Label, Button, Placeholder, Tooltip, Fehlermeldung) MUSS mit `t('Deutscher Klartext')` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt. Import: `const { t } = useLanguage();`
- Alle internen Funktionen mit `_` Prefix
- camelCase für Variablen und Funktionen
@@ -76,7 +78,9 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
| `components/FlowEditor/editor/CanvasHeader.tsx` | Versioning, Template-Management, Workflow-Aktionen |
| `components/FlowEditor/editor/TemplatePicker.tsx` | Template-Auswahl-Modal |
| `pages/views/graphicalEditor/GraphicalEditorPage.tsx` | Feature-Seite mit KeepAlive, URL-basiertem Workflow-Loading |
-| `locales/*` | Übersetzungen (z. B. de / en / fr) |
+| `locales/index.ts`, `types.ts` | i18n: API-basiertes Language-Loading (DB-backed, keine statischen Files) |
+| `providers/language/LanguageContext.tsx` | `t()`-Hook mit `{variable}`-Interpolation, Fallback-Kette, `availableLanguages` |
+| `pages/admin/AdminLanguagesPage.tsx` | Admin-Seite: Sprachset-Verwaltung (CRUD, AI-Übersetzung) |
## Regeln / Invarianten
diff --git a/b-reference/gateway/architecture.md b/b-reference/gateway/architecture.md
index 3524f67..808fe06 100644
--- a/b-reference/gateway/architecture.md
+++ b/b-reference/gateway/architecture.md
@@ -1,5 +1,5 @@
-
+
# Gateway -- Architektur
@@ -17,11 +17,11 @@ Unter `gateway/modules/` (Kontext-Audit):
| `aicore/` | Model-Registry, Model-Selector, Provider-Plugins (Anthropic, OpenAI, Mistral, Perplexity, Tavily, PrivateLLM) |
| `auth/` | Authentifizierung, CSRF, Token-Refresh-Middleware, JWT |
| `connectors/` | DB-Connector (PostgreSQL), Provider-Subpakete (Microsoft, Google, ClickUp, FTP), Ticket/Messaging/Geo-Konnektoren |
-| `datamodels/` | Pydantic-Datenmodelle (u. a. Ai, Billing, Chat, Content, Files, Knowledge, Rbac, Subscription, Workflow) |
+| `datamodels/` | Pydantic-Datenmodelle (u. a. Ai, Billing, Chat, Content, Files, Knowledge, Rbac, Subscription, UiLanguage, Workflow) |
| `features/` | Feature-Module (autonome Domänen): workspace, graphicalEditor, chatbot, commcoach, neutralization, realEstate, trustee, teamsbot |
| `interfaces/` | DB-Interfaces (App, Billing, Chat, Knowledge, Management, Subscription), AI-Objects, RBAC, Features, Messaging |
| `migration/` | Daten-Migrationen |
-| `routes/` | REST-API-Routen (u. a. Admin, Billing, DataFiles, DataSources, Security, Store, System) |
+| `routes/` | REST-API-Routen (u. a. Admin, Billing, DataFiles, DataSources, i18n, Security, Store, System) |
| `security/` | RBAC (`rbac.py`, `rbacCatalog.py`), Root-Access |
| `serviceCenter/` | Zentrale Service-Orchestrierung (Registry, Resolver, Kontext, Haupt-Services) |
| `serviceHub/` | Service-Registry und Dependency Injection (u. a. `PublicService`-Wrapper) |
@@ -70,7 +70,7 @@ Die genannten Module kapseln die Datenbankzugriffe bzw. die zugehörigen Fachver
| `interfaceDbBilling.py` | BillingAccount, BillingTransaction, Subscriptions |
| `interfaceDbChat.py` | ChatWorkflow, ChatMessage, ChatDocument |
| `interfaceDbKnowledge.py` | FileContentIndex, ContentChunk, RoundMemory (RAG/Knowledge Store) |
-| `interfaceDbManagement.py` | FileItem, Folder, Prompt, DataSource (mandantenweite Stammdaten) |
+| `interfaceDbManagement.py` | FileItem, Folder, Prompt, DataSource (mandantenweite Stammdaten), UiLanguageSet-Seeding |
| `interfaceDbSubscription.py` | Subscription-Verwaltung |
| `interfaceAiObjects.py` | AI-Call-Abstraktion (Text, Embedding, Vision, Streaming) |
| `interfaceFeatures.py` | Feature-Instanz-Lifecycle, Template-Rollen-Kopie |
@@ -88,7 +88,7 @@ Weitere Interface-Dateien im Ordner (z. B. Voice, Tickets, Messaging, Bootstrap)
| `gateway/modules/serviceCenter/registry.py` | Service-Registry (CORE / IMPORTABLE) |
| `gateway/modules/serviceCenter/resolver.py` | Auflösung von Service-Instanzen inkl. Cache |
| `gateway/modules/serviceHub/__init__.py` | Hub / DI, `PublicService`-Wrapper für kontrollierte Oberflächen |
-| `gateway/modules/routes/*.py` | HTTP-Endpunkte, Aufruf in Richtung Service Center / Features |
+| `gateway/modules/routes/*.py` | HTTP-Endpunkte, Aufruf in Richtung Service Center / Features (inkl. `routeI18n.py` für DB-backed i18n mit AI-Übersetzung) |
| `gateway/modules/interfaces/*.py` | Stabile Verträge und DB-Zugriffe (keine direkte Vendor-Logik in Services) |
| `gateway/modules/connectors/*.py` | Vendor-spezifische Adapter (Auth, Transport, Mapping) |
| `gateway/modules/security/*` | RBAC-Auswertung, RBAC-Katalog, Root-Access |
diff --git a/c-work/1-plan/2026-04-analysis-editor.md b/c-work/1-plan/2026-04-analysis-editor.md
deleted file mode 100644
index 0a126a7..0000000
--- a/c-work/1-plan/2026-04-analysis-editor.md
+++ /dev/null
@@ -1,398 +0,0 @@
-
-
-
-# Analyse: Typed Node Handover System für den Graphical Editor
-
-## 1 Problemstellung
-
-Der Graphical Editor besitzt heute **keine formale Typisierung** der Daten, die zwischen Nodes fließen. Jede Node produziert ein freiformiges Dict; die Folge-Node versucht heuristisch, daraus das Passende zu extrahieren. Das führt zu:
-
-| Symptom | Wo sichtbar |
-|---------|-------------|
-| Spezialcode pro Paar (z.B. `_extractEmailContentFromUpstream`, `_getContextFromUpstream`, `_gatherAttachmentDocumentsFromUpstream`) | `actionNodeExecutor.py` (~860 Zeilen, >50 % handover Heuristik) |
-| DataPicker zeigt **hartcodierte** Beispiel-Schemas (`outputPreviewRegistry.ts`) — stimmt oft nicht mit echten Laufzeit-Outputs überein | Frontend `outputPreviewRegistry.ts` |
-| Kein `data.transform`, `data.filter`, `data.aggregate` implementiert — nur in `automation.md` als Placeholder aufgelistet | `nodeDefinitions/` hat keine Datei dafür |
-| Kein „Aggregate" (Gegenstück zu `flow.loop`'s For-Each) | Engine sammelt Loop-Body-Resultate nicht auf |
-| Frontend-Parameter haben **eigene** Typdefinitionen (node-level `type: "string"`) statt der in `MethodBase` etablierten `WorkflowActionParameter` mit `FrontendType`-Enum + Validierung | `ai.py`, `email.py` etc. vs. `methodBase.py` |
-
-**Kern:** Die Method/Action-Schicht hat bereits eine saubere Parameter-Typisierung (`WorkflowActionParameter`, `_validateType`, `_validateParameters`, `FrontendType`). Aber der Graph-Editor nutzt sie weder für Input-Konfiguration noch für die Handover-Logik.
-
----
-
-## 2 Ist-Zustand (Layer für Layer)
-
-### 2.1 Node-Definitionen (`nodeDefinitions/*.py`)
-
-Jede Node ist ein Dict mit:
-
-```python
-{
- "id": "email.draftEmail",
- "parameters": [
- {"name": "subject", "type": "string", "required": True, ...},
- ],
- "inputs": 1, "outputs": 1,
- "_method": "outlook", "_action": "composeAndDraftEmailWithContext",
- "_paramMap": {"connectionId": "connectionReference", ...},
-}
-```
-
-**Was fehlt:**
-- Kein `outputSchema` — was liefert die Node zurück?
-- Kein `inputSchema` — was erwartet die Node am Eingang?
-- `type: "string"` ist Node-Editor-intern, nicht identisch mit `WorkflowActionParameter.type` (`str`, `int`, `List[str]`, …).
-- Kein `frontendType` (d.h. Frontend baut eigene Config-Components per Node-Typ statt generisch).
-
-### 2.2 Execution Engine (`executionEngine.py`)
-
-- Topologische Sortierung → iteriert Nodes → ruft Executor auf → speichert Output in `nodeOutputs[nodeId]`.
-- **Kein Output-Vertrag**: der Executor liefert `Any`, das Dict wird 1:1 in `nodeOutputs` gelegt.
-- Folge-Nodes holen via `inputSources[nodeId][0]` den Vorgänger-Output und _hoffen_, dass er das richtige Format hat.
-
-### 2.3 ActionNodeExecutor (`actionNodeExecutor.py`)
-
-- ~860 Zeilen, davon ~60 % dedizierte Merge-Logik für Paare: `email→AI`, `AI→email.draftEmail`, `file.create` content-gathering, ClickUp merge, etc.
-- Aufruft am Ende `ActionExecutor.executeAction(method, action, params)` — dessen `MethodBase._validateParameters` prüft die **Action**-Parameter, nicht den Graph-Port-Kontrakt.
-- Baut ein Output-Dict mit teilweise inkonsistenten Keys (`documents` vs. `documentList` vs. `data` vs. `context`).
-
-### 2.4 DataPicker + outputPreviewRegistry (Frontend)
-
-- `outputPreviewRegistry.ts` registriert **statische Beispiele** pro Node-Typ.
-- DataPicker zeigt daraus einen Baum und erzeugt `DataRef { type: "ref", nodeId, path }`.
-- `resolveParameterReferences` (Backend) löst `DataRef` gegen echte `nodeOutputs`.
-- **Problem:** Die Preview-Struktur ist manuell gepflegt und **divergiert** vom echten Output. Der User wählt Pfade, die zur Laufzeit nicht existieren oder anders heißen.
-
-### 2.5 Method/Action Parameter-System (bereits existent)
-
-`WorkflowActionParameter` + `MethodBase._validateParameters`:
-
-```python
-class WorkflowActionParameter(BaseModel):
- name: str
- type: str # 'str', 'int', 'List[str]', 'Dict[str, Any]'
- frontendType: FrontendType # TEXT, TEXTAREA, SELECT, USER_CONNECTION, …
- frontendOptions: Optional[...]
- required: bool
- default: Optional[Any]
- validation: Optional[Dict]
-```
-
-`_validateType` konvertiert + prüft: `str→str`, `int→int`, `List[str]→[str,…]`, usw.
-
-**Dieses System funktioniert bereits** für Workspace-Actions. Es wird aber **nicht** für Node-Ein-/Ausgänge genutzt.
-
----
-
-## 3 Ziel-Architektur: Typed Port System
-
-### 3.1 Kern-Konzept: Port-Schema
-
-Jede Node deklariert **typisierte Ports** für Ein- und Ausgänge — analog zu `WorkflowActionParameter`, aber für den Graphen.
-
-```
-┌──────────────────────────────────────────────────────────────────────┐
-│ Node Definition (Beispiel: email.draftEmail) │
-│ │
-│ inputPorts: │
-│ [0]: { schema: EmailDraft } ← structured type │
-│ EmailDraft = { subject: str, body: str, to: List[str] } │
-│ │
-│ outputPorts: │
-│ [0]: { schema: ActionResult } │
-│ ActionResult = { success: bool, error: str?, │
-│ documents: List[Document] } │
-│ │
-│ parameters: (config, unabhängig von Ports) │
-│ connectionId: { type: str, frontendType: USER_CONNECTION } │
-└──────────────────────────────────────────────────────────────────────┘
-```
-
-### 3.2 Port-Schema Definition
-
-```python
-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):
- fields: List[PortField]
-
-class NodePortDefinition(BaseModel):
- inputPorts: Dict[int, PortSchema] # port-index → schema
- outputPorts: Dict[int, PortSchema] # port-index → schema
-```
-
-### 3.3 Einheitliche Output-Typen (Port-Typen-Katalog)
-
-Statt freier Dicts gibt es benannte Schemas, die Node-übergreifend wiederverwendbar sind:
-
-| 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?: DocumentList` | ai.prompt (mode=email) | email.draftEmail, email.sendEmail |
-| `EmailList` | `emails: List[{subject, from, to, body, date, attachments}]` | email.checkEmail, email.searchEmail | ai.prompt, flow.loop, flow.ifElse |
-| `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, Keys = Feldnamen) | trigger.form, input.form | Alle (via Referenz auf Einzelfelder) |
-| `AiResult` | `prompt: str, response: str, 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** (NEU) | Alle |
-
-### 3.4 Schema auf Node-Definitions
-
-Erweiterung der bestehenden Node-Definitions um `outputPorts` und `inputPorts`:
-
-```python
-{
- "id": "ai.prompt",
- "category": "ai",
- "parameters": [...], # Config — bleibt wie bisher
- "inputs": 1,
- "outputs": 1,
-
- # NEU ─────────────────────────────────────────
- "inputPorts": {
- 0: {"accepts": ["DocumentList", "TextResult", "FormPayload", "EmailList", "AiResult", "Any"]},
- },
- "outputPorts": {
- 0: {"schema": "AiResult"},
- },
-}
-```
-
-```python
-{
- "id": "email.draftEmail",
- "inputPorts": {
- 0: {"accepts": ["EmailDraft", "AiResult", "TextResult"]},
- },
- "outputPorts": {
- 0: {"schema": "ActionResult"},
- },
-}
-```
-
-### 3.5 Konsequenzen für bestehende Schichten
-
-#### A) Node-Definitionen — einmalige Migration
-
-Jede Node bekommt `inputPorts` + `outputPorts`. Bestehende `inputs`/`outputs` (Zählwerte) bleiben für Rückwärtskompatibilität, aber die Schemas werden die Quelle der Wahrheit.
-
-#### B) Executors — Output normalisieren
-
-Jeder Executor erhält eine `_normalizeOutput(rawResult, portSchema) → Dict` Funktion. Sie sorgt dafür, dass die Keys/Typen dem deklarierten Schema entsprechen. Für `AiResult` z.B.:
-
-```python
-def _normalizeAiResult(raw: Any) -> Dict:
- return {
- "prompt": raw.get("prompt", ""),
- "response": raw.get("response", raw.get("context", "")),
- "context": raw.get("context", ""),
- "documents": _ensureDocumentList(raw.get("documents", [])),
- }
-```
-
-Der Spezialcode in `actionNodeExecutor.py` (~400 Zeilen Paar-Heuristik) fällt dann weg und wird durch generische Normalizer pro Port-Typ ersetzt.
-
-#### C) ExecutionEngine — Handover-Validierung
-
-Nach jedem Node-Execute: `nodeOutputs[nodeId]` wird gegen `outputPorts[0].schema` geprüft (Warnung oder Fehler bei Mismatch). Vor jedem Node-Execute: Input-Port-Kompatibilitäts-Check (`accepts` enthält den Schema-Typ des Vorgänger-Outputs).
-
-#### D) Frontend — DataPicker aus Schema generieren
-
-`outputPreviewRegistry.ts` wird ersetzt durch eine generische Funktion, die aus dem PortSchema den Preview-Baum baut. Kein manuelles Pflegen mehr:
-
-```typescript
-function buildPreviewFromSchema(schema: PortSchema): Record {
- // Iteriert schema.fields → Beispielwert pro Typ
-}
-```
-
-#### E) Frontend — Generisches Parameter-Rendering
-
-Statt pro Node-Typ eine eigene Config-Component zu schreiben (aktuell 12+ Dateien), können die meisten Parameter-Felder generisch aus dem `WorkflowActionParameter`-Schema gerendert werden. Spezial-UIs (FormBuilder, ClickUp Browse, etc.) bleiben als Override.
-
-#### F) Verbindungs-Validierung im Editor
-
-Beim Ziehen einer Kante: prüfe `sourceNode.outputPorts[outputIdx].schema` ∈ `targetNode.inputPorts[inputIdx].accepts`. Falls inkompatibel: Kante rot markieren / verhindern.
-
----
-
-## 4 Fehlende Nodes / Konzepte
-
-### 4.1 data.aggregate (Gegenstück zu flow.loop)
-
-Loop verteilt Items → Body → aber am Ende gibt es keinen Collector. Heute liegt nur das letzte Body-Ergebnis in `nodeOutputs`.
-
-**Lösung: `data.aggregate`-Node**
-
-```python
-{
- "id": "data.aggregate",
- "category": "data",
- "label": {"en": "Aggregate", "de": "Sammeln", "fr": "Agréger"},
- "description": {"en": "Collect results from loop body into a list", ...},
- "parameters": [
- {"name": "mode", "type": "string", "options": ["collect", "concat", "sum", "count"], "default": "collect"},
- ],
- "inputs": 1, "outputs": 1,
- "inputPorts": {0: {"accepts": ["Any"]}},
- "outputPorts": {0: {"schema": "AggregateResult"}},
- "executor": "data",
-}
-```
-
-**Engine-Änderung:** Im Loop-Body erkennt die Engine eine `data.aggregate`-Node und sammelt pro Iteration das Ergebnis in einer Liste. Nach Loop-Ende: `nodeOutputs[aggregateNodeId] = { items: [...], count: N }`.
-
-### 4.2 data.transform
-
-Reiner Mapping-Node: Felder umbenennen, extrahieren, umstrukturieren. Konfiguration per Key-Value-Mapping oder einfachem Expression-Sprache.
-
-```python
-{
- "id": "data.transform",
- "category": "data",
- "parameters": [
- {"name": "mapping", "type": "json", "description": "Key-Value Mapping: {outputField: inputRef}"},
- ],
- "inputPorts": {0: {"accepts": ["Any"]}},
- "outputPorts": {0: {"schema": "Dict"}},
-}
-```
-
-### 4.3 data.filter
-
-Filtert Items einer Liste nach Bedingung (wie WHERE in SQL). Eingabe: List, Ausgabe: gefilterter List.
-
-```python
-{
- "id": "data.filter",
- "category": "data",
- "parameters": [
- {"name": "condition", "type": "string", "description": "Filter expression (z.B. item.status == 'open')"},
- ],
- "inputPorts": {0: {"accepts": ["AggregateResult", "FileList", "TaskList", "EmailList"]}},
- "outputPorts": {0: {"schema": "AggregateResult"}},
-}
-```
-
-### 4.4 flow.merge
-
-Heute in `automation.md` gelistet, aber nicht implementiert. Wartet auf N Eingänge und kombiniert sie.
-
----
-
-## 5 Migrations-Strategie
-
-### Phase 1: Port-Schema-Deklaration (Backend, kein Breaking Change)
-
-1. `PortSchema` als Pydantic-Modell definieren.
-2. Katalog der Port-Typen (`DocumentList`, `AiResult`, `EmailDraft`, …) anlegen.
-3. Jede Node-Definition in `nodeDefinitions/*.py` um `inputPorts` / `outputPorts` erweitern.
-4. API `GET /node-types` liefert die Schemas mit.
-
-### Phase 2: Output-Normalizer (Backend)
-
-1. Pro Port-Typ einen Normalizer schreiben (10–15 Funktionen).
-2. In `executionEngine.py`: nach jedem Execute `_normalizeOutput(result, schema)` aufrufen.
-3. Spezialcode in `actionNodeExecutor.py` schrittweise durch Normalizer ersetzen.
-4. Step-Logs mit normalisiertem Output (Debugging/Tracing sofort besser).
-
-### Phase 3: Frontend — DataPicker aus Schema (Frontend)
-
-1. `outputPreviewRegistry.ts` durch Schema-basierte Preview-Generierung ersetzen.
-2. DataPicker zeigt korrekte Felder + Typen an.
-3. Verbindungs-Validierung (rote Kanten bei Typ-Mismatch).
-
-### Phase 4: Generisches Parameter-Rendering (Frontend)
-
-1. `NodeConfigPanel` rendert Parameter generisch aus `WorkflowActionParameter`-Schema.
-2. Spezial-Components (`FormNodeConfig`, `ClickUpNodeConfig`, …) bleiben als Override für komplexe UIs.
-3. Neue Nodes brauchen **keine eigene Config-Component** mehr.
-
-### Phase 5: Neue Data-Nodes
-
-1. `data.aggregate` implementieren (Engine + Node-Definition + Frontend).
-2. `data.transform` implementieren.
-3. `data.filter` implementieren.
-4. `flow.merge` implementieren.
-
----
-
-## 6 Konkretes Beispiel: AI-Mail-Entwurf → Mail-Versand
-
-### Heute (heuristisch)
-
-```
-[email.checkEmail] ──→ [ai.prompt] ──→ [email.draftEmail]
- ↓ ↓ ↓
- freeform Dict freeform Dict ~100 Zeilen Spezialcode
- (data.emails…) (context, docs) in actionNodeExecutor:
- _extractEmailContentFromUpstream
- _getIncomingEmailFromUpstream
- _unpackIncomingEmail
- _gatherAttachmentDocumentsFromUpstream
-```
-
-### Ziel (typisiert)
-
-```
-[email.checkEmail] [ai.prompt] [email.draftEmail]
- outputPort[0]: outputPort[0]: inputPort[0]:
- schema: EmailList schema: AiResult accepts: [EmailDraft, AiResult]
-
- ──────────────────→ ──────────────────→ Normalizer kennt AiResult und
- Input: EmailList Input: EmailList mapped response → body,
- Engine prüft Kompatibilität Engine prüft kein Spezialcode nötig.
-```
-
-**Entscheidend:** Der AI-Node kann so konfiguriert werden, dass sein Output-Typ `EmailDraft` statt `AiResult` ist (z.B. via Parameter `outputMode: "emailDraft"` oder via Prompt-Instruktion + structured output). Dann ist der Downstream-Merge trivial.
-
----
-
-## 7 Offene Fragen / Entscheidungen
-
-| # | Frage | Optionen |
-|---|-------|----------|
-| 1 | Soll Typ-Mismatch eine Kante verhindern (hard) oder nur warnen (soft)? | **Empfehlung: soft** (Warnung + gelbe Kante). Hard-Block wäre zu restriktiv bei generischen Nodes. |
-| 2 | Port-Typ `Any` erlauben? | Ja, als Fallback. Nodes wie `flow.ifElse` leiten den Input transparent durch. |
-| 3 | Dynamische Schemas (z.B. `input.form` Payload hängt von Felddefinition ab)? | Ja. `outputPorts` kann eine Funktion referenzieren, die das Schema aus den `parameters` ableitet (wie heute `outputPreviewRegistry`). |
-| 4 | Sollen bestehende Workflows beim Upgrade automatisch migriert werden? | **Nein.** Alte Workflows laufen weiter (Normalizer fängt fehlende Keys ab). Neue Workflows profitieren von Validierung. |
-| 5 | Separater `data.aggregate`-Node oder implizit in `flow.loop`? | **Empfehlung: separater Node.** Explizit ist klarer; der Loop-Node bleibt rein für Iteration. |
-| 6 | Braucht es einen `data.join`-Node (merge zwei Listen)? | Später. Erst aggregate + filter + transform etablieren. |
-
----
-
-## 8 Aufwand-Schätzung
-
-| Phase | Geschätzter Aufwand | Abhängigkeiten |
-|-------|-------------------|----------------|
-| 1 — Port-Schema-Deklaration | 2–3 Tage | Keine |
-| 2 — Output-Normalizer | 3–5 Tage | Phase 1 |
-| 3 — Frontend DataPicker aus Schema | 2–3 Tage | Phase 1 |
-| 4 — Generisches Parameter-Rendering | 3–4 Tage | Phase 1 |
-| 5 — Data Nodes (aggregate, transform, filter) | 3–5 Tage | Phase 2 |
-| **Gesamt** | **~13–20 Tage** | Phasen 1–3 sind der kritische Pfad |
-
----
-
-## 9 Schlüssel-Dateien
-
-| Bereich | Pfade |
-|---------|-------|
-| Node-Definitionen | `gateway/modules/features/graphicalEditor/nodeDefinitions/*.py` |
-| Execution Engine | `gateway/modules/workflows/automation2/executionEngine.py` |
-| ActionNodeExecutor (Handover-Heuristik) | `gateway/modules/workflows/automation2/executors/actionNodeExecutor.py` |
-| Graph-Utilities | `gateway/modules/workflows/automation2/graphUtils.py` |
-| Method/Action Typsystem | `gateway/modules/workflows/methods/methodBase.py`, `gateway/modules/datamodels/datamodelWorkflowActions.py` |
-| FrontendType Enum | `gateway/modules/shared/frontendTypes.py` |
-| Output-Preview (Frontend) | `frontend_nyla/src/components/FlowEditor/nodes/shared/outputPreviewRegistry.ts` |
-| DataPicker (Frontend) | `frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx` |
-| DataRef/DynamicValue (Frontend) | `frontend_nyla/src/components/FlowEditor/nodes/shared/dataRef.ts` |
-| Node Config Registry (Frontend) | `frontend_nyla/src/components/FlowEditor/nodes/configs/index.ts` |
-| DataFlow Context (Frontend) | `frontend_nyla/src/components/FlowEditor/context/Automation2DataFlowContext.tsx` |
diff --git a/c-work/1-plan/2026-04-gateway-int-stability-and-bugfixes.md b/c-work/1-plan/2026-04-gateway-int-stability-and-bugfixes.md
new file mode 100644
index 0000000..417b4a0
--- /dev/null
+++ b/c-work/1-plan/2026-04-gateway-int-stability-and-bugfixes.md
@@ -0,0 +1,123 @@
+
+
+
+
+# Gateway: INT-Stabilität — Analyse (Log) und Fix-Plan
+
+## Beschreibung und Kontext
+
+Beim Testen der Applikation auf der **Integrations-Instanz (INT)** mit **mehreren parallelen Nutzern** wurden Symptome im Gateway-Log sichtbar (u. a. `gateway_log_2026-04-08T11-14-17-514Z.log`). Ziel dieses Dokuments ist eine **codebezogene Ursachenanalyse** und ein **Umsetzungs- und Testplan** — ohne die Themen mit Architektur-Storys zu vermischen.
+
+**Business-Treiber:** Zuverlässiger Multi-User-Betrieb (Upload/Index, Workspace-Agent, Outlook-Tools, Knowledge/Neutralisierung) und weniger „sporadische“ Fehler, die Support-Zeit kosten.
+
+**Risiko bei Nicht-Umsetzung:** Weiterhin nicht reproduzierbar wirkende Permission-Fehler nach erfolgreicher Verarbeitung, fehlgeschlagene OpenAI-Streaming-Calls wegen Tool-Schema, nutzloser Outlook-Tool-Flow bei UUID-Referenzen, Crashes/Fail-Safe-Sprünge bei Bild-only-Indexierung.
+
+**Abhängigkeiten:** Änderungen betreffen primär `gateway`; keine zwingende Frontend-Änderung, ausser Tool-Beschreibungen/UX explizit angepasst werden.
+
+## Fokus und kritische Details
+
+### Thema A — Management-Interface: globaler Singleton
+
+- **Fragil:** `interfaceDbManagement.getInterface()` liefert eine **gemeinsame** `ComponentObjects`-Instanz (`"default"`); jeder Aufruf setzt `setUserContext` auf demselben Objekt.
+- **Edge Case:** Async-Pfade (z. B. `_autoIndexFile` mit `await knowledgeService.indexFile`) — zwischen Start und `updateFile` kann ein anderer Request den Kontext überschreiben → `PermissionError` auf `FileItem.update` trotz erfolgreicher Indexierung.
+- **Code:** `gateway/modules/interfaces/interfaceDbManagement.py`, `gateway/modules/routes/routeDataFiles.py` (`_autoIndexFile`).
+
+### Thema B — Agent-Tool-JSON-Schema: Arrays ohne `items`
+
+- **Fragil:** `ActionToolAdapter` mappt `List[str]` auf JSON-Schema `type: array` **ohne** `items`.
+- **Edge Case:** OpenAI lehnt Tools mit HTTP 400 ab (*array schema missing items*); Failover zu anderen Providern maskiert das Problem.
+- **Code:** `gateway/modules/serviceCenter/services/serviceAgent/actionToolAdapter.py`; betroffene Aktion u. a. `ai.process` (`methodAi.py`, Parameter `documentList`).
+
+### Thema C — Outlook: Referenzformat vs. Lookup
+
+- **Fragil:** `listConnections` zeigt u. a. `id: `; `getUserConnectionFromConnectionReference` parst nur `connection:{authority}:{username}`.
+- **Edge Case:** Modell übergibt UUID an Outlook-Tools → „Connection not found“.
+- **Code:** `coreTools/_connectionTools.py`, `mainServiceChat.getUserConnectionFromConnectionReference`, `methodOutlook/helpers/connection.py`.
+
+### Thema D — Knowledge: `_neutralSvc` bei nur Bildern
+
+- **Bug:** `_neutralSvc` wird nur im Zweig mit Textobjekten gesetzt; Bild-Neutralisierung nutzt `_neutralSvc` danach — bei **keinen** Textobjekten → `UnboundLocalError` (im Log als fail-safe sichtbar).
+- **Code:** `gateway/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py` (`indexFile`).
+
+### Thema E — Workspace: Mandats-Kontext aus zwei Quellen
+
+- **Fragil:** `_getChatInterface` nutzt `context.mandateId`; `ServiceCenterContext` für den Agent nutzt Mandat aus `_validateInstanceAccess` (Instanz).
+- **Edge Case:** Unterschiedliche `interfaceDbChat`-Cache-Keys für „denselben“ Workspace → seltene Inkonsistenzen (Workflow sichtbar / nicht sichtbar).
+- **Code:** `gateway/modules/features/workspace/routeFeatureWorkspace.py`.
+
+### Thema F — Chat: `createMessage` / `getWorkflow` → None
+
+- **Fragil:** `getWorkflow` liefert `None` bei leerem RBAC-Recordset **und** bei Exceptions beim Validieren des `ChatWorkflow`-Modells; `createMessage` meldet pauschal „No access to workflow“.
+- **Edge Case:** Agent-Lauf endet „complete“, Persistenz der Assistant-Message schlägt fehl — Ursache ohne besseres Logging schwer trennbar.
+- **Code:** `gateway/modules/interfaces/interfaceDbChat.py`.
+
+## Ziel und Nicht-Ziele
+
+- **Ziel:** Die oben genannten **konkreten** Defekte/Risiken beheben oder absichern; INT- und Last-Szenarien reproduzierbar testen; Logging dort verbessern, wo die Ursache heute verschleiert wird.
+- **Explizit NICHT:** Grossrefactor des gesamten Service-Centers; neue Features; umfassende Wiki-b-reference Pflege (erst nach Abschluss laut README-Lebenszyklus).
+
+## Betroffene Module
+
+- **Gateway:** `interfaceDbManagement`, `routeDataFiles`, `actionToolAdapter`, `mainServiceChat`, `mainServiceKnowledge`, `routeFeatureWorkspace`, `interfaceDbChat`, Agent-Core-Tools (`_connectionTools`), optional `connection.py` (Outlook).
+- **Frontend:** nein (ausser spätere Texte/Hinweise für Connection-Referenz — optional).
+- **DB-Migration:** nein (für diese Fixes nicht erwartet).
+- **Andere Komponenten:** nein.
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begründung |
+|-------|-------------|------------|
+| 2026-04-08 | Plan-Dokument angelegt | INT-Log + Code-Review; Umsetzung folgt in `2-build`. |
+
+## Umsetzungs-Checkliste (Fix-Reihenfolge empfohlen)
+
+- [ ] **D** `_neutralSvc` / Bildpfad in `mainServiceKnowledge.indexFile` absichern (klein, klar).
+- [ ] **B** JSON-Schema für `List[str]` / `List[int]` (und ggf. weitere Array-Typen) in `actionToolAdapter` ergänzen.
+- [ ] **C** Connection-Lookup UUID **oder** einheitliche Ausgabe/Beschreibung in `listConnections` + Parser in `getUserConnectionFromConnectionReference`.
+- [ ] **A** Management-Interface: Request-scoped Instanz statt globalem Singleton **oder** dokumentierter alternativer Ansatz (kein geteilter mutable User-Kontext über `await`-Grenzen).
+- [ ] **E** Workspace: einheitliche `mandateId`-Quelle für Chat-Interface und `ServiceCenterContext` nach Instanz-Validierung.
+- [ ] **F** `getWorkflow` / `createMessage`: präziseres Logging bei Validierungsfehlern; ggf. Unterscheidung „nicht gefunden“ vs. „Daten ungültig“.
+
+Weitere Checkliste aus Template:
+
+- [ ] API-Endpunkte (nur falls Signatur/Kontrakt ändert)
+- [ ] DB-Schema / Migration — nein
+- [ ] Frontend-Komponenten — nein (optional)
+- [ ] RBAC / Permissions — prüfen nach Fix A/E
+- [ ] Neutralisierung betroffen? — ja (Thema D)
+- [ ] Navigation / Routing — nein
+- [ ] Billing-Impact? — nein (keine bewusste Änderung der Abrechnungslogik)
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|---------------------------|------|
+| AC1 | Given eine Datei mit nur Bild-`contentObjects` und aktiver Neutralisierung, When `indexFile` läuft, Then kein `UnboundLocalError` und definiertes Verhalten (OK/skip mit Log). | must |
+| AC2 | Given generiertes Tool-Schema für `ai_process`, When es an die OpenAI-kompatible API geht, Then `documentList` ist ein `array` **mit** `items` (kein 400 wegen fehlender `items`). | must |
+| AC3 | Given `listConnections` liefert eine Verbindungs-UUID, When ein Outlook-Tool dieselbe Referenz nutzt, Then Verbindung wird aufgelöst **oder** die Tool-Konvention verbietet UUID explizit und der Agent folgt zuverlässig der Konvention. | must |
+| AC4 | Given zwei parallele Uploads/Auto-Index-Läufe unterschiedlicher User, When Indexierung endet, Then `updateFile(..., active)` schlägt nicht wegen **fremdem** User-Kontext fehl. | must |
+| AC5 | Given Workspace-Stream mit gültiger Instanz, When Chat-Interface und Agent-Chat-Service erzeugt werden, Then identische effektive `mandateId`/`featureInstanceId`-Kombination für den Chat-Cache. | should |
+| AC6 | Given `getWorkflow` scheitert an Datenvalidierung, When `createMessage` läuft, Then Log enthält **ursächliche** Exception, nicht nur „No access“. | should |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad / Methode | Status |
+|----|----|-----|--------------|---------------------|--------|
+| T1 | AC1 | unit | ja | `gateway/tests/...` — `indexFile` mit Mock neutralization, nur image `contentObjects` | pending |
+| T2 | AC2 | unit | ja | Test: `_buildToolDefinition` / Schema für `ai_process` → assert `properties.documentList.items` | pending |
+| T3 | AC3 | unit/integration | ja/teils | `getUserConnectionFromConnectionReference(uuid)` vs. `connection:msft:user@…` | pending |
+| T4 | AC4 | integration | empfohlen | Zwei parallele Requests gegen INT oder lokaler Stress-Skript + assert File-Status | pending |
+| T5 | AC5 | integration | optional | Request ohne Header-Mandat, mit Instanz-ID — ein Log-Punkt für effektive Chat-Keys | pending |
+| T6 | AC6 | manuell/log | teils | Nach Fix: künstlich kaputtes Workflow-Datum in DB oder temporärer Log-Level | pending |
+
+## Links
+
+- PR: (nach Umsetzung)
+- Issue: (falls extern getrackt)
+- Ausgangs-Log (lokal): `local/debug/gateway_log_2026-04-08T11-14-17-514Z.log`
+
+## Abschluss
+
+- [ ] b-reference/ aktualisiert — z. B. `b-reference/gateway/architecture.md` nur wenn Architektur-Entscheid (Singleton) dauerhaft geändert wird
+- [ ] TOPICS.md aktualisiert (falls neues Thema)
+- [ ] Dieses Dokument bei Umsetzungsbeginn nach `c-work/2-build/` verschieben; nach Release nach `4-done/` bzw. `z-archive/`
diff --git a/c-work/2-build/2026-04-generic-graph-editor.md b/c-work/2-build/2026-04-generic-graph-editor.md
new file mode 100644
index 0000000..e9f9369
--- /dev/null
+++ b/c-work/2-build/2026-04-generic-graph-editor.md
@@ -0,0 +1,694 @@
+
+
+
+# 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`
+
+```python
+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:
+
+```python
+{
+ "_transit": True,
+ "_meta": { "branch": 0, "conditionResult": True }, # Routing-Metadaten
+ "data": ,
+}
+```
+
+| 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):
+
+```python
+{
+ "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):
+
+```python
+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`:
+
+```python
+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
+
+```python
+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:
+
+```python
+{
+ "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
+
+```typescript
+const FRONTEND_TYPE_RENDERERS: Record> = {
+ 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 ;
+ });
+}
+```
+
+---
+
+## 5 Neue Nodes
+
+### 5.1 data.aggregate
+
+```python
+{
+ "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
+
+```python
+{
+ "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
+
+```python
+{
+ "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
+
+```python
+{
+ "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 16–63).
+
+**Ä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 54–75):
+
+- `_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 78–89): bleibt, `_method`/`_action` weiterhin verwendet.
+
+### 7.5 ÄNDERN: `gateway/modules/workflows/automation2/graphUtils.py`
+
+**Funktion `resolveParameterReferences`** (Zeile 185–243):
+
+- Neuen Case hinzufügen für `type: "system"`:
+
+```python
+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 86–120): 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 55–65): Output ändern zu Transit-Envelope:
+
+```python
+# 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 199–207): analog Transit-Envelope.
+
+**Funktion `_loop`** (Zeile 261–272): 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 71–85): Case für `data.*` hinzufügen → `DataExecutor`.
+
+**Funktion `_is_node_on_active_path`** (Zeile 39–68): Transit-Envelope berücksichtigen — `branch`/`match` jetzt in `out["_meta"]` statt direkt in `out`.
+
+**Loop-Body-Verarbeitung** (Zeile 400–460): 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 146–168): Response-Struktur erweitern:
+
+```python
+# 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 14–20): erweitern um `frontendType`, `frontendOptions`, `options`, `validation`.
+
+**Interface `NodeType`** (Zeile 22–39): erweitern um `inputPorts`, `outputPorts`.
+
+**Interface `NodeTypesResponse`** (Zeile 46–49): 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:
+
+```typescript
+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 ;
+});
+```
+
+### 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:
+
+```typescript
+function buildPreviewFromSchema(
+ node: CanvasNode,
+ nodeTypes: NodeType[],
+ portTypeCatalog: Record
+): Record {
+ 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`:
+
+```typescript
+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 20–40): **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 10–19): 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 |
diff --git a/c-work/4-done/2026-04-ui-i18n-dynamic-language-sets.md b/c-work/4-done/2026-04-ui-i18n-dynamic-language-sets.md
new file mode 100644
index 0000000..bd4c42d
--- /dev/null
+++ b/c-work/4-done/2026-04-ui-i18n-dynamic-language-sets.md
@@ -0,0 +1,195 @@
+
+
+
+
+
+# UI-Mehrsprachigkeit: Dynamische Sprachsets (DB-backed i18n)
+
+## Beschreibung und Kontext
+
+Das UI nutzt ein **eigenes i18n-System** (`LanguageContext`, `t()`-Hook, `useLanguage`). Sprachsets werden dynamisch aus der Datenbank via public API geladen — keine statischen TypeScript-Dateien mehr.
+
+**Business-Treiber:** Multi-Tenant-Plattform mit Kunden in CH/DE/AT und perspektivisch FR/EN — neue Sprachen ohne Code-Änderung + Redeploy; Kunden können selbst neue Sprachen anlegen (AI-generiert).
+
+**ISO-Code Deutsch:** `de` (ISO 639-1 Standard).
+
+## Ergebnis (Ist-Zustand nach Umsetzung)
+
+| Aspekt | Umgesetzt |
+|--------|-----------|
+| Sprachen | Dynamischer `string`-Code, kein hardcoded Union-Type |
+| Quelle | DB via public API (`GET /api/i18n/sets/{code}`) |
+| Neue Sprache anlegen | AI-generiert, async, User-ausgelöst via Admin-UI |
+| `t()` Coverage | Bestehende Dateien migriert; **jede neue Code-Anpassung muss UI-Texte mit `t()` erfassen** |
+| Key-Schema | **Deutscher Text = Key** (kein Dot-Notation-Schema) |
+| Variable Interpolation | Nativ in `t()`: `t('Text mit {variable}', {variable: 'Wert'})` |
+| Sprachset-Verwaltung | CRUD-API: get codes, add, get, update, delete, download, update-all, export, import |
+| Admin-UI | Administration → System → UI-Sprachen (`/admin/languages`) inkl. Export/Import |
+| AI-Übersetzung | Batch-Pipeline via `AiObjects.callWithTextContext` + Billing |
+| Statische Locale-Files | Entfernt (`de.ts`, `en.ts`, `fr.ts` gelöscht); Seed-Daten in DB |
+
+## Key-Konvention: Deutscher Text = Key
+
+**Grundprinzip:** Der deutsche Klartext IST der Key. Das `de`-Set ist trivial (Key = Value). Alle anderen Sets mappen denselben Key auf die jeweilige Übersetzung.
+
+```tsx
+t('Abbrechen') // de: "Abbrechen", en: "Cancel", fr: "Annuler"
+t('Speichern') // de: "Speichern", en: "Save", fr: "Enregistrer"
+t('{authority} Verbindung bearbeiten', {authority: 'Google'})
+ // de: "Google Verbindung bearbeiten"
+ // en: "Edit Google connection"
+```
+
+### DB-Struktur
+
+```
+de-Set: { "Abbrechen": "Abbrechen", "Speichern": "Speichern", ... }
+en-Set: { "Abbrechen": "Cancel", "Speichern": "Save", ... }
+fr-Set: { "Abbrechen": "Annuler", "Speichern": "Enregistrer", ... }
+```
+
+## Entwickler-Pflicht: t()-Tagging bei jeder Code-Änderung
+
+> **Regel:** Jeder neue oder geänderte UI-Text (Label, Button, Placeholder, Tooltip, Fehlermeldung) MUSS mit `t('Deutscher Klartext')` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt.
+
+### Workflow für Entwickler
+
+1. **Neuer Text:** `t('Mein neuer Text')` verwenden — der Key wird automatisch Teil des `de`-Masters
+2. **Text ändern:** `t('Alter Text')` → `t('Neuer Text')` — der alte Key verwaist, der neue fehlt in anderen Sets
+3. **Variable Interpolation:** `t('{count} Einträge gefunden', { count: String(total) })` — Platzhalter `{...}` werden ersetzt
+4. **Kein Plural-Framework:** Separate Keys verwenden, z.B. `t('1 Eintrag')` vs. `t('{count} Einträge', { count })`
+5. **Import:** `const { t } = useLanguage();` — in jeder Komponente die `t()` nutzt
+6. **Sync:** Admin klickt "Update All" in Administration → System → UI-Sprachen → System scannt automatisch die Codebase, synchronisiert das `de`-Master-Set (neue Keys rein, verwaiste raus), dann AI übersetzt fehlende Keys in allen anderen Sets
+
+### Sonderfall: gleicher Text, anderer Kontext
+
+Falls nötig (z.B. "Offen" = "Open" vs. "Outstanding"): `t('Offen (Status)')` vs. `t('Offen (Zustand)')`. Die Klammer ist Teil des Keys und dient AI als Kontext-Hinweis.
+
+## Architektur-Übersicht
+
+### Backend (Gateway)
+
+| Datei | Zweck |
+|-------|-------|
+| `modules/datamodels/datamodelUiLanguage.py` | Pydantic-Model `UiLanguageSet` (id, label, keys, status, isDefault) |
+| `modules/routes/routeI18n.py` | API-Routen: public GET, auth POST, SysAdmin PUT/DELETE |
+| `modules/interfaces/interfaceDbManagement.py` | `_seedUiLanguageSetsIfEmpty()` — initiales DB-Seeding |
+| `modules/migration/seedData/ui_language_seed.json` | Seed-Daten für `de`, `en`, `fr` |
+| `modules/system/mainSystem.py` | Navigationseintrag `admin-languages` |
+| `scripts/build_ui_language_seed_json.py` | Script zur Seed-JSON-Generierung |
+| `scripts/i18n_rekey_plaintext_keys.py` | Script zur Migration Dot-Notation → Klartext-Keys |
+
+### Frontend (Nyla)
+
+| Datei | Zweck |
+|-------|-------|
+| `src/locales/index.ts` | API-basiertes Language-Loading (kein static-import) |
+| `src/locales/types.ts` | `Language = string` (dynamisch) |
+| `src/providers/language/LanguageContext.tsx` | `t()` mit Interpolation, Fallback-Kette, `availableLanguages` |
+| `src/pages/admin/AdminLanguagesPage.tsx` | Admin-Seite mit FormGeneratorTable |
+| `src/config/pageRegistry.tsx` | Icon-Mapping `page.admin.languages` |
+
+### API-Endpunkte
+
+| Methode | Pfad | Auth | Zweck |
+|---------|------|------|-------|
+| GET | `/api/i18n/codes` | public | Liste aller Sprachcodes + Status |
+| GET | `/api/i18n/sets/{code}` | public | Sprachset laden |
+| GET | `/api/i18n/sets/{code}/download` | auth | JSON-Download |
+| POST | `/api/i18n/sets` | auth + billing | Neue Sprache anlegen (async AI) |
+| PUT | `/api/i18n/sets/sync-de` | SysAdmin | `de`-Master aus Codebase synchronisieren (t()-Scan) |
+| PUT | `/api/i18n/sets/{code}` | SysAdmin | de-Sync + Set synchronisieren (AI für fehlende Keys) |
+| PUT | `/api/i18n/sets/update-all` | SysAdmin | de-Sync + alle Non-`de`-Sets synchronisieren |
+| DELETE | `/api/i18n/sets/{code}` | SysAdmin | Set löschen (nicht `de`) |
+| GET | `/api/i18n/export` | SysAdmin | Komplette Sprachdatenbank als JSON exportieren |
+| POST | `/api/i18n/import` | SysAdmin | JSON-Datei importieren (upsert, kein Löschen) |
+
+### AI-Pipeline
+
+- **Create:** Background-Job übersetzt alle ~928 Keys in Batches à 80 via `AiObjects.callWithTextContext`
+- **Update:** Synchron — nur fehlende Keys werden per AI übersetzt, überzählige entfernt
+- **Billing:** Jeder AI-Call wird via `BillingService.recordUsage` abgerechnet (Mandats-Pool des auslösenden Users)
+- **Fallback:** Bei AI-Fehler wird `[Deutscher Klartext]` als Platzhalter gesetzt (eckige Klammern = erkennbar unübersetzt), Status `incomplete`
+- **de-Master-Sync:** Vor jedem Update/Update-All wird automatisch `_syncDeMasterFromCodebase()` ausgeführt — scannt alle `t()`-Aufrufe im Frontend, fügt neue Keys hinzu, entfernt verwaiste
+
+### de-Master-Sync aus Codebase
+
+Bei **Update**, **Update All** und dem dedizierten Endpunkt `PUT /api/i18n/sets/sync-de` wird automatisch:
+
+1. Alle `.ts`/`.tsx`-Dateien unter `frontend_nyla/src/` nach `t('...')`-Aufrufen gescannt
+2. Neue Keys (in Codebase, nicht in DB) → zum `de`-Set hinzugefügt (Key = Value = deutscher Klartext)
+3. Verwaiste Keys (in DB, nicht mehr in Codebase) → aus dem `de`-Set entfernt
+4. Danach erst werden die Non-`de`-Sets synchronisiert (fehlende Keys per AI übersetzen, überzählige entfernen)
+
+### t()-Funktion Fallback-Kette
+
+1. Ziel-Sprachset (z.B. `en`)
+2. `de`-Master-Set (immer geladen)
+3. Zweites Argument als String-Fallback (falls übergeben)
+4. **`[Key]`** — eckige Klammern markieren den Key als unübersetzt/fehlend, damit er im UI sofort erkennbar ist
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begründung |
+|-------|-------------|------------|
+| 2026-04-08 | ISO-Code `de` für Deutsch | ISO 639-1 Standard |
+| 2026-04-08 | Eigenes `t()`-System beibehalten | 150+ Dateien integriert; i18next wäre Overhead |
+| 2026-04-08 | **Deutscher Text = Key** | Selbst-dokumentierend, AI-Kontext eingebaut |
+| 2026-04-08 | Keine Plural-Logik in `t()` | Separate Keys reichen |
+| 2026-04-08 | Keine statischen Locale-Files | DB ist einzige Quelle; kein Fallback |
+| 2026-04-08 | Key-Sync über Update-API | Dev taggt `t()`, Admin klickt "Update All" |
+| 2026-04-08 | AI-Batch-Übersetzung mit Billing | `AiObjects` + `BillingService.recordUsage` |
+| 2026-04-08 | de-Master-Sync aus Codebase | Automatischer t()-Scan vor jedem Update; kein manuelles Pflegen des de-Sets |
+| 2026-04-08 | Fallback `[Key]` statt nackter Key | Eckige Klammern machen unübersetzte Texte im UI sofort sichtbar |
+| 2026-04-08 | Export/Import der kompletten Sprachdatenbank | Instanz-übergreifender Transfer (INT → PROD) ohne DB-Zugriff |
+
+## Umsetzungs-Checkliste (abgeschlossen)
+
+### Phase 0 — t()-Tagging: Klartext-Keys + vollständige Coverage ✅
+
+- [x] AI-Scan: Replacement-Liste erstellt (read-only)
+- [x] Script `i18n_rekey_plaintext_keys.py`: Replacements mechanisch ausgeführt
+- [x] Script `build_ui_language_seed_json.py`: `de`-Master-Set extrahiert
+- [x] `en`/`fr`-Sets migriert (mechanisch, Key-Remapping)
+- [x] Seed-Daten als `ui_language_seed.json` bereitgestellt
+- [x] Statische Locale-Files `de.ts`, `en.ts`, `fr.ts` entfernt
+
+### Phase 1 — Gateway: Datamodel + API ✅
+
+- [x] Datamodel `UiLanguageSet` (`datamodelUiLanguage.py`)
+- [x] DB-Tabelle registriert (Auto-Deploy)
+- [x] Routes `routeI18n.py` (7 Endpunkte)
+- [x] `createUiLanguage`: Pre-flight Billing → Background-Job → AI-Batch-Übersetzung → Notification
+- [x] `updateUiLanguage`: `de`-Master → AI-Übersetzung fehlender Keys → überzählige entfernt
+- [x] Seed: `_seedUiLanguageSetsIfEmpty()` in `interfaceDbManagement.py`
+- [x] AI-Pipeline: `AiObjects.callWithTextContext` + `BillingService.recordUsage`, Batch-Size 80
+
+### Phase 2 — Frontend: LanguageContext + t() ✅
+
+- [x] `Language`-Type → `string`
+- [x] `loadLanguage` → API statt static-import
+- [x] `t()` mit `{variable}`-Interpolation
+- [x] Fallback-Kette: Ziel → `de` → Fallback-String → Key
+- [x] `availableLanguages` + `refreshAvailableLanguages`
+- [x] Sprach-Dropdown in Settings: dynamisch
+
+### Phase 3 — Admin-UI: Sprachverwaltung ✅
+
+- [x] Admin-Seite `/admin/languages` (`AdminLanguagesPage.tsx`)
+- [x] FormGeneratorTable: Code, Label, Status, Keys-Count
+- [x] Row-Actions: Update, Delete (nicht `de`), Download
+- [x] Toolbar-Actions: Add (Billing-Warning), Update All, Export, Import
+- [x] Navigationseintrag in `mainSystem.py` + `pageRegistry.tsx`
+
+### Querschnitt ✅
+
+- [x] RBAC: POST → auth; PUT/DELETE → SysAdmin; GET → public
+- [x] Billing: AI-Credits via `BillingService.recordUsage`
+- [x] Navigation: Admin-Route + Menüeintrag
+
+## Links
+
+- LanguageContext: `frontend_nyla/src/providers/language/LanguageContext.tsx`
+- API-Route: `gateway/modules/routes/routeI18n.py`
+- Admin-Seite: `frontend_nyla/src/pages/admin/AdminLanguagesPage.tsx`
+- Seed-Daten: `gateway/modules/migration/seedData/ui_language_seed.json`
diff --git a/d-guides/coding-conventions.md b/d-guides/coding-conventions.md
index 585d2f2..6c1f3d5 100644
--- a/d-guides/coding-conventions.md
+++ b/d-guides/coding-conventions.md
@@ -1,4 +1,4 @@
-
+ doku nachgeführt werden an
# Coding-Konventionen
@@ -17,6 +17,30 @@
- Hooks-Pattern fuer State und API-Zugriffe (`useApiRequest`, `useBilling`, etc.)
- Fehler propagieren -- keine stillen Fallbacks bei kritischen Pfaden
+### i18n-Pflicht: `t()` fuer alle UI-Texte
+
+Jeder sichtbare Text im UI (Labels, Buttons, Placeholders, Tooltips, Fehlermeldungen) **muss** mit `t()` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt.
+
+```tsx
+import { useLanguage } from '../../providers/language/LanguageContext';
+
+const { t } = useLanguage();
+
+// Einfacher Text
+t('Speichern')
+
+// Mit Variablen-Interpolation
+t('{count} Eintraege gefunden', { count: String(total) })
+
+// Gleicher Text, anderer Kontext → Klammer als Kontext-Hinweis
+t('Offen (Status)') // vs. t('Offen (Zustand)')
+```
+
+- **Key = deutscher Klartext** (kein Dot-Notation-Schema)
+- Kein Plural-Framework -- separate Keys verwenden: `t('1 Eintrag')` vs. `t('{count} Eintraege', { count })`
+- Fehlende Keys erscheinen als `[Text]` (eckige Klammern = unuebersetzt)
+- Admin synchronisiert Sprachsets ueber Administration → System → UI-Sprachen → "Alle aktualisieren"
+
## Backend (FastAPI/Python)
- **Pydantic-Models** als einzige Quelle fuer UI-Feld-Definitionen
diff --git a/d-guides/doc-sync.mdc b/d-guides/doc-sync.mdc
index 3ff3bb2..dada1bf 100644
--- a/d-guides/doc-sync.mdc
+++ b/d-guides/doc-sync.mdc
@@ -14,6 +14,7 @@ When a code change touches any of these areas, remind the user to update the wik
- Navigation / routing structure
- Feature boundaries (new feature module, moved responsibilities)
- Billing / subscription logic
+- UI text changes (all visible UI strings must use `t()` for i18n -- see `wiki/d-guides/coding-conventions.md`)
## During active work