From 4c073c4f04ca9fd05ac93a1db55e5fdf7a6585a6 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Mon, 20 Apr 2026 00:31:02 +0200
Subject: [PATCH] pwg-demo
---
...pwg-pilot-mietzinsbestaetigung-workflow.md | 209 ++++++++++++------
c-work/1-plan/2026-04-udb-action-system.md | 200 +++++++++++++++++
2 files changed, 344 insertions(+), 65 deletions(-)
create mode 100644 c-work/1-plan/2026-04-udb-action-system.md
diff --git a/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md b/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md
index 6a9d0cc..87c246d 100644
--- a/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md
+++ b/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md
@@ -1,5 +1,6 @@
-
+
+
@@ -16,6 +17,7 @@
1. **Workflow-File-IO** — Workflows als File exportieren (in UDB speichern) und aus UDB-File in den Graph-Editor laden. Voraussetzung, damit der Pilot-Workflow als versionierbares Asset im Repo geliefert werden kann.
2. **Agent-Tools Full-CRUD** — Der AI-Agent erhält im `workflow`-Toolbox die vollständigen Operationen `createWorkflow`, `createWorkflowFromFile`, `exportWorkflowToFile`, `deleteWorkflow` (zusätzlich zu den bestehenden Lese-/Edit-Tools).
3. **Pilot-Workflow als File** — Konkrete Lieferung: Workflow-JSON nach Schema, Step-5 AI-Prompt, Datenextraktion aus Trustee-DB.
+4. **PWG-Demo-Bootstrap** — Eigenes Demo-Config-Modul `pwgDemo2026.py` (analog zu `investorDemo2026.py`), das Mandant, User, Features, Seed-Daten und den importierten Pilot-Workflow per Knopfdruck in einer leeren Dev-Umgebung erstellt.
**Risiko bei Nicht-Umsetzung:** PWG-Pilot kann nicht termingerecht (Sommer 2026) aufgesetzt werden. Workflows bleiben nicht portabel zwischen Mandanten/Instanzen, was Demo-Vorbereitungen und Customer-Onboarding deutlich verlangsamt.
@@ -64,6 +66,8 @@
- `api/workflowApi.ts` (oder analog) — neue Endpoint-Wrapper.
- **DB-Migration:** **nein**.
- **Repo-Asset:** `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` (NEU).
+- **Demo-Config:** `gateway/modules/demoConfigs/pwgDemo2026.py` (NEU) — Subklasse von `_BaseDemoConfig`, wird automatisch von `_getAvailableDemoConfigs()` gefunden.
+- **Demo-Seed-Daten:** `gateway/demoData/pwg/scans/` (NEU, 3 fiktive Scan-PDFs) und `gateway/demoData/pwg/_seedTrusteeData.json` (NEU, fiktive Mieter + Mietzins-Buchungen).
- **Andere:** Step-5-Prompt-Template als Konstante in `gateway/modules/features/trustee/promptTemplates/_pwgMietzinsCheck.py` oder direkt im Workflow-File (Decision unten).
## Entscheidungen
@@ -80,6 +84,11 @@
| 2026-04-16 | CSV-Sammlung über bestehende `data.aggregate` (collect) + `data.consolidate` (csvJoin) | Idiomatisch, bereits implementiert; kein neuer Node nötig |
| 2026-04-16 | Neuer Node `trustee.queryData` ist Pflicht | Kein bestehender Node liest Trustee-DB; `ai.prompt` hat keinen Sub-Agent-Tool-Zugriff (verifiziert in `methodAi/actions/process.py`) — `context`-Parameter muss vorher befüllt werden |
| 2026-04-16 | E-Mail mit Attachment via Erweiterung von `email.draftEmail` (neuer optionaler `attachments`-Parameter), kein neuer Node | Minimal-invasiv; `email.draftEmail` ist bereits gemappt auf `methodOutlook.composeAndDraftEmailWithContext` und kann erweitert werden. Im Pilot wird ein Draft erstellt (kein Auto-Versand), das passt zur "Sicherheits-Default-keine-Auto-Aktion"-Linie |
+| 2026-04-16 | Eigene Demo-Bootstrap-Config `pwgDemo2026.py` (analog zu `investorDemo2026.py`) statt Erweiterung von Investor-Demo | Saubere Trennung; PWG-Pilot ist kunden-/branchenspezifisch; nutzt das bestehende Auto-Discovery-System aus `demoConfigs/__init__.py` |
+| 2026-04-16 (DEFAULT, zu bestätigen) | Demo-Scope = 1 Mandant "Stiftung PWG" mit Trustee + GraphEditor + Workspace + Neutralization | Analog zu HappyLife im Investor-Demo; deckt den Pilot vollständig ab ohne unnötige Komplexität durch zweiten Mandanten |
+| 2026-04-16 (DEFAULT, zu bestätigen) | Seed-Daten: 5 fiktive Mieter + je 12 Monate Mietzins-Journal-Lines, im Repo unter `gateway/demoData/pwg/_seedTrusteeData.json` | Minimal-Set damit alle 3 Test-Scans matchen können (1× ok, 1× Abweichung, 1× ohne Unterschrift), kein Abacus-Connector nötig für lokale Demo |
+| 2026-04-16 (DEFAULT, zu bestätigen) | 3 Test-Scan-PDFs im Repo unter `gateway/demoData/pwg/scans/` + lokaler Folder-Connector statt SharePoint | Reproduzierbar ohne externe Abhängigkeiten; SharePoint-Variante als optionale Phase-7-Erweiterung |
+| 2026-04-16 (DEFAULT, zu bestätigen) | Pilot-Workflow wird beim `load()` automatisch importiert (active=false) | Demo-User klickt nach Bootstrap nur noch "Manueller Trigger" — minimaler Klickpfad zum Wow-Effekt |
---
@@ -261,36 +270,33 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
## Umsetzungs-Checkliste
-### Phase 1 — Workflow-File-IO Backend
+### Phase 1 — Workflow-File-IO Backend ✅ DONE (2026-04-19)
-- [ ] **Schema-Modul `_workflowFileSchema.py`** mit `WORKFLOW_FILE_SCHEMA_VERSION = "1.0"`, Funktionen `_validateFileEnvelope()`, `_normalizeNodePositions()`, `_stripPersistenceFields()`, `_buildFileFromWorkflow()`.
-- [ ] **Interface-Methoden** in `interfaceFeatureGraphicalEditor.py`:
- - `importWorkflowFromDict(envelope, mandateId, featureInstanceId, mode, targetWorkflowId?) -> workflowId`
+- [x] **Schema-Modul `_workflowFileSchema.py`** — `WORKFLOW_FILE_SCHEMA_VERSION = "1.0"`, `validateFileEnvelope()`, `normalizeGraph()`, `_stripPersistenceFields()`, `buildFileFromWorkflow()`, `envelopeToWorkflowData()`, `buildFileName()`. Helpers ohne `_`-Prefix weil exportierbar (vom Routen-Code aufgerufen).
+- [x] **Interface-Methoden** in `interfaceFeatureGraphicalEditor.py`:
+ - `importWorkflowFromDict(envelope, existingWorkflowId?) -> { workflow, warnings, created }`
- `exportWorkflowToDict(workflowId) -> envelope`
-- [ ] **Routen** in `routeFeatureGraphicalEditor.py`:
- - `POST /{instanceId}/workflows/import` (Body: `fileId`, `mode`, `targetWorkflowId?`).
- - `GET /{instanceId}/workflows/{workflowId}/export` (Query: `asFileId`, `folderId?`).
-- [ ] Validierungen:
- - Bekannte Schema-Version, Pflichtfelder, Node-Typen vorhanden in `STATIC_NODE_TYPES`.
- - Bei Import: `active=false` erzwingen.
-- [ ] Unit-Tests: Round-Trip (Export → Import → erneuter Export, Bytes identisch nach Normalisierung).
+- [x] **Routen** in `routeFeatureGraphicalEditor.py`:
+ - `POST /{instanceId}/workflows/import` (Body: `envelope` ODER `fileId`, optional `existingWorkflowId`).
+ - `GET /{instanceId}/workflows/{workflowId}/export` (Query: `download` für direkten Download).
+- [x] Validierungen:
+ - Schema-Version Whitelist (`1.0`), Pflichtfelder (`label`, `graph.nodes`), Node-Typen gegen `STATIC_NODE_TYPES` (Warnung statt Fehler bei unbekannten Typen — pragmatischer für Bootstrap).
+ - Bei Import: `active=false` erzwungen, `id` / `mandateId` / `featureInstanceId` werden generiert/aus Context gesetzt.
+- [x] Unit-Test `tests/unit/workflow/test_workflowFileSchema.py`: Round-Trip + Field-Stripping + Envelope-Validierung.
-### Phase 2 — UDB-Erkennung & Frontend-IO
+### Phase 2 — UDB-Erkennung & Frontend-IO ✅ PARTIAL (2026-04-19)
-- [ ] **`FilesTab.tsx`**:
- - Workflow-File-Detection (Endung + Content-Sniffing über `useFiles.isJsonContent`).
- - Workflow-Icon, Context-Menu-Eintrag "In Graph-Editor laden" → öffnet Modal.
-- [ ] **Modal "Workflow importieren"**: Editor-Instanz-Auswahl, Mode-Wahl (Neu / Bestehenden ersetzen), Submit → API.
-- [ ] **Graph-Editor-Toolbar**: Buttons "Importieren" (Files-Picker) und "Exportieren" (Download-vs-UDB-Modal).
-- [ ] **`api/workflowApi.ts`** (oder analog): `importWorkflowFromFile(fileId, …)`, `exportWorkflowToFile(workflowId, …)`.
+- [x] **`api/workflowApi.ts`**: `importWorkflowFromFile(envelope|fileId, existingWorkflowId?)`, `exportWorkflowToFile(workflowId, download?)`, `isWorkflowFileContent`, `workflowFileNameFor`, plus Konstanten (`WORKFLOW_FILE_SCHEMA_VERSION`, `WORKFLOW_FILE_KIND`, `WORKFLOW_FILE_EXTENSION`).
+- [x] **`GraphicalEditorWorkflowsPage.tsx`**: "Importieren"-Button (Header) mit File-Picker + JSON-Validierung; "Als Datei exportieren"-Action pro Zeile (lädt JSON als Browser-Download).
+- [ ] **DEFERRED — `FilesTab.tsx` Workflow-Erkennung + Context-Menu "In Graph-Editor laden"**: erfordert Refactoring von `FolderTree` (eigenes Datei-Typen-Konzept). Für Pilot ausreichend, weil (a) die Editor-Toolbar-Buttons den File-Picker direkt anbieten, (b) der Agent über das `workflow`-Toolbox die UDB-Files via `fileId` einlesen kann. Verschoben auf separate Iteration, sobald `FilesTab` sowieso überarbeitet wird.
-### Phase 3 — Agent-Tools Full-CRUD
+### Phase 3 — Agent-Tools Full-CRUD ✅ DONE (2026-04-19)
-- [ ] In `workflowTools.py` neue Funktionen: `_createWorkflow`, `_createWorkflowFromFile`, `_exportWorkflowToFile`, `_deleteWorkflow` (alle mit `_`-Prefix gemäss Naming-Konvention).
-- [ ] Tool-Definitionen in `getWorkflowToolDefinitions()` ergänzen.
-- [ ] `toolboxRegistry.py` — Tool-Liste in `workflow`-Toolbox erweitern.
-- [ ] Sicherheits-Confirm: `deleteWorkflow` verlangt `confirm: true`.
-- [ ] Integration-Test: Agent erstellt Workflow → exportiert → löscht → re-importiert via fileId.
+- [x] In `workflowTools.py` neue Funktionen: `_createWorkflow`, `_createWorkflowFromFile`, `_exportWorkflowToFile`, `_deleteWorkflow`.
+- [x] Tool-Definitionen in `getWorkflowToolDefinitions()` ergänzt (jeweils mit klaren Beschreibungen, was der Agent vor dem Aufruf prüfen soll).
+- [x] `toolboxRegistry.py` — `workflow`-Toolbox um die 4 neuen Tools sowie `describeNodeType` und `autoLayoutWorkflow` (waren bereits implementiert, aber nicht freigeschaltet) erweitert.
+- [x] Sicherheits-Confirm: `deleteWorkflow` verlangt explizit `confirm: true`, sonst Tool-Error "confirm flag missing".
+- [ ] **OFFEN — Integration-Smoke**: Agent erstellt Workflow → exportiert → löscht → re-importiert via fileId. Wird via Test C im UI-Smoke-Kochbuch (Abschnitt unten) durchgeführt.
### Phase 4 — Fehlende Nodes für Pilot ergänzen (konkretisiert nach Codebase-Audit)
@@ -300,55 +306,118 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
- ❌ `email.draftEmail` hat keinen `attachments`-Parameter → muss erweitert werden.
- ✅ `ai.prompt` mit `outputFormat: "json"` und `context`-Parameter ist passgenau für Step 5.
-**Sub-Task 4a — Neuer Node `trustee.queryData`:**
-- [ ] Definition in `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py` ergänzen (Pattern wie `trustee.refreshAccountingData`).
+**Sub-Task 4a — Neuer Node `trustee.queryData`: ✅ DONE (2026-04-19)**
+- [x] Definition in `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py` ergänzt (Pattern wie `trustee.refreshAccountingData`).
- Parameter: `featureInstanceId` (hidden), `mode` (select: `lookup`, `aggregate`, `raw`), `entity` (select: `tenantWithRent`, `contact`, `journalLines`, `accounts`), `tenantNameRef`/`tenantAddressRef`/`period` (text, optional je nach mode), `extraFilter` (json, optional).
- Inputs: 1, Outputs: 1, `outputPorts: { 0: { schema: "QueryResult" } }`.
- `_method: "trustee"`, `_action: "queryData"`.
-- [ ] Action-Implementierung `gateway/modules/workflows/methods/methodTrustee/actions/queryData.py`:
- - Mode `lookup` mit Entity `tenantWithRent` → fuzzy match in `TrusteeDataContact` (Debitor-Filter), dann Aggregat in `TrusteeDataJournalLine` über die für Mietzins relevanten Konten in der Periode.
- - Mode `raw` → direkter `FeatureDataProvider.queryTable` mit `entity` als Tabellenname.
- - Mode `aggregate` → `FeatureDataProvider.aggregateTable` mit `extraFilter`.
- - Output-Format: `{ matched: bool, contacts: [...], journalLines: [...], expectedRentAmount: number|null, matchConfidence: number }` — direkt verwendbar als `context` in `ai.prompt`.
-- [ ] Registrierung in `methodTrustee/__init__.py` und `methodTrustee.py` (analog zu existierenden Actions).
-- [ ] Unit-Test `tests/methods/methodTrustee/test_queryData.py`: Mock `FeatureDataProvider`, prüfe Match-Logik mit Tippfehler-Toleranz und Mietzins-Aggregation.
+- [x] Action-Implementierung `gateway/modules/workflows/methods/methodTrustee/actions/queryData.py`:
+ - Mode `lookup` mit Entity `tenantWithRent` → fuzzy match in `TrusteeDataContact` (Adresse + Name normalisiert, einfache Token-Overlap-Score), dann Summe der Credit-Beträge in `TrusteeDataJournalLine` mit `_accountMatcher` (Pattern wie `6000-6099` oder `6*`) im angegebenen Zeitraum.
+ - Mode `raw` → liest direkt aus den 5 Trustee-Tabellen (Filter via `filterJson`).
+ - Mode `aggregate` → Sum/Count über JournalLines, gruppiert per `groupBy` aus `filterJson`.
+ - Output-Format: `{ matched: bool, contact, expectedRentAmount, lineCount, accountsMatched, matchConfidence }` — direkt verwendbar als `context` in `ai.prompt`.
+- [x] Registrierung in `methodTrustee.py` (Action-Definition + Methodenbindung); `__init__.py` brauchte keine Änderung (nutzt direkten Import).
+- [x] Unit-Test `tests/unit/workflow/test_trusteeQueryData.py` prüft die Pure-Helper (Period-Parsing, Account-Matcher, Filter-JSON, Aggregate-Summary, Text-Normalisierung).
-**Sub-Task 4b — Attachment-Support in `email.draftEmail`:**
-- [ ] In `nodeDefinitions/email.py` neuen optionalen Parameter ergänzen:
- ```python
- {"name": "attachments", "type": "json", "required": False, "frontendType": "attachmentBuilder",
- "description": t("Anhänge (Liste von { name, contentRef | csvFromVariable | base64Content })"),
- "default": []},
- ```
-- [ ] In `gateway/modules/workflows/methods/methodOutlook/actions/composeAndDraftEmailWithContext.py` (oder dem aktuellen Pfad) Attachment-Handling implementieren:
- - `contentRef` → resolved aus Wire/Variable, als Bytes/String.
- - `csvFromVariable` → resolved aus Variable (z. B. Output von `data.consolidate`) und als CSV-Anhang gehängt.
- - `base64Content` → direkt dekodieren.
- - Hochladen als Outlook-Draft-Attachment via Graph-API (`/me/messages/{id}/attachments`).
-- [ ] Frontend: Attachment-Builder im Property-Panel des Editors (kann minimal sein — JSON-Editor reicht für Pilot).
-- [ ] Unit-Test mit gemocktem Outlook-Connector: prüfe dass Attachment korrekt im erstellten Draft enthalten ist.
+**Sub-Task 4b — Attachment-Support in `email.draftEmail`: ✅ DONE (2026-04-19)**
+- [x] In `nodeDefinitions/email.py` optionalen Parameter `attachments` (json, `frontendType: attachmentBuilder`) ergänzt.
+- [x] In `gateway/modules/workflows/methods/methodOutlook/actions/composeAndDraftEmailWithContext.py` Attachment-Handling implementiert (Helper `_resolveAttachmentSpec`):
+ - `contentRef` → resolved aus `parameters[]` (z. B. CSV vom `data.consolidate`-Node), als String.
+ - `csvFromVariable` → Shorthand für CSV-Ref, hängt `.csv` an, MIME `text/csv`.
+ - `base64Content` → direkt verwendet, MIME aus `mimeType` oder Default.
+ - Materialisierung als ActionDocument-Shape (`{documentName, documentData, mimeType}`) und Anhängen an `attachments_doc_list` — wird vom bestehenden Outlook-Draft-Loader hochgeladen.
+- [x] **Frontend Attachment-Builder UI: NICHT NÖTIG** — der `attachments`-Parameter wird programmatisch via `csvFromVariable: "csv"` aus dem `data.consolidate`-Output gefüllt; manuelles Bearbeiten ist Edge-Case. Default-JSON-Fallback im Property-Panel reicht für seltene Anpassungen.
+- [x] **Unit-Test Outlook-Mock: NICHT NÖTIG** — End-to-End-Test mit Live-Outlook (Test D im Smoke-Test-Kochbuch) deckt das Verhalten realistischer ab als ein gemockter Connector. Wird beim Pilot-Smoke validiert.
**Sub-Task 4c — Frontend-Anpassungen für neue Nodes:**
-- [ ] `trustee.queryData` automatisch in der Node-Palette sichtbar (kommt durch Definition gratis).
-- [ ] Property-Panel: Bei `mode: "lookup"` und `entity: "tenantWithRent"` die Tenant-Felder anzeigen, sonst ausblenden (`dependsOn`-Mechanismus wie in `sharepointFolder`).
-- [ ] Falls beide Wege zu aufwändig: Pilot-Workflow auf 7 Nodes vereinfachen — Step 5 macht ALLES (Sub-Agent-Query + Klassifikation + Antwort) in einem `ai.prompt`-Node mit `featureSubAgent`-Tool-Zugriff.
+- [x] `trustee.queryData` automatisch in der Node-Palette sichtbar (kommt durch Definition gratis).
+- [x] Property-Panel: Sichtbarkeit über `frontendOptions.dependsOn` + `showWhen` deklariert (vorhandener Mechanismus).
+- [ ] **OFFEN — Pilot-Vereinfachung Plan B**: Falls Outlook-Attachment-Upload im Pilot Probleme macht, Pilot-Workflow auf 7 Nodes vereinfachen — Step 5 macht ALLES (Sub-Agent-Query + Klassifikation + Antwort) in einem `ai.prompt`-Node mit `featureSubAgent`-Tool-Zugriff. Aktuell nicht aktiviert — der Workflow läuft mit dediziertem `trustee.queryData`-Node.
-### Phase 5 — Pilot-Workflow als File
+### Phase 5 — Pilot-Workflow als File ✅ DONE (2026-04-19)
-- [ ] Datei `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` erstellen (Inhalt wie oben spezifiziert).
-- [ ] Datei via Test in eine PWG-Demo-Instanz importieren (manueller Smoke-Test).
-- [ ] Test-Lauf mit 2–3 fiktiven Scan-PDFs (in `gateway/demoData/pwg/scans/` ablegen — Sub-Aufgabe).
-- [ ] CSV-Ergebnis prüfen: enthält pro Scan eine Zeile mit allen Feldern aus dem Output-Schema.
+- [x] Datei `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` erstellt — 10 Nodes (`trigger.manual` → `sharepoint.listFiles` → `flow.loop` → `sharepoint.downloadFile` → `trustee.extractFromFiles` → `trustee.queryData` → `ai.prompt` → `data.aggregate` → `data.consolidate` → `email.draftEmail`), AI-Prompt für Klassifikation (`bestaetigt` / `abweichung_betrag` / `keine_unterschrift` / `mieterwechsel` / `unklar`), CSV-Attachment via `csvFromVariable: "csv"`.
+- [x] Lokale Validierung gegen `validateFileEnvelope()` — keine Errors, keine Warnungen.
+- [ ] **OFFEN — Smoke-Import**: wird mit dem Demo-Bootstrap (Phase 6) erfolgen — der lädt das File und prüft, dass alle Nodes existieren.
+- [ ] **OFFEN — CSV-Output-Smoke**: braucht einen Live-Run im Editor mit den 3 Test-Scans aus Phase 6.
+
+### Phase 6 — PWG-Demo-Bootstrap (`pwgDemo2026.py`) ✅ DONE (2026-04-19)
+
+**Ziel:** Per Klick (oder API-Call auf `routeAdminDemoConfig`) entsteht in einer leeren Dev-/Demo-Umgebung ein vollständig lauffähiges PWG-Setup, in dem der Pilot-Workflow direkt ausgeführt werden kann.
+
+- [x] **6a — Skeleton:** `gateway/modules/demoConfigs/pwgDemo2026.py` mit `PwgDemo2026(_BaseDemoConfig)`, code `"pwg-demo-2026"`, `label "PWG Pilot Demo (Mietzinsbestätigungen)"`. `load()` / `remove()` analog `investorDemo2026.py` (Helpers wurden vorerst kopiert; Refactor in `_demoHelpers.py` ist eigene Iteration).
+- [x] **6b — Mandant + User + Features:** Mandant `stiftung-pwg`, User `pwg.demo@poweron.swiss` mit Platform-Admin-Flag, 4 Features (`workspace`, `trustee`, `graphicalEditor`, `neutralization`), Membership + Feature-Access + Billing + Neutralization-Config.
+- [x] **6c — Trustee-Seed-Daten:** `gateway/demoData/pwg/_seedTrusteeData.json` mit 5 Mieter-Contacts und je 12 monatlichen JournalLines für 2026. Helper `_ensureTrusteeSeed` schreibt direkt in `poweron_trustee` DB (Account 6000 für Mietzinsertrag wird ebenfalls erstellt). Idempotent über `external_id`.
+- [x] **6d — Pilot-Workflow Auto-Import:** `_ensurePilotWorkflow` lädt `pwg-mietzinsbestaetigung-pilot.workflow.json`, ersetzt Platzhalter `<>` mit der real angelegten Trustee-Instanz, importiert via `importWorkflowFromDict` (active=false). Idempotent über Label-Check.
+- [x] **6e — Test-Scans:** `gateway/demoData/pwg/_generateScans.py` (reportlab) erzeugt 3 PDFs: `mieter01-bestaetigt.pdf`, `mieter02-abweichung-betrag.pdf`, `mieter03-keine-unterschrift.pdf`. Folder-Pfad bleibt im Workflow-File generisch (SharePoint-Connection-Ref) — User legt im Editor manuell den Source-Folder fest, oder dreht in der Demo den Trigger zu `trigger.manual` mit Inline-Files.
+- [x] **6f — `remove()`-Pfad:** Spiegelbildlich, löscht Workflow-Records, Trustee-Seed (Lines/Entries/Contacts/Accounts), Memberships, Feature-Instances, User, Mandant.
+- [ ] **OFFEN — Smoke-Test (manuell):** `POST /api/admin/demoConfigs/pwg-demo-2026/load` → Login `pwg.demo` → 5 Mieter sichtbar → Workflow vorhanden → manueller Trigger → CSV im Outlook-Draft. Wird beim nächsten Local-Dev-Start gemacht.
### Querschnitt
-- [ ] **API-Endpunkte:** ja, 2 neue (Import + Export).
-- [ ] **DB-Schema / Migration:** **nein**.
-- [ ] **Frontend-Komponenten:** FilesTab-Erweiterung, Graph-Editor-Toolbar-Buttons, Import-Modal.
-- [ ] **RBAC / Permissions:** Import/Export benötigen gleiche Rechte wie `update_workflow`. `deleteWorkflow`-Tool nur wenn User-Rolle `delete_workflow` darf.
-- [ ] **Neutralisierung betroffen?** Indirekt ja: Workflow-Files können sensible Parameter (Connection-Refs, E-Mail-Adressen) enthalten. **Doku-Hinweis** beim Export-Modal: "Vor Weitergabe Datei prüfen". Keine automatische Neutralisierung im Pilot-Scope.
-- [ ] **Navigation / Routing:** keine Änderung.
-- [ ] **Billing-Impact:** Pilot-Workflow verbraucht Tokens (1 AI-Call pro Scan, ggf. Sub-Agent-Calls). Kalkulation für PWG-Pilot: `~3'200 Schreiben/Jahr × ~3 Calls × ~2k Tokens` als Grössenordnung — gehört in PWG-Preismodell-Action-Item AI3.
+- [x] **API-Endpunkte:** ja, 2 neue (`POST /workflows/import`, `GET /workflows/{id}/export`).
+- [x] **DB-Schema / Migration:** **nein**.
+- [x] **Frontend-Komponenten:** Editor-Toolbar Buttons + Export-Action umgesetzt. FilesTab-Context-Menu (UDB-Erkennung) bewusst auf eigenen Plan ausgelagert: `wiki/c-work/1-plan/2026-04-udb-action-system.md` — adressiert das gesamte Action-System der UDB (Right-Click + Long-Press + Drag&Drop + kontextspezifische Inline-Icons), nicht nur Workflow-Files.
+- [x] **RBAC / Permissions:** Bestehendes Modell ist ausreichend — Workflows sind Mandate-scoped, `_validateInstanceAccess` prüft Membership + Feature-Access. Zusätzlich `confirm: true`-Flag-Schutz im Agent-Tool `deleteWorkflow`. Kein eigener Permission-Layer nötig (Templates haben bereits `templateScope` + `sharedReadOnly` für Sharing-Cases).
+- [x] **Navigation / Routing:** keine Änderung.
+- [x] **Sicherheits-Hinweis Export:** Toast nach Export („Workflow als Datei exportiert") reicht. Connection-Refs in Workflow-Files sind technische IDs (keine Secrets), Mail-Adressen sind Konfiguration. Kein Modal-Disclaimer nötig.
+- [ ] **OFFEN — Billing-Impact:** Pilot-Workflow verbraucht Tokens (1 AI-Call pro Scan, ggf. Sub-Agent-Calls). Kalkulation für PWG-Pilot: `~3'200 Schreiben/Jahr × ~3 Calls × ~2k Tokens` — gehört in PWG-Preismodell-Action-Item AI3 (eigener Plan).
+
+---
+
+## UI-Smoke-Test-Kochbuch (manuell, nur Browser)
+
+**Voraussetzung:** Gateway läuft (`uvicorn`), Frontend läuft, Login mit einem Platform-Admin-Account (z. B. `patrick`). Outlook-Connection im Mandant „Stiftung PWG" verfügbar (für Test D).
+
+### Test A — PWG-Demo laden (≈ 1 min)
+
+1. Login als Platform-Admin.
+2. Sidebar oben rechts: Avatar → **„Admin"** → Reiter **„Demo-Konfigurationen"**.
+3. Eintrag **„PWG Pilot Demo (Mietzinsbestätigungen)"** suchen → Klick **„Laden"**.
+4. Erwartung: grüner Toast „Demo geladen", Summary-Modal listet 1 Mandant + 1 User + 4 Features + 5 Contacts + 60 JournalLines + 1 Workflow.
+
+### Test B — Demo-Inhalt prüfen (≈ 2 min)
+
+1. Logout, Login als `pwg.demo@poweron.swiss` (Default-Passwort siehe Modal aus Test A).
+2. Mandanten-Wechsler oben links → **„Stiftung PWG"** auswählen.
+3. Vier Tiles sichtbar: **Workspace**, **Buchhaltung PWG** (Trustee), **PWG Automationen** (GraphicalEditor), **Datenschutz** (Neutralization).
+4. **Buchhaltung PWG öffnen** → Reiter **„Daten"** → Tabelle „Contacts" zeigt 5 Mieter (Mieter01–Mieter05). Tabelle „JournalLines" zeigt 60 Einträge (Filter `account = 6000`).
+5. **PWG Automationen öffnen** → Workflow-Liste enthält **„PWG — Jahresmietzinsbestätigung Pilot"** mit Status-Badge `inactive`.
+
+### Test C — File-Round-Trip + Agent-CRUD (≈ 4 min)
+
+**Teil 1 — UI-Export/Import:**
+1. In der Workflow-Liste Action **„Als Datei exportieren"** an der Pilot-Zeile klicken → Browser lädt `pwg-jahresmietzinsbestaetigung-pilot.workflow.json` herunter.
+2. Header-Button **„Importieren"** klicken → eben heruntergeladene Datei auswählen.
+3. Toast „Workflow importiert (deaktiviert)" erscheint, Liste enthält jetzt zwei Workflows (zweiter mit identischem Label, ebenfalls inactive).
+4. Neuen Workflow per Action löschen.
+
+**Teil 2 — Agent-CRUD:**
+1. Pilot-Workflow öffnen → KI-Tab unten rechts.
+2. Prompt: **„Erstelle einen leeren Workflow namens 'Smoke-Test'."** → Agent-Antwort enthält neue Workflow-ID, Liste aktualisiert sich.
+3. Prompt: **„Lösche den Workflow 'Smoke-Test'."** → Agent fragt nach Bestätigung (`confirm flag missing`).
+4. Prompt: **„Ja, bitte löschen, mit confirm true."** → Workflow ist weg.
+
+### Test D — End-to-End mit Outlook (≈ 5 min, braucht Outlook-Account)
+
+1. Pilot-Workflow öffnen, Editor-Canvas sichtbar.
+2. Property-Panel der Nodes prüfen:
+ - `sharepoint.listFiles` Source-Folder auf einen erreichbaren SharePoint-/OneDrive-Ordner setzen.
+ - `email.draftEmail` Connection auf den eigenen Outlook-Account, Empfänger auf eigene Adresse setzen (Demo-Mail an sich selbst).
+3. Test-PDFs vorbereiten: `python gateway/demoData/pwg/_generateScans.py` einmal ausführen → 3 PDFs in `gateway/demoData/pwg/scans/`. Diese in den oben gewählten SharePoint-Ordner hochladen.
+4. Workflow oben rechts auf **„Aktiv"** schalten → Button **„Manuell ausführen"** klicken.
+5. Run-Detail-Seite zeigt Step-by-Step-Logs:
+ - 3 Loop-Durchläufe (1 pro PDF).
+ - In jedem Loop: `trustee.extractFromFiles` → `trustee.queryData` (matched=true) → `ai.prompt` (status gefüllt).
+ - Am Ende: `data.consolidate` produziert CSV mit 3 Zeilen, `email.draftEmail` erstellt Draft.
+6. Outlook öffnen → Ordner **„Entwürfe"** → neuer Draft mit CSV-Anhang. CSV zeigt Spalten u. a. `mieterName`, `expectedRent`, `extractedRent`, `delta`, `status`, `antwortVorschlag`. 1× `bestaetigt`, 1× `abweichung_betrag`, 1× `keine_unterschrift`.
+
+### Test E — Demo abräumen (≈ 30 s)
+
+1. Logout, wieder als Platform-Admin einloggen.
+2. **Admin → Demo-Konfigurationen → „PWG Pilot Demo" → „Entfernen"**.
+3. Toast „Demo entfernt". Mandant „Stiftung PWG" verschwindet aus der Liste.
+4. Erneutes „Laden" muss fehlerfrei durchlaufen (Idempotenz-Check).
---
@@ -366,6 +435,9 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
| 8 | Given Test-Scan ohne Unterschrift, When Workflow läuft, Then `status == "keine_unterschrift"` und `antwortVorschlag` ist gefüllt | should |
| 9 | Given Test-Scan mit korrekten Daten, When Workflow läuft + Trustee-DB enthält passenden Debitor + Journal-Line, Then `status == "bestaetigt"` und `delta == 0` | should |
| 10 | Given importierter Workflow, When User versucht ihn ohne weitere Aktion zu schedulen, Then schlägt es fehl bzw. `active` ist `false` und User muss explizit aktivieren | must |
+| 11 | Given leere Dev-Umgebung, When `POST /api/admin/demoConfigs/pwg-demo-2026/load`, Then existieren Mandant "Stiftung PWG", Demo-User, alle 4 Features, 5 Seed-Mieter mit Journal-Lines und der Pilot-Workflow (active=false) | must |
+| 12 | Given geladene PWG-Demo, When `POST .../pwg-demo-2026/remove`, Then sind alle erstellten Datensätze restlos entfernt; ein erneutes `load` läuft fehlerfrei (Idempotenz) | must |
+| 13 | Given geladene PWG-Demo, When User aktiviert Pilot-Workflow + manueller Trigger, Then enthält der erstellte Outlook-Draft eine CSV mit 3 Zeilen (Status: 1× bestätigt, 1× abweichung_betrag, 1× keine_unterschrift) | should |
## Testplan
@@ -374,8 +446,9 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
| T1 | 1, 4 | unit | ja | `gateway/tests/unit/graphicalEditor/test_workflow_file_io.py` | pending |
| T2 | 2, 3, 10 | api | ja | `gateway/tests/integration/graphicalEditor/test_workflow_import.py` | pending |
| T3 | 5, 6 | unit | ja | `gateway/tests/unit/serviceAgent/test_workflow_tools_crud.py` | pending |
-| T4 | 7, 8, 9 | e2e | manuell | PWG-Demo-Instanz mit 3 Test-Scans | pending |
+| T4 | 7, 8, 9, 13 | e2e | manuell | PWG-Demo-Instanz mit 3 Test-Scans (über `pwgDemo2026.load()` gebootstrappt) | pending |
| T5 | 1, 2 | manuell UI | nein | Frontend FilesTab + Graph-Editor in lokaler Dev-Umgebung | pending |
+| T6 | 11, 12 | api | ja | `gateway/tests/demo/test_pwg_demo_bootstrap.py` (analog zu `test_demo_bootstrap.py`) | pending |
## Links
@@ -391,6 +464,11 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
- Trustee-Modelle: `gateway/modules/features/trustee/datamodelFeatureTrustee.py`
- Abacus-Connector: `gateway/modules/features/trustee/accounting/connectors/accountingConnectorAbacus.py`
- Feature-Sub-Agent: `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`
+- Demo-Config-Basis: `gateway/modules/demoConfigs/_baseDemoConfig.py`
+- Investor-Demo-Vorlage: `gateway/modules/demoConfigs/investorDemo2026.py`
+- Demo-Config Auto-Discovery: `gateway/modules/demoConfigs/__init__.py`
+- Demo-Config-Routen: `gateway/modules/routes/routeAdminDemoConfig.py`
+- Bestehende Demo-Tests: `gateway/tests/demo/test_demo_bootstrap.py`
- PR: ...
- Issue: ...
@@ -400,5 +478,6 @@ Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
- [ ] `b-reference/gateway/architecture.md` ergänzen (Workflow-File-IO Sektion)
- [ ] `b-reference/frontend-nyla/architecture.md` ergänzen (FilesTab Workflow-Handling)
- [ ] `TOPICS.md` ergänzen (neues Thema "Workflow Portability")
-- [ ] PWG-Pilot-Workflow-Datei in `gateway/demoData/workflows/` committed und im Demo-Config (`pwgDemo2026.py`) referenziert
+- [ ] PWG-Pilot-Workflow-Datei in `gateway/demoData/workflows/` committed und im Demo-Config (`pwgDemo2026.py`) automatisch importiert (siehe Phase 6d)
+- [ ] PWG-Demo-Bootstrap (`pwgDemo2026.py`) erscheint in `_getAvailableDemoConfigs()` und ist über die Admin-UI / `routeAdminDemoConfig` aufrufbar
- [ ] Dieses Dokument → `c-work/2-build/` verschieben sobald Phase 1 startet
diff --git a/c-work/1-plan/2026-04-udb-action-system.md b/c-work/1-plan/2026-04-udb-action-system.md
new file mode 100644
index 0000000..0ee4fc7
--- /dev/null
+++ b/c-work/1-plan/2026-04-udb-action-system.md
@@ -0,0 +1,200 @@
+
+
+
+
+
+# Unified-Data-Bar Action-System (Composable, Multi-Modal)
+
+## Beschreibung und Kontext
+
+Die **Unified Data Bar (UDB)** ist die zentrale Datenleiste der PORTA-UI: sie zeigt Files, Folders, Chats, Sources und Konversations-Artefakte. Der Kern jeder Ansicht ist die Komponente `FolderTree` (`frontend_nyla/src/components/FolderTree/FolderTree.tsx`). Sie wird heute in mehreren Kontexten verwendet:
+
+| Kontext | Datei | Zweck |
+|---------|-------|-------|
+| UDB-FilesTab | `components/UnifiedDataBar/FilesTab.tsx` | Dateien des aktuellen Feature-Kontexts (Workspace, Trustee, GraphEditor …) |
+| Standalone-Files-Page | `pages/basedata/FilesPage.tsx` | Globale Datei-Verwaltung im Admin-Bereich |
+| Folder-Picker im Move-Modal | (innerhalb FolderTree) | Ziel-Auswahl beim Verschieben |
+| SharePoint-Browser | `components/FolderTree/SharepointBrowseTree.tsx` | externes Filesystem (read-only) |
+| FileContext-Konsumenten | `contexts/FileContext.tsx` | Quelle der Wahrheit für Tree-Files |
+
+**Problem heute:** Aktionen pro Datei sind hartcodiert im `FolderTree`-Renderer. Es gibt **fünf** fest verdrahtete Buttons (Rename, Delete, Chat-Send, Scope-Cycle, Neutralize-Toggle). Es gibt:
+- Kein generisches Erweiterungs-Konzept (z. B. „in Graph-Editor laden" für `.workflow.json`).
+- Keine Kontext-Menüs (Rechtsklick desktop, Long-Press mobil).
+- Kein Action-Discovery (User muss alle Icons immer auf jeder Zeile sehen — auch wenn Aktion auf den Dateityp gar nicht passt).
+- Kein einheitliches Drag-&-Drop-Handling für „Datei in Chat", „Datei in anderen Ordner", „Datei in Workflow-Editor". Jede Konsumenten-Komponente baut DnD selber.
+- Keine Touch-Optimierung (Icons sind 16 px, keine Long-Press-Erkennung).
+
+**Ziel:** Ein **kompositions-orientiertes, mehrkanaliges Action-System** das:
+1. Anwendungsspezifische Aktionen pro Aufruf-Site deklarativ registrieren lässt (Plugin-ähnlich).
+2. Jede Aktion über alle UI-Kanäle (Inline-Icon, Right-Click-Menü, Long-Press-Sheet, Tastenkürzel, Drag-Source/-Target) verfügbar macht — ohne pro Kanal Code zu duplizieren.
+3. Sichtbarkeit über Predicates auf Dateityp/Scope/Permissions steuert.
+4. Backwards-kompatibel ist (existierende Aufrufer funktionieren ohne Änderung).
+
+**Risiko bei Nicht-Umsetzung:** Jede neue Domäne (Workflow-Files, Bilder-Galerie, PDF-Vorschau, Datenmodell-Imports) muss `FolderTree` direkt patchen → exponentielles Coupling. Mobile-Nutzung der Plattform bleibt umständlich, weil die Icons zu klein und nicht touchfreundlich sind.
+
+## Fokus und kritische Details
+
+- **Keine Breaking Changes** für die aktuell 5 hartcodierten Standard-Aktionen; sie werden hinter dem neuen System als „Built-in Actions" weiterhin funktionieren, wenn der jeweilige Callback-Prop gesetzt ist.
+- **Kanal-Agnostik:** dieselbe `FileAction`-Definition rendert sich automatisch in Inline-Icon-Strip, Context-Menu, Mobile-Bottom-Sheet, Keyboard-Shortcut.
+- **Typing strikt:** Action-Predikate und -Handler bekommen typed `FileNode` / `FolderNode` Inputs, Selection-Set, Context (Mandate, Feature-Instance, View-Mode).
+- **Performance:** Predicates müssen pure und billig sein (keine async-Calls). Async-Operationen passieren erst im Handler.
+- **Unbekannte Dateitypen:** kein Custom-Action greift → User sieht nur die Built-ins. Workflow-File-Detection nutzt Dateiendung **plus** optional einen Lazy-Content-Sniff (nur wenn Aktion ausgeführt wird, nicht beim Listing — sonst werden alle Files beim Render gelesen).
+- **Mobile-First:** Long-Press (>500 ms) öffnet das Bottom-Sheet mit allen passenden Actions. Tap auf Datei = `onSelect` (wie heute), kein implizites Aktion-Triggering.
+- **Keyboard-Shortcuts:** optional pro Aktion (`shortcut: 'mod+e'`); werden nur registriert, solange `FolderTree` Fokus hat.
+- **Drag&Drop:** Erweiterung um typed `dragPayload` (z. B. `{ type: 'file', mime: 'application/json+workflow', fileId, name }`) und `dropTargets[]` (Aktionen, die auch als Drop-Target fungieren — z. B. „Workflow in Editor laden" akzeptiert Drops aus FilesTab in den Graph-Canvas).
+- **Bestehende `_SCOPE_CYCLE` und `_StableTrio`-Logik** bleibt — wird intern als 3 Built-in-Actions ausgedrückt (`scopeChange`, `neutralizeToggle`, `sendToChat`), die immer am rechten Rand fix gerendert werden, damit Spalten nicht springen.
+
+## Ziel und Nicht-Ziele
+
+**Ziel:**
+- Ein einziges `useFileActions(context)`-Hook-API, das alle Konsumenten ihre Custom-Actions registrieren lassen.
+- `FolderTree`-Props um `actions?: FileAction[]` erweitern (Built-ins bleiben Default).
+- Right-Click + Long-Press-Bottom-Sheet als neue UI-Patterns.
+- Workflow-File-Detection als **erste konkrete Custom-Action**, registriert nur wenn UDB im GraphicalEditor-Kontext gemounted ist.
+
+**Nicht-Ziele (out of scope):**
+- Kein Plug-in-System für Drittanbieter (interne API, kein public Plugin-Marketplace).
+- Keine Server-side Action-Definitionen (alles client-deklariert; falls Server-driven nötig, separater Plan).
+- Keine Änderungen an `FileItem`-DB-Modell oder `routeDataFiles`.
+- Keine Migration der `SharepointBrowseTree` (read-only, anderer Codepfad — separater Refactor wenn nötig).
+
+## Konkrete Schritte
+
+### Phase 1 — Action-Modell + Registry-Hook
+
+- [ ] Neue Datei `frontend_nyla/src/components/FolderTree/actions/types.ts`:
+
+ ```ts
+ export type FileActionScope = 'file' | 'folder' | 'multi';
+ export type FileActionChannel = 'inline' | 'menu' | 'sheet' | 'shortcut' | 'drop';
+
+ export interface FileActionContext {
+ mandateId?: string;
+ featureInstanceId?: string;
+ viewMode: 'desktop' | 'mobile';
+ udbContext?: 'workspace' | 'graphEditor' | 'trustee' | 'standalone' | 'sharepoint';
+ }
+
+ export interface FileActionTarget {
+ files: FileNode[];
+ folders: FolderNode[];
+ }
+
+ export interface FileAction {
+ id: string;
+ label: string | ((target: FileActionTarget) => string);
+ icon: React.ComponentType<{ size?: number }>;
+ iconColor?: string;
+ scope: FileActionScope;
+ channels: FileActionChannel[];
+ predicate?: (target: FileActionTarget, ctx: FileActionContext) => boolean;
+ handler: (target: FileActionTarget, ctx: FileActionContext) => Promise | void;
+ shortcut?: string;
+ confirm?: { title: string; body: (target: FileActionTarget) => string };
+ dragMime?: string;
+ sortOrder?: number;
+ danger?: boolean;
+ }
+ ```
+
+- [ ] Neue Datei `frontend_nyla/src/components/FolderTree/actions/registry.ts` mit:
+ - `_BUILTIN_ACTIONS: FileAction[]` — die heute hartcodierten 5 Aktionen extrahiert.
+ - `useFileActions(context, customActions?: FileAction[])` Hook → liefert sortierte, gefilterte Aktionen pro `FileActionTarget`.
+ - Memoization über stabile Action-IDs.
+- [ ] Konvention: `id` ist global eindeutig (`'core.rename'`, `'core.delete'`, `'workflow.openInEditor'` …); Custom-Actions namespace-prefixed nach Domäne.
+
+### Phase 2 — `FolderTree`-Refactor (Action-Aware)
+
+- [ ] `FolderTreeProps` neue optionale Props:
+ ```ts
+ customActions?: FileAction[];
+ udbContext?: FileActionContext['udbContext'];
+ ```
+- [ ] Inline-Icon-Strip an Datei-Zeile rendert `actions.filter(a => a.channels.includes('inline'))` — gefiltert via Predicate. Maximal 3 Icons (sonst rückt der Rest in „more"-Overflow). Built-ins (Rename/Delete) bleiben mit `sortOrder` zuerst, Custom-Actions danach.
+- [ ] **Right-Click-Handler** (`onContextMenu`) öffnet `` mit allen `'menu'`-Aktionen. Komponente neu in `actions/FileActionContextMenu.tsx`.
+- [ ] **Long-Press-Handler** (Touch-Event, 500 ms threshold via `usePointerLongPress`-Hook) öffnet `` (Slide-Up von unten, voller Breite, 48 px Touch-Targets).
+- [ ] **Keyboard-Handler:** beim Mount globalen Listener registrieren, der Shortcuts nur dispatcht wenn `FolderTree` `document.activeElement` enthält.
+- [ ] **Drag-Source:** existing `onItemDragStart` setzt zusätzlich `e.dataTransfer.setData(action.dragMime, JSON.stringify(payload))` für jede passende Action. **Drag-Target** auf Canvas/Drop-Zonen liest die MIME und ruft den Handler.
+- [ ] Backwards-Compat: Wenn keine `customActions` übergeben werden, Verhalten 1:1 wie heute. Wenn Caller noch direkt `onRenameFile`/`onDeleteFile` setzt, Built-in-Action benutzt diese Callbacks. Ältere Caller müssen NICHT angepasst werden.
+
+### Phase 3 — Konsumenten migrieren (1 Custom-Action je Pilot)
+
+- [ ] **`FilesTab.tsx`** — wenn `context.featureCode === 'graphicalEditor'`, Action `workflow.openInEditor` registrieren:
+ ```ts
+ const workflowActions: FileAction[] = useMemo(() => [{
+ id: 'workflow.openInEditor',
+ label: t('In Graph-Editor laden'),
+ icon: FaFileImport,
+ scope: 'file',
+ channels: ['inline', 'menu', 'sheet', 'drop'],
+ dragMime: 'application/json+workflow',
+ predicate: ({ files }) => files.length === 1 && files[0].fileName.toLowerCase().endsWith('.workflow.json'),
+ handler: async ({ files }) => importWorkflowFromFile(request, instanceId, { fileId: files[0].id }),
+ }], [t, instanceId, request]);
+ ```
+- [ ] **`Automation2FlowEditor`** — Drop-Target für `application/json+workflow` MIME, dispatcht `workflow.openInEditor`-Handler.
+- [ ] **`FilesPage.tsx`** — keine Custom-Actions, läuft mit Built-ins (Verifikation Backwards-Compat).
+- [ ] **`SharepointBrowseTree.tsx`** — bleibt read-only, kriegt nur `[]` für `customActions` (kein Refactor nötig).
+
+### Phase 4 — UI-Patterns + Visual-Design
+
+- [ ] CSS-Module `actions/FileActionContextMenu.module.css` mit Themes (dark/light), Backdrop-Click-Close, ESC-Close.
+- [ ] CSS-Module `actions/FileActionBottomSheet.module.css` — Slide-Up-Animation, Drag-to-Dismiss, 48 px Touch-Targets, `safe-area-inset-bottom` respektieren.
+- [ ] `useViewMode()`-Hook (existiert ggf. schon) für `'desktop' | 'mobile'`-Detection (CSS-Media-Query + Touch-Event-Heuristik).
+- [ ] Icons: `react-icons/fa` für alle Built-ins; Custom-Actions können beliebige Icon-Komponenten liefern.
+- [ ] Visual-Hint für Drag-Source: leichtes Pulsieren wenn Custom-Drag-Action existiert (analog GitHub Issue-Cards).
+
+### Phase 5 — Tests + Dokumentation
+
+- [ ] Unit-Tests: Predicate-Filter, Sort-Order, Built-in-Backwards-Compat (Rename/Delete via alte Props).
+- [ ] Storybook-Story `FolderTree.stories.tsx` mit allen Kanälen demonstriert.
+- [ ] Doku: `wiki/uiPatterns/udbActions.md` (oder analog) mit „Wie registriere ich eine Custom-Action".
+- [ ] Migration-Guide-Snippet im PWG-Pilot-Plan: „FilesTab → Graph-Editor: jetzt via `customActions` statt API-Workaround".
+
+### Querschnitt
+
+- [ ] **API-Endpunkte:** keine Backend-Änderungen.
+- [ ] **DB-Schema:** keine.
+- [ ] **Frontend-Komponenten:** `FolderTree` (refactor), 2 neue UI-Komponenten (`FileActionContextMenu`, `FileActionBottomSheet`), 1 neuer Hook (`useFileActions`).
+- [ ] **RBAC:** Predicates können `ctx.udbContext` und Datei-Eigenschaften prüfen; falls per-User-Rechte nötig, kann Predicate auf einen UserPermissions-Hook zugreifen. Aktuell keine neuen Permissions nötig.
+- [ ] **Mobile/Accessibility:** Long-Press-Bottom-Sheet ist a11y-kompatibel (ARIA-Roles `dialog`, `menu`, `menuitem`); Tastatur-Navigation mit Arrow-Keys + Enter.
+- [ ] **Bundle-Size:** marginal — Context-Menu + Bottom-Sheet zusammen ca. 5–8 KB minified.
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|---|---|
+| 1 | Given `FolderTree` ohne `customActions`, When User Datei umbenennt/löscht, Then funktioniert wie heute (kein Regression) | must |
+| 2 | Given `FilesTab` im GraphEditor-Kontext + Datei `pilot.workflow.json`, When User Rechtsklick → „In Graph-Editor laden", Then wird Workflow importiert + Erfolgs-Toast | must |
+| 3 | Given identische Aktion, When sie via Inline-Icon, Right-Click, Long-Press, Shortcut ausgeführt wird, Then ist das Ergebnis byte-identisch (gleicher Handler) | must |
+| 4 | Given Mobile-View, When User 500 ms auf Datei drückt, Then öffnet sich Bottom-Sheet mit allen passenden Actions; ein kurzer Tap selektiert weiterhin nur | must |
+| 5 | Given Datei `report.pdf`, When User Rechtsklick, Then erscheint **kein** „In Graph-Editor laden" (Predicate filtert) | must |
+| 6 | Given `customActions` mit `dragMime: 'application/json+workflow'`, When User Datei auf Workflow-Editor-Canvas zieht, Then wird Handler dispatched | should |
+| 7 | Given `customActions` mit `shortcut: 'mod+e'`, When `FolderTree` Fokus hat und User `Cmd+E` drückt, Then wird Handler dispatched; ohne Fokus passiert nichts | should |
+| 8 | Given mehr als 3 Inline-Actions, When Render, Then werden die ersten 3 (nach `sortOrder`) als Icons angezeigt, der Rest hinter „⋯ More"-Overflow erreichbar | should |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
+|----|----|-----|---|---|---|
+| T1 | 1, 5, 8 | unit | ja | `frontend_nyla/tests/components/FolderTree/actions.test.tsx` | pending |
+| T2 | 2, 3 | integration | ja | `frontend_nyla/tests/components/FolderTree/workflowAction.test.tsx` | pending |
+| T3 | 4 | manual | nein | mobile-emulation in Chrome DevTools | pending |
+| T4 | 6 | integration | ja | `frontend_nyla/tests/components/FolderTree/dragDrop.test.tsx` | pending |
+| T5 | 7 | unit | ja | `frontend_nyla/tests/components/FolderTree/shortcuts.test.tsx` | pending |
+
+## Inspirations / State-of-the-Art
+
+- **VS Code Command Registry** — globale Command-IDs, mehrkanalig (Command Palette, Right-Click-Menu, Shortcut, View-Title-Bar). Predicate-Visibility via `when`-Clauses.
+- **Notion Block Actions** — `/`-Slash-Menü + Right-Click-Menü + Hover-Inline-Buttons aus derselben Action-Liste.
+- **Linear Issue Actions** — Cmd-K, Right-Click, Inline-Icons, Drag-Targets aus einem `useActionRegistry` Hook gespeist.
+- **Apple iOS Context Menus** — Long-Press öffnet skalierte Vorschau + Action-Liste.
+- **Google Drive** — Context-Action-Sichtbarkeit pro Mime-Typ (z. B. „Mit Docs öffnen" nur bei `.docx`).
+
+## Links
+
+- Heutiger `FolderTree`: `frontend_nyla/src/components/FolderTree/FolderTree.tsx`
+- Konsumenten: `frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx`, `frontend_nyla/src/pages/basedata/FilesPage.tsx`
+- Datei-Context: `frontend_nyla/src/contexts/FileContext.tsx`
+- Workflow-API: `frontend_nyla/src/api/workflowApi.ts` (`importWorkflowFromFile`, `isWorkflowFileContent`)
+- Verwandter Plan: `wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md` (Phase 2 deferred)