----------------- DONE (2026-04-24, trustee-schema-compliance-pick-not-push) [x] Trustee Schema-Compliance im typisierten Port-System (Pick-not-Push) Kontext: Mit dem 2026-04 typed-generic-handover Cut ist `_wireHandover` entfernt; Datenfluss läuft ausschliesslich über DataRefs gegen typisierte Output-Schemas. Trustee war nur teilweise migriert (extract deklarierte UdmDocument, lieferte aber list-of-ActionDocuments — Schema-Mismatch). Entscheidung: Schema-Pfad "DocumentList" statt UdmDocument-Refactor. Grund: extractFromFiles produziert pro Datei einen ActionDocument-Blob (semantisch eine Liste, kein einzelnes strukturiertes Doc mit Pages/Blocks). UdmDocument-Refactor wäre semantischer Riesen-Cut ohne Nutzer-Benefit; DocumentList passt zur Realität und ist von processDocuments bereits in inputPorts.accepts. Umsetzung: - gateway/.../nodeDefinitions/trustee.py: * trustee.extractFromFiles outputPorts.schema "UdmDocument" -> "DocumentList". * trustee.processDocuments inputPorts.accepts: "UdmDocument" entfernt (-> ["DocumentList", "Transit"]). - gateway/.../actionNodeExecutor.py: * Legacy-Alias `out["documentList"] = docsList` entfernt (Zeile 374). Kanonisches Feld ist jetzt ausschliesslich `out["documents"]`. * Verifiziert: kein DataRef im Repo nutzt path=['documentList'] mehr. (frontend GraphicalEditorWorkflowsTasksPage.tsx hat `o.documents ?? o.documentList` als defensiven Fallback — bleibt harmlos.) - gateway/.../actions/processDocuments.py + syncToAccounting.py: * Docstrings präzisiert: DataRef-Konvention (DocumentList.documents) statt unscharfes "reference to ... result". - frontend_nyla/.../trusteePipelineGraph.ts: * Bereits in vorigem Schritt auf path=['documents'] umgestellt — passt jetzt zur typisierten DocumentList.documents-Field. Test: - gateway/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py (8 Cases: Schema-Deklarationen, Param-Typen, End-to-End-validateGraph des Trustee-Pipeline-Graphs, Source-Code-Assertion gegen Re-Add des Legacy-Alias). Alle grün; bestehende graphicalEditor- und nodeDefinitions-Tests weiterhin grün (32/32). Offen / Nicht-Ziele: - PORT_TYPE_CATALOG.Document-Schema wurde NICHT um ActionDocument- spezifische Felder (documentData, documentName, validationMetadata) erweitert. Begründung: Document ist generisch; im Trustee-Pipeline bindet niemand einzelne Document-Subfelder via DataPicker (process konsumiert die ganze Liste). Falls künftig User-Picks in einzelne ActionDocument-Felder gewünscht werden, separater Catalog-Eintrag `ActionDocumentItem` einführen. - UdmPage/UdmBlock sind im Catalog vorhanden (portTypes.py:117ff) — bleiben für künftige echte UDM-Producer reserviert. ----------------- DONE (2026-04-24, trustee-pipeline-documentlist-binding) [x] Workflow "Spesenbelege einlesen": Dokumente werden extrahiert, aber Positionen landen nicht in den Tabellen. Workflow-Log: process: error="documentList is required (reference to extractFromFiles result)" obwohl extract erfolgreich 2 EXPENSE_RECEIPT-Records produziert hat. Root-Cause: - frontend_nyla/.../trusteePipelineGraph.ts (alle 3 Builder: scanUpload, expenseImport, scheduledExpenseImport) hat den Pipeline-Graph mit `parameters.documentList: []` als statisch leeres Array gebaut. - Connections (extract -> process -> sync) steuern in automation2 nur die Ausführungs-Reihenfolge, aber NICHT den Datenfluss zwischen Parametern. - Datenfluss verlangt explizite Refs: { type:'ref', nodeId, path }. - Folge: process bekam `documentList=[]`, `if not documentListParam` triggerte die "documentList is required"-Failure-Antwort sofort (9ms duration im Log). Fix: - documentList für `process` -> { type:'ref', nodeId:'extract', path:['documentList'] } - documentList für `sync` -> { type:'ref', nodeId:'process', path:['documentList'] } - In allen 3 Pipeline-Buildern angewendet. Hinweis: actionNodeExecutor schreibt `documentList` (Liste der dumped ActionDocuments) als Top-Level-Feld in den Node-Output, daher path=['documentList'] sowohl von extract als auch von process korrekt. ----------------- DONE (2026-04-24, trustee-import-clarity) [x] Issue Trustee Import: Falsche Datensätze + Bedeutung "Clear AI Cache" + Vollständigkeitsanzeige Root-Cause-Analyse: - totalAmount in RMA-Connector wurde verdoppelt: `+= max(debit, credit)` pro Zeile einer balancierten Buchung (Soll 100 + Haben 100) ergab 200 statt 100. Sowohl im Bulk-Pfad (`_fetchGlBulk`) als auch im per-Konto-Fallback (`getJournalEntries`). Fix: `totalAmount += debit` (für balancierte Buchungen identisch zur Summe Haben). - "KI-Cache leeren" (alt) leert NUR den 5-Min-Antwort-Cache des Agenten in `_featureSubAgentTools._featureQueryCache`. Synchronisierte TrusteeData*-Tabellen bleiben unangetastet. Button-Label irreführend. - Stale Records aus früheren Test-Imports konnten nur durch erneuten Import oder manuelles Entfernen + Neuanlegen der Connector-Konfig bereinigt werden. Umsetzung: - gateway/.../accountingDataSync.py: `_persistJournal` liefert zusätzlich `oldestBookingDate` / `newestBookingDate` (ISO YYYY-MM-DD) und schreibt sie in `TrusteeAccountingConfig.lastSyncCounts`. - gateway/.../routeFeatureTrustee.py: * `POST /api/trustee/{id}/accounting/clear-cache` Docstring präzisiert (löscht KEINE synchronisierten Daten). * Neu: `POST /api/trustee/{id}/accounting/wipe-imported-data` löscht alle TrusteeData*-Zeilen (Konten, Buchungen, Zeilen, Kontakte, Salden) für die Instanz, setzt lastSync*-Marker zurück, leert Antwort-Cache. Connector- Konfiguration bleibt erhalten. - frontend_nyla/.../TrusteeAccountingSettingsView.tsx: * Statistik nach Import zeigt jetzt zusätzlich: "Tatsächlich erhaltene Buchungen: bis " -> Vollständigkeitsprüfung möglich. * "Angefragtes Zeitfenster" vs. "Tatsächlich erhaltene Buchungen" klar getrennt. * Button umbenannt: "KI-Cache leeren" -> "KI-Antwort-Cache leeren" (mit Tooltip + präzisem Toast-Text). * Neuer Button "Importierte Daten löschen" (rot, mit Confirm-Dialog). Wiki-Sync (offen): - b-reference/gateway/features/trustee.md fehlt im Repo; bei nächster canonical-Pflege Endpoints + Cache-Trennung dokumentieren. ----------------- DONE (2026-04-21, ui-polish-bundle) [x] Zahlen Finanz mit 2 Kommastellen --> FOrmat bei Float angeben (frontend_format) [x] COntainers auch mit drag&drop [x] Automation in public (Store) [x] Modal editor weg bei Automation Editor Save, Rename Feature instanz [x] Nodes: Auswahl der Buha --> Felder mapping und Beschriftungen [x] Outlook: Reply/Forward/Move/Delete/Archive/ReadState/Flag Tools für Agent [x] Outlook: Mail-Limit ~20 statt 100 (browseDataSource Description) [x] UDB: Indentation aktive Datenobjekte [x] UDB: i18n-Texte [Konfiguration] etc. (mainRedmine/mainTrustee/...) [x] Trustee Daten-Tabellen-Header: 3-Kategorien-Struktur wie UDB ----------------- DONE (2026-04-21, testing int platform) [x] Wizard Sprachen hardcoded -> AdminLanguagesPage: lokale `_PRIORITY_CODES`/`_isoChoices` entfernt. -> Gateway expose: GET /api/i18n/iso-choices (single source of truth, _ISO_LABELS + _ISO_PRIORITY_CODES). -> Frontend laedt Katalog beim Mount via api.get('/api/i18n/iso-choices'). [x] Subscriptions Error int (kein Code-Bug reproduzierbar; user-bestaetigt erledigt) [x] Stripe aufladen Fehler (Top-Up-Flow inkl. Session-Idempotenz; user-bestaetigt erledigt) [x] Bei buy: Trial sofort beenden, Paid-Sub sofort starten -> mainServiceSubscription.py + routeBilling.py forceExpire-Pfad. [x] Trustee Sync separater Thread -> POST /accounting/import-data war bereits Background-Job + asyncio.to_thread. -> POST /accounting/sync (push) jetzt ebenfalls Background-Job: * neuer JobType `trusteeAccountingPush` + `_trusteeAccountingPushJobHandler` * Endpoint liefert HTTP 202 + `{ jobId }`, Frontend `syncPositionsToAccounting` pollt `/api/jobs/{jobId}` und liefert dasselbe Result-Payload zurueck. * Connector/Config wird einmalig resolved, jede Position ruft progressCb auf. 2026-04-23 Test-Suite-Bereinigung: 16 pre-existing Failures behoben (alle gruen) Was: Nach dem PWG-Pilot-Test-Run (siehe Eintrag darunter) blieben 16 Failures uebrig, die nichts mit PWG zu tun hatten, sondern aus frueheren Refactors stammten und nie nachgezogen wurden. Alle 7 Themen jetzt sauber bereinigt. Themen + Loesungen: 1) tests/test_phase123_basic.py CRASHTE den Pytest-Kollektor Root Cause: Datei war ein Standalone-Skript, kein Pytest-Modul; der sys.exit(1) am Ende lief beim Import und brach den Kollektor. Fix: Datei geloescht. Inhalt ("hat Modell X Feld Y?") wird heute durch echte Unit-Tests in tests/unit/* abgedeckt. 2) tests/demo/test_demo_bootstrap.py::test_happylifeFeaturesExist[chatbot] Root Cause: Chatbot-Feature wurde am 2026-04-20 bewusst aus dem InvestorDemo entfernt (siehe Eintrag), Tests wurden nicht nachgezogen. Fix: "chatbot" aus parametrize-Liste entfernt; positiver Negativtest test_happylifeNoChatbot ergaenzt (analog zu test_alpinaNoChatbot). 3) tests/demo/test_demo_uc3_chatbot.py::test_chatbotInstanceHappylife Gleicher Cause wie (2). Fix: in test_chatbotNotInHappylife umgedreht. 4) tests/integration/rbac/test_rbac_database.py ::testBuildRbacWhereClauseUserConnectionTable Root Cause: Mandate.name = "RBAC test mandate" verletzt das seit der Slug-Migration eingefuehrte Pattern ^[a-z0-9]+(-[a-z0-9]+)*$. Fix: Mandate.name = "rbac-test-mandate-uc". 5) tests/test_service_redmine_stats.py::test_aggregateProducesAllSections Root Cause: _aggregate() bekam in einem spaeteren Refactor zwei neue Pflicht-Kwargs (categoryIdsFilter, statusFilter), Test wurde nicht nachgezogen. Fix: categoryIdsFilter=[], statusFilter="" im Test-Aufruf ergaenzt. 6) tests/unit/services/test_json_extraction_merging.py (11 Tests) Root Cause: ExtractionService.__init__ verlangt jetzt context UND get_service-Resolver, alle 11 Tests riefen ExtractionService(None) auf. Fix: ExtractionService.__new__(ExtractionService) statt __init__-Aufruf — die getesteten Methoden (_isJsonExtractionResponse, _mergeJsonExtractionResponses, _isElementsResponse, _mergeElementsResponses) sind reine Daten-Methoden ohne self-Zugriff auf _context oder _get_service. Kein Eingriff in Produktivcode. Zusatz: Zwei Tests (test_merges_tables_with_same_headers, test_real_world_scenario) erwarteten Row-Deduplizierung; die dokumentierte Merge-Semantik ("duplicates preserved") sieht das aber gerade NICHT vor. Tests an aktuelles Verhalten angepasst und mit Begruendung versehen (Dedup ist Aufgabe des Konsumenten, nicht des Mergers). 7) tests/unit/workflows/test_automation2_graphUtils.py::test_ref_missing_node Root Cause: resolveParameterReferences gibt fuer einen Ref auf einen nicht in node_outputs registrierten Node None zurueck (statt das urspruengliche Ref-Dict). Der Test war auf das alte Verhalten gemuenzt. Fix: Test an aktuelles Runtime-Verhalten angepasst (erwarte None) + Begruendung im Test als Kommentar dokumentiert. Die Code-Semantik wurde bewusst NICHT geaendert — der Workflow-Engine erwartet diese None-Reduktion fuer "no value yet"-Behandlung downstream. Pytest-Ergebnis (lokal, ohne expensive/live): 371 passed, 7 skipped, 15 deselected, 0 failed. Geaenderte Dateien: geloescht: gateway/tests/test_phase123_basic.py geaendert: gateway/tests/demo/test_demo_bootstrap.py gateway/tests/demo/test_demo_uc3_chatbot.py gateway/tests/integration/rbac/test_rbac_database.py gateway/tests/test_service_redmine_stats.py gateway/tests/unit/services/test_json_extraction_merging.py gateway/tests/unit/workflows/test_automation2_graphUtils.py ---------------------------------------------------------------- 2026-04-23 PWG-Pilot Tests T1/T3/T6 (Phase 1/2 abgeschlossen) Was: Die Pytest-Abdeckung fuer Phase 1 (Workflow-File-IO) und Phase 2 (Agent-CRUD + PWG-Demo-Bootstrap) des PWG-Pilot-Plans wurde komplettiert. T2 wurde als doppelt markiert (Schema-Layer in T1, Tool-Layer in T3). Neue/aktualisierte Test-Dateien: 1) gateway/tests/unit/workflow/test_workflowFileSchema.py [T1, schon vorhanden] 17 Tests: envelopeVersioned-Build, Field-Stripping (id/mandateId/...), Round-Trip (export -> validate -> envelopeToWorkflowData), Validierung (missing schemaVersion/label/graph, unknown nodeType, dangling connection), Helpers (isWorkflowFileEnvelope, buildFileName-Slug, normalizeGraph). Status: 17 passed. 2) gateway/tests/unit/serviceAgent/test_workflow_tools_crud.py [T3, NEU] 20 Tests gegen einen In-Memory-_FakeInterface (kein DB-Zugriff): - createWorkflow happy-path + missing-label/missingInstanceId/blank-label + initial graph/tags/description - deleteWorkflow ohne confirm / mit confirm=False blockiert (kein Interface-Call), confirm=True loescht; Unknown-ID = error - updateWorkflowMetadata: Rename ohne Graph-Touch, Empty-Patch + Blank- Label rejected - createWorkflowFromFile (inline envelope) + existingWorkflowId-Replace - exportWorkflowToFile: Envelope mit $kind + schemaVersion + Filename - Tool-Definitionen: Alle 5 CRUD-Tools registriert, deleteWorkflow markiert confirm als required Status: 20 passed. 3) gateway/tests/demo/test_pwg_demo_bootstrap.py [T6, NEU] 15 Tests, markiert mit @pytest.mark.expensive + @pytest.mark.live; laufen nicht im default-Run (`-m 'not expensive and not live'`), sondern explizit: pytest -m "expensive or live" tests/demo/test_pwg_demo_bootstrap.py Abdeckung: - load() idempotent (zweimal hintereinander -> 0 errors) - credentials surface aus load() (pwg.demo) - Mandate stiftung-pwg + User pwg.demo + Membership existieren - genau 4 FeatureInstances (workspace, trustee, graphicalEditor, neutralization) - Trustee-Seed: rentAccount 6000 aktiv, 5 PWG-Mieter, >=60 PWG- Journal-Entries (5 Mieter x 12 Monate) - Pilot-Workflow importiert mit active=False und Nodes vorhanden - remove + reload: Mandant + User weg, dann reload erfolgreich Plan-Update: wiki/c-work/4-done/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md: Testplan-Tabelle aktualisiert (T1/T3/T6 = done, T2 = abgedeckt). Pytest-Ergebnis (gesamt, ohne expensive/live + ohne kaputtes test_phase123_basic.py das sys.exit(1) at module-load aufruft): 355 passed, 7 skipped, 16 failed (alles 16 sind PRE-EXISTING und unabhaengig: chatbot-Demo, redmine-stats Signature-Drift, ExtractionService-Signature, automation2_graphUtils.test_ref_missing_node, rbac_database pydantic-Pattern - kein Bezug zu PWG/Workflow-File-IO). ---------------------------------------------------------------- 2026-04-21 Bugfix i18n: Platzhalter-Namen wurden mit-uebersetzt Symptom: Im UI wurden in nicht-deutschen Sprachen rohe Platzhalter angezeigt: "Last import: 21.4.2026, 10:28:21 {accounts} accounts, {transactions} transactions ({rows} rows), {contacts} contacts, {balances} balances" Im Quelltext (TrusteeAccountingSettingsView.tsx) lautet der Aufruf: t('{konten} Konten, {buchungen} Buchungen ({zeilen} Zeilen), ...', { konten: ..., buchungen: ..., zeilen: ... }) Die Param-Namen sind Deutsch (konten/buchungen/zeilen), aber die in der DB gespeicherte englische Uebersetzung enthielt {accounts}, {transactions}, {rows} - so fand _applyParams() im Frontend keine Uebereinstimmung und liess die geschweiften Klammern stehen. Root cause: Der AI-Uebersetzungs-Prompt in routeI18n._translateBatch hatte zwar die Regel "KEEP placeholders like {variable}", aber das Modell hat sie ignoriert, weil die Platzhalter-Namen wie deutsche Woerter aussahen ({konten} -> Modell uebersetzt zu {accounts}). Ohne harte Validierung nach der AI-Antwort wurden diese kaputten Werte einfach in die DB geschrieben und beim Boot in den i18n-Cache geladen. Fix: 1) i18nRegistry._enforceSourcePlaceholders(sourceKey, translatedValue) - Neue, konservative Funktion: gleiche Anzahl Platzhalter aber andere Namen -> positionsweises Ersetzen mit den Source-Tokens - Andere Anzahl -> unangetastet lassen (zu riskant zu raten) - Identische Tokens oder keine Platzhalter -> No-op 2) i18nRegistry._loadCache: ruft den Guard bei jedem Boot fuer jeden Cache-Eintrag auf -> Self-Healing zur Laufzeit, ohne DB-Mutation. Loggt repariert-Counts pro Sprache. 3) routeI18n._translateBatch: Prompt-Regel #4 ist jetzt "PLACEHOLDERS ARE SACRED" mit explizitem Beispiel ({konten} bleibt {konten}). Zusaetzlich laeuft _enforcePlaceholdersOnBatch() ueber jede AI- Antwort und repariert Mismatch deterministisch -> auch wenn das Modell die Regel ignoriert, kommen niemals mehr kaputte Platz- halter in die DB. 4) Neue SysAdmin-Endpunkte fuer persistente Reparatur bestehender DB-Eintraege: POST /api/i18n/sets/{code}/repair-placeholders POST /api/i18n/sets/repair-placeholders-all Jede Sprache wird einmal sauber gemacht, anschliessend Cache-Reload. Files: gateway/modules/shared/i18nRegistry.py gateway/modules/routes/routeI18n.py Aktion fuer User: KEINE notwendig. i18nRegistry._loadCache laeuft bei jedem Boot und repariert sowohl den In-Memory-Cache als auch persistent in der DB (recordModify pro betroffenes Sprachset). Beim allerersten Restart nach diesem Fix steht im Log z. B.: "i18n boot repair: fixed and persisted 12 placeholder mismatches in language 'en'" Die SysAdmin-Endpunkte POST /api/i18n/sets/{code}/repair-placeholders POST /api/i18n/sets/repair-placeholders-all sind weiterhin verfuegbar als manueller Trigger, aber im Normalbetrieb redundant. 2026-04-21 Feature AI-Agent: temporaler Kontext im System-Prompt (Browser-TZ) Symptom: Der AI-Agent erfand das aktuelle Datum aus seinem Training-Cutoff ("Juni 2025") und musste sich beim User dafuer entschuldigen. Bei Datenanalyse-Anfragen mit relativen Zeit-Filtern ("letzten Monat", "Q1", "diese Woche") fuehrte das zu falschen SQL-Filtern und falschen Antworten. Root cause: LLMs haben keinen eingebauten Zugriff auf "jetzt". Weder buildSystemPrompt() in conversationManager.py noch _buildSchemaContext() im featureDataAgent.py injizierten das aktuelle Datum. Modelle haben keine Wahl als ihren Trainings- Cutoff zu verwenden. Fix: Spiegelt das bestehende Accept-Language-Pattern fuer die Browser-Zeitzone: 1) frontend_nyla/src/api.ts (Axios-Interceptor): sendet X-User-Timezone-Header mit Intl.DateTimeFormat().resolvedOptions().timeZone (z.B. "Europe/Zurich"). 2) gateway/app.py: bestehende _i18nMiddleware umbenannt zu _requestContextMiddleware, liest zusaetzlich X-User-Timezone und ruft _setRequestTimezone(). 3) gateway/modules/shared/timeUtils.py: neue ContextVar _CURRENT_TIMEZONE plus _setRequestTimezone(), getRequestTimezone(), getRequestNow(). Validiert IANA-Namen gegen zoneinfo, faellt auf UTC zurueck. Analog zu _setLanguage / _getLanguage in i18nRegistry. 4) conversationManager.buildSystemPrompt: neuer Helper _buildTemporalContext() injiziert Block "Current Date & Time" mit Today, Now, TZ und Anweisung relative Zeit-Referenzen gegen DIESES Datum aufzuloesen. 5) featureDataAgent._buildSchemaContext: gleicher Block fuer den Daten-Sub-Agent (kritisch fuer SQL-Filter). Wichtig: - Storage bleibt UTC (getUtcTimestamp, getIsoTimestamp). - Nur user-sichtbare "jetzt"-Werte (Prompt, Display) gehen ueber getRequestNow(). - Kein Hardcoding einer Default-TZ wie "Europe/Zurich" - Fallback ist UTC, damit Mandate-Zeitzonen-Vielfalt korrekt abgebildet wird. - Pattern dokumentiert in d-guides/coding-conventions.md Abschnitt "Datum/Zeit: UTC fuer Storage, Request-TZ fuer User-sichtbare Werte". - Architektur dokumentiert in b-reference/gateway/ai-agent.md Abschnitt "System-Prompt: temporaler Kontext". Files: frontend_nyla/src/api.ts gateway/app.py gateway/modules/shared/timeUtils.py gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py wiki/b-reference/gateway/ai-agent.md wiki/d-guides/coding-conventions.md 2026-04-21 Bugfix i18n: Accounting-Connector Feldlabels uebersetzen Symptom: In "Trustee → Buchhaltungssystem-Anbindung → Zugangsdaten" wurden in nicht-deutschen Sprachen die Connector-Feldlabels mit eckigen Klammern angezeigt: [API Base URL] * [Mandantenname] * [API-Schluessel] * Eckige Klammern sind der Cache-Miss-Fallback in t() (modules/shared/i18nRegistry.py, return f"[{key}]"). Root cause: Die Connector-Feldlabels werden via t("API Base URL") in der Methode getRequiredConfigFields() der Connector-Klassen getaggt (accountingConnectorRma.py, accountingConnectorBexio.py, accountingConnectorAbacus.py). t() registriert den Schluessel aber erst beim Methodenaufruf - und der erfolgt erst beim ersten Frontend-Request, nachdem _syncRegistryToDb() bereits gelaufen ist. Folge: - Schluessel fehlt im xx-Basisset - KI-Uebersetzung im Admin-UI hat nichts zu uebersetzen - _loadCache() laedt keine Werte fuer diese Keys - t() liefert [key] Fallback Das Muster "t() in einer Methode statt auf Modulebene" ist in coding-conventions.md Abschnitt "Statische Dicts mit i18n-Keys" bereits als Anti-Pattern dokumentiert. Plugin-Architekturen koennen das Pattern aber nicht vermeiden (Connectors werden dynamisch via importlib geladen). Fix: 1) i18nRegistry.py: Neue Funktion _registerAccountingConnectorLabels() importiert die AccountingRegistry beim Boot, ruft einmal getRequiredConfigFields() auf jedem Connector auf und registriert die Labels mit context "connector.accounting.". 2) _syncRegistryToDb() ruft den neuen Hook nach _registerDatamodelOptionLabels() auf - vor dem DB-Sync. 3) coding-conventions.md: Pflicht-Hook fuer dynamisch geladene Quellen ergaenzt im Abschnitt "Statische Dicts mit i18n-Keys". Wirkung nach naechstem Boot: - 6 neue Keys im xx-Set: "API Base URL", "Mandantenname", "API-Schluessel", "Persoenlicher Zugriffstoken", "Client-ID", "Client-Secret". - KI-Uebersetzung im Admin-UI uebersetzt sie in alle aktiven Sprachsets. - t() liefert die korrekten Uebersetzungen statt [key]. Files: gateway/modules/shared/i18nRegistry.py wiki/d-guides/coding-conventions.md 2026-04-21 Bugfix Trustee: Instance Roles & Permissions blank screen Symptom: Die Seite "Trustee → Instance Roles & Permissions" lieferte einen weissen Bildschirm mit React-Error "Objects are not valid as a React child (found: object with keys {xx})" in TrusteeInstanceRolesView.tsx:147 (Span um role.description). Root cause: Role.description ist im Backend ein TextMultilingual ({xx, de, en, ...}). Die Endpoints GET /api/trustee/{instanceId}/instance-roles GET /api/trustee/{instanceId}/instance-roles/{roleId} in routeFeatureTrustee.py haben das Role-Modell ueber `r.model_dump()` 1:1 ausgeliefert, also das ganze Multilingual- Objekt im Feld `description`. Das Frontend deklariert `description?: string` und rendert `getTextValue(role.description)` direkt in einem . Sobald description ein Objekt ist, wirft React beim Reconcile. Konventionalitaet im Repo: TextMultilingual wird IMMER backend- seitig via resolveText() zu einem String aufgeloest, bevor es an das Frontend geht (siehe getQuickActions, FormGeneratorTable Hinweis in _objectToDisplayString). Fix: 1) routeFeatureTrustee.py: Neue Helferfunktion _serializeRoleForApi loest description ueber resolveText() in der Request-Sprache (kommt aus dem Language-Middleware-Context) auf. Beide Role- Endpoints geben jetzt diese serialisierte Form zurueck. 2) TrusteeInstanceRolesView.tsx: defensiver Hardening-Schritt: - InstanceRole.description akzeptiert string ODER {xx, ...} - getTextValue extrahiert xx bzw. ersten String-Wert, falls doch mal ein roher TextMultilingual durchrutscht. Files: gateway/modules/features/trustee/routeFeatureTrustee.py frontend_nyla/src/pages/views/trustee/TrusteeInstanceRolesView.tsx 2026-04-21 KRITISCHER BUGFIX: Trustee Accounting-Sync friert ganzen Server ein Symptom (INT 07:06-07:13): Beim Trustee-Buchhaltungs-Import (rma-Connector, 271 Konten + 7'679 Journal-Entries + ~30k Journal-Lines) blockierte der gesamte FastAPI-Worker fuer mehrere Minuten. Im Log sieht man eine Luecke von 3.5 Minuten ohne irgendwelche Eintraege. Health-Checks liefen nicht, andere Requests hingen, der User musste den Server hart neu starten. Root cause #1 (Event-Loop-Block): `serviceBackgroundJobs.startJob` startet Jobs per `asyncio.create_task`, d.h. sie laufen auf demselben Event-Loop wie alle HTTP-Requests. `AccountingDataSync.importData` war zwar `async def`, machte aber intern ausschliesslich synchrone psycopg2-Calls (`recordCreate`, `recordDelete`, `getRecordset`) ohne `asyncio.to_thread`. Mit 7'679 Entries + ~30k Lines = 40'000+ blockierende DB-Round-Trips auf dem Event-Loop -> alles steht. Root cause #2 (N+1 SQL): - `_clearTable` lud alle Rows und loeschte jede einzeln (Re-Sync eines bestehenden Mandanten = nochmal 37'000 Single-DELETEs). - `recordCreate` machte pro Zeile einen INSERT + COMMIT + `_registerInitialId`-Schreiboperation in der System-Tabelle. Root cause #3 (Debug-Dump in INT/PROD): Hardcoded Windows-Pfad `D:/Athi/.../local/debug/sync` lief auf Linux als Relativpfad an, dumpte 7'679-Zeilen-JSON-Files waehrend des Imports (zusaetzliche RAM/IO-Last). Flag war an `APP_LOGGING_FILE_ENABLED` gekoppelt, die in INT/PROD `True` ist. Fix: 1. Connector (`connectorDbPostgre.py`): - Neue Methode `recordCreateBulk(model, records)` -> ein einziger `psycopg2.extras.execute_values` mit page_size=500 + ein COMMIT. Initial-ID wird einmalig pro Batch registriert. Mirror-Coercion der Per-Spalten-Logik aus `_save_record` (Timestamps als float, Enums als string, Vector als pgvector-Text, JSONB als JSON). - Neue Methode `recordDeleteWhere(model, recordFilter)` -> ein einziges `DELETE WHERE ...` statt N+1; sicher gegen versehent- liches Truncate (lehrt bei leerem Filter ab); cleart das `initialId`-Registry-Entry korrekt, falls die geloeschte Menge es enthaelt. 2. AccountingDataSync (`accountingDataSync.py`): - Komplette Trennung: async-HTTP-Fetch + `await asyncio.to_thread( self._persistXxx, ...)` pro Phase. Damit ist der Event-Loop waehrend der DB-Schreibphasen frei fuer alle anderen Requests. - Vier neue Persistenz-Helper (`_persistAccounts`, `_persistJournal`, `_persistContacts`, `_persistBalances`) -- alle synchron, alle nutzen `_bulkClear` + `_bulkCreate`. Erwartete Performance-Gewinn pro Phase: Faktor 50-100x. - Heartbeat-Logging: alle 500 Entries waehrend der Build-Phase + Phasen-Zeitmessung (`Persisted N rows in Xs`). - Defensiver Fallback: wenn der Connector noch keine Bulk-Methoden hat (alte Deployments), faellt der Code auf die alte Schleife zurueck UND warnt im Log. 3. Debug-Dump: neue Env-Keys `APP_DEBUG_ACCOUNTING_SYNC_ENABLED` (FALSE in INT/PROD, True in DEV) + `APP_DEBUG_ACCOUNTING_SYNC_DIR`. Pfad-Resolution analog zu `debugLogger.py` (relativ wird relativ zum gateway/-Root). Auf INT/PROD passiert ohne Flag-Aenderung absolut nichts mehr auf der Disk. Auswirkungen / Migration: - Keine DB-Schema-Migration noetig. - Bestehende Deployments koennen `recordCreateBulk`/`recordDeleteWhere` sofort nutzen; Fallback-Pfad existiert nur fuer den unwahrschein- lichen Fall, dass der neue Code vor dem neuen Connector deployed wird. - `APP_DEBUG_ACCOUNTING_SYNC_ENABLED` muss in INT/PROD-Envs gesetzt sein (FALSE), default ist ohnehin False. Dateien: gateway/modules/connectors/connectorDbPostgre.py gateway/modules/features/trustee/accounting/accountingDataSync.py gateway/env_dev.env gateway/env_int.env gateway/env_prod.env 2026-04-21 UDB Datenquellen: klare Trennung Katalog vs. Chat-Anhaenge Spec: 1. Files/Folders: mit Prompt mitschicken, keine Persistierung. 2. DataSources / FeatureDataSources: - In der UDB-Seitenleiste KEIN "x"-Button (kein attach/detach). - Attach an Chat: per 💬-Symbol oder Drag-Drop. Damit wird eine Quelle als Chip oben am Prompt angeheftet. - Persistenz: Chip ueberlebt Chat-Reload (per Workflow-Felder attachedDataSourceIds / attachedFeatureDataSourceIds). - Detach vom Chat: nur ueber Chip-x oben am Prompt. Aenderungen Frontend: - SourcesTab.tsx: vier "Entfernen"-x-Buttons entfernt (DataSource-Browse-Tree, Feature-Gruppen-Wildcard, Feature-Record-Zeile, Feature-Tabellen-Zeile). Damit gibt es keine vermeintliche "Detach"-Aktion mehr in der UDB, die in Wahrheit den ganzen Katalog-Eintrag geloescht hat. - SourcesTab.tsx: Wrapper _removeDatasource und _removeFeatureDataSource sowie die Props onRemoveDs / onRemoveFds durch alle Sub-Views entfernt (kein Konsument mehr). - WorkspaceInput.tsx: pendingAttachDsId- und pendingAttachFdsId-Effects rufen jetzt zusaetzlich _persistAttachments() auf. Damit wird ein per 💬 oder Drop frisch attachierter Chip sofort persistiert; ein Chat-Reload ohne vorheriges Senden zeigt den Chip jetzt korrekt wieder an. Hinweis: Es existiert kein UI-Pfad mehr, um eine FeatureDataSource / DataSource aus dem Workspace-Katalog zu loeschen. Per Spec gewollt - Detach passiert ausschliesslich am Chip; Katalog- Aufraeumarbeiten waeren ein separater Admin-Workflow. Dateien: frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx frontend_nyla/src/pages/views/workspace/WorkspaceInput.tsx 2026-04-21 Bugfix ChatWorkflow: Persistierte UDB-Anhaenge wurden still verworfen Symptom: Im AI-Chat angeheftete DataSources / FeatureDataSources (Chips in der Unified Data Bar) waren nach einem Reload des Chats verschwunden. Der Agent rief in der Folge `queryFeatureInstance` mit raten Tabellennamen (z.B. "TrusteeDataJournalEntry") oder veralteten IDs auf -> "Feature instance not found". Root cause (interfaceDbChat._separateObjectFields): Die zwei neuen ChatWorkflow-Felder attachedDataSourceIds: Optional[List[str]] attachedFeatureDataSourceIds: Optional[List[str]] fielen durch die Typ-Erkennung im generischen Field-Separator. Diese pruefte `fieldType.__origin__ in (dict, list)`. Fuer `Optional[List[str]]` ist `__origin__` aber `Union` (das `Optional` schaelt sich nicht von selbst weg). Werte landeten daher in `objectFields` und wurden vor dem `recordModify` herausgefiltert. Der `PATCH /workflows/{id}/attachments` antwortete mit 200, schrieb aber nichts in die DB-Spalten. Fix: Neuer Helper `_unwrapOptional(fieldType)` schaelt `Optional[X]` / `Union[X, None]` zu `X` ab und wird einmal vor der bestehenden JSONB-Erkennung aufgerufen. Damit werden alle nullable JSONB-Felder (`Optional[List[...]]`, `Optional[Dict[...]]`) korrekt als simple JSONB-Spalten persistiert. Keine zusaetzliche Sonderlogik, keine Datenmigration noetig (Spalten existieren bereits, alte Workflows haben einfach `[]`). Datei: gateway/modules/interfaces/interfaceDbChat.py 2026-04-21 Bugfix Stripe-Subscription: "The price specified is inactive" auf INT Symptom: Beim Subscription-Checkout auf der INT-Instanz lieferte Stripe: "Subscription konnte nicht erstellt werden: Request req_yYNz9... The price specified is inactive. This field only accepts active prices." Auf DEV trat der Fehler nicht auf. Root cause: `gateway/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py` Der Reconciler `_reconcilePrice` retrievet den persistierten Stripe-Price via `stripe.Price.retrieve(...)`. Diese API liefert auch ARCHIVIERTE Prices zurueck. Der Reconciler verglich nur `unit_amount` und `recurring` -- aber NICHT das `active`-Flag. Wenn beide passten, wurde der inactive priceId unveraendert zurueckgegeben. INT-Szenario: INT und DEV teilen sich denselben Stripe-Test-Account. DEVs Bootstrap (`_archiveOtherRecurringPrices`) archivierte INT's Price beim Rotieren, weil er auf demselben Product (gleiches Intervall) lag aber nicht der "neueste" war. INTs `StripePlanPrice`-DB-Eintrag blieb unveraendert auf dem nun inactive ID. Beim naechsten Subscription- Create lieferte Stripe den o.g. Fehler. Fix: - gateway/modules/serviceCenter/services/serviceSubscription/stripeBootstrap.py: * `_reconcilePrice` retrievet den Price jetzt NUR EINMAL und extrahiert `unit_amount`, `recurring` UND `active` in einem Durchgang. * Rotation-Trigger erweitert: zusaetzlich zu Drift/RecurringMismatch auch `not isActive` und `retrieveFailed` => Rotate. * Aussagekraeftigeres Log "Rotating Stripe Price ... active=... amount=..." mit allen Faktoren. * Hilfsfunktion `_getStripePriceAmount` entfernt (war redundanter zweiter Retrieve-Roundtrip). - gateway/modules/serviceCenter/services/serviceSubscription/mainServiceSubscription.py: * Defense-in-depth: VOR `checkout.Session.create(mode="subscription")` prueft die neue Methode `_areStripePricesActive(stripe, mapping)`, ob alle persistierten Price-IDs noch active sind. Wenn nicht, wird `bootstrapStripePrices()` inline neu ausgefuehrt und das Mapping neu geladen. Erst dann wird die Subscription erstellt. * Schlaegt das Re-Bootstrap fehl oder bleiben die Preise weiterhin inactive, kommt eine sprechende Fehlermeldung statt des Stripe- Roh-Errors. Effekt: - INT-Restart wuerde die Preise jetzt automatisch rotieren. - Selbst OHNE Restart wird der naechste Subscription-Versuch durch das Inline-Bootstrap im Service erfolgreich neu durch den Reconciler laufen. Empfehlung Multi-Env: - INT und DEV sollten getrennte Stripe-Accounts (oder zumindest getrennte STRIPE_SECRET_KEYs) nutzen, damit `_archiveOtherRecurringPrices` nicht die Preise des jeweils anderen Env-Boots archiviert. Aktuell maskiert der Inline-Reconciler das Symptom, aber die Root-Cause "geteilter Stripe-Account" bleibt. 2026-04-21 Bugfix Workspace: Drag-Drop einer DataSource liess Chip wieder verschwinden Symptom: Wenn ein User eine DataSource (oder FeatureDataSource) per Drag-Drop in den AI-Chat zog, erschien der Chip im Unified-Data-Bar-Anhang kurz und verschwand sofort wieder. Beim naechsten Senden war die Quelle nicht mit gehaengt. Root cause (race in WorkspaceInput.tsx): Der "drop unresolved IDs"-useEffect lief KONTINUIERLICH bei jeder Aenderung von `dataSources` bzw. `featureDataSources`. Beim Drop geschah: 1. WorkspacePage._handleDataSourceDrop: POST /datasources -> newId 2. setPendingAttachDsId(newId) + workspace.refreshDataSources() (async!) 3. WorkspaceInput effect: pendingAttachDsId -> attachedDataSourceIds += newId -> Chip erscheint 4. dataSources im State enthaelt newId NOCH nicht (Refresh in flight) 5. Filter-Effect: validIds.has(newId) === false -> filter zieht ihn raus -> Chip verschwindet 6. Refresh kommt zurueck, dataSources enthaelt jetzt newId, aber der Chip ist schon weg. Selbe Mechanik fuer FeatureDataSources via _handleSendToChat_FeatureSource. Fix: - frontend_nyla/src/pages/views/workspace/WorkspaceInput.tsx: * Filter-Effects auf "Einmal-pro-Chat-Load" umgestellt: zwei useRef-Token (`_reconciledDsForNonce`, `_reconciledFdsForNonce`) merken, fuer welchen `loadedNonce` die Reconciliation bereits gelaufen ist. * Reconciliation feuert NUR, wenn: - loadedNonce ist gesetzt (chat ist geladen), - der Token != aktueller loadedNonce (noch nicht reconciliert), - die jeweilige Quellliste ist nicht leer (Refresh war erfolgreich). * Nach erfolgreicher Reconciliation wird der Token auf den aktuellen loadedNonce gesetzt -> spaetere Drag-Drops triggern den Filter nicht mehr. * Dependency-Arrays geaendert: jetzt `[loadedNonce, dataSources]` bzw. `[loadedNonce, featureDataSources]` (statt `[dataSources, attachedDataSourceIds.length]`). 2026-04-20 Bugfix STRIPE_AUTOMATIC_TAX_ENABLED Boolean-Parsing Symptom: APP_CONFIG._loadEnv (gateway/modules/shared/configuration.py) liest .env-Werte ALS ROHE STRINGS ein. `STRIPE_AUTOMATIC_TAX_ENABLED = false` wurde damit in Python zur Truthy-Wert `bool("false") == True`. Effekt: Stripe-Checkout aktivierte automatisch Stripe Tax und ueberging den `tax_rates`-Branch -- der manuelle `STRIPE_TAX_RATE_ID_CH_VAT` wurde folglich NIE auf die Line-Items gehaengt. Bei Mandanten ohne aktive Stripe-Tax-Registration in der Schweiz waere die Rechnung dann ohne MWST erschienen (nicht treuhand-konform). Fix: - `gateway/modules/serviceCenter/services/serviceBilling/stripeCheckout.py`: * Neuer Helper `_isAutomaticTaxEnabled()` parst den String explizit (true/1/yes/on -> True, alles andere -> False, Bool- Eingabe wird unveraendert durchgereicht). * `create_checkout_session` ruft jetzt diesen Helper statt `bool(APP_CONFIG.get(...))`. * `_resolveCheckoutTaxRates` casten den raw-Wert defensiv mit `str(raw)` vor dem Split (falls zukuenftig non-string-Defaults reinkommen). 2026-04-20 Mandate.invoice*: strukturierte Adressfelder + Stripe-konformes Mapping Hintergrund: Stripe akzeptiert Empfaengeradressen NUR strukturiert (`customer.address.line1/postal_code/city/country/...`). Der zuvor hinzugefuegte mehrzeilige Freitext-Block (`Mandate.invoiceAddress`, Optional[str]) hatte den Nachteil, dass Stripe ihn nur als `customer.description` rendern konnte -- er erschien im Stripe-UI, aber NICHT als richtige Empfaengeradresse im Header der erzeugten Rechnung. Treuhand-Praxis verlangt aber den vollstaendigen Adress-Block in der Rechnungs-Adresszeile, nicht nur in einer Description. Fix: - `gateway/modules/datamodels/datamodelUam.py`: * `Mandate.invoiceAddress` (Optional[str]) entfernt. * Stattdessen 10 strukturierte Felder direkt am Mandate: invoiceCompanyName, invoiceContactName, invoiceEmail, invoiceLine1, invoiceLine2, invoicePostalCode, invoiceCity, invoiceState, invoiceCountry (default `CH`, ISO-2), invoiceVatNumber. * `frontend_type` jeweils `text` bzw. `email`, mit `order: 200..209` damit der FormGenerator die Felder visuell am Ende des Mandate-Edit-Formulars gruppiert. * Validatoren: `_coerceInvoiceTextField` (Trim + Empty -> None), `_coerceInvoiceCountry` (Upper-Case + Default `CH`). * `_flattenLegacyInvoiceAddress` und `_coerceInvoiceAddress` ersatzlos entfernt (`InvoiceAddress`-Klasse bleibt nur als historische Doku stehen). - `gateway/modules/serviceCenter/services/serviceBilling/stripeCheckout.py`: * `_normalizeInvoiceAddress` entfernt. * `_buildStripeAddress(invoiceAddress)` -> Stripe-`address`-Dict (line1/line2/postal_code/city/state/country). Faellt auf `None` zurueck wenn line1 oder city fehlt -> dann faellt der Checkout auf `billing_address_collection: required` zurueck. * `_buildStripeTaxIdData(invoiceAddress)` -> mappt UID-Praefix auf Stripe-Type (CHE -> `ch_vat`, LI -> `li_uid`, sonst `eu_vat`). Wird nur beim Customer-_Create_ angewendet (Stripe erlaubt kein Modify auf `tax_id_data`). * `_ensureStripeCustomer` setzt jetzt `name` (companyName Fallback Mandant-Label), `email` (invoiceEmail), `address`, `shipping` (z. H. + Adresse), `metadata.contactName`/`vatNumber` und beim Create `tax_id_data`. Beim Modify werden `address`/`name`/`email` aktualisiert -- die UID muss bei Bestands-Customern manuell in Stripe nachgepflegt werden. * `create_checkout_session`: `invoice_data.description` wieder auf die einfache Top-Up-Zeile reduziert; UID/z.H. wandern in `invoice_data.custom_fields` (max. 4 Eintraege). `customer_email` Fallback gesetzt wenn kein Customer-ID existiert aber `invoiceEmail` vorhanden ist. - `gateway/modules/routes/routeBilling.py`: * Baut den `invoice_address`-Dict aus den strukturierten `mandateRecord.invoice*`-Attributen statt aus dem alten `invoiceAddress`-Freitext. - `gateway/modules/routes/routeDataMandates.py`: * `_MANDATE_ADMIN_EDITABLE_FIELDS` von 2 auf 11 Eintraege erweitert (label + alle 10 invoice*-Felder), damit MandateAdmins die neuen Felder per PUT aendern koennen. - `frontend_nyla/src/utils/mandateBillingFormMerge.ts`: * `splitMandateAndBillingFromForm` iteriert ueber die Konstante `_MANDATE_INVOICE_FIELDS` und reicht jedes Feld einzeln durch (Trim, leerer String -> `null`). - `wiki/d-guides/stripe-ch-vat.md`: * Tabelle in Abschnitt 1, Abschnitt 3 (Mandant-Erfassung) und Test-Checkliste in Abschnitt 4 auf die neuen strukturierten Felder umgeschrieben. Mapping-Tabelle Mandate-Feld -> Stripe- Property hinzugefuegt. Hinweis auf `tax_id_data`-Limitierung bei Bestands-Customern und Migrations-Hinweis fuer alte `invoiceAddress`-JSONB-Spalte. Migration: - DB: die alte `invoiceAddress`-Spalte (TEXT bzw. JSONB) bleibt in der Tabelle stehen. Der Schema-Reconciler ignoriert sie (kein Drop), die App liest/schreibt sie nicht mehr. Bei Bedarf manuell mit einem einmaligen SQL-Update in die neuen Spalten kopieren. Fuer Dev-Umgebungen empfiehlt sich, die Adresse pro Mandant neu im Form zu erfassen. - Stripe: Bestands-Customer behalten ihre alte `description`. Beim naechsten Top-Up wird das Feld nicht mehr gepflegt; `address`, `name`, `email` und `shipping` werden ueber `Customer.modify` aktualisiert sobald Daten am Mandant hinterlegt sind. 2026-04-20 Mandate.invoiceAddress: mehrzeiliges Textfeld + Form persistiert Symptome: 1) Die Mandant-Rechnungsadresse war als strukturiertes JSON-Objekt modelliert (`InvoiceAddress` mit companyName/line1/.../vatNumber). Im UI wurde das mit `frontend_type: "json"` gerendert -- das FormGenerator-Mapping kennt diesen Typ nicht, also entweder kein Eingabefeld oder unbenutzbar. 2) Selbst wenn der User in `AdminMandatesPage` etwas eintrug, hat `splitMandateAndBillingFromForm` nur `name`, `label`, `enabled` weitergeleitet -- `invoiceAddress` wurde stillschweigend gedroppt. Resultat: Adresse erschien nie im Mandate-PUT und wurde nicht persistiert. Fix: - `gateway/modules/datamodels/datamodelUam.py`: * `Mandate.invoiceAddress` -> `Optional[str]` mit `frontend_type: "textarea"`, `minRows: 4`, `maxRows: 8` und Placeholder-Beispieladresse. * Validator `_coerceInvoiceAddress` aktzeptiert weiterhin Dicts und JSON-Strings (Bestandsdaten) und plaettet sie ueber den neuen `_flattenLegacyInvoiceAddress`-Helper in einen lesbaren Mehrzeilen-Block (Firma, z. H. Kontakt, Strasse, PLZ Ort, Land, UID:..., E-Mail). * `InvoiceAddress`-Klasse bleibt vorhanden, ist aber als Legacy- Decoder dokumentiert und wird nirgends mehr aktiv eingebunden. - `gateway/modules/connectors/connectorDbPostgre.py`: * Schema-Reconciler erkennt jetzt zusaetzlich JSONB->TEXT- Downgrades und fuehrt `ALTER TABLE ... ALTER COLUMN ... TYPE TEXT USING "col"::text` aus. Damit migriert die bereits angelegte JSONB-Spalte `Mandate.invoiceAddress` beim naechsten App-Start automatisch ohne Datenverlust. - `gateway/modules/routes/routeDataMandates.py`: * `_MANDATE_ADMIN_EDITABLE_FIELDS` um `invoiceAddress` erweitert, damit auch MandateAdmins (nicht nur PlatformAdmins) ihre eigene Rechnungsanschrift pflegen koennen. - `frontend_nyla/src/utils/mandateBillingFormMerge.ts`: * `splitMandateAndBillingFromForm` traegt `invoiceAddress` ins `mandatePayload` ein (Trim, leerer String -> `null`). `mergeBillingIntoMandateFormData` reicht den Wert via `...mandate` weiter, sodass das Form ihn beim Edit hydratisiert. - `gateway/modules/serviceCenter/services/serviceBilling/stripeCheckout.py`: * Strukturiertes `_buildStripeAddress` entfernt; neuer Helper `_normalizeInvoiceAddress` macht aus dem Wert (Plain-String oder Legacy-Dict) einen sauberen Mehrzeilen-Text. * `_ensureStripeCustomer` setzt jetzt `description` auf den Adress-Freitext (statt structured `address`/`shipping`). `name` = Mandant-Label, Metadata trag mandateId/Label. `email` und `vatNumber` werden nicht mehr separat gesetzt -- beides darf der Anwender im Freitext mitschreiben (UID, E-Mail). * `invoice_data.description` enthaelt jetzt den Block "Rechnungsanschrift:\n{adresse}" damit die Adresse direkt auf der Stripe-Rechnung erscheint, sicher truncated auf 1400 Zeichen. * `customer_email`-Override im Guest-Branch entfernt; Stripe Checkout sammelt die Mailadresse selbst ein. - `wiki/d-guides/stripe-ch-vat.md`: Abschnitt 1 + 3 + 4 auf das neue Plain-Text-Modell umgeschrieben (inkl. Beispieladresse, Hinweis auf Legacy-Migration und auto-Schema-Migration). Files: gateway/modules/datamodels/datamodelUam.py, gateway/modules/connectors/connectorDbPostgre.py, gateway/modules/routes/routeDataMandates.py, gateway/modules/serviceCenter/services/serviceBilling/stripeCheckout.py, frontend_nyla/src/utils/mandateBillingFormMerge.ts, wiki/d-guides/stripe-ch-vat.md 2026-04-20 UDB Chat-Anhaenge: "x"-Button + Per-Chat-Persistenz Symptome: 1) Im AI-Chat: Klick auf "x" einer angehaengten Quelle (Datenquelle oder Feature-Datenquelle) im Chip-Bar des WorkspaceInput entfernte den Chip optisch erst nach Re-Render und ueberlebte ein Wiedereroeffnen des Chats nicht. 2) Beim Wiedereroeffnen eines bestehenden Chats waren alle vorher angehaengten Quellen weg -- der Chat hatte keine Erinnerung daran, welche Quellen der User zuletzt gepinnt hatte. Ursache: - `ChatWorkflow` hatte keine Felder zum Persistieren der angehaengten `dataSourceIds` / `featureDataSourceIds`. - `WorkspaceInput` hielt die Chip-Bar-IDs ausschliesslich als lokalen React-State, ohne Backend-Roundtrip beim Detach. - `loadWorkflow` setzte den Chip-Bar nicht aus persistierten Daten zurueck. Fix: - `datamodelChat.py`: `ChatWorkflow` um `attachedDataSourceIds` und `attachedFeatureDataSourceIds` (Optional[List[str]], JSONB) erweitert. Bestehende Schema-Reconciliation in `connectorDbPostgre.py` legt die Spalten beim Boot automatisch an (kein Migrations-SQL noetig). - `interfaceDbChat.py`: `getWorkflow` und `updateWorkflow` schreiben / lesen die zwei neuen Felder; bei `null` faellt der Code auf eine leere Liste zurueck. - `routeFeatureWorkspace.py`: * `start/stream` persistiert vor dem Agent-Lauf die uebergebene Anhang-Liste auf den Workflow. * `GET /workflows/{id}/messages` gibt zusaetzlich `attachedDataSourceIds` und `attachedFeatureDataSourceIds` zurueck. * Neuer Endpunkt `PATCH /workflows/{id}/attachments` fuer das Sofort-Persistieren bei "x"-Klicks (kein Warten auf naechste sendMessage-Runde). - `useWorkspace.ts`: `loadWorkflow` befuellt neue Felder `loadedAttachedDataSourceIds`, `loadedAttachedFeatureDataSourceIds` und einen `loadedNonce`-Counter, damit `WorkspaceInput` die Hydration idempotent triggern kann. - `WorkspaceInput.tsx`: * Hydratisiert den Chip-Bar bei jedem `loadedNonce`-Wechsel aus den persistierten IDs. * Filtert IDs, die nicht (mehr) in `dataSources` / `featureDataSources` aufloesbar sind, automatisch raus (loescht-im-Hintergrund-Drop) -- so verschwinden Chips von zwischenzeitlich geloeschten Quellen still. * `_removeAttachedDataSource` / `_toggleFeatureDataSource` rufen `PATCH .../attachments` auf, sodass der "x"-Klick auch ohne Folge-Send dauerhaft wirkt. Klarstellung "x"-Verhalten: Im Chip-Bar des WorkspaceInput entfernt "x" die Quelle nur aus der Chat-Anhaengung (Detach). In der UDB > Sources-Tab loescht "x" die DataSource-Registrierung selbst (unveraendertes Verhalten). Files: gateway/modules/datamodels/datamodelChat.py, gateway/modules/interfaces/interfaceDbChat.py, gateway/modules/features/workspace/routeFeatureWorkspace.py, frontend_nyla/src/pages/views/workspace/useWorkspace.ts, frontend_nyla/src/pages/views/workspace/WorkspaceInput.tsx, frontend_nyla/src/pages/views/workspace/WorkspacePage.tsx 2026-04-20 Orphan-False-Positive Root-Cause: Feature-Registry vs. Feature-DB-Tabelle Symptom: Admin > DB-Health meldet alle FeatureInstance-Zeilen als Orphans (FeatureInstance.featureCode -> Feature.code), obwohl die Features ("trustee", "chatbot", "workspace", ...) im laufenden System klar existieren. Ursache: Feature-Definitionen werden beim Boot aus den Modulen `modules/features/*/main*.py` via `registerAllFeaturesInCatalog` ausschliesslich in den In-Memory-RBAC-Catalog (`_featureDefinitions`) geschrieben. Die persistente DB-Tabelle `Feature` wird dabei NIE geseedet -- sie wuerde nur ueber expliziten Admin-Aufruf von `createFeature(...)` befuellt. Der FK ist also realistisch verwaist, obwohl die Feature-Definition fachlich existiert. Fix (Option A: Auto-Sync beim Boot, behaelt FK-Integritaet): - Neue Methode `FeatureInterface.upsertFeature(code, label, icon)` in `interfaceFeatures.py`: idempotenter Insert/Update inkl. Drift-Check auf Label und Icon (akzeptiert String, Dict oder TextMultilingual). - Neue Funktion `syncCatalogFeaturesToDb(catalogService)` in `modules/system/registry.py`: walkt alle catalog-registrierten Definitionen und ruft `upsertFeature` pro Eintrag auf. Returnt `{code: action}` mit action in {"created", "updated", "unchanged", "error"} und loggt eine Boot-Zeile mit Aggregat-Counts. - `app.py` (lifespan) ruft `syncCatalogFeaturesToDb` direkt nach `registerAllFeaturesInCatalog` auf, sodass die Feature-Tabelle bei jedem Server-Start mit der Code-Wahrheit synchron ist. Effekt: Beim naechsten Restart sind alle Feature-Codes in der DB vorhanden; Orphan-Scan fuer `FeatureInstance.featureCode` faellt auf Null. Der Mechanismus ist additiv: bestehende DB-Eintraege werden nicht geloescht, nur fehlende erzeugt und veraltete Labels/Icons aktualisiert. 2026-04-20 Vier Issues aus int-Test-Run: Stripe-Top-Up, Stripe-Rechnungen CH, AI-Agent Outlook 25-Limit und Admin Orphan-Cleanup False-Positives 1) Stripe AI-Top-Up: UI-Eingabe als freier Betrag wurde durch Dropdown ersetzt, das die server-seitige Allow-List ALLOWED_AMOUNTS_CHF aus `serviceCenter/services/serviceBilling/stripeCheckout.py` zieht. Neuer Endpoint `GET /api/billing/checkout/amounts` liefert die Liste, `frontend_nyla/src/pages/billing/BillingAdmin.tsx` rendert sie. Damit scheitert der Submit nicht mehr server-seitig wegen "ungueltiger Betrag". 2) Stripe-Rechnungen CH-Treuhand-konform: - Neues optionales Feld `Mandate.invoiceAddress` (datamodelUam.py) + Pydantic-Validator, der Dict / `InvoiceAddress`-Instanz / JSON-String in dict-Form fuer JSONB-Storage normalisiert (vorher wurde komplexes Pydantic-Objekt von `_separateObjectFields` verworfen). - `stripeCheckout.create_checkout_session` erweitert um Mandanten-Label, Invoice-Address, Settings, BillingInterface. Erstellt/aktualisiert Stripe-Customer (`_ensureStripeCustomer`), aktiviert `invoice_creation` mit Description, Metadata, Footer ("bereits via Kreditkarte bezahlt") und UID-Custom-Field. Tax: entweder Stripe Tax (STRIPE_AUTOMATIC_TAX_ENABLED) oder fester `STRIPE_TAX_RATE_ID_CH_VAT` (8.1% MWST exclusive). - `routeBilling.createCheckoutSession` lädt den Mandanten via `interfaceDbApp.getMandate(...)` und reicht Label + invoiceAddress weiter. - Doku: neues Wiki-Howto `wiki/d-guides/stripe-ch-vat.md` mit Stripe-Dashboard-Setup (Tax-Rate, Invoice-Template, UID) und Test-Checkliste. 3) AI-Agent Outlook 25-Limit: Hardcoded `$top=25` in `connectors/providerMsft/connectorMsft.py` (OutlookAdapter.browse + .search) wurde aufgehoben. ServiceAdapter-Basisklasse hat jetzt `limit: Optional[int]` in `browse()` und `search()`; OutlookAdapter paginiert ueber `@odata.nextLink` bis Limit (Default 100, max 1000), GmailAdapter analog (Default 100, max 500). Alle uebrigen Adapter (Sharepoint, OneDrive, Drive, FTP, ClickUp) wurden auf die neue Signatur gehoben (slicen lokal). AI-Tool `browseDataSource` und `searchDataSource` in `serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py` akzeptiert neuen `limit`-Parameter und beschreibt ihn im Schema, damit der Agent ihn bei "lese alle Mails" hochziehen kann. 4) Admin Orphan-Cleanup False-Positives + Download-Button: - Root Cause vieler False-Positives in `gateway/modules/system/databaseHealth._scanOrphans`: FK-Annotationen auf Pydantic-Feldern, die nicht als physische Scalar-Spalten in der DB existieren (List/Dict -> JSONB-blob, virtuelle/computed Felder). Der SQL-Vergleich `s."col" = t."col"` schlug auf JSONB-Arrays oder fehlenden Spalten zwangslaeufig fehl und meldete die KOMPLETTE Source-Tabelle als Orphans. Fix: neue Helper `_loadPhysicalColumns` prueft via information_schema.columns, ob `sourceColumn` und `targetColumn` physisch existieren; nicht-physische FKs werden uebersprungen (mit Debug-Log). - Neuer Endpoint `GET /api/admin/database-health/orphans/list` (und Backend-Funktion `_listOrphans`) liefert bis zu N (default 1000, max 10000) konkrete Orphan-Datensaetze mit der unaufloesbaren FK-Value plus dem vollen Source-Row als JSON. Werte werden JSON-safe normalisiert (datetime, decimal, uuid, bytes). - Frontend `pages/admin/AdminDatabaseHealthPage.tsx`: pro Orphan-Zeile neuer Download-Button (FaDownload), laedt das JSON-Snapshot (`orphans____.json`), damit der SysAdmin die Orphan-Liste ueberpruefen kann BEVOR er die Bereinigung ausloest. Files: * gateway/modules/routes/routeBilling.py * gateway/modules/serviceCenter/services/serviceBilling/stripeCheckout.py * gateway/modules/datamodels/datamodelUam.py * gateway/modules/connectors/connectorProviderBase.py * gateway/modules/connectors/providerMsft/connectorMsft.py * gateway/modules/connectors/providerGoogle/connectorGoogle.py * gateway/modules/connectors/providerFtp/connectorFtp.py * gateway/modules/connectors/providerClickup/connectorClickup.py * gateway/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py * gateway/modules/system/databaseHealth.py * gateway/modules/routes/routeAdminDatabaseHealth.py * frontend_nyla/src/api/billingApi.ts * frontend_nyla/src/pages/billing/BillingAdmin.tsx * frontend_nyla/src/pages/admin/AdminDatabaseHealthPage.tsx * wiki/d-guides/stripe-ch-vat.md (neu) 2026-04-20 Admin Orphan-Cleanup: Schutz gegen kaskadierendes Wipen ganzer Tabellen Bug: Beim Ausführen von "Alle bereinigen" im Admin → Database-Health wurden alle Feature-Instanzen (und kaskadierend zugehoerige User-Daten) geloescht. Root Cause in `gateway/modules/system/databaseHealth.py`: * `_cleanOrphans` (cross-DB-Pfad) interpretierte einen leeren Parent-IDs-Set als "kein gültiger FK-Wert mehr existiert" und loeschte ALLE Source-Zeilen mit non-null FK. Der same-DB-Pfad verhielt sich identisch via `NOT EXISTS (SELECT … target)`, das bei leerer Target-Tabelle alle Source-Zeilen matcht. * `_cleanAllOrphans` iterierte die vorher gescannte Orphans-Liste sequentiell; sobald eine Iteration eine Target-Tabelle leerte (z.B. `User`), sahen Folgeiterationen frisch eine leere Parent-Tabelle (`_loadParentIds` lief pro Aufruf neu) und loeschten daraufhin alle abhaengigen Tabellen (`FeatureInstance`, etc.) → Kaskade. Fix: * `_scanOrphans` liefert jetzt zusaetzlich `sourceRowCount`, `targetRowCount`, `targetEmpty`, `wouldDeleteAll` pro FK-Beziehung. Frontend kann damit Warnungen anzeigen. * `_cleanOrphans(force=False)` verweigert die Loeschung wenn: - die Target-Tabelle leer ist obwohl Source non-null FK-Werte hat (typische Fehlkonfiguration / lazy table / falsch gemappte FK). - die Loeschung ≥ 50% (`_MAX_CLEANUP_FRACTION`) der Source-Zeilen mit non-null FK entfernen wuerde. In beiden Faellen wird `OrphanCleanupRefused` geworfen → Route gibt HTTP 409 mit `{refused: true, reason: …}` zurueck. * `_cleanAllOrphans(force=False)` skippt jede betroffene Beziehung statt die ganze Aktion abzubrechen; pro Result steht jetzt entweder `deleted`, `skipped` oder `error`. * Route `POST /api/admin/database-health/orphans/clean` und `…/clean-all` akzeptieren neuen Body-Parameter `force` (default false). Force wird explizit ge-loggt. Frontend (`AdminDatabaseHealthPage.tsx`): * `OrphanEntry` und `CleanResult` um neue Felder erweitert. * `_cleanOne`: zeigt bei `targetEmpty || wouldDeleteAll` zusaetzliche Warnung im Confirm-Dialog. Auf 409 wird ein zweiter expliziter Confirm geoeffnet, erst dann mit `force: true` retried. * `_cleanAll`: nach dem ersten Pass wird der Anwender, falls Skips vorliegen, gefragt ob er mit `force: true` retryen will. Note: Bestehende Daten lassen sich aus dem Postgres-Audit-Log / Backups rekonstruieren; diese Aenderung verhindert nur eine Wiederholung. 2026-04-20 PeriodPicker / Schritt 3+4: Backend Clean-Cut auf dateFrom/dateTo + bucketSize Motivation: nach den Schritten 1+2 (FormGeneratorReport, ComplianceAuditPage) blieben drei Statistik-Endpunkte (`/api/audit/stats`, `/api/billing/statistics`, `/api/billing/view/statistics`), die ihre Date-Range mit Bucket-Granularitaet (`period: day|month|year`) bzw. einer Tagesanzahl (`timeRange`) vermischten, und ein Adapter `_periodToDays` im Frontend, der "01.04 - 03.04" als "letzte 3 Tage ab heute" interpretierte → falsche KPIs. Plan: Backends sauber auf `dateFrom`/`dateTo` (ISO YYYY-MM-DD) + separate `bucketSize` migrieren, alle Frontend-Caller in derselben atomaren Aenderung mitziehen, keine Backwards-Compat-Aliasse. Umgesetzt: Backend (Gateway): - Neuer Helper `gateway/modules/shared/dateRange.py` mit `parseIsoDate`, `parseIsoDateRange`, `isoDateRangeToLocalEpoch` (inklusive End-of-Day- Boundary, lokale Zeitzone konsistent zu bestehender `datetime.combine(...).timestamp()`-Semantik) und `daysInRange`. 14 Unit-Tests in `gateway/tests/test_dateRange.py`, alle gruen. - `aiAuditLogger.getAiAuditStats(mandateId, *, fromTs, toTs, groupBy)` - Pflicht-Range, `timeRangeDays` entfernt; Response enthaelt jetzt `fromTs`, `toTs`, `days`. - `routeAudit.getAuditStats`: Query-Params `dateFrom`/`dateTo` (Pflicht, ISO YYYY-MM-DD) + `groupBy` (regex `model|user|feature|day`). `timeRange` ist entfallen (HTTP 422 bei Verwendung). - `interfaceDbBilling.getTransactionStatisticsAggregated`: Param `period` umbenannt zu `bucketSize`, unterstuetzt jetzt zusaetzlich `year`. Eine interne Lookup-Map ersetzt die bisherige if/else-Verzweigung. - `routeBilling.getStatistics`: Pfad `GET /api/billing/statistics/{period}` -> `GET /api/billing/statistics`. Pflicht-Query-Params `dateFrom`, `dateTo`, `bucketSize`. Response-Schema `UsageReportResponse` enthaelt nun `dateFrom`, `dateTo`, `bucketSize` statt `period`. - `routeBilling.getUserViewStatistics`: analog auf `dateFrom`/`dateTo`/ `bucketSize` umgestellt; alte `period`/`year`/`month`-Params entfallen. Logging-Format mit aktualisiert. - `routeSystem`: einziger interner Caller von `getTransactionStatisticsAggregated` auf `bucketSize="month"` migriert. Frontend (Nyla): - `billingApi.fetchStatistics(request, { dateFrom, dateTo, bucketSize })` neu signiert; `UsageReport`-Type angepasst; neuer Type `StatisticsRangeRequest` + `BillingBucketSize`. - `useBilling.loadStatistics(range)` neu signiert. - `BillingDashboard`: hartkodierter Period/Year/Month-Selector durch `` + Bucket-Toggle ersetzt. Default-Preset `thisMonth`, Heuristik `_suggestBucketSize` waehlt initialen Bucket (=<62 Tage:day, <=24 Monate:month, sonst year), User-Override haftet. - `BillingDataView`: `periodSelectorConfig` -> `dateRangeSelectorConfig`, Tab "Diagramme" konsumiert `filterState.dateRange` und steuert `bucketSize` lokal. Initialer Load ueber `statsPeriod`/`statsBucketSize`. - `ComplianceAuditPage`: Adapter `_periodToDays` ersatzlos entfernt, `_loadStats` akzeptiert direkt `{ dateFrom, dateTo }`. Custom-Range liefert jetzt korrekte KPIs fuer den exakt gewaehlten Bereich. - `PeriodPicker`-Logik: `daysInRange` als public Helper hinzugefuegt und aus `index.ts` exportiert. Konsumenten-Pruefung im Workspace `poweron`: keine externen Aufrufer ausserhalb der drei Frontend-Stellen; das parallele `actan/sanctions`- Projekt hat eigenes Backend und ist nicht im Scope dieses PR. Plan: `wiki/c-work/4-done/2026-04-period-picker-billing-audit-migration.md` (verschoben aus `1-plan/`). TypeScript-Build (`tsc --noEmit`) clean. dateRange-Tests 14/14 gruen. Lints clean. 2026-04-20 InvestorDemo April: Chatbot-Instanz nicht mehr automatisch anlegen Auf Wunsch wurde die Chatbot-Feature-Instanz aus dem HappyLife-Mandanten des InvestorDemo entfernt. Der Demo-Bootstrap legt die Chatbot-Instanz nicht mehr an (alpina hatte sie schon nicht). Geaendert: gateway/modules/demoConfigs/investorDemo2026.py Hinweis: Bestehende Chatbot-Instanzen in bereits ausgerollten Demo-Mandanten werden NICHT zurueckgerollt — nur Neueinspielungen sind betroffen. 2026-04-20 Bugfix: Outlook-UDB zeigt Inbox/Posteingang jetzt zuverlaessig Symptom: In der UDB war beim MSFT-Outlook-Connector kein Inbox-/Posteingang- Ordner sichtbar, obwohl das der Default-Ordner ist. Root Cause: `OutlookAdapter.browse()` in connectorMsft.py rief `GET /me/mailFolders` ohne `$top` und ohne Pagination auf. Microsoft Graph liefert in dem Fall Default-Pagesize 10 Top-Level-Mailfolders. Bei Konten mit vielen System-Ordnern (Archiv, Gesendet, Entwuerfe, Postausgang, Geloescht, Junk, Notizen, RSS, Kalender, Verlauf …) faellt der Inbox- Ordner haeufig aus den ersten 10 raus und ist damit unsichtbar. Fix: - `$top=100` + Pagination via `@odata.nextLink` (neue Helperfunktion `_stripGraphBase`), damit ALLE Top-Level-Folder geliefert werden. - Hard-Fallback: wenn nach Pagination kein Folder mit Name "Inbox" oder "Posteingang" dabei ist, wird der Well-Known-Endpoint `/me/mailFolders/inbox` zusaetzlich abgeholt und vorne eingefuegt. Damit ist der Default-Ordner unabhaengig von Sprache/Reihenfolge immer sichtbar. - Metadata-Felder in der ExternalEntry erweitert: `unreadItemCount`, `childFolderCount` (nuetzlich fuer UI-Anzeige). Geaendert: gateway/modules/connectors/providerMsft/connectorMsft.py 2026-04-20 Bugfix: Workflow-Bootstrap bei neuer Feature-Instanz haerten Symptom: Beim Hinzufuegen einer neuen Feature-Instanz (z.B. Trustee) fehlten immer die Template-Workflows. Admin musste manuell pro Instanz "Sync Workflows" klicken um die 7 Trustee-Templates nachzutragen. Root Cause: `_copyTemplateWorkflows()` in `interfaceFeatures.py` hatte ein zu breites `except Exception` das Fehler nur als WARNING (ohne Traceback) loggte. Wenn beim Erstellen ein Workflow fehlschlug, wurde das ohne sichtbaren Hinweis verschluckt. Zusaetzlich wurde `getRootInterface().currentUser` verwendet statt `getRootUser()` aus `security.rootAccess` (was im funktionierenden Sync-Endpoint verwendet wird). Fix: - `_copyTemplateWorkflows()` macht das Logging laut: INFO am Anfang (mit Anzahl der zu kopierenden Workflows) und ERROR mit Traceback bei jedem Einzelfehler. Wirft RuntimeError wenn mind. 1 Workflow fehlschlug. - `createFeatureInstance()` faengt den RuntimeError bewusst ab und loggt eine klare Recovery-Anleitung (sync-workflows-Endpoint), damit eine Workflow-Bootstrap-Fehlfunktion die Instanz-Erstellung nicht killt. - User-Lookup via `getRootUser()` (konsistent mit working sync-workflows-Route). Geaendert: gateway/modules/interfaces/interfaceFeatures.py DONE. UDB Sources Tab - hierarchische Datenstruktur ueberarbeitet (Trustee + CommCoach), Backend + Frontend + Catalog. Motivation: (a) Trustee-Tabellen mit "(Sync)"-Suffix waren visuell unstrukturiert und vermischten lokale Daten, Konfiguration und externe Buchhaltungsdaten; (b) CommCoach hatte unlogische Einrueckung weil `CoachingMessage`/`CoachingScore` keine `parentTable`-Referenz auf `CoachingSession` hatten und Stammdaten (Profile, Persona, Badge) ohne Trennung in der gleichen Liste standen; (c) UDB konnte keine echte Mehr-Ebenen-Record-Hierarchie (Context -> Session -> Message) und keine kategorischen Gruppen-Ordner darstellen. - Backend Catalog: neue Meta-Properties `isGroup: True` (kategorischer Ordner ohne eigene Tabelle) und `group: ` (verweist auf Gruppe). `mainTrustee.DATA_OBJECTS` umstrukturiert in 3 Gruppen: "Lokale Daten" (Position, Dokument), "Konfiguration" (Buchhaltungs-Verbindung, Sync-Protokoll), "Daten aus Buchhaltungssystem" (5 ehemalige `(Sync)`-Tabellen ohne Suffix). `mainCommcoach.DATA_OBJECTS`: 2-stufige Record-Hierarchie eingefuehrt - `CoachingSession` ist jetzt selbst `isParent: True` und `parentTable: CoachingContext` (nesting!), `CoachingMessage`/`CoachingScore` haben `parentTable: CoachingSession, parentKey: sessionId`. `CoachingTask` bleibt `parentTable: CoachingContext`. Stammdaten (`CoachingUserProfile`, `CoachingPersona`, `CoachingBadge`) sind in der neuen Gruppe "Stammdaten". Teamsbot bleibt unveraendert (1-Ebenen-Hierarchie ist sauber genug). - Backend Route `routeFeatureWorkspace.listFeatureConnectionTables`: Response um `isGroup` und `group` erweitert. Group-Eintraege werden zusaetzlich included wenn mindestens eine Kind-Tabelle accessible ist (umgeht RBAC-Lookup fuer reine Metadaten-Folder). - Backend Route `routeFeatureWorkspace.listParentObjects`: 2 neue optionale Query-Params `parentKey` und `parentValue`. Wenn gesetzt, wird die Records-Liste zusaetzlich nach diesem FK gefiltert (Spaltenexistenz wird via information_schema verifiziert; falls Spalte nicht existiert, wird der Filter mit Warnung ignoriert -> abwaertskompatibel). Damit kann der Frontend "alle Sessions DIESES Context X" laden, nicht nur "alle Sessions der Instanz". - Frontend `SourcesTab.tsx` komplett refactored. Neue path-aware State-Keys (Set) statt flachem `featureInstanceId-tableName`-Schluessel: Segmente `g:` (Group-Folder), `p:
` (Parent-Group/Record-Liste), `r:
:` (einzelner Record). Beispiel-Pfad fuer Sessions von Context ctx-1: `|p:CoachingContext|r:CoachingContext:ctx-1|p:CoachingSession`. Records werden in `featureRecordsByPath: Record` gehalten, sodass dieselbe Tabelle (z.B. CoachingMessage) an verschiedenen Stellen im Baum verschiedene Record-Listen tragen kann. - Neuer Tree-Builder `_buildTopFeatureTree(tables)` baut aus der flachen Catalog-Liste die Top-Level-Items (Group-Folder, Parent-Group, Standalone-Table). Tabellen mit `parentTable` werden NICHT top-level gerendert, sondern dynamisch via `_childrenForRecord(allTables, parentTableName)` wenn ein Record expandiert wird -> ermoeglicht echte Rekursion. Helper `_closestRecordSegment(segments)` walked path zurueck und liefert den naechsten `r:`-Segment, damit beim Records-Loading automatisch `parentKey`/`parentValue` an Backend geschickt werden. - Neue rekursive Render-Komponenten: `_FeatureItemView` (dispatcher), `_GroupFolderView` (Ordner mit chevron), `_ParentGroupView` (Tabelle -> lazy-loadet Records), `_RecordRowView` (Record mit "+"-Button um Record + Direkt-Kinder als FeatureDataSource hinzuzufuegen, expandiert dann rekursiv `_FeatureItemView` fuer Kind-Tabellen). Bestehende `_FeatureTableRow` und `_FeatureFieldRow` bleiben funktional, bekommen aber path-relative `depth` fuer korrekte Einrueckung. Drag&Drop, Chat-Send, Scope-Cycle, Neutralize: alle bestehenden Handler unveraendert verdrahtet, aber path-aware (record-Drag liefert jetzt das richtige objectKey + Label inkl. Record-Bezeichnung). Group-Folder werden beim ersten Tabellen-Load automatisch expandiert (defaultExpansions in `_toggleFeatureNode`), damit Inhalt sofort sichtbar ist. - Geaenderte Dateien: `gateway/modules/features/workspace/routeFeatureWorkspace.py`, `gateway/modules/features/trustee/mainTrustee.py`, `gateway/modules/features/commcoach/mainCommcoach.py`, `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx`. DONE. Trustee Dashboard / Quick-Actions Rolling-Lookup-Bug behoben (zusammen mit dem i18n-Fix unten). Symptom: Auf dem Trustee-Dashboard erschien die Sektion "Schnellaktionen" zuerst kurz als Loading-Skeleton und verschwand dann komplett. Ursache: `getQuickActions` (`routeFeatureTrustee.py`) hat die Rollen des Users so ermittelt: `roleIds = fa.roleIds if hasattr(fa, "roleIds") and fa.roleIds else []` Der `FeatureAccess`-Pydantic-Model (in `datamodelMembership.py`) hat aber GAR KEIN `roleIds`-Feld - die Rollen sind in der Junction-Tabelle `FeatureAccessRole` und muessen explizit ueber `rootInterface.getRoleIdsForFeatureAccess(featureAccessId)` geholt werden. Folge: `userRoleLabels` blieb fuer alle Nicht-PlatformAdmins leer, der Filter `required.intersection(userRoleLabels)` matchte nichts, und das Endpoint lieferte `actions: []` zurueck. Im Frontend rendert `QuickActionBoard` bei `actions.length === 0` `null` -> Sektion verschwindet nach dem Loading. PlatformAdmin-User waren nicht betroffen, weil `if context.isPlatformAdmin: userRoleLabels.add("trustee-admin")` als Shortcut greift. Fix: Code rueckt das `getFeatureAccessesForUser` aus dem `else`-Zweig heraus (laeuft jetzt fuer alle User) und nutzt `rootInterface.getRoleIdsForFeatureAccess(str(fa.id))` fuer die korrekte Rollen-Auflistung. PlatformAdmin-Shortcut bleibt zusaetzlich erhalten, sodass ein User, der gleichzeitig PlatformAdmin und z.B. trustee-accountant ist, ohnehin alle requiredRoles erfuellt. DONE. Trustee Dashboard / Mandant-Anzeige + Quick-Actions i18n: zwei zusammenhaengende Bugs behoben, die der User als "Mandant zeigt Kurzzeichen statt Voller Name" und "Texte sind hardcoded, nicht i18n" gemeldet hat. 1) Backend `routeAdminFeatures.get_my_feature_instances` (`/api/features/my`): Der Mandate-Eintrag im Response enthielt nur `id`, `name`, `code` - das Feld `label` (Voller Name, Pflichtfeld im Datenmodell `Mandate`) wurde nie mitgesendet. Frontend-Typ `Mandate` deklariert `label: string` (mandatory), `mandate.label` war zur Laufzeit aber `undefined`. Folge: `TrusteeDashboardView`-Fallback `mandate?.label || instance?.mandateName` fiel immer auf `mandateName` zurueck, das im selben Endpoint mit `mandate.name` (= Slug / Kurzzeichen) gefuellt wird -> User sah "soha-treuhand" statt "Soha Treuhand". Fix: `mandatesMap[mandateId]` enthaelt jetzt zusaetzlich `label` (= `mandate.label` oder Fallback auf `name`); jede Instance-Antwort traegt analog `mandateLabel`. Frontend-Typ `FeatureInstance` um optionales `mandateLabel?: string` erweitert. Dashboard-Fallback-Kette ausgebaut auf `mandate?.label || instance?.mandateLabel || mandate?.name || instance?.mandateName || '-'` (zwei unabhaengige Quellen, robuster bei Race-Conditions zwischen FeatureStore-Hydration und URL-Auswertung). 2) Backend `routeFeatureTrustee.getQuickActions` (`/api/trustee/{instanceId}/quick-actions?language=...`): Der Endpoint nahm `language` als Query-Parameter entgegen, gab ihn aber nie an `resolveText()` weiter, weshalb alle Quick-Action-Labels und -Kategorien immer in der Default-Sprache des Servers (i.d.R. de) gerendert wurden, egal welche UI-Sprache der User hatte. Fix: `lang = (language or "de").strip() or "de"` und alle 4 `resolveText(...)`-Calls erhalten jetzt `lang=lang`. Damit greift fuer Quick-Action-Labels jetzt die gleiche Logik wie ueberall: `t()` schlaegt im i18n-Catalog nach und faellt auf den deutschen Key zurueck, wenn keine Uebersetzung existiert. Voraussetzung fuer komplette EN/FR/IT-Anzeige bleibt das Pflegen der i18n-Catalog-Eintraege (separater Folge-Schritt). Build: `npm run build` -> Exit 0. DONE. Trustee Top-Level-Seiten "Positionen" und "Dokumente" entfernt - voll konsolidiert in der neuen "Daten-Tabellen"-Seite (Tabs `?tab=positions` und `?tab=documents`). Alte Routen werden jetzt nicht mehr ausgeliefert; bestehende Links / Bookmarks landen im 404 (sind aber leicht migrierbar). - Frontend: Routen `` und `` aus `App.tsx` entfernt. View-Mappings + Imports aus `pages/FeatureView.tsx` entfernt (TrusteePositionsView/TrusteeDocumentsView werden nur noch von TrusteeDataTablesView importiert -> bleiben als Komponenten erhalten und werden als Tab-Inhalt eingebettet, damit Sync/Beleg-Download/Edit/Delete-Logik nicht dupliziert wird). FEATURE_REGISTRY in `types/mandate.ts` um die zwei Eintraege bereinigt. - Backend: UI_OBJECTS `ui.feature.trustee.positions` und `ui.feature.trustee.documents` aus `mainTrustee.py` entfernt. Alle 4 Template-Roles (trustee-viewer, trustee-user, trustee-accountant, trustee-client) bereinigt -> sie verweisen nur noch auf `ui.feature.trustee.data-tables`. DATA-Permissions (`data.feature.trustee.TrusteePosition` / `.TrusteeDocument`) bleiben unveraendert und gaten weiterhin den Datenzugriff pro Tabelle. Bestehende AccessRules in der DB mit den alten Object-Keys werden inert (kein Schaden), koennen aber via Migrationsskript bereinigt werden, falls gewuenscht. - Pre-existing Build-Bug in `TrusteeAbschlussView.tsx` mitkorrigiert: `tabDef.templateTag` (string|null) wurde an `Array.includes()` (string) uebergeben -> Null-Guard hinzugefuegt. - `npm run build` (vite + tsc) gruen, 2125 Module transformiert, dist generiert. - Geaenderte Dateien: `frontend_nyla/src/App.tsx`, `frontend_nyla/src/pages/FeatureView.tsx`, `frontend_nyla/src/types/mandate.ts`, `frontend_nyla/src/pages/views/trustee/TrusteeAbschlussView.tsx`, `gateway/modules/features/trustee/mainTrustee.py`. DONE. Trustee "Daten-Tabellen" Action-Icons (RBAC-aware) ergaenzt. Die generischen Tabs hatten zuvor keine Edit/Delete/Custom-Actions, sodass z.B. die Position-Tab nicht mit der bestehenden "Positionen"-Seite uebereinstimmte (kein Sync, kein Beleg-Download). - `TrusteeDataTab.tsx`: nimmt jetzt optional einen `operationsHook` (handleDelete/handleUpdate/deletingItems) entgegen. Bei `readOnly=false` und vorhandenem Operations-Hook werden Edit + Delete actionButtons gerendert, RBAC-gegated via `permissions.update`/`permissions.delete` (n = ausgeblendet). Edit oeffnet ein FormGeneratorForm-Modal (System-Felder ausgeblendet), Delete laeuft optimistisch mit Rollback bei Fehler. Inline-Update wird ebenfalls verdrahtet, wenn handleUpdate vorhanden ist. Sync-Tabellen bleiben aktionsfrei. - `TrusteeDataTablesView.tsx`: Wrapper-Strategie nach Entitaets-Typ: (a) **Position** und **Dokument** rendern direkt `` bzw. `` - dort sind Sync-to-Accounting (Position) + Beleg-Download (1-2 Icons je Position) + Download (Dokument) bereits voll implementiert inkl. RBAC und Optimistic-Updates. (b) **Organisation, Rolle, Zugriff, Vertrag** verwenden den generischen `TrusteeDataTab` mit Operations-Hook -> Edit/Delete. (c) Die 7 Sync-/Accounting-Tabellen bleiben read-only ohne Aktionen. So entsteht keine Code-Duplikation, und Aenderungen an Position/Dokument-Logik wirken sofort in beiden Sichten. - Geaenderte Dateien: `frontend_nyla/src/pages/views/trustee/dataTables/TrusteeDataTab.tsx`, `frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx`. DONE. FormGeneratorTable Layout-Chain Fix (Trustee "Daten-Tabellen" Seite war abgeschnitten / nicht auf Seitenbreite). Root Cause: `TrusteeDataTablesView` und `TrusteeDataTab` wickelten die Tabelle in `.listView` + `.expenseImportSection` (`max-width: 800px`) ein und etablierten keine bounded height chain -> `FormGeneratorTable` (intern `flex:1; min-height:0`) kollabierte horizontal und vertikal. Same-Bug-Pattern wie wir ihn schon mehrfach hatten. - Fix: Beide Komponenten nutzen jetzt das kanonische Pattern aus `Admin.module.css`: Outer `${adminStyles.adminPage} ${adminStyles.adminPageFill}` (= `flex:1 1 auto; min-height:0; overflow:hidden`), direktes Tabellen-Eltern-Element `${adminStyles.tableContainer}` (= `flex:1; min-height:0; overflow:hidden; flex-direction:column`). Die Tab-Wrapper-Komponente `TrusteeDataTab` reicht die Chain via inline `_rootStyle`/`_tableWrapStyle` (flex column, flex:1, min-height:0, width:100%) weiter; Toolbar bekommt `flexShrink:0`. Tab-Bar ebenfalls `flexShrink:0`. - Wiki: neuer Abschnitt "Page Layout Chain (Pflicht)" in `wiki/b-reference/frontend-nyla/formgenerator.md` mit korrektem Pattern, Anti-Patterns (`max-width`-Wrapper, fehlendes `adminPageFill`, fehlendes `flex:1; min-height:0` im Wrapper) und Tab-/Wrapper-Spezialfall - damit der Bug bei naechsten Einbindungen vermieden wird. - Geaenderte Dateien: `frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx`, `frontend_nyla/src/pages/views/trustee/dataTables/TrusteeDataTab.tsx`, `wiki/b-reference/frontend-nyla/formgenerator.md`. DONE. Trustee "Daten-Tabellen" Seite: konsolidierte UI-Sicht auf alle 13 Trustee-Tabellen einer Instanz (Organisation, Rolle, Zugriff, Vertrag, Dokument, Position + 5 Sync-Tabellen + 2 Accounting-Tabellen). Motivation: bisher waren nur "Positionen" und "Dokumente" als eigene Top-Level-Seiten sichtbar, die uebrigen Tabellen (TrusteeData* aus Bexio/RMA-Sync, TrusteeAccountingConfig/Sync) nur via JSON-Export oder AI-Agent erreichbar -> intransparent fuer Treuhaender:innen und Auditor:innen. - Backend: 7 neue paginierte Read-only-Endpunkte mit Unified Filter API (`mode=filterValues|ids`) in `gateway/modules/features/trustee/routeFeatureTrustee.py` (`GET /api/trustee/{instanceId}/data/{accounts|journal-entries|journal-lines|contacts|account-balances}` und `GET /api/trustee/{instanceId}/accounting/{configs|syncs}`). Generischer Helper `_paginatedReadEndpoint(...)` haelt die Logik DRY. `_TRUSTEE_ENTITY_MODELS` (fuer `/attributes/{entityType}`) um die 7 Modelle erweitert. Neuer UI-Object-Eintrag `ui.feature.trustee.data-tables` in `mainTrustee.py`; `trustee-viewer`, `trustee-user`, `trustee-accountant` AccessRules ergaenzt (Admin hat Wildcard). - Frontend: neue Seite `TrusteeDataTablesView` (`frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx`) mit Tab-Bar (URL-State `?tab=`, Lazy-Mount: nur aktiver Tab fuehrt Datenfetch aus). Generischer Tab-Body `TrusteeDataTab` (`.../dataTables/TrusteeDataTab.tsx`) bindet `FormGeneratorTable` mit Pagination/Sort/Filter/Search ein - Read-only-Mode versteckt Edit/Delete/Select. 7 neue Read-only-Hooks in `useTrustee.ts` via `_createTrusteeEntityHook`, 7 neue API-Funktionen in `trusteeApi.ts`. Routing: `App.tsx` (``), `FeatureView.tsx` (Mapping `'data-tables': TrusteeDataTablesView`), `index.ts` (Export), `types/mandate.ts` (FEATURE_REGISTRY). - Bestehende Seiten "Positionen" und "Dokumente" bleiben vorerst unveraendert (Sicherheitsnetz bis neue Seite produktiv verifiziert; Aufraeumen folgt in eigenem Plan). - Plan: `wiki/c-work/2-build/2026-04-trustee-data-tables-page.md`. DONE. PeriodPicker / Rollout Schritt 1+2: zwei weitere Stellen migriert. Motivation: nach dem Initial-Rollout in den Trustee-Views (siehe naechster Eintrag) gibt es im Codebase noch drei weitere Ad-hoc-Period-UIs, die alle auf PeriodPicker konsolidiert werden sollten - aber mit unterschiedlichem Aufwand: `FormGeneratorReport.dateRangeSelector` (drop-in, da kein Caller das Feature heute aktiv nutzt), `ComplianceAuditPage` Tab "Statistics" (Adapter, da Backend `/api/audit/stats` nur `timeRange={days}` versteht), und `BillingDashboard`/`BillingDataView` (braucht erst Backend-Erweiterung der Stat-Endpunkte). Diese Aenderung erledigt die ersten beiden, der Rest steht als Plan in `wiki/c-work/1-plan/2026-04-period-picker-billing-audit-migration.md` (umfasst Backend-Erweiterung von `routeAudit.getAuditStats`, `routeBilling.getStatistics`, `routeBilling.getUserViewStatistics` um `dateFrom`/`dateTo` plus die separierte `bucketSize`-Verantwortung, und anschliessend die Billing-Frontend-Migration). Schritt 1: `FormGeneratorReport.dateRangeSelector` - die Toolbar enthielt zwei `` Von/Bis-Felder direkt im Markup. `ReportDateRangeSelectorConfig` um `direction`, `defaultPresetKind`, `enabledPresets`, `minDate`, `maxDate` erweitert. Im `_Toolbar` werden die zwei ``s durch einen `` ersetzt, der intern in `PeriodValue` haelt und beim onChange auf `filterState.dateRange = { from: Date, to: Date }` zurueckmappt -> kein einziger Caller von `FormGeneratorReport` muss angefasst werden, der externe Output-Kontrakt (`ReportFilterState.dateRange`) bleibt 1:1 stabil. Da `dateRangeSelector?.enabled` bisher von keinem Caller (geprueft per ripgrep) aktiviert war, ist das Risiko effektiv null. Sobald irgendein Report `dateRangeSelector={{ enabled: true, direction: 'past' }}` setzt, bekommt er ohne Mehraufwand den vollen PeriodPicker mit allen Presets. Schritt 2: `ComplianceAuditPage.tsx` Tab C "Statistics" - die drei Buttons `7 / 30 / 90 Tage` ersetzt durch einen `` mit `enabledPresets=['lastN','last12Months','lastYear','thisMonth','lastMonth','thisQuarter','lastQuarter','ytd','custom']`. Da das Backend `/api/audit/stats?timeRange={days}` heute nur eine Tagesanzahl erwartet (kein Date-Range), wurde ein Adapter `_periodToDays(value: PeriodValue): number` eingebaut: `lastN`+`day` -> `amount` direkt, sonst Anzahl Tage zwischen `fromDate` und `toDate` (inkl. beider Tage). UX-Gewinn: User kann jetzt z.B. "Letztes Quartal", "Letzter Monat", "Letzte 12 Monate" oder Custom-Range waehlen, statt nur 7/30/90. Limitation: bei Custom-Range mit z.B. 60 Tagen sieht der User KPIs fuer "die letzten 60 Tage ab heute", nicht fuer den exakten gewaehlten Bereich - das wird Schritt 3a (Audit-Stats Backend auf `dateFrom`/`dateTo` erweitern) loesen, danach faellt `_periodToDays` ersatzlos weg. Naechste Schritte stehen als detaillierter Plan in `wiki/c-work/1-plan/2026-04-period-picker-billing-audit-migration.md` (Backend-Erweiterung Audit + Billing, dann Billing-Frontend-Migration; mit Akzeptanzkriterien, Testplan und Backwards-Compat-Strategie). DONE. PeriodPicker: neue, wiederverwendbare UI-Komponente fuer Zeitraum-Auswahl (Von/Bis) mit semantischen Presets, integriert in 3 Trustee-Seiten. Motivation: bisher gab es 3 parallele Ad-hoc-Loesungen fuer "Zeitraum" (`TrusteeAccountingSettingsView` mit Inline Von/Bis + 3 Buttons, `FormGeneratorReport.Toolbar` mit eigener `ReportPeriod`-Selektion, `BillingDashboard` mit `selectedPeriod=month|year`+Jahr/Monat-Dropdowns), und in `TrusteeAnalyseView` (Budget/KPI/Cashflow/Forecast) sowie `TrusteeAbschlussView` (Year-End) wurden die Workflows komplett ohne Zeitraum-Param gestartet, obwohl die Actions (`refreshAccountingData` und nachgelagerte) `dateFrom`/`dateTo` aus `parameters` bereits auslesen. Neue Komponente in `frontend_nyla/src/components/PeriodPicker/`: - `PeriodPickerTypes.ts`: `PeriodValue = { preset, fromDate, toDate }` mit Preset-Kinds `ytd | lastYear | nextYear | last12Months | next12Months | thisMonth | lastMonth | thisQuarter | lastQuarter | lastN | nextN | custom`. `lastN`/`nextN` tragen `amount` + `unit` (`day|week|month|year`). - `PeriodPickerLogic.ts`: `resolvePeriod(preset)` (pure, lokale Daten ohne Timezone), `isDateDisabled`, `isPresetDisabled`, `isValueAllowed`, `buildMonthCells`. Semantische Presets werden in `PeriodPicker.tsx` per `useMemo` bei jedem Render frisch resolved -> Dashboards mit `ytd` zeigen am 1. Januar nicht mehr das Vorjahr. - `PeriodPickerCalendar.tsx`: zwei Monate vertikal gestackt (passt in Popover-Spalte ohne Quetsch), Range-Selection per Klick (1. Klick=Start, 2. Klick=Ende, kleinerer Wert wird automatisch zu `from`), Today-Marker, gestrichene Disabled-Tage, Mo-So-Header (ISO-Wochenstart). - `PeriodPickerPopover.tsx`: 3-Spalten-Layout (Presets | "Letzte/Naechste N Einheit" mit Toggle/Number/Unit-Select | Kalender) + Footer (Von/Bis-Date-Inputs + Abbrechen/Uebernehmen). Esc=Cancel, Enter=Apply. - `PeriodPicker.tsx`: Trigger-Button mit kompakter Anzeige (z.B. "Laufendes Jahr · 01.01.2026 - 20.04.2026"), Outside-Click via `mousedown` (nicht `click`!) - sonst zerlegt das Re-Rendern der Kalender-Buttons den `e.target.closest()`-Check und der Popover schliesst nach jedem Custom-Klick. Bei verbotenem Value (gegen `direction`/`min`/`max`) faellt die Komponente still auf den Default-Preset zurueck. - `PeriodPicker.module.css`: nutzt globale Theme-Variablen (`--primary-color`, `--bg-input`, `--border-color`, `--text-*`), `:hover`/`:disabled`-States, Mobile-Fallback `<600px` (Spalten gestackt, Presets als Pills). - `usePeriod.ts` Hook: URL-Sync via `useSearchParams` mit konfigurierbarem `paramKey` (Default `period`), URL-Keys `${k}Preset|From|To|Amount|Unit`. Custom-Range serialisiert From/To, semantische Presets nur den Kind (werden bei jedem Mount neu resolved). Constraints-Props: `direction='past'|'future'|'any'`, `minDate`, `maxDate`, `enabledPresets[]`, `defaultPreset`. Komponente blockiert sowohl die unpassenden Presets (ausgegraut, `disabled`) als auch die Kalender-Tage in der falschen Richtung. Output ist immer `{preset, fromDate, toDate}` als ISO `YYYY-MM-DD`. Integration: 1) `TrusteeAccountingSettingsView`: Inline Von/Bis-Block + 3 Quick-Buttons (Laufendes Jahr / Letztes Jahr / Letzter Monat) ersetzt durch einen ``. Der Import-Body sendet `dateFrom`/`dateTo` nur wenn der User einen Zeitraum gewaehlt hat (sonst bleibt es weiter "alle Daten importieren"). 2) `TrusteeAnalyseView`: pro Tab (budget/kpi/cashflow/forecast) eigener PeriodValue-State (`Record`), Defaults via `_periodConfigForTab`: budget/kpi/cashflow = `ytd` mit `direction='any'`, forecast = `next12Months` mit `direction='future'`. Workflow-Body bekommt jetzt zwingend `payload: { dateFrom, dateTo, ...documentList? }` (vorher nur documentList beim Budget-Tab) - Actions wie `refreshAccountingData` haben die Parameter bereits. 3) `TrusteeAbschlussView`: `` fuer das Geschaeftsjahr; Workflow-Execute-Body sendet `payload: { dateFrom, dateTo }`. Bewusst NICHT in dieser PR: Migration von `BillingDashboard`, `ComplianceAuditPage`, `FormGeneratorReport.Toolbar`, AdminLogs/DatabaseHealth, WorkspaceRagInsights (Folge-PR mit eigenem Scope, da unterschiedliche API-Kontrakte). Tests fuer `resolvePeriod`, Constraints und URL-Sync sind im Plan (`.cursor/plans/period-picker-component_*.plan.md`) als T1-T7 erfasst, aber noch nicht geschrieben - der User hat zuerst das UX-Verhalten sowohl im Standalone-Demo (`local/notes/period-picker-demo.html`) als auch in der React-Integration validiert. DONE. Trustee / UDB-Sources-Bar: 500-Error beim Aufklappen der "Organisation"-Gruppe behoben + Tabelle "Positionen" wird jetzt sichtbar. Symptom: In der UnifiedDataBar (UDB) → Sources-Tab → Trustee-Feature-Connection lieferte das Aufklappen von "Organisation" `GET /api/workspace/.../feature-connections/.../parent-objects/TrusteeOrganisation` → `psycopg2.errors.UndefinedTable: Relation »TrusteeOrganisation« existiert nicht` → 500. Zudem fehlte die "Positionen"-Tabelle komplett in der UDB. Ursache: `mainTrustee.DATA_OBJECTS` widersprach der seit der Architektur-Umstellung gültigen Regel "feature instance IS the organisation" (siehe Kommentar in `datamodelFeatureTrustee.TrusteeDocument`): a) `TrusteeOrganisation` war als `isParent: True` eingetragen, obwohl das Modell zwar noch existiert, aber lazy nie eine Tabelle in `poweron_trustee` erzeugt wird (DatabaseConnector legt Tabellen erst beim ersten `recordCreate` an). b) `TrusteePosition` und `TrusteeAccountingConfig` waren als Kinder mit `parentTable: "TrusteeOrganisation"`, `parentKey: "organisationId"` deklariert — aber beide Modelle haben gar kein `organisationId`-Feld (nur `featureInstanceId`/`mandateId`), der Filter wäre also auch bei existierender Parent-Tabelle ins Leere gelaufen. Die Frontend-Filterlogik in `SourcesTab.tsx` (`parentTables = tables.filter(t => t.isParent)` + `standaloneTables = tables.filter(t => !t.isParent && !t.parentTable)`) sortierte `TrusteePosition` damit weder als Standalone noch als Top-Level-Parent ein, sondern ausschliesslich als Kind innerhalb der `TrusteeOrganisation`-Gruppe — die wegen (a) nie aufgeklappt werden konnte. Folge: Positionen permanent unsichtbar. Fix in `mainTrustee.DATA_OBJECTS`: 1) `TrusteeOrganisation`-Eintrag entfernt — der Catalog reflektiert die neue Architektur. 2) `TrusteePosition`: `parentTable`/`parentKey` entfernt, wird jetzt als Standalone-Tabelle angeboten. `fields`-Liste auf echte Modell-Felder korrigiert (`valuta`, `company`, `desc`, `bookingAmount`, `bookingCurrency`, `debitAccountNumber`, `creditAccountNumber`) — vorher waren `label`, `description`, `organisationId` aufgelistet, die im Pydantic-Modell gar nicht existieren. 3) `TrusteeAccountingConfig`: `parentTable`/`parentKey` entfernt, `fields` auf echte Felder korrigiert (`connectorType`, `displayLabel`, `isActive`, `lastSyncAt`, `lastSyncStatus`). 4) `TrusteeDocument`-Felder ebenfalls an Modell angepasst (`documentName`, `documentMimeType`, `documentType`, `sourceType` statt der nicht existierenden `filename`, `mimeType`, `fileSize`, `uploadDate`). Bewusst nicht angefasst: `TrusteeOrganisation`-Pydantic-Modell, die `/organisations`-CRUD-Routen in `routeFeatureTrustee.py`, der ungemountete `useTrusteeOrganisations`-Hook und die orphaned `/organisations`-Route in `App.tsx` bleiben als Dead Code stehen — Aufräumen ist Scope-Erweiterung. Die Änderung berührt nur den Catalog. AccessRules in `TEMPLATE_ROLES` referenzieren `TrusteeOrganisation` ohnehin nirgends explizit (nur `TrusteePosition` und `TrusteeDocument` in `trustee-client`), die Wildcard-Regel `item: None` in den anderen Rollen deckt die verbleibenden Tabellen weiterhin vollständig ab. DONE. Trustee / UI-Restrukturierung: konsistente Seitenstruktur mit Tabs statt vieler Top-Level-Pages. Motivation: Das Dashboard zeigte 3 Kategorien ("Import & Verarbeitung", "Analyse & Reporting", "Abschluss & Prüfung") als Zielstruktur, aber in der Sidebar erschienen die zugehörigen Aktionen als separate Pages ("Spesen Import", "Scannen / Hochladen", "Buchhaltungseinstellungen"). Inkonsistent zwischen Dashboard-Quick-Actions und Navigation. Umbau auf Tab-basierte Seiten entsprechend der 3 Kategorien: 1) Backend `mainTrustee.py`: - `UI_OBJECTS`: `ui.feature.trustee.expense-import` und `ui.feature.trustee.scan-upload` entfernt, dafür neu `ui.feature.trustee.import-process` (Label "Import & Verarbeitung"). Analyse/Abschluss/Settings bleiben unverändert. - `TEMPLATE_ROLES` (trustee-user, trustee-client): Permission-Items auf `ui.feature.trustee.import-process` umgestellt. - `QUICK_ACTIONS`: `trustee-process-receipts` → `targetView=import-process, tab=receipts`; `trustee-upload-receipt` → `targetView=import-process, tab=upload`; `trustee-sync-accounting` → Label umbenannt auf "Daten einlesen", `targetView=settings, tab=import-data`. 2) Frontend — neue Wrapper-Komponente `TrusteeImportProcessView.tsx`: Rendert Tab-Bar mit 3 Tabs. "Belege verarbeiten" (receipts) und "Beleg hochladen" (upload) embedden `TrusteeExpenseImportView` und `TrusteeScanUploadView` via neuem `embedded?: boolean` Prop (unterdrückt den eigenen `listView`/`expenseImportSection`/`

` Wrapper, damit keine doppelten Section-Header entstehen). Der 3. Tab "Daten einlesen" ist bewusst nur ein Redirect: `useEffect` navigiert sofort auf `/mandates/:m/trustee/:i/settings?tab=import-data` — so bleibt die Buchhaltungs-Sync an genau einer Stelle (Settings-Page), wie vom User gewünscht. 3) Frontend — `TrusteeAccountingSettingsView.tsx` in 2 Tabs gesplittet (via `?tab=settings|import-data`): Tab 1 "Verbindungseinstellungen" zeigt Header, ggf. Sync-Error-Box und die 3 Setup-Schritte (Connector wählen, Zugangsdaten, Test/Speichern). Tab 2 "Buchhaltungsdaten importieren" enthält den bisherigen Schritt 4 (Zeitfenster-Chips, Datumsauswahl, Import-Buttons, Cache-Leeren, JSON-Export, Status-Box mit letztem Import und Counts). Wenn Tab 2 ohne konfigurierte Verbindung aufgerufen wird, erscheint ein InfoBox-Hinweis, zuerst Tab 1 auszufüllen. 4) Frontend — `TrusteeAbschlussView.tsx` Platzhalter-Tabs ergänzt: Tab 1 "Jahresabschluss prüfen" (produktiv, wie bisher), Tab 2 "MWST-Abrechnung" und Tab 3 "Reporting Behörden" mit `comingSoon: true`. Coming-Soon-Tabs zeigen eine InfoBox statt des Workflow-Execution-UI. 5) Frontend — `FeatureView.tsx` VIEW_COMPONENTS.trustee: `expense-import` und `scan-upload` aus der Registry entfernt (alte URLs nicht mehr erreichbar, QuickActions verweisen jetzt auf `import-process?tab=…`). Neu: `'import-process': TrusteeImportProcessView`. `App.tsx` Router-Routes analog: `` entfernt, neu `}>`. `types/mandate.ts` Legacy-`FEATURE_REGISTRY` analog angepasst. 6) Frontend — `TrusteeDashboardView.tsx` Fix Instanz-Details: "Mandant"-Wert zeigte `instance.mandateName` (Kurzzeichen/Slug); jetzt `mandate?.label` (voller Anzeigename) mit Fallback auf `mandateName`. Zusätzlich `t(role)` für jede Rolle und `t(lastSyncStatus)` für den Sync-Status-Tag, damit Rollenlabels und Statuswerte übersetzbar sind (bisher hart englisch, z. B. "trustee-admin" / "success"). Nicht-Ziele / bewusst nicht angefasst: Die Views `TrusteeExpenseImportView` und `TrusteeScanUploadView` bleiben als wiederverwendbare Komponenten und werden weiterhin aus `index.ts` exportiert (für zukünftige Einbettungen). Deep-Links auf die alten Pfade brechen — explizit vom User so gewählt ("alte Pages komplett entfernen"). DONE. Datei-Download in Workflow-Outputs (Analyse & Reporting / Cashflow / KPI-Dashboard / Forecast etc.). Symptom: Beim Klick auf einen generierten Report-Download-Link kam `{"detail":"File with ID not found"}` (HTTP 404), obwohl die Datei in Postgres vorhanden und im UI als Download-Link sichtbar war. Ursache: Der Download-Link wird im Frontend als `` gerendert (TrusteeAnalyseView.tsx). Der Browser sendet beim Direkt-Download keine Custom-Headers `X-Mandate-Id` / `X-Instance-Id`. Die Datei wurde vom Workflow-Executor mit `scope="featureInstance"`, `mandateId=`, `featureInstanceId=` (und `sysCreatedBy=root`) gespeichert. Der Download-Endpoint hat den `getFile()`-RBAC-Filter aber nur mit dem Request-Scope (= leer ohne Header) ausgeführt → `getRecordsetWithRBAC` filtert die Datei raus → 404. Fix in `routeDataFiles.py` (`get_file`, `download_file`, `preview_file`): 1) Neuer Helper `_resolveFileWithScope(currentUser, context, fileId)`. Schritt 1 (Fast-Path): versucht `getFile()` mit dem Request-Scope (Header). Schritt 2 (Fallback): wenn Fast-Path scheitert, liest der Helper die `FileItem`-Metadaten privilegiert aus der DB (`db.getRecordset(FileItem, {"id": fileId})`) und liest daraus den File-eigenen `mandateId`/`featureInstanceId`. Schritt 3: erstellt ein neues `interfaceDbManagement.getInterface(currentUser, mandateId=, featureInstanceId=)` und ruft `getFile()` erneut auf. So läuft der RBAC-Check im Scope der Datei selbst — nur wer in diesem Scope `view`-Permission auf `FileItem` hat, bekommt die Datei. 2) Falls die Datei wirklich nicht existiert (Schritt 2 leer) ODER der User keinen RBAC-Zugriff im Datei-Scope hat (Schritt 3 leer): echtes 404. 3) Personal-Files (mandateId & featureInstanceId leer) werden im Fallback nicht aufgelöst (kein Effekt, klassisches RBAC bleibt aktiv). Resultat: Direkte File-Download-Links via `` funktionieren ohne Custom-Header, RBAC bleibt aber strikt im File-eigenen Scope. Sauber, ohne Workaround, semantisch korrekt — die Datei "weiss" zu welcher Instance sie gehört. DONE. GraphicalEditor / SharePoint-Folder/File-Picker statt rohem Textfeld in Node-Properties. Symptom: In Nodes wie `sharepoint.listFiles` ("Scan Ordner auflisten") war der Parameter "Ordnerpfad" nur ein leeres Textfeld — der User musste den vollen `/sites/host,siteId,webId/...`-Pfad von Hand kennen und eintippen, obwohl es im UI bereits einen funktionierenden SharePoint-Folder-Browser in `TrusteeExpenseImportView.tsx` gibt (Sites laden → Site wählen → durch Ordner klicken → "Diesen Ordner auswählen"). Ursache: Der `FolderPicker` in `frontendTypeRenderers/index.tsx` (mappte `sharepointFolder` und `sharepointFile`) war ein einfaches ``. Fix: 1) Backend: `/api/sharepoint/folder-options` (in `routeSharepoint.py`) um optionalen Query-Param `includeFiles=true` erweitert. Standard ist `False` (also weiterhin reine Folder-Liste für Folder-Picker), mit `includeFiles=true` werden zusätzlich Dateien (`type=file`, mit `mimeType` und `size`) zurückgegeben — wird vom Datei-Picker (`sharepointFile`) genutzt. 2) Frontend: neuer `SharepointPathPicker` in `frontendTypeRenderers/index.tsx`, der für `frontendType: 'sharepointFolder'` (Folder-only) und `frontendType: 'sharepointFile'` (Folder + File, mit `?includeFiles=true`) registriert ist. Liest den `connectionReference`-Wert aus dem Geschwister-Parameter (via `param.frontendOptions.dependsOn`, default `connectionReference`), zeigt: - Editierbares Textfeld mit dem aktuell gewählten Pfad (für Copy/Paste oder manuelle Eingabe). - Daneben einen "Browsen"-Button. Klick öffnet ein Inline-Panel mit: - Site-Dropdown (lädt Sites über `/api/sharepoint/folder-options?connectionReference=...`). - Aktueller Pfad als Breadcrumb plus "↑ Hoch"-Button. - Liste der Unterordner (📁) klickbar zum Reinnavigieren bzw. mit "Wählen"-Button. Bei `sharepointFile` zusätzlich Dateien (📄), die direkt das Textfeld setzen und das Panel schliessen. - "Diesen Ordner wählen"-Button setzt den vollen `${site.path}/${currentPath}` ins Textfeld und schliesst das Panel. Bei fehlendem `connectionReference` ist der Picker disabled mit Hinweis "Zuerst connectionReference wählen". DONE. GraphicalEditor / Connection-Picker leer in SharePoint/Email/ClickUp-Nodes. Symptom: Im Editor-Properties-Panel zeigt das Dropdown "SharePoint-Verbindung" (Node "Scan Ordner auflisten" alias `sharepoint.listFiles` und alle anderen `sharepoint.*` / `email.*` / `clickup.*` Nodes) nur den Platzhalter "Verbindung wählen", obwohl der User aktive `UserConnection`-Einträge in `/api/connections/` hat. Ursache: Frontend `ConnectionPicker` (`frontend_nyla/src/components/FlowEditor/nodes/frontendTypeRenderers/index.tsx` Zeile 158) ruft `/api/graphicalEditor/{instanceId}/options/user.connection` auf — diesen Endpoint gibt es im Backend nicht (Editor-Router läuft unter `/api/workflows`, nicht `/api/graphicalEditor`). Der 404 wurde von `.catch(() => {})` stillschweigend verschluckt, also blieb das Dropdown leer ohne jeden Hinweis. Fix: 1) Backend: neuer Endpoint `GET /api/workflows/{instanceId}/options/user.connection` in `routeFeatureGraphicalEditor.py` — validiert Feature-Access, lädt `interfaceDbApp.getUserConnections(user.id)`, filtert standardmäßig auf `status=active`, liefert `{options: [{value, label}]}`. Optional `?authority=msft|google|clickup|local` für Provider-Filter; `value` ist `connection:{authority}:{username}` (passt zu `getUserConnectionFromConnectionReference`), `label` enthält `[authority] username — email`. 2) Frontend: `ConnectionPicker` ruft jetzt `/api/workflows/{instanceId}/options/user.connection` und reicht `param.frontendOptions.authority` als Query-Parameter durch. Bei Fehlern wird der echte Error in der Konsole geloggt (statt verschluckt) und unter dem Select erscheint "Verbindungen konnten nicht geladen werden". Bei leerem Ergebnis erscheint ein Hinweistext "Keine {authority}-Verbindung gefunden — Verbindung in den Account-Einstellungen anlegen." 3) Node-Definitionen: `frontendOptions.authority` an allen Connection-Parametern ergänzt — `sharepoint.py` (alle 6 Nodes → `msft`), `email.py` (alle 3 Nodes → `msft`), `clickup.py` (alle 6 Nodes → `clickup`), `trustee.extractFromFiles` (`msft`). So zeigt das Dropdown pro Node nur die jeweils passenden Connections. DONE. GraphicalEditor / UI nach Agent-Delete sauber abräumen. Symptom: Agent löscht Workflow erfolgreich (Backend antwortet 200, Eintrag aus DB weg), aber das UI versucht weiter den jetzt nicht mehr existierenden Workflow zu laden → Konsole spammt 404 für `/api/workflows/{instance}/workflows/{deletedId}` (siehe useApi.ts:120). Ursache: `EditorChatPanel.onGraphUpdated` → `Automation2FlowEditor.handleLoad(currentWorkflowId)` ruft `fetchWorkflow` auf — nach Delete wirft das 404, der bisherige `catch` setzte aber nur `executeResult.error`. Workflow blieb in der `workflows`-Liste, `currentWorkflowId` blieb gesetzt, Header zeigte den toten Workflow weiter, jeder weitere toolResult-Tick wiederholte das 404. Fix in `handleLoad`: 404 erkennen via `err.response.status === 404` und sauber abräumen — den Workflow aus `workflows` entfernen, `currentWorkflowId` auf `null` (nur falls es genau dieser war), Canvas auf leere Default-Invocations zurücksetzen, anschließend `fetchWorkflows` zur Re-Hydration der Liste. Der Cache (`useApi.ts`) wird bei Errors ohnehin schon invalidiert (Zeile 154–160), also bleibt nichts Altes hängen. DONE. GraphicalEditor / UI-Refresh nach Agent-Rename. Symptom: Agent renamt Workflow korrekt im Backend (DB hat neuen Label), aber der Titel im Editor-Header zeigt weiter den alten Namen — erst nach Page-Reload sichtbar. Ursache: `Automation2FlowEditor.tsx` ruft nach jedem `toolResult`/`toolCall` `handleLoad(workflowId)` auf (`EditorChatPanel.onGraphUpdated`), `handleLoad` lädt aber NUR `graph` + `invocations` neu. `CanvasHeader.tsx` (Zeile 90/154) liest den Titel aus `workflows.find(w => w.id === currentWorkflowId).label` — die `workflows`-Liste blieb stale. UI-Rename via `handleWorkflowRename` patchte sie korrekt (`setWorkflows(prev => prev.map(...))`), Agent-Pfad nicht. Fix in `handleLoad`: nach `fetchWorkflow(...)` zusätzlich `setWorkflows(prev => …)` — entweder mergen falls Eintrag existiert (`{...prev[idx], ...wf}`), oder anhängen falls noch nicht in der Liste. Spielt automatisch auch für `description`/`tags`/`active` sowie für externe Updates aus anderen Tabs/Sessions. DONE. GraphicalEditor / Editor-Agent System-Prompt + Toolbox erweitert — Folge-Issue zum Rename-Bug. Symptom: Nach dem Hinzufügen von `updateWorkflowMetadata` antwortete der Agent auf "Erstelle einen leeren Workflow namens 'Smoke-Test'" mit "Leider kann ich … keinen neuen Workflow erstellen oder umbenennen … Meine Fähigkeiten beschränken sich auf das Bearbeiten des aktuell geöffneten Workflow-Graphen." (Debug-Message `local/debug/messages/20260419-220012-370_m_0_0_0/message.json`). Ursachen: 1) `toolboxRegistry.py` listete `updateWorkflowMetadata` nicht in der `workflow`-Toolbox → der Agent hatte das Tool gar nicht. Behoben: in die Tool-Liste der `workflow`-Toolbox aufgenommen. 2) Der System-Prompt in `routeFeatureGraphicalEditor.py` (`POST /{instanceId}/workflows/{workflowId}/chat`) sagte explizit "Your ONLY job is to BUILD or MODIFY the workflow graph" und zählte unter "Graph-mutating tools" nur `readWorkflowGraph/listAvailableNodeTypes/describeNodeType/addNode/removeNode/connectNodes/setNodeParameter/autoLayoutWorkflow/validateGraph` auf. Lifecycle-Tools (`createWorkflow`, `updateWorkflowMetadata`, `createWorkflowFromFile`, `exportWorkflowToFile`, `deleteWorkflow`) wurden im Prompt verschwiegen — also dachte das Modell, sie seien tabu, obwohl sie in der Toolbox aktiv waren. Behoben: Prompt umformuliert auf "MANAGE workflows … create, rename, import/export, edit the graph", drei Tool-Gruppen sauber benannt (Graph-mutating / Workflow lifecycle / History), plus eine explizite "Intent → tool"-Mappingtabelle (rename → updateWorkflowMetadata, neuer Workflow → createWorkflow, import → createWorkflowFromFile, export → exportWorkflowToFile, activate/deactivate → updateWorkflowMetadata) und nochmal die Warnung "NEVER batch-call removeNode to 'rebuild' or 'rename' a workflow". DONE. GraphicalEditor / Agent-Workflow-Rename — destruktiver Bug behoben. Symptom: User bittet im Editor-Chat "Workflow umbenennen", Agent löscht stattdessen ALLE Nodes (Log `log_app_20260419.log` Zeilen 3736–3855: 10× `removeNode` für `n1…n10` auf dem PWG-Pilot-Workflow `35e035d3-…`), anschließend Anthropic-500 in Round 3. Ursache: `workflowTools.py` exponierte zwar `createWorkflow`/`createWorkflowFromFile`/`exportWorkflowToFile`/`deleteWorkflow`, aber KEIN Tool für Workflow-Metadaten (Label/Description/Tags/Active). claude-opus-4-6 wählte den nächstbesten Pfad ("graph leeren und neu bauen") und feuerte `removeNode` n-mal. Fix: 1) Neues Tool `updateWorkflowMetadata` in `workflowTools.py` (Handler `_updateWorkflowMetadata`): patcht ausschließlich `label` / `description` / `tags` / `active` über `iface.updateWorkflow(workflowId, patch)` — Graph + Invocations bleiben unangetastet. Mindestens ein Feld erforderlich, leeres `label` wird abgelehnt. 2) Tool-Beschreibung erklärt explizit, dass dies der EINZIGE korrekte Rename-Pfad ist und `removeNode`/`deleteWorkflow` für Renames verboten sind. 3) `removeNode`-Description verschärft: "DESTRUCTIVE — only call when user explicitly asks to delete that specific node. NEVER use this to 'rename' or 'rebuild' a workflow … NEVER batch-remove all nodes." 4) Recovery für PWG-Pilot: Workflow ist leer, aber das Demo-File `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` existiert — User kann es via Import-Button (heute neu in `GraphicalEditorWorkflowsPage.tsx`) zurückladen oder das `pwgPilot`-Demo neu ausführen (`/api/admin/demo-config/pwgPilot/load`). DONE. AI-Workspace / Sources-Drop zeigt jetzt Chip oberhalb des Prompts. Bug: `WorkspacePage._handleSendToChat_FeatureSource` hat die im Backend angelegte `featureDataSource` zwar via `refreshFeatureDataSources()` nachgeladen, aber **nicht** als Pending-Attachment ans `WorkspaceInput` propagiert — Folge: kein Chip in der Attachment-Bar (während Files via `pendingFiles` und Datasources via `pendingAttachDsId` korrekt erschienen). Fix paritätisch zu Datasources: 1) `WorkspaceInput`: neue Props `pendingAttachFdsId` + `onPendingAttachFdsConsumed` und ein Effect, der die ID einmal in `attachedFeatureDataSourceIds` einfügt — symmetrisch zum vorhandenen `pendingAttachDsId`-Pfad. 2) `WorkspacePage`: neuer State `pendingAttachFdsId`; `_handleSendToChat_FeatureSource` liest die neue ID aus der API-Response (`res.data.id` / `featureDataSource.id` / `dataSource.id`) und setzt sie nach `refreshFeatureDataSources()`. Wirkt für beide Pfade — Drag&Drop in den Prompt UND "Senden an Chat"-Icon, da beide auf denselben Handler verdrahtet sind. DONE. GraphicalEditor / Workflow-File-IO + PWG-Pilot-Demo (PWG-Pilot, Phasen 1, 3, 4, 5, 6 + Frontend-Toolbar von Phase 2). Plan: `wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md`. 1) Workflow-File-Format `envelopeVersioned` v1.0 — neues Modul `gateway/modules/features/graphicalEditor/_workflowFileSchema.py`: `WORKFLOW_FILE_SCHEMA_VERSION="1.0"`, `WORKFLOW_FILE_KIND="poweron.workflow"`, `WORKFLOW_FILE_EXTENSION=".workflow.json"`. Funktionen `buildFileFromWorkflow`, `validateFileEnvelope`, `envelopeToWorkflowData`, `normalizeGraph`, `buildFileName`. Persistenz-Felder (`id`, `mandateId`, `featureInstanceId`, `currentVersionId`, `eventId`, Zeitstempel) werden beim Export gestrippt; beim Import wird `active=false` erzwungen, Knoten-Typen werden gegen `STATIC_NODE_TYPES` validiert (unbekannt → Warnung, nicht Error — pragmatischer für Bootstrap). Round-Trip-Test: `gateway/tests/unit/workflow/test_workflowFileSchema.py`. 2) Interface `interfaceFeatureGraphicalEditor.py`: `exportWorkflowToDict(workflowId)` und `importWorkflowFromDict(envelope, existingWorkflowId?)` → `{workflow, warnings, created}`. 3) Routen `routeFeatureGraphicalEditor.py`: `POST /{instanceId}/workflows/import` (Body: `envelope` ODER `fileId`, optional `existingWorkflowId`) und `GET /{instanceId}/workflows/{workflowId}/export?download=true` (Inline-JSON oder Download-Response). Helper `_loadEnvelopeFromFile` liest UDB-File via `routeDataFiles.getFileData`. 4) Agent-Tools (`workflowTools.py`): neue Tools `_createWorkflow`, `_createWorkflowFromFile` (akzeptiert `fileId` aus UDB ODER `envelope` inline), `_exportWorkflowToFile`, `_deleteWorkflow` (verlangt `confirm: true`). `_loadEnvelopeFromUdb` jetzt mandate-aware via `interfaceDbManagement.getInterface(user, mandateId)`. `toolboxRegistry.py`: `workflow`-Toolbox um die 4 neuen Tools plus `describeNodeType` und `autoLayoutWorkflow` erweitert. 5) Neuer Node `trustee.queryData` (`nodeDefinitions/trustee.py` + `methodTrustee/actions/queryData.py`): liest aus dem `poweron_trustee` DB ohne externen Sync. Modes `lookup` (Tenant + Mietzins-Aggregation), `raw` (Recordset über die 5 Trustee-Tabellen), `aggregate` (Sum/Count über JournalLines). Lookup macht Token-Overlap-Score auf `TrusteeDataContact.name` + `addressLine` (toleriert Tippfehler) und summiert die Credit-Beträge der `TrusteeDataJournalLine` mit `_accountMatcher` (Patterns wie `6000-6099` oder `6*`) im Zeitraum. Pure Helpers (`_normalizeText`, `_parsePeriod`, `_accountMatcher`, `_parseFilterJson`, `_summarizeAggregate`) sind via `gateway/tests/unit/workflow/test_trusteeQueryData.py` getestet. 6) `email.draftEmail` jetzt mit Attachments — `nodeDefinitions/email.py`: neuer optionaler Parameter `attachments` (json, `frontendType: attachmentBuilder`). `composeAndDraftEmailWithContext.py`: `_resolveAttachmentSpec` materialisiert die Specs (`csvFromVariable` für CSV-Refs, `contentRef` für beliebige Wire-Variablen, `base64Content` für Inline-Bytes) als ActionDocument-Shape und hängt sie an die `attachments_doc_list` an, die der bestehende Outlook-Draft-Loader hochlädt. 7) Pilot-Workflow-File `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json`: 10 Nodes (`trigger.manual` → `sharepoint.listFiles` → `flow.loop` → `sharepoint.downloadFile` → `trustee.extractFromFiles` → `trustee.queryData` → `ai.prompt` → `data.aggregate` → `data.consolidate` → `email.draftEmail`). AI-Prompt klassifiziert in `bestaetigt` / `abweichung_betrag` / `keine_unterschrift` / `mieterwechsel` / `unklar`. Ergebnis-CSV wird via `csvFromVariable: "csv"` als Anhang an den Outlook-Draft gehängt. 8) PWG-Demo-Bootstrap `gateway/modules/demoConfigs/pwgDemo2026.py` (auto-discovered durch `demoConfigs/__init__.py`): Mandant `stiftung-pwg`, User `pwg.demo@poweron.swiss` mit Platform-Admin-Flag, 4 Features (`workspace`, `trustee`, `graphicalEditor`, `neutralization`) inkl. Feature-Access + Billing + Neutralization-Config. `_ensureTrusteeSeed` lädt `gateway/demoData/pwg/_seedTrusteeData.json` (5 Mieter, je 12 Monats-JournalLines 2026, Account 6000 Mietzinsertrag) idempotent in die `poweron_trustee` DB. `_ensurePilotWorkflow` lädt das Workflow-File, ersetzt `<>` durch die real angelegte Instanz und importiert via `importWorkflowFromDict` (active=false). 3 Test-PDFs werden via `gateway/demoData/pwg/_generateScans.py` (reportlab) erzeugt: `mieter01-bestaetigt.pdf`, `mieter02-abweichung-betrag.pdf`, `mieter03-keine-unterschrift.pdf`. `remove()` räumt alle erzeugten Datensätze sauber ab. 9) Frontend (`workflowApi.ts` + `GraphicalEditorWorkflowsPage.tsx`): API-Wrapper `importWorkflowFromFile`, `exportWorkflowToFile`, `isWorkflowFileContent`, `workflowFileNameFor` plus Konstanten. Header-Button "Importieren" mit File-Picker (validiert JSON + Envelope-Sniffing, lädt deaktiviert in den Editor) und pro Zeile Action "Als Datei exportieren" (Browser-Download). FilesTab-Erkennung + Context-Menu **bewusst ausgelagert** in eigenen Plan `wiki/c-work/1-plan/2026-04-udb-action-system.md` — nicht „deferred", sondern Scope-Erweiterung, weil das ein generelles UDB-Aktion-System (Rechtsklick / Long-Press / Drag&Drop / kontextspezifische Inline-Icons über alle Mount-Sites) braucht, nicht nur Workflow-Files. Aktueller Workaround deckt den Pilot komplett ab: (a) File-Picker direkt in der Workflow-Liste, (b) Agent kann UDB-Files via `fileId` einlesen. 10) Plan-Status `2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md` bereinigt: vermeintliche Restpunkte D (Frontend-Attachment-Builder), E (Outlook-Mock-Test), F (Export-Sicherheits-Modal) und C (Per-Action-RBAC) als „nicht nötig" markiert mit Begründung — Mandate-Scope + Feature-Access-RBAC + Confirm-Flag im `deleteWorkflow`-Tool reichen aus; manuelle CSV-Builds sind Edge-Case (programmatischer Pfad via `csvFromVariable` ist Default); Live-Outlook-Test ist aussagekräftiger als Mock; Toast-Feedback nach Export reicht (Connection-Refs in Files sind keine Secrets). UI-only Smoke-Test-Kochbuch (Tests A–E) im Plan ergänzt — User testet direkt im Browser ohne API-Calls. DONE. GraphicalEditor / AI-erstellte Nodes: Pflichtparameter werden jetzt gesetzt (keine leeren Konfigurationskarten mehr, z.B. `connectionReference` an `email.checkEmail` oder `aiPrompt` an `ai.prompt`). 1) Ursache: `listAvailableNodeTypes` (in `gateway/modules/serviceCenter/services/serviceAgent/workflowTools.py`) gab dem Modell nur `{id, category, label}` zurück. Das LLM hatte also keine Information darüber, welche Parameter ein Node hat, welche `required` sind, welche Auswahlwerte erlaubt sind oder welcher Port welches Schema erwartet — und rief `addNode` daher konsistent mit `parameters: {}` auf. Folge: leere Auswahlfelder, fehlende Connections, fehlende Prompts. 2) `listAvailableNodeTypes` liefert jetzt einen kompakten Katalog mit zusätzlich `description`, `paramCount`, `requiredParamCount`, `usesAi` — genug, um zu erkennen, ob das Modell weiterbohren muss, ohne den ganzen Schema-Block 50× ins Modell-Kontextfenster zu pumpen. 3) Neues Tool `describeNodeType(nodeType)` — gibt das volle Schema EINES Node-Typs zurück: `parameters[].{name, type, required, frontendType, description, default, allowedValues}` plus `inputPorts` / `outputPorts` mit `schema` / `accepts`. Für Parameter mit `frontendType='userConnection'` wird zusätzlich ein Hint mitgesendet, dass `listConnections` aufzurufen und der `connectionId` zu nehmen ist (`listConnections` ist Teil von `coreTools/_connectionTools.py` und ist im Editor-Agent über `registerCoreTools` ohnehin schon registriert — wird also nicht doppelt gemounted). `requiredParameters: [...]` als bequeme Liste der Pflicht-Felder ist ebenfalls Teil der Antwort. 4) System-Prompt im `_runEditorAgent` (`gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py`) auf eine verbindliche Build-Sequenz umgestellt: read → list → describe (für jeden geplanten Node-Typ) → listConnections (falls userConnection-Felder) → addNode mit allen Pflichtparametern → connectNodes → autoLayoutWorkflow (genau einmal als letzter Mutationsschritt) → validateGraph → Antwort. Wenn ein Required-Parameter aus dem User-Request nicht ableitbar ist und keinen sinnvollen Default hat, soll das Modell einmal gezielt nachfragen statt ein leeres Feld zu lassen. DONE. GraphicalEditor / AI-erstellte Workflows: Knoten landen nicht mehr alle aufeinander, sondern werden automatisch via "Arrange"-Algorithmus angeordnet. 1) Bug in `_addNode` (`gateway/modules/serviceCenter/services/serviceAgent/workflowTools.py`): die Tool-Variante hat Position als `position: {x, y}` ins Graph-JSON geschrieben — das Frontend liest aber `n.x` / `n.y` direkt (siehe `fromApiGraph` / `toApiGraph` in `frontend_nyla/src/components/FlowEditor/nodes/shared/graphUtils.ts`). Folge: alle AI-Knoten landeten auf (0, 0). Fix: `_addNode` schreibt jetzt Top-Level `x` / `y` und akzeptiert optional `x` / `y` ODER `position={x,y}` vom Modell. Fallback ist eine horizontale Reihe statt (0, 0), damit selbst ohne Auto-Layout nichts mehr stapelt. 2) Neues Tool `autoLayoutWorkflow` (gleiche Datei) — 1:1-Port von `computeAutoLayout` aus `FlowCanvas.tsx`: topologische Layer-Sortierung, BFS-Ebenen, gleiche Geometrie (`NODE_WIDTH=200`, `NODE_HEIGHT=72`, `LAYOUT_V_GAP=80`, `LAYOUT_H_GAP=60`, Start `(40, 40)`), gleiche Cycle-Behandlung (übrige Knoten als Single-Node-Layer angehängt). Schreibt das aufgeräumte `x` / `y` direkt in den persistierten Graph und entfernt zur Sicherheit das Legacy-`position`-Dict, damit es nicht zwei Quellen der Wahrheit gibt. Idempotent, daher gefahrlos mehrfach aufrufbar. 3) System-Prompt im `_runEditorAgent` (`routeFeatureGraphicalEditor.py`) erweitert: Modell wird ausdrücklich angewiesen, Koordinaten NICHT selbst auszurechnen, `addNode` ohne Position aufzurufen und `autoLayoutWorkflow` als letzten graph-mutierenden Schritt genau einmal vor der finalen Antwort zu callen. Tool-Liste im Prompt entsprechend ergänzt. 4) Frontend-Integration besteht bereits: `EditorChatPanel` ruft `onGraphUpdated()` nach jedem `toolResult`/`toolCall`/Stream-Ende, was in `Automation2FlowEditor` ein `handleLoad(currentWorkflowId)` triggert — der Canvas zeigt das neue Layout also unmittelbar nach dem `autoLayoutWorkflow`-Tool-Call ohne Reload. DONE. GraphicalEditor / Dark Mode für die Canvas-Fläche: Die Editor-Fläche blieb im Dark Mode hellgrau, weil `.canvas { background: var(--canvas-bg, #fafafa) }` auf eine nirgendwo definierte CSS-Variable zugriff und immer auf den Light-Fallback `#fafafa` zurückfiel. Fix in `frontend_nyla/src/styles/themes/light.css`: `--canvas-bg` wird jetzt für beide Themes definiert (`#FAFAFA` light / `#131820` dark — leicht dunkler als `--bg-primary`, damit Nodes und Verbindungslinien gegen den Hintergrund stehen). Zusätzlich neue Variable `--canvas-grid` (`#D9DEE5` / `rgba(226,232,240,0.18)`) für die Punkt-Raster-Hintergrundgrafik, damit die Punkte im Dark Mode sichtbar bleiben, ohne aufdringlich zu wirken; `Automation2FlowEditor.module.css` nutzt diese statt `--border-color`. Knoten-Titel/Text laufen bereits über `--text-primary`, Connection-Pfeile über `--text-secondary`/`--primary-color` — die passen sich automatisch mit. DONE. GraphicalEditor / Editor-Chat-Persistenz: Editor-AI-Chat wird jetzt sauber im Standard `ChatWorkflow`/`ChatMessage`-Storage gespeichert (gleicher Storage, den der Workspace nutzt) — Verlauf überlebt Reload, Tab-/Workflow-Wechsel und Server-Restart. 1) `ChatWorkflow.linkedWorkflowId: Optional[str] = None` neu — verknüpft eine Chat-Session mit einer externen Entity (hier `Automation2Workflow.id`). Pro `(featureInstanceId, linkedWorkflowId)` exakt **eine** ChatWorkflow-Row → 1:1-Mapping. Keine Schema-Migration nötig: das auto-additive `_ensureTableExists` in `connectorDbPostgre.py` legt die Spalte beim ersten Aufruf an. RBAC unverändert (User-owned). 2) `interfaceDbChat`: zwei Helper ergänzt — `getWorkflowByLink(featureInstanceId, linkedWorkflowId)` und `getOrCreateLinkedWorkflow(featureInstanceId, linkedWorkflowId, name)`. `getWorkflow`/`createWorkflow` geben `linkedWorkflowId` und `featureInstanceId` jetzt mit zurück. 3) `routeFeatureGraphicalEditor.post_editor_chat`: vor jedem Stream-Start wird die ChatWorkflow geholt/angelegt (Name = `wf.label` des Automation2Workflow), die User-Message direkt persistiert (`ChatMessage` mit `status='first'/'step'`), und die Conversation-History für den Agent **server-seitig** aus der ChatWorkflow geladen — der Client muss nichts mehr mitschicken. Im `_runEditorAgent` wird die Assistant-Message bei FINAL/ERROR persistiert (`status='last'`); bei Cancel wird der bisher gestreamte Text noch gesichert (gleiche Semantik wie Workspace, damit der Verlauf bei Reload zeigt, was der Nutzer tatsächlich sah). 4) Neuer Endpoint `GET /api/workflows/{instanceId}/{workflowId}/chat/messages` → `{ chatWorkflowId, messages: [{id, role, content, timestamp, sequenceNr}] }`. Gibt leere Liste zurück, wenn noch kein Chat zu diesem Workflow existiert (kein Eager-Create — Row entsteht erst beim ersten POST). 5) Frontend `EditorChatPanel`: `useEffect([instanceId, workflowId])` ruft den GET-Endpoint und befüllt `messages` mit den persistierten Einträgen (`timestamp` von UTC-Sekunden auf JS-Millisekunden umgerechnet). Während des Lade-Fetches zeigt `ChatMessageList` "Lade Verlauf…" als Empty-Message und `isProcessing=true`. `conversationHistory` aus dem Stream-Body entfernt — Quelle der Wahrheit ist jetzt der Server. `_handleSend` hängt nicht mehr an `messages` als Dep, weil keine Client-side History-Logik mehr drin ist. DONE. GraphicalEditor / KI-Tab: Chatverlauf bleibt beim Tab-Wechsel erhalten. `EditorChatPanel` wurde bisher conditional (`{udbTab === 'ai' && }`) gemountet — beim Wechsel auf Chats/Dateien/Quellen und zurück war der `messages`-State weg. Fix: Panel bleibt immer gemountet, nur per CSS (`display: flex` ↔ `display: none`) umgeblendet. `key={currentWorkflowId}` setzt den Verlauf sauber zurück, wenn der Nutzer einen anderen Workflow auswählt. (Persistenz über Browser-Reloads/Sessions hinweg ist Sache des Backends — dafür gibt es derzeit noch keinen Editor-Chat-Storage; das wäre ein eigenes Feature.) DONE. GraphicalEditor / Workflow-Tools: vier Bugs in `workflowTools.py` gefixt, die jeden AI-Editor-Lauf direkt nach dem ersten Tool-Call abschossen. 1) `ToolResult(...)` wurde ohne die pflicht-Felder `toolCallId`/`toolName` instanziiert → `pydantic_core.ValidationError: 2 validation errors for ToolResult` bei jedem Fehler-/Erfolgs-Pfad. Konvention aus `actionToolAdapter`/`coreTools` übernommen: `toolCallId=""`, `toolName=""` immer mitgeben (Dispatcher überschreibt `toolCallId` nachträglich, validiert aber beim Konstruieren). 2) `ToolResult.data` ist `str`, die Tools übergaben Dicts (`data={"workflowId": …, "nodes": [...]}`) → silent type-mismatch / Müll-Ausgabe. Helper `_toData(payload)` JSON-encodiert strukturierte Payloads (`json.dumps(default=str, ensure_ascii=False)`). 3) `_listAvailableNodeTypes` rief `n.get("label", {}).get("en", …)` auf — `label` ist in `STATIC_NODE_TYPES` aber ein **String** (übersetzt via `t(...)`), kein Locale-Dict → `AttributeError: 'str' object has no attribute 'get'`. Helper `_coerceLabel(rawLabel, fallback)` akzeptiert String, Dict ({en/de/fr}) und beliebige Werte sicher. 4) Tools forderten `workflowId`/`instanceId` auf JEDEM Call und der Agent-Context war ein **dict** (`{workflowId, userId, featureInstanceId, mandateId}`), nicht ein Objekt mit `.user`/`.mandateId`. → AI musste die IDs raten/durchreichen, `getattr(context, "user", None)` lieferte `None` und der Interface-Aufruf wäre selbst bei korrekten Args fehlgeschlagen. Fix: `_resolveIds(params, context)` injiziert `workflowId`/`instanceId` aus dem Context (Editor-Agent läuft genau in einem Workflow), `_resolveUser(context)` löst die User-ID lazy via `interfaceDbApp.getRootInterface().getUser(userId)` auf, `_resolveMandateId` zieht die Mandate-ID aus dem Dict. Tool-Definitionen markieren `workflowId`/`instanceId` nicht mehr als `required` — der Agent kann sie weiterhin explizit setzen, muss aber nicht. DONE. GraphicalEditor / AI-Editor-Chat: drei Issues gefixt. 1) AI hat User-Workflows direkt **ausgeführt** statt sie zu **bauen** ("create a workflow that …" → tatsächlicher AI-Call/Mail/etc.). Ursache: `mainServiceAgent._buildToolRegistry` registriert per `ActionToolAdapter.registerAll` ALLE Workflow-Aktionen als Agent-Tools — inkl. `aiprocess_*`, `email_*`, `sharepoint_*`. Der Editor-Agent hatte damit Ausführungs- UND Graph-Tools, wählte aber bevorzugt Ausführung. Fix sauber via `AgentConfig.excludeActionTools` (neues Feld). Wenn `True`, überspringt das Service den ActionToolAdapter UND den `requestToolbox`-Meta-Tool — der Agent kann dann nichts mehr ausführen, nur noch den Graph mutieren. Editor-Route `_runEditorAgent` legt jetzt `AgentConfig(toolSet="core", excludeActionTools=True)` an und schärft den Systemprompt: explizit "BUILD ONLY, NEVER EXECUTE" mit Negativbeispiel. 2) Stop-Button im Editor-Chat tat nichts (Backend lief weiter, kein Feedback). Ursache: `queueId = f"ge-chat-{workflowId}-{id(request)}"` war pro Request einzigartig → ein Stop konnte den laufenden Task nicht treffen. Fix: deterministische ID `_editorChatQueueId(workflowId)` (analog zu Workspace-Pattern), neuer Stream-Start ruft vorher `cancel_agent` (verdrängt alten Lauf), neuer Endpoint `POST /api/workflows/{instanceId}/{workflowId}/chat/stop` cancelled den Task und emittet `stopped`. Frontend `EditorChatPanel`: neuer `_handleStop` ruft den Endpoint, zeigt sofort "Stoppen…" am Button + an der laufenden Assistant-Bubble, fängt das `stopped`-Event final ab und hängt "_Gestoppt._" an. 3) UDB im GraphEditor zeigte den laufenden Editor-Chat im Tab "Chats" an statt der Chat-Liste — verwirrend (sah aus wie eine schlechte Workspace-Chat-Kopie). Fix: linke Tab-Leiste neu auf `[KI | Chats | Dateien | Quellen]`. **KI** = `EditorChatPanel` (AI-Editor-Assistent, default beim Öffnen). **Chats** = neue `EditorWorkflowChatList`-Komponente (jeder AutoWorkflow ist eine "Chat-Session" in diesem Feature) mit Suche, "+ Neu" und Live-Indikator für laufende Workflows; selektiert direkt via `handleWorkflowSelect`. **Dateien**/**Quellen** rendern weiterhin `UnifiedDataBar` mit `hideTabs=['chats']`. DONE. UDB / FolderTree: Stabile Icon‑Reihenfolge im Files‑ und Sources‑Tab. Rechts an jeder Zeile sitzt ein fixes Trio (chat | scope | neutralize) in fester Position und Reihenfolge — auch wenn einzelne Aktionen nicht verfügbar sind (Placeholder mit fester Slot‑Breite). Dynamische Aktionen (rename, delete, add‑folder, download, multi‑actions, remove‑DS) erscheinen on‑hover **links** vom Trio und verschieben die stabilen Icons nicht mehr. Folder bekommen jetzt Scope‑ + Neutralize‑Toggle (vorher nur Neutralize); `FolderNode.scope`, `onFolderScopeChange` Prop, FilesTab/FilesPage rufen `PATCH /api/files/folders/{id}/scope`. Backend: `ComponentObjects.updateFolder(folderId, updateData)` in `interfaceDbManagement.py` ergänzt — vorher schlugen `PATCH .../scope` und `PATCH .../neutralize` mit `'ComponentObjects' object has no attribute 'updateFolder'` als HTTP 500 fehl. `FolderInfo` (fileApi) + FileContext exposed `scope` + `neutralize` typsicher (kein `as any` mehr). Sources‑Tab analog: Remove‑X gewandert vor das Trio, Trio‑Buttons bekommen feste Slot‑Breite, `_FeatureFieldRow` Reihenfolge auf chat → scope → neutralize korrigiert. DONE. GraphicalEditor / AI-Chat: `EditorChatPanel` SSE-Stream nutzt jetzt `api.defaults.baseURL` als Prefix (analog Commcoach/Chatbot). Vorher wurde `fetch` mit relativem Pfad `/api/workflows/.../chat/stream` aufgerufen — landete im Vite-Dev-Server auf Port 5176 statt im FastAPI-Backend (Port 8000) und antwortete mit `404`. Im Backend-Log war daher kein Treffer, im UI erschien "Fehler: HTTP 404:". DONE. UDB / Chat-Liste: Zeitstempel zeigt jetzt die letzte Nachricht statt das Erstelldatum. Helper `_lastTouchValue/_lastTouchTs` (= `lastMessageAt ?? updatedAt`) zentral genutzt für Anzeige + Sortierung in Tree- und Flat-Mode. Backend `interfaceDbChat.getLastMessageTimestamp` gibt jetzt `Optional[float]` (UTC-Sekunden) zurück statt `str(...)` — konsistent zu `lastActivity`/`startedAt`/`publishedAt`. Vorher kamen Strings wie `"1761859200.123"` an, die `new Date(...)` als Invalid Date parste und das Datum-Label leer ließ. DONE. Voice/Sprache: zentraler Voice-Catalog als Single Source of Truth. Backend `gateway/modules/shared/voiceCatalog.py` definiert {bcp47, iso, label, flag, defaultVoice} für 35 Sprachen. Verbraucher konsolidiert: Connector `_getDefaultVoice` delegiert, `_mediaTools._ISO_TO_BCP47` entfernt → `catalog.isoToBcp47`, Routes `/api/voice/languages` und `/voice-google/languages` liefern denselben Katalog (Liste von Objekten). Alte Live-`getAvailableLanguages` (Connector + Interface) entfernt — Catalog ist die Wahrheit. Frontend: `voiceCatalogApi.ts` + `VoiceCatalogProvider`/`useVoiceCatalog`-Hook (1× geladen, app-weit gecached); `WorkspaceInput._STT_LANGUAGES`, `VoiceLanguageSelect.voiceLanguages`+`profileToVoiceLanguage`, `Settings._defaultLangs`, `teamsbotApi.fetchLanguages` allesamt entfernt — alle Komponenten konsumieren den Catalog. Keine Backwards-Compat behalten. DONE. AI Workspace / Voice: `textToSpeech` läuft jetzt für jede Sprache. `_getDefaultVoice` deutlich erweitert (ru/es/pt/nl/pl/ja/zh/ko/ar/hi/tr/sv/da/nb/fi/cs/el/hu/ro/uk/vi/th/id), und falls keine kuratierte Stimme passt, lässt der Connector Google selbst auto-wählen (kein "No voice specified"-Hardfail mehr). Tool-Pfad funktioniert damit für alle BCP-47, CommCoach-Pfad bleibt unverändert (nutzt User-Voice-Preferences explizit). DONE. AI Workspace / Voice: `translateText` normalisiert Source-Language zentral im Connector (`_normalizeLanguageCode`: 'auto'/''/BCP-47 → ISO oder None) — fixt Google-Translate `400 Invalid Value` bei `source: auto`. Interface + Tool-Schema ziehen `Optional[str] = None` als "Auto-Detect"-Vertrag durch; Tool-Default ist nicht mehr der Magic-String "auto". Bonus: doppeltes `split('-')[0]` in `speechToTranslatedText` entfällt. DONE. Wiki: Plan „Unified Document Model (UDM)“ abgeschlossen — `wiki/z-archive/2026-04-unified-document-model.md`, TOPICS auf Archiv-Link + „done“; Abschlussbericht, Testplan-Stand, Section D ZIP+Fan-out+Merge ergänzt. DONE. AI Desktop: Die mitgelieferten Files im Chat prompt anzeigen DONE. alle initialen users solen mit rolle user erfasst werden, nicht mit viewer. sie erhalten keine initialen feature instanzen, sind aber dem root mandanten zugewiesen. DONE. UI: bei leerer übersicht seite wird statt dieser der store angezeigt. DONE. im store auch dem comcoach aufnehmen. DONE. bug: wenn ein user im store ein feature aktiviert, wird er ohne rolle zugewiesen. es sollte aber die user rolle des features sein! DONE. User aus Mandant entfernen: FeatureAccess (Instanzen dieses Mandanten) wird mit bereinigt. DONE. RBAC: user-owned Tabellen (chat/files/…) in geteilten Feature-Instanzen — GROUP-Zugriff filtert zusätzlich nach _createdBy (kein Fremd-Workspace-Datenleck). DONE. AI Workspace: workspace-admin DATA nur noch MY (wie alle User); plus buildRbacWhereClause: chat + featureInstance + read ALL erzwingt trotzdem _createdBy (alte Regeln). DONE. Billing: bei aufgebrauchtem Budget strukturierter Hinweis (PREPAY_USER → Billing aufladen; PREPAY_MANDATE → Admin informieren) + HTTP 402 detail; Mandanten-Pool: E-Mail an BillingSettings.notifyEmails (throttled). DONE. RBAC für neue Features initial DONE. RBAC für AI Desktop: Regeln Review pro roleLabels TO enhance: Action set for dynamic workflow to mark with a flag in the definition (only tagged actions are visible in the action planning and refinement prompts) Those actions shall be used in "Dynamic mode", so to get the flag: "ai.process": "Universal AI document processing action - accepts multiple input documents in any format and processes them together with a prompt", "ai.webResearch": "Web research with two-step process: search for URLs, then crawl content", "ai.summarizeDocument": "Summarize one or more documents, extracting key points and main ideas", "ai.translateDocument": "Translate documents to a target language while preserving formatting and structure", "ai.convertDocument": "Convert documents between different formats (PDF→Word, Excel→CSV, etc.)", "ai.generateDocument": "Generate documents from scratch or based on templates/inputs", "ai.generateCode": "Generate code files - explicitly sets intent to 'code'", "context.getDocumentIndex": "Generate a comprehensive index of all documents available in the current workflow", "context.extractContent": "Extract raw content parts from documents without AI processing. Returns ContentParts with different typeGroups (text, image, table, structure, container). Images are returned as base64 data, not as extracted text. Text content is extracted from text-based formats (PDF text layers, Word docs, etc.) but NOT from images (no OCR). Use this action to prepare documents for subsequent AI processing actions.", "outlook.readEmails": "Read emails and metadata from a mailbox folder", "outlook.searchEmails": "Search emails by query and return matching items with metadata", "outlook.composeAndDraftEmailWithContext": "Compose email content using AI from context and optional documents, then create a draft", "outlook.sendDraftEmail": "Send draft email(s) using draft email JSON document(s) from action outlook.composeAndDraftEmailWithContext", "sharepoint.findDocumentPath": "Find documents and folders by name/path across sites", "sharepoint.readDocuments": "Read documents from SharePoint and extract content/metadata", "sharepoint.uploadDocument": "Upload documents to SharePoint", "sharepoint.listDocuments": "List documents and folders in SharePoint paths across sites", "sharepoint.analyzeFolderUsage": "Analyze usage intensity of folders and files in SharePoint", "sharepoint.findSiteByUrl": "Find SharePoint site by hostname and site path", "sharepoint.downloadFileByPath": "Download file from SharePoint by exact file path", "sharepoint.copyFile": "Copy file within SharePoint", "sharepoint.uploadFile": "Upload raw file content (bytes) to SharePoint" RBAC Migration (2024-12-07): - ✅ UserPrivileges in pydantic model and calling references replaced by new RBAC roles model - ✅ Removed all DEPRECATED Logic for UAM/RBAC, and all backwards compatibility for UAM - ✅ Migration script removed (not needed - clean RBAC implementation) - ✅ All privilege checks replaced with roleLabels checks - ✅ createUser() now uses roleLabels instead of privilege parameter - ✅ Bootstrap initialization only sets roleLabels - ✅ All RBAC tests passing (17 unit + 6 integration = 23 tests) FRONTEND - the application initiation gets userdata with the token over apiCall.js:/api/local/me --> object: username fullName email language list of connections with attributes: id authority externalUsername Backend in the backend to handle the routes as follows: - routeSecurityLocal.py to handle all local endpoints, to include token generation from local authority in auth.py - routeSecurityMsft.py and routeSecurityGoogle.py to handle all their endpoints - all routeSecurity*.py to use the same interface to manage tokens and userdata: serviceUserClass.py. This class to have following logic: - all tokens are stored in one tabel, where each token has the attribute of the according authenticationAuthority - login and logout endoints for "local" use a function "getUseridFromToken" to identify the user context. If user does not exist, error message - login and logout endoints for "msft" and "google" use a function "getUseridFromToken" to identify the user context. If user does not exist for login, to register a new "local" user with the external user data and to attach the external connection. within the identified user context and the connection in its list to send back user context as tokenLocal and connection as tokenExt - the important thing is, that login endpoint serves for two different actions: a) without user context (no tokenLocal), it makes login for a user by external authority and sets user context b) with user context (a tokenLocal provided), it does NOT set a nwe user context, but manipulate a connection in the connection list of a local user - illustrative example of token data to send to UI (attributes): connect and { "token_type": "Bearer", "expires_in": , "access_token": , "id_token": , "client_info": , "user_info": { "name": "Patrick Motsch", "email": "p.motsch@valueon.ch", "id": "xxx" }, "mandateId": "", "userId": "", "id": "tokenid", } We have to correct the following wrong user access management. Issue is: when user logs in with "local" managed account and then logs in to msft account with "msft" authority, the userid is switched to the microsoft instance in the workflow. this must not happen. Objective: The correct logic is, that a user logs in with an account (managed by "local" or other authority). Once logged in, his login does not change, also if he connects to microsoft account afterwards. Problem: We have a mix between user-login (creating currentUser profile) and user-connections (attaching user to a service, like "msft" - and future other services in parallel). Concept: We need to separate user-login and user-connections: 1. the ui login and register modules produce a user-login, resulting in a currentUser profile in the backend to be used for workflow and other activities. the user gets a token (from "local" or "msft" or furthers). this token has to be checked when user logs in. ALWAYS a check is required by the according registration authority. those use cases: - if user registers with a "local" profile, a new user is created, a local token is produced - if user logs in with a "local" profile for an existing user, a local token is produced - if user logs in with a "local" profile for a non-existing user, login is denied (no user) - if user logs in with a "msft" profile (or other foreign profile) for an existing user, a local token AND a token in "msft" database (or other foreign system) is produced - if user logs in with a "msft" profile (or other foreign profile) for a non-existing user, a local profile is generated based on information from foreign account, then a local token AND a token in "msft" database (or other foreign system) is produced 2. the ui navigation buttons for "Login MSFT" or future other buttons to connect to services (like e.g. google account or github account or microsoft "msft" account, etc.) does NOT generate a user-login, only a user-connection to a service. soloution: So there must be a mechanism, which manages user-login and user-connection. Following proposition: User has a user profile to login and a list of profiles for user-connections. Examples: - user registers with "local" profile --> he gets local profile with 0 user-connections - user registers with "msft" profile --> he gets local profile with 1 user-connections to "msft". Then he connects to another "msft" profile. Now he gets local profile 2 user-connections "msft" - user registers with "google" profile (future) --> he gets local profile and 1 user-connections to "google". Then he connects to another "msft" profile. Now he gets local profile and 1 user-connections "msft" and 1 user-connection "google". can you tell me, how you would implement this adapted model into the pydantic model and into the code modules in a structured and maintainable way? i want to refactor the user management in the backend through the user journey. currrently we have two problems: we always pass _userid and _mandate or id with _mandate from function to function, which blocks scaling. this is too complicated and non-logic. to adapt the following: 1. The attributes _mandateid and _userid to be removed from @connectorDbJson.py. the attribute _userid to rename to "userId". this is the id of the user, who creates the record. This is the passed attribute instead of _userid and _mandate id., which is stored as userId. The default value to be "" (if None, then set to ""). All new created records get an additional "_createdBy" and "modifiedBy" attribute =self.userId. A modified record gets adapted "modifiedBy" attribute = "userId" when modified. 2.@gatewayModel.py to adapt class User: add mandateId. This is set to the same mandateId like the mandateId of the user, who creates the user. 3. @lucydomModel.py to adapt classes Prompt, FileItem, ChatWorkflow: add mandateId. This is set to the same mandateId like the mandateId of the user, who creates the user. Also to add "workflowId" to ChatStat, it is missing there. 4. @gatewayInterface.py and @lucydomInterface.py to adapt according to the changes of point 1, 2, 3. Also to integrate their according "*Model.py" to use for record creation with correct attributes. Also to separate class initiation and function call getInterface(). Class initiation without parameter userid and mandateid. Initialize database and records. Like this it is ensured, when the first function call happens to the class, it is initiated correctly. Initiate the module class automaitcally when module loading. function getInterface(currentUser with default value = None) makes this: - if currentUser is None, then only database is initialized (e.g. for refresh folders and files) and an empty object given back with logger info for databse refresh - if currentUser is provided, then uses the id of the user for contextkey, creates ne instance of the class, gives self.user=currentUser to the class to have user context, initializes AI service self.aiService=ChatService(), initializes access control: self.access = LucydomAccess(self.currentUser, self.db) - now to adapt code in the *Interface.py modules to use currentUser attributes. like this we have a proper object usage - modules.interfaces.*Interface to import as module and not the functions. This ensure, that module is initiated when imported. 5. @auth.py : getRootInterface to call getInterface(rootUser), where rootUser is the user with initialId indatabase (use function for this) FRONTEND: - login page and register page withoug fallback. they have mandatory to load their login.html or register.html pages to work (not html in the code). I want formCeneric module to use api calls over apiCalls.js module, not directly. So please adapt formCeneric parameter "apiEndpoint" with the respective api-functions as objects, handed over by the modules: - apiEndpoint.get --> the api to get data - apiEndpoint.update --> the api to update data - apiEndpoint.delete --> the api to delete data then to use those api-functions in the module formGeneric instead of direct api calls the modules mandates, users, files, prompts, to adapt accordingly - all api calls from workflowUI.js and workflowData.js also to transfer to apiCall.js. There to integrate ALL route endpoints from all routes and to call over window.utils.api..... - handleFileUplad and uploadfile is on nmany places. To have the api functionality only in apiCall.js. please refactor those topics. - all api calls from workflowUI.js and workflowData.js also to transfer to apiCall.js. There to integrate ALL route endpoints from all routes and to call over window.utils.api..... - Functions to handleFileUplad and uploadfile are on many places. To have the api functionality only in apiCall.js. no api relevant code in other modules than apiCall.js. In apiCalls.js to remove the generic functions get, post, put, delete from the public set. those not to expose. only the specific endpoints from the routes to expose. If more than 3 changes in a module, give me the full module. otherwise tell me the parts to change. Please enhance this: - config & env variables integration to have config variables in the globalState set in category "config" integrated. cleanup utils.js: - remove all elements in the context of workflow and messages. those elements have to be integrated within workflow... modules. Some functions within utils.js anyway are not used anymore, so to remove anyway. - extract all api-call functions to a separate submodule "apiCalls.js" module. There to implement one interface function for each api-call. all calls to put into one object "api" to be accessed. - at the end utils.js shall only include config & environment data management, show general toast and error, uiUtils, dataUtils - in workflow... modules there are some redundant functions like in utils.js (e.g. showToast, showError, etc.). Those to remove in workflow and to get from utils.js - utils data shall be accesssible within those categories: - window.utils.api --> the functions from apiCalls.js - window.utils.ui --> what is in uiUtils currently, plus showError, showToast and similair - window.utils.data --> what is in dataUtils currently plus handleFileUpload adapt other modules accordingly. for workflowUi.js only give me the parts to adapt. If only 1-3 adaptions for module, just give me the changes. Otherwise the revised module. please adapt module workflowUi.js with this input (the other modules have already been adapted): - Error handling for file parsing failures to add - Clear indication of workflow completion status - The message object structure to fully match the documented model - Status field handling to be exaclty and only the implementation according documentation (adjustment to recognize "first", "step", "last") - File preview to better handle the documented document structure - File actions to use the correct API paths - log progress indicator implementation to improve, e.g. the feature to collapse/expand details - Agent-specific log formatting to fully match the documented model Updates Required: - Update message rendering to handle status field correctly - Improve file preview to handle documented document structure - Update API paths for file operations - Add better indication of workflow completion status - Improve log progress indicator implementation also remove unused functionality and objects. Can you adapt following two modules. the modules workflowCoordination.ja and workflowData.js have already been updated. please remove unused functionality and objects. please adapt module workflow.js with this input: Updates Required: - Implement explicit state machine transitions - Update API interaction to match documented endpoints - Improve error handling to match documented failure states - Align status handling with the documented state transitions - Ensure proper handling of the "last" message status please adapt module workflow.js with this input: - adapt: Explicit handling of the workflow status transitions per state machine, clean separation of workflow states according to the documentation - The workflow state management to align with the documented state machine - Status transition handling to be more explicit - Verify API paths and request structures - Response handling to match the documented workflow object Updates Required: - Implement explicit state machine transitions - Update API interaction to match documented endpoints - Improve error handling to match documented failure states - Align status handling with the documented state transitions - Ensure proper handling of the "last" message status please adapt module workflowCoordination.js with this input: - the workflow state object structure to be updated to match documentation - Status transitions to follow the documented state machine - message Status Handling to properly handle message status ("first", "step", "last") Updates Required: - Update workflowState object to match documented model - Implement proper status transitions (null → running → completed/failed/stopped) - Ensure message status field handling ("first", "step", "last") - Ensure correct polling mechanism with log/message IDs - Add missing getWorkflowStatus() function - Fix the updateWorkflowStatus() function to handle all status transitions please adapt module workflowData.js with this input: - estimateJsonSize not to be in frontend. data stats is delivered in workflow object with attribute "tokensUsed" - pollWorkflowStatus to implement - adapt object Model Discrepancies: workflow object structure to match the state machine docs, File object structure to follow the documented model - API Endpoint paths to correct to be: /api/workflows/${workflowId}/logs?id=${workflowState.lastPolledLogId}; Same issue to adapt for messages endpoint - Data Handling: lastPolledLogId and lastPolledMessageId tracking variable paths to ensure corretly - uploadAndAddFile: no change, this happens in the backend - submitUserInput() and createWorkflow() to align response handling with the documented workflow object Updates Required: - Implement pollWorkflowStatus() function - Define estimateJsonSize() function - Fix API endpoint paths to match documentation (?logId= → ?id=) - Update object models to match documentation - Improve error handling according to the state machine - Fix file handling to match documented file object model Can you please refactor the workflow_utils.js. The other documents we have. Attached: Frontend State machine Documentation as ruleset for the refactory, the current frontend To organize workflow... modules like this: 1. Centralized State Management: Use a single state object that all modules reference. 2. Event-Based Updates: Use a simple event system to trigger UI updates when state changes. 3. Clear Separation of Concerns: * Model: Manages workflow state and API communication * View: Purely responsible for rendering the UI based on state * Controller: Connects user actions to model updates Comments: - all variables and objects and functions and classes to name in camelCase, not in snake_case - Adapted routes to implement - I do not need backwards compatibility - please remove all unnecessary elements and provide smart, well structured code, which is maintainable New File names: - workflow.js - The main module as manager and coordinator - workflow_state.js - Centralized state management - workflow_api.js - API communication layer - workflow_ui.js - UI rendering layer Can you please refactor the backend with those inputs: Attached: Backend State machine Documentation as ruleset for the refactory Comments: - all variables and objects and functions and classes to name in camelCase, not in snake_case - Adapted routes to implement - I do not need backwards compatibility - please remove all unnecessary elements and provide smart, well structured code, which is maintainable If you need further documents, please tell me. I like your proposition. So do the refactory according to your proposition to clean and structure with these documents: - workflow_presentation.js - workflow_presentation_core.js - workflow_presentation_components.js (here to group the functions accordingly for log, chat, files, ui) - workflow_presentation_utilities.js Can you also split the css files to: - styles_workflow.css --> here only to keep the basic formatting for the layout - styles_workflow_log.css - styles_workflow_chat.css - styles_workflow_files.css - styles_workflow_ui.css I like to refactor frontend to match updated backend. Please this to do: - General: Adapt to backend changes and simplify polling and frontend objects status, remove unnecessary elements. - The Workflow object has only one attribute for status of workflow and for polling to know, if polling shall be active orn not. this is "status" with value "completed" or "running". All other status objects for workflow to remove. - polling start/finish and frontend elements status have only to look for "status" value of workflow. especially all the routines for button "stop", "send", animations only rely on this status. - based on this create one centralized function, which gets workflow status and all other status changes in the front end. based on this this function manages ui adaptions. so we have a mainatenable place to control and debug all status changes. - for log entries to show in the console: always check last log-entry for progress update. logging is done, that also progress information is passed. is this clear for you? what other simplifications or consolidations do you see to improve code for clear debugging and maintainability? please first to give review plan, before doing code. can you do following adaptions for the workflow management for the frontend: - german comments in logs and prompts to translate to english. where to adapt what? - ai calls to adapt for user language if necessary (additional parameter in the lucydom ai call) - can you check all self.log_add(...) statements and rearrange them for the revised function call. They are for the progress of a workflow to show in the front-end. I want all messages to be in a standardizes format and organized along the workflow, that user understands the logical progress. Not too much information, but the relevant steps to show. Within loops to tell progress in percent by having a log_add in the loops (so to add progress attribute to the function call) please deliver adapted modules when more than 3 parts have to be adapted, otherwise the parts to adapt. can you do following adaptions for document class: - class Document to have a "data" attribute, where the file-data is stored in base64 format based on this: - task object for agents to enhance with this attribute for content in contents in documents, when adding a file to a document object: - to set "base64_encoded" if encoded. this should already be, to check when building task for the agents: - ensure attribute "data" is integrated, containing filedata base64 encoded - in each content to deliver "data" as it is, optional "base64_encoded" attribute depending on data format, to add attribute "data_extracted" and to store here the extracted data from ai call everywhere: - to remove base64 checks ot tests. only to use base64_encoded attribute - to use the enhanced attributes for document ("data" containing filedata in base64 format) and content ("data", "base64_encoded", "data_extracted") please tell me, where to adapt what in the code. I do not neew fully new code. please revise all chat_agents* modules: - all comments, logs and outputs in english language - all ai answers in the language of the user - no language specific features like analysis of words. a prompt in japanese would not work with this! i need it generically. - why are there still data extraction routines in the modules? - data is already delivered in the input_documents section. documentation agent: - why to try to find out document type, when in the "label" of the files to deliver the extension is ALWAYS indludes (e.g. .docx, .csv, etc.). Please revise, this can be very much shortened and simplified webcrawler_agent: - there is a try - except mapping problem in the code. please also fix this - also attached chat.py and chat_content_extraction (centralized), that you can see the scrutcure of passed parameters. alle expliziten prompt ersetzen. kannst du mir zusammenstellen, wo es überall in chat.py explizite texte an den user in den messages drin hat? - stell dir vor, es arbeitet ein japaner, der würde es nicht verstehen. die referenzen der code-elemente reicht. die agents registry bereinigen inkl agents die file upload & dragdrop bereinigen, dass einfach file in db geschrieben wird mit file im file-object funktion für integration von file in message, als basis db-file-id oder document-part-from-agent; damit alle attribute füllen inkl zusammenfassung pro content --> pro extractor-typ ein file Workflow: - NO-FILES for the workflow! - All documents in message objects - Uploads only to store in document object with file inline and parsed into content[] kannst du bitte den Code Vorschlag von Dir als class "ChatManager" ins modul "chat.py" umbauen und mir diese class liefern. hier zusätzliche infos und dokumente. für die implementierung der funktionen bitte die beiliegenden module als grundlage verwenden, aber allen code neu erstellen. denn die heutigen codes sind viel zu lange haben zuviele details auf allen levels drin. die implementierung der funktionen soll ebenfalls high-level sein, indem alle detail-ausführungen in grundlagen-funktionen ausgelagert werden. folgende anhänge dazu: - lucydom_model und lucydom_interface : datenmodell und interface zum datenmodell (wir arbeiten nur mir dem workflow object) - workflow.py: die routerdatei, welche die funktionen von lucydom_interface über den gateway nutzt - agentservice_registry (old): registry der agenten, diese bitte neu und kompakt erstellen als "chat_registry.py" - agentservice_base (old): template für agents definitionen. kannst du bitte mit dem datenmodell (es wurde angepasst) folgendes tun: 1. lcuydom_interface.py überarbeiten, damit es mit dem angepassten datenmodell wieder korrekt funktioniert. 2. workflow.py überarbeiten, sodass die immer wieder gleichen funktionen der routes in hilfsfunktionen ausgelagert werden und alle routinen umschreiben, dass sie nicht agentservice_workflow_manager.py" aufrufen, sondern "chat.py". in der router funktion "workflow.py" keine implementierungen, sondern diese in die chat.py funktion übergeben. Die route "submit_user_input" umschreiben, dass workflow_id auch leer sein kann. direkt die funktion "workflow_integrate_userinput" aufrufen. 3. die funktionen implementieren mit diesen hinweisen: workflow_integrate_userinput: - den parameter workflow umbenennen in optional workflow_id. dieser kann initial None sein, wenn ein neuer workflow startet. daher zuerst die zu implementierende funktion workflow_init(workflow_id) aufrufen, welche das workflow object zurückgibt. - generell werden 2 kommunikationen geführt: - a) "log_add" (umbenennen von "send_message_to_user") sendet einen log-eintrag, implementiert in mit der implementierung in "lucydom_interface.create_workflow_log" und gleichzeitig einen "Info" Eintrag im logger erstellen - b) "message_add" speichert eine message im workflow objekt. Implementierung über lucydom_interface - Vor Step 1. die message_user im workflow als neue message speichern - Anstatt "# Send initial response" die "user_response" als message object im workflow speichern und auch gleich den obj_answer und obj_workplan in den logger schreiben mittels einer hilfsfunktion "json2text(), welche das json-Objekt als Strukturobjekt lesbar schreibgeschützt - send_message_to_user(step_info), dies als log_add schreiben - format_final_response umbenennen in format_final_message und damit das finale message objekt mit den documents erstellen, dieses dann mit messagE_add dem workflow zufügen - update_workflow(...) nicht mehr nötig, dafür workflow_finish prompt_project_manager: - mach nur einen typ "doc_type" und gib dafür eine abschliessende liste von optionen an, welche aus der funktion get_available_document_types() kommen - der obj_workplan soll pro listenelement doc_input und doc_output ein Dict haben mit den Elementen "label","doc_type". auch hier die abschliessende liste der möglichen werte angeben, welche aus der funktion get_available_document_types() kommt. workflow_init: - wenn die workflow_id leer ist oder nicht existiert, wird ein neuer workflow erzeugt, andernfalls wird der bestehende workflow geladen - die statuswerte werden gesetzt: status="running", started_at, last_activity=strated_at workflow_finish: - die statuswerte werden gesetzt: status="stopped", last_activity message_add: - die message dem workflow ergänzen - die statuswerte werden gesetzt: last_activity, last_message_id get_available_agents: - die function aus der agents_registry aufrufen get_available_document_types: - liste dieser doc-types ausgeben: text, csv, png, html summarize_workflow(workflow,prompt): - in der chronologie der messages von aktuell zu historisch pro message mit der funktion summarize_message(prompt) die zusammenfassung holen. Die zusammenfasusng ausgeben mit agent-name, generierte zusammenfassung, liste der dokumente mit jeweils ihrer zusammenfassung summarize_message(prompt): - mit ai call die zusammenfassung der message mit dem prompt generieren. Die zusammenfasusng ausgeben mit agent-name, generierte zusammenfassung des contents, liste der dokumente mit jeweils ihrer zusammenfassung summarize_user_documents: - pro document mit dem angegebenen prompt den content zusammenfassen und die liste ausgeben mit [document.content: text] call_agent: braucht es nicht, ai calls können direkt über den connector erfolgen, welcher initial eingebunden wird: "from connectors.connector_aichat_openai import ChatService" Kannst Du mir die python funktion erstellen, um nachfolgendes zu tun. Ich möchte eine kompakte Funktion, welche keine Details enthält, ausser den Prompt-Teil bis und mit Antwort an den user. Alle nötigen Datenkonversionen und Details bitte in Hilfs-funktionen auslagern. Diese müssen nicht implementiert sein, sondern nur deren input und output definieren. # Kontext Der User liefert im AI Chat eine Anfrage in einem Message Objekt. Dieses beinhaltet seinen Prompt und eine Liste der mitgelieferten Dokumente mit ihnen contents im "message" objekt. Ebenfalls verfügbar ist der bisherige Chatverlauf im objekt "workflow". Wir befinden uns in der python funktion "workflow_integrate_userinput", wo der User prompt ankommt, also diese 2 parameter: "message_user" und "workflow". Es steht eine Liste von agents zur Verfügung. Das agents in der Art: - Loop: Er führt repetitive Aufgaben aus. Er benötigt eine Liste von Dokumenten und einen Prompt zur Anwendung auf jedes Dokument, er liefert eine liste von "content" . Coder: Er führt Pyton Code aus. Benötigt als Input einen Prompt, content und die spezifikation des resultatformates. (weitere...) # Auftrag Kannst Du mir bitte den Prompt für den Projektleiter zusammenstellen, welcher dem User die Antwort liefert. Dies soll er tun: 1. Eine Liste von Resultaten, welche der User für seine Antwort benötigt, als json-Objekt "obj_answer" liefern. Die Antworten des Projektleiters sollen strikt in einem vorgegebenen json-format geliefert werden. 2. Antwort des Vorgehens an den Benutzer mit den Resultat-Dokumenten als Liste senden 3. Falls für die Antwort oder die Resultate Inputs von Agenten (diese sind gemäss "obj_agents" mit ihren eigenschaften definiert) benötigt werden, diese als json Liste (ich nenne sie "obj_workplan") angeben, welcher agent welches resultat liefern soll Dann soll der Code dies machen: 4. die agenten gemäss obj_workplan ausführen lassen und den user über jeden schritt informieren. die gelieferten dokumente als liste sammeln "obj_results". Jeden Agenten mit den Datenobjekten gemäss seiner Datenstruktur bedienen. Dann anhand der gelieferten Dokumente die finale Antwort an den Benutzer senden. Dokumente vom Typ "text" direkt in die Antwort an den Benutzer integrieren. Die Dokumente referenzieren. Dann im Code: 5. Dem benutzer die antwort mit den dokumenten senden Jedes Dokument soll anhand des Labels eindeutig identifizierbar sein. Du hast alle Dokument-conteot-labels im workflow objekt. Diese Objektinformationen dazu: - datenmodell für workflow inklusive message: - workflow - messages: list of message - message - agent (who created message) - input (the input prompt) - content (text) - documents: list of document - document - source - contents: list of content - content - label - format: formatType - data: the data of the content in the format according to formatType - formatType: [text, csv, jpg, gif, png] - obj_answer: json-Liste mit diesen Attributen: - label: document label (unique name in the documents list) - doc_type_src: document type des zu liefernden dokumentes: [text, csv, png, html] - doc_type_final: document type des dokumentes an den Benutzer: [text, csv, jpg, gif, png, pdf, html, docx, xlsx] - summary: summary of required document content - obj_workplan: json-Liste mit diesen Attributen: - agent: agent identifier based on the given agent list with the skills of the agents - doc_output: List of label,doc_type_src (documents to deliver) - prompt: Prompt to use for answer delivery and document-content-extraction - doc_input: List of label,doc_type_src (documents to read with prompt) - obj_agents: Pro Agent sind diese Informationen verfügbar: - name: Sein Name, um die entsprechende Funktion aufzurufen - skills: Was dieser Agent macht - input: datenformat, in welchem der agent die informationen benötigt - obj_result: List of documents with label, format, data Es soll durchgängig mit dem content objekt gearbeitet werden, wenn content übergeben wird. backend: all object actions in interfaces generic for the objects in models for CRU-methods We have here an ai agents workflow. a big problem is document extraction. i uploaded a pdf file with a picture inside. in the database i see, that the document has 1 contents, "text" with a endline, marked as "is_extracted=True". it is missing the picture inside the pdf. I would like to have the following implementation for files in a workflow: How do documents arrive in the workflow: a) user input with upload or drag&drop: the file shall be stored in the database (files) and its content stored in the workflow message as documents item with reference to the file_id in the database. all contents of the file will be stored as content items in the document item of the message object. according to the content type whey will be extracted as text or as base64 string (e.g. images). the document id will be a uuid and the document-source id the integer from the object in the database "files" b) produces documents delivered by the agents: exactly the same like a) the content provided to an agent will now be a document consisting of the content of all previous messages including the extracted content of the documents within the messages. the extracted content of the documents is produced for each content of the document: - for text: An ai call with the extraction prompt delivers the text to be integrated - for an image (it is available as base64 content) an ai call with the extraction prompt delivers the text to be integrated Like this we have not anymore the problem, that file content is not found by the agents. For code implementation I see a big opportunity to massively reduce code. To build basic methods to be used everywhere: 1. function "document_store_upload(message_id,fileName,filepath...) --> function to store an uploaded or drag&drop document from the user and return the document object. This function does the steps for a) respectively b) like described above and identified the filetype 2. function "document_store_agent(message_id,fileName,document_content,document_type...) --> function to store the produced document from the agent and return the document object. This function does the steps like described in section a) above 3. function "document_get_from_message() Based on these 3 functions all operations can be done much more comfortable in the workflow, but also in connection with the ui (download file, copy file, preview file), because all references to the files are always ensured. Can you analyze this idea? What did I not yet consider, that would be relevant for the current code to adapt? how big is the effort to have this logic implemented? Currently the webcrawler is always called for unclear prompts. Can you please add an agent for "Creative" or "knowledge" answers and select him rather than the webcrawler (meaning to adapt criteria for webcrawler, that he is only called for explicit web research or internet search). The Creative Agent shall be selected for open questions or simple documentation topics, e.g. writing an email, write a birthday card, what to consider if going 1 year to usa, etc. He can also deliver documents. So to specify in his prompt, that it is clear what he delivers and how it it taken out for the next agent. The exception for "poweron" keyword shall also be routed to this agent. This means, he is the one to answer the keyword "poweron". Like this you can please remove all "poweron"-specific code in the modules and integrate the answer for poweron in this "Creative" agent. Please use the agentservice_base.py to create this agent (same template as for all other agents). Modul "agentservice_agent_documentation.py": Bitte die Berichterstellung adaptiv zum Prompt machen. Bei einfachen Berichten eher eine Zusammenfassung, bei komplexen Berichten mit Kapiteln arbeiten. PowerOn Message: Kannst Du einbauen, dass bei einem User Prompt, welcher in irgend einer Sprache fragt, "was PowerOn ist", dass dann die Rückmeldung is der Sprache der Anfrage etwas in dieser Art ist (bitte schön formulieren): *Ich bin glücklich, Teil der PowerOn Familie zu sein, welche sich dafür einsetzt, dass wir einander unterstüzzen und Gutes tun". DOKUS Doku des Systems für Investoren (Hi-level Struktur, Integrationsfähigkeit und Skalierbarkeit) Doku des Systems für Code Integration Release Notes (was kann das Teil) Log der Anpassungen Systemarchitektur (Grundsätze der Architektur, Komponenten und deren Aufbau) # WORKFLOW EXECUTION Die workflow execution soll so angepasst werden: 1. Der Workflow startet wie bisher bis und mit message initialisierung 2. Dann wird über den AI Call der Arbeitsplan erstellt, welcher als Resultat eine Liste der Aktivitäten liefert, die auszuführen sind. Pro Schritt ist strukturiert erfasst: - Was ist im Schritt zu tun? Dies als AI Prompt, um anschliessend die Agenten für den Schritt zu definieren - Welche Daten sind dazu nötig? Dies formuliert als AI Prompt an den Dateien-Manager - Welches Resultat soll geliefert werden? - Strukturierte Angabe von Formatvorgaben (z.B. "Liste von Dateien","Text","JSON", "Tabelle", etc.) 3. Nun wird die Liste der Aktivitäten abgearbeitet. Pro Aktivität erfolgt dies: - Agenten mit ihren Eigenschaften und dem Resultatformat zusammenstellen - Mit AI Call festlegen, welche Agenten in welcher Reihenfolge nötig sind. - Nun die Agenten schrittweise ausführen lassen. dazu diese schritte pro agent: -- message object mit prompt und der angabe des letzten message objectes im workflow vorbereiten -- Mit dem Hilfsmodul "agentservice_dataextraction.py" die nötigen Daten aus dem Workflow extrahierenund dem message object des agenten zufügen. Im Hilfsmodul noch das Objekt messages definieren. -- agent liefert das resultat, welches als message object im workflow ergänzt wird. 4. Nun die Zusammenfassung der durch die agenten erstellten resultate für den User erstellen und ebenfalls als message im workflow speichern. # CODE STRUKTUR Aktuell hat es in jedem Modul und auch im Hauptmodul von agentservice* detaillierten Code drin. Kannst Du im gleichen Zug den Code aufräumen, dass "agentservice_workflow_manager" als master-modul nur funktionen aufruft und nicht noch details bearbeitet. so kann der workflow besser geführt werden. Die Meldungen im "_add_log()" sowie die Logger-Mledungen sind unübersichtlich und helfen kaum zur Analyse. Bitte diese Meldungen anhand des Workflows strukturieren und auch die Moderator-Anweisungen (zusammengefasst im _add_log und mit den parametern (lange texte gekürzt) im logger) ausgeben, damit eine Fehlersuche einfacher ist. Bitte Hilfsfunktionen, welche überall immer wieder verwendet werden, in ein utility modul auslagern. Als Idee Dinge wie - Class mit Methoden zum lesen, schreiben, extrahieren von messages im workflow inklusive Typenkonversion von Dict in str. Dass ich z.B. schreiben kann (nur als idee, gibt eventuell schlauere funktionen): workflow(id).documents.extract_by_prompt(prompt).to_str() - Bitte analysiere den code, was an Funktionen Sinn macht Allenfalls noch andere Themen, die helfen, den Code zu vereinfachen. Das Ziel soll es sein, dass der Workflow und die Agentencodes nicht jedes Detail immer codiert haben müssen mit immer wieder fehlerabfangroutinen, sondern dass wie auf vordefinierte module zugreifen können und diese durchgängig nutzen. damit soll der code massiv verkürzt werden. # DATEIEN EINLESEN Wenn eine Datei/File (in der datenbank ein Dokument) als Text lesbar ist (txt, csv, html, Text in Pdf etc.), dann wird der text des dokumentes direkt ausgelesen und als DocumentContent in der DB erfasst --> is_extracted=True. Wenn ein Dokument nicht als Text lesbar ist (Bilder, Videos, Bilder in PDF etc.), dann wird der text des entsprechenden DocumentContent nicht extrahiert, also is_extracted=False. (hinweis: Die extraktion findet dann erst im workflow mit einem prompt statt.) # AGENTEN In jedem Agenten-Profil ein Attribut ergänzen, welches spezifisch angibt, in welchem Format der Agent das Resultat zurückliefert (z.b. "DocumentID" oder "Text" oder "List of ..." etc.). # HILFSFUNKTIONEN 1. data_extraction(prompt) --> messages: ai call durchführen mit einer liste aller dateien mit ihren metadaten und aller messages im workflow. mit dem prompt prüfen, welche inhalte von welchem datenobjekt erforderlich sind. das resultat soll eine liste sein, welche pro datenobjekt den prompt enthält, um die nötigen daten zu extrahieren. diese liste abarbeiten (falls ein dokument den inhalt nicht extrahiert hat, diesen nun mit der entsprechenden funktion extrahieren; bild-extraktion ist bereits als funktion verfügbar) und die extrahierten daten mit ihren kontext-informationen als strukturiertes text-object zurückgeben (metadaten mit extrahierten inhalten) # ZUSAETZLOCHE AGENTEN NEU: Der Filecreator kann dies tun, welche relevant für seine Fähigkeiten sind: Datei erstellen --> Document object in der Datenbank mit dem mitgelieferten inhalt und datentyp erzeugen und die id zum Datenobjekt zurückliefern Implementieren: Coder Dieser soll python code generieren und als Parameter die verfügbaren Funktionen im Umsystem (z.b. für Files laden und speichern) (als Erweiterung im Beispiel soll pro Funktion angegeben werden, welche Parameter und welches Resultat-format, hier ein geeigneter vorschlag von dir bitte). Den Code anschliessend ausführen, so wie im Code Beispiel "_code_exec_temp.py". Dann das Resultat zurückgeben. Workflow module refactored Summary of Changes I've refactored the workflow module into separate modules with clear responsibilities: 1. workflow.js - Main Coordinator Acts as the central controller for the workflow functionality Coordinates interactions between all other modules Manages the workflow lifecycle (starting, stopping, resetting) Contains minimal direct DOM manipulation Maintains the core workflow state 2. workflow_ui.js - UI Rendering and Layout Handles all DOM rendering functionality Manages layout changes (resize, expand/collapse) Sets up UI-related event listeners Updates visual status (buttons, statistics) Completely separates UI concerns from data and business logic 3. workflow_data.js - Data Management Handles all API communication (via utils.js) Centralizes state management for workflow data Processes data from API responses Manages file references and retrieval Handles data statistics tracking 4. workflow_features.js - Feature Modules Manages chat functionality Handles file upload/processing Controls log management Processes user input Encapsulates drag-and-drop functionality 5. workflow_utils.js - Helper Functions Contains shared utility functions Text formatting helpers File-related utility functions Error/dialog management Data validation and conversion * Refactoring-Auftrag: Workflow-System-Überarbeitung * ## Übersicht Dieser Auftrag umfasst eine vollständige Überarbeitung des Workflow-Ablaufs sowohl im Frontend als auch im Backend. Das Ziel ist eine Vereinfachung der Benutzeroberfläche, bessere Modularisierung des Codes und Optimierung der Datenhaltung, sowie Hinzufügen einer Löschfunktion für einzelne Nachrichten zur Datenmengenbegrenzung. ## Anforderungen Frontend ### 1. UI-Elemente entfernen - Vollständige Entfernung der Sektion "Prompt eingeben" - Entfernung aller Buttons im Bereich "Ausführung & Ergebnisse", mit Ausnahme des "Workflow stoppen"-Buttons, der nur während eines aktiven Workflows sichtbar sein soll - Der "Workflow stoppen"-Button soll automatisch ausgeblendet werden, sobald eine Benutzereingabe angefordert wird - Entfernung der Anzeige des ausgewählten Workspaces in der index.html ### 2. User-Input-Modul auslagern - Extraktion aller Funktionen zur Benutzereingabe aus "workflow.js" in ein neues separates Modul "workflow_userinput.js" - Das neue Modul soll sowohl für den initialen Prompt als auch für alle weiteren Benutzerantworten im Workflow verwendet werden ### 3. Funktionalität User-Input-Modul Das neue "workflow_userinput.js" Modul soll folgende Funktionen enthalten: - Erkennung, wann eine Benutzereingabe erforderlich ist (initial und wenn der User-Agent aufgerufen wird) - Auswahl vordefinierter Prompts ermöglichen - Datei-Upload und Drag & Drop Funktionalität - Senden des Prompts an das Backend mit der workflowid, falls vorhanden - Implementation einer Löschfunktion ("x") für jede Nachricht und angehängte Datei im Chat-Protokoll ### 4. Nachrichtenlöschfunktion - Jede Nachricht im Multi-Agent Chat Protokoll erhält einen "x"-Button zum Löschen - Löschfunktion soll auch für Dateien innerhalb einer Nachricht implementiert werden - Nahtlose API-Integration mit dem neuen DELETE-Endpunkt für Nachrichten ## Anforderungen Backend ### 1. Route "workflow.py" - Reduzierung auf minimale Routing-Funktionalität - Verlagerung aller Implementierungslogik in den "agentservice_workflow_manager" - Hinzufügen eines neuen Endpunkts: `DELETE /api/workflows/{workflow_id}/messages/{message_id}` ### 2. Workflow-Manager-Logik Überarbeitung des "agentservice_workflow_manager" mit folgender Ablauflogik: 1. Workflow-Initialisierung: - Bei neuem Workflow: Initialisierung mit leerem Messages-Objekt - Bei bestehendem Workflow: Übernahme des vorhandenen Messages-Objekts 2. Message-Objekt-Verwaltung: - Starten eines neuen Message-Objekts für jede Interaktion - Vollständige Nutzung des Datenmodells aus "lucydom_model.py" - Korrekte Speicherung in der Datenbank 3. Dateivorbereitung: - Erstellung von Datei-Kontexten und Integration ins neueste Message-Objekt - Extraktion und Speicherung von Dateiinhalten - Formatierung der Daten für die Agenten-Verarbeitung 4. Agent-Workflow: - Initialisierung verfügbarer Agenten einschließlich User-Agent - Implementierung der Moderator-Entscheidungslogik (bereits implementiert) -> entweder wird eine liste von agenten verarbeitet (OHNE User agent!), oder der user agent aufgerufen. 4a) - Ausführung der Agenten in der festgelegten Reihenfolge 4b) - Abschluss mit User-Agent und Prompt-Aufforderung (Anmerkung: die wofkflow id mitgeben, damit nach dem senden der user antwort im frontend der workflow weitergeführt wird bei Punkt 1. Aber im Backend ist es hier fertig) 5. Nachrichten-Löschfunktion: - Implementierung der Löschlogik für einzelne Nachrichten - Vollständige Entfernung der Nachrichtendaten auch aus dem Backend-Speicher ## Betroffene Dateien ### Frontend-Dateien: 1. `workflow.js` - Umfassende Überarbeitung und Entfernung von User-Input-Funktionalität 2. `workflow_userinput.js` - Neue Datei für die ausgelagerte User-Input-Funktionalität 3. `index.html` - Entfernung der nicht mehr benötigten UI-Elemente und Integration des neuen Moduls 4. `main.js` - Anpassungen für die geänderte Modularität 5. `globalState.js` - Ggf. Anpassungen für die geänderte Workflow-Struktur 6. `utils.js` - Erweiterung um die neue DELETE-Funktion für Nachrichten ### Backend-Dateien: 1. `workflows.py` - Vereinfachung und Hinzufügen des neuen DELETE-Endpunkts 2. `agentservice_workflow_manager.py` - Umfassende Überarbeitung der Workflow-Logik 3. `lucydom_interface.py` - Erweiterung um Methoden zum Löschen von Nachrichten 4. `agentservice_agent_user.py` - Anpassungen für das neue User-Input-Handling ## Fehlerbehandlung - Frontend: - Konsistente Fehlerbehandlung für alle API-Aufrufe implementieren - Benutzerfreundliche Fehlermeldungen bei fehlgeschlagenen Operationen anzeigen - Status-Indikatoren während laufender Operationen (z.B. Löschen von Nachrichten) - Backend: - HTTP-Statuscode 404 zurückgeben, wenn eine zu löschende Nachricht nicht gefunden wird - Sicherstellen, dass alle Workflow-Operationen Transaktionssicherheit bieten - Ausführliche Logging-Funktionalität für Fehlerdiagnose ## Richtlinien zur Codequalität - Clean Code-Prinzipien beachten (DRY, SOLID) - Konsistente Benennung und Dokumentation - Entfernung ungenutzter Funktionen und Code-Teile - Ausreichende Kommentierung für komplexe Logik ## Zusätzliche Hinweise - Daten dürfen ohne Bedenken gelöscht werden - Keine Übergangsstrategie erforderlich, System startet neu - Keine Bestätigungsdialoge für das Löschen von Nachrichten erforderlich - Keine speziellen Berechtigungsanforderungen für das Löschen von Nachrichten *WORKFLOW* ich habe das backend komplett angepasst mit dem workflow und dem datenmodell. die wichtigsten anpassungen sind das datenmodell für workflow und messages. Nun muss das Frontend entsprechend angepasst werden. hier der ablauf des workflows im backend zur information: 1. Der User kann (A) einen neuen Workflow starten oder (B) bei einem bestehenden Workflow einen user Input liefern. Die Endpunkte liegen bei. . Varinate (A): Der User sendet einen Prompt mit Dateien für einen neuen Workflow. Damit wird ein neuer leerer Workflow erstellt . Varinate (B): Es erfolgt ein User Input mit allenfalls Dateien zu einem bestehenden Workflow. Als Input wird ein messages objekt geliefert. Der workflow Status wird auf "running" gesetzt. 2. Message Initialisierung: Das letzte Message Objekt wird abgeschlossen (falls eines existiert) und ein neues Message Object erstellt. Dieses wird nun komplettiert. 3. Dateivorbereitung: Datei-Kontexte werden erstellt und ins neuste Message objekt abgefüllt. Dateiinhalte werden gelesen, extrahiert und ins message objekt abgefüllt. Daten werden für die Verarbeitung durch die Agenten formatiert 4. Agent-Initialisierung: Die verfügbaren Agenten werden geladen inklusive der user agent. 5. Moderator-Entscheidung 6. Agent-Ausführung, bis am Schluss der User aufgerufen wird, um einen Input zu geben. 7. Nun ist der "User Agent" an der Reihe. Der user Input hat immer obendran die Frage, die dem User gestellt wird. der user input hat die workflow id dabei. hier ist der workflow beendet. wenn der user seine antwort sendet, geht es weiter bei punkt 1 Variante (B) Dies zusätzlich anzupassen: - Der initiale Prompt mit File-Upload ist gleichzeitig auch der Prompt, der dem User angeboten wird, wenn im Chat ein Input von ihm nötig ist. Dieses Eingabefeld soll an die Stelle verschoben werden, wo aktuell der User-Dialog angezeigt wird bei "wait for user". So gibt der User die Daten immer am gleichen Ort ein. - Für den File Upload sollen zwei Methoden möglich sein. -- a: Upload-Button direkt unten am Prompt. Jedes geladene File wird dann als kleines Icon mit dem Filenamen unter dem Prompt ergänzt mit einem "x", damit es wieder gelöscht werden kann, wenn nicht benötigt. Wenn Text aus dem File extrahiert werden konnte, so ist das Feld mit dem Dateinamen grün, sonst rot. Ist Dir klar, wie Du diese Information abfragen kannst? - Wenn der User den Prompt absetzt, wird dieser über das Backend anschliessend in die Resultate geliefert. Du musst dies nicht im Frontend machen, sonst haben wir es doppelt. -- b: Drag & Drop: Ein File kann in den Prompt-Bereich gezogen werden, dann wird es auch hochgeladen. - Das Auswahlfenster für vordefinierte Prompts soll direkt über dem Eingabefeld für den Benutzer sein. - Die Buttons zur Steuerung des Workflows sollen oben am Bereich "Ausführung & Ergebnisse" verschoben werden. - Der Bereich "1. Dateien auswählen" entfällt somit, da integriert bei mUser Prompt. - Der Bereich "2. Promot eingeben oder auswählen" entfällt auch - Der Bereich "3. Agenten auswählen" entfällt auch - Resultateintrag: Jeder Eintrag im Resultat-Log hat zuoberst Icons mit den Files, welche die Agenten zurückliefern, dann den Text dazu. Jedes Icon eines Files hat die Buttons "Download" und "Copy" (für Clipboard") und "Vorschau" - Damit entfällt der Bereich "Workflow-Konfiguration" komplett. Die beiden Bereiche sollen aber beibehalten werden, einfach mit anderem Inhalt. Im aktuellen Bereich "Workflow-Konfiguration" soll neu der Bereich "Ausführung & Ergebnisse" drin sein. Im aktuellen Bereich"Ausführung & Ergebnisse" soll NEU der Bereich "Dateivorschau" hinkommen. Dort kann eine von den Agenten gelieferte Datei (siehe Punkt "Resultateintrag" zuvor) als "Vorschau" angeschaut werden. Oben rechts hat es zwei Icons "Download" und "Copy" (für Clipboard). * WEITERE ANPASSUNGEN * - Die Objekte "agents" und "workspaces" sind eliminiert und zu entfernen. Somit fallen auch die entsprechenden Navigationseinträge weg und alle Funktionen im Zusammenhang mit Workspaces. Es gibt keine Workspaces mehr. Kannst Du vor der Umsetzung prüfen, ob Du alle nötigen Dateien und Informationen hast und mir zusammenstellen, was Du machen wirst? Ich möchte den agentenchat workflow ändern. kannst du mir bitte dazu in einem ersten schritt das backend anpassen. 1. das datenobjekt *workspaces" und "agents" wird nicht mehr benötigt, und kann entfernt werden. Der user arbeitet mit einzelnen workflows. Agenten sind systemseitig fix definiert. 2. alle workflow router endpunkte bleiben bestehen, wie sie sind 3. Neue Objektstruktur für den workflow ablauf: 4. Die Schritte in einem Workflow (neu) - bitte den code revidieren und alle unnötigen teile entfernen. 4.1 Der User kann (A) einen neuen Workflow starten (Enpunkt api/workflows/run) oder (B) bei einem bestehenden Workflow einen user Input liefern (Endpunkt /api/workflows/{workflow_id}/user-input). Mit beiden Varianten soll bei execute_workflow() gestartet werden. . Varinate (A): Der User sendet einen Prompt mit Dateien für einen neuen Workflow. Damit wird ein neuer Workflow mit execute_workflow() erstellt, aber noch ohne message objekt. Als Input wird ein messages objekt geliefert. Initialer workflow Status wird auf "running" gesetzt. . Varinate (B): Es erfolgt ein User Input mit allenfalls Dateien zu einem bestehenden Workflow. Als Input wird ein messages objekt geliefert. Der workflow Status wird auf "running" gesetzt. 4.2.- Message Initialisierung: Das letzte Message Objekt wird abgeschlossen (falls eines existiert) und ein neues Message Object erstellt. Dieses wird nun komplettiert. 4.3- Dateivorbereitung: Datei-Kontexte werden mit prepare_file_contexts() erstellt und ins neuste Message objekt abgefüllt. Dateiinhalte werden mit read_file_contents() gelesen, extrahiert und ins message objekt abgefüllt. Daten werden für die Verarbeitung durch die Agenten formatiert 4.4 Agent-Initialisierung: Die verfügbaren Agenten werden mit initialize_agents() aus dem Modul "agentservice_part_agents" geladen inklusive der user agent. 4.5. Moderator-Entscheidung: Es gibt keinen agenten "Moderator". Anhand des des neusten Message Objektes und den Profilen der verfügbaren Agenten wird mit dem OpenAI Call abgefragt, wie die Anfrage gelöst werden soll. Als Resultat-Format soll ein json-objekt vorgegeben werden, welcher agent welchen job (=Prompt für diesen) ausführen soll, mit welchen antworten und welchen datenobjekten. dazu sind keine weiteren subfunktionen nötig. das antwortformat soll so vorgegeben werden, dass zwingend pro auftrag verfügbare agenten rückgemeldet werden. Das Agentenset soll immer entweder nur der User oder nur system-agenten sein. somit ist das antwortformat eine liste mit agenten und deren aufträgen. 4.6. Agent-Ausführung: Falls eine agentenliste zürückgegeben wird (und nicht der user), werden die Agenten in der angegeben Sequenz aufgerufen werden, um ihren Beitrag zu liefern. Agent-Antworten werden mit create_agent_result() ins message objekt integriert, die verschiedenen files separiert. Als nächstes wird mit den gelieferten Antworten der Agenten (nur dieser Teil, nicht die früheren Nachrichten) über den OpenAI Call eine Zusammenfassung erstellt und als Input-Text dem user Agenten übergeben, welcher nun als nächsten Agenten ausgewählt wird. Der ablauf wird gestoppt, wenn der Workflow manuell mit stop_workflow() gestoppt wird (status auf "stopped"), oder ein Fehler auftritt (status auf "failed"). 4.7. Nun ist der "User Agent" an der Reihe. Der Workflow-Status wird auf "waiting_for_user" gesetzt. Der ganze Teil mit _process_user_input() etc. entfällt. Nach der Benutzereingabe wird der Workflow nicht mit _continue_workflow_after_user_input() fortgesetzt, sondern regulär wieder bei Punkt 4.1 über den Zweig (B). 4.8. Protokollierung: Jeder Schritt wird mit _add_log() protokolliert. Logs werden im Workflow-Objekt gespeichert. 4.9. Hier endet der Workflow regulär, bis der User eine neue Anfrage macht. Das heisst, es benötigt keine Moderatoren-Checks mehr, keine maximale Rundenzahl. Der Workflow wird mit save_workflow_results() gespeichert. 5. Fortlaufendes Polling: Der Client kann den Workflow-Status mit get_workflow_status() abfragen. Protokolle können mit get_workflow_logs() abgerufen werden. Ergebnisse können mit get_workflow_results() abgerufen werden. 6. agents: Die verfügbaren Agenten werden mit initialize_agents() aus dem Modul "agentservice_part_agents" geladen. die agentendaten, werden in separaten dateien abgelegt, damit dies wartbar ist. Es werden diese agenten-module vorbereitet: .agentservice_agent_user .agentservice_agent_coder .agentservice_agent_analyst .agentservice_agent_webcrawler .agentservice_agent_sharepoint .agentservice_agent_documentation Pro agent werden diese attribute definiert: .name .description .capabilities Jeder Agent hat dann seine eigenen Funktionen in seinem File integriert, die er benötigt. 7. Konnektorenbereinigung: - Alle Konnectoren in einen subfolder "connectors" verschieben, d.h. alle files mit "connector_..." - Die zwei Konnektoren "connector_aichat..." so umschreiben, dass sie daten als Input im format des messgaes Objekt gemäss Punkt 3 als input übernehmen und auch wieder zurückgeben. 8. geänderte speicherng von workflows: Bitte den code so anpassen, dass workflows als datenbankobjekte gespeichert werden, analog so wie prompts. D.h. die routes für "workflows" ergänzen mit "GET /api/workflows", "PUT /api/workflows/{workflow_id}", "DELETE /api/workflows/{workflow_id}" Die Route "POST /api/workflows/run" umbenennen in "POST /api/workflows" Die Route "/api/workflows/{workflow_id}/results" umbenennen in "GET /api/workflows/{workflow_id}" Sinngemäss alle module anpassen und die Datenbankklassen vorbereiten. Die Buttons "Workflow starten" und "Zurücksetzen" haben keinen Rahmen. Ist hier ggf. die Style Class falsch oder nicht appliziert? Anpassung des Visuals "Ausführung & Ergebnisse": - Das Ausführungsprotokoll so belassen. Einen Button rechts von den anderen zwei Buttons (alle anzeigen / Details zuklappen) ergänzen, für dies mit dem Ausführungsprotokollfenster: toggle function collapse and restore - Die Bereiche "Multi-Agent-Chat" und "Ergebnisse" machen so keinen Sinn. Diese beiden Bereiche bitte zusammenlegen in einen grossen Bereich mit dem Namen "Multi-Agent Chat Area". Dort laufend die Messages der Agenten in einer HTML-Ansicht der Messages protokollieren. Jeweils der Name des Agenten im Titel und darunter seine Message. Die letzte Message soll aufgeklappt sein, alle früheren sollen jeweils zugeklappt sein, aber durch den User soll ein toggle pro Message möglich sein, um die Details zu sehen. Kannst Du den Ablauf des Agenten-Chats wie folgt optimieren: - Bei jedem Chat einen "User Agent" mit dem Namen des eingelogten Benutzers ergänzen. Wenn etwas im Chat nicht klar ist, oder zusätzliche Informationen nötig sind, so fragt er den User Agent. Auch bevor er den Chat beendet, fragt er den User Agent, ob dieser einverstanden ist. - Wenn der User Agent eine Anfrage erhält, so kann er direkt unter der Chat History im Bereich ereiche "Multi-Agent-Chat" seinen Text in einem mehrzeiligen Textfeld erfassen. Er kann auch zusätzliche Files hochladen. Wenn er "Enter" drückt, werden die zusätzlichen Daten mit den ergänzten Files zur Message ergänzt, das Eingabefenster verschwindet wieder und der Moderator führt den Chat fort. Immer nach einer Benutzereingabe startet der Zähler wieder bei Runde 1. Statistik ergänzen: Kannst Du bitte rechtsbündig neben dem Titel des "Ausführungsprotokolls" laufend die Statistik nachführen, wieviele kBytes (kB) Daten über den Connector zum AI-Modell gesendet wurden (dies ist die Datengrösse des Message-Objektes) und wieviele kB an Messages zurückgeliefert wurden. Diese angabe pro Workflow-Durchlauf, also immer beim Start eines neuen Workflows wird der Zähler auf 0 gesetzt. In diesem Format: "^ 250k v 1'250k ", v und ^ durch Pfeile ersetzt. In den Einstellungen des Frontends soll die Sprache des aktiven benutzers gemäss den Listenoptionen in den "...model.py" angepasst werden können. die sprache gilt dann auch für die Attributnamen in einem Formularfeld im "generic-entity.js". eine sprachänderung zieht somit eine anpassung des Users über das API nach sich, indem die Sprache in der Datenbank angepasst wird. kannst du die ausführungsprotokollierung anpassen? das protokoll soll laufend anzeigen, welcher assistent welches resultat produziert hat und welcher assistent aktuell am arbeiten ist. Prozentzahlen sind keine nötig, diese machen keinen sinn. das polling so beibehalten, aber wenn keine neuen Daten bereitstelen, dann beim letzten Timestamp einfach laufend "." ergänzen, bis die nächste Meldung ausgegeben wird. hast du alle daten, um dies im frontend und im backend anzupassen? Im Ausführungsprotokoll pro Eintrag nur den Titel zeigen und die Details zwar ins Protokoll nehmen, aber ausblenden. Der Benutzer kann dann im Protokoll die zugeklappten Texte aufklappen, um die gewünschten Details gezielt zu sehen. Im Front-End beim Workflow-Modul bitte das Ausführungsprotokoll-Fenster dynamisch in der Grösse anpassbar machen. in der Breite und der Höhe. Dasselbe für das Ergebnis-Fenster. Zudem die Ansicht so gestalten, dass die Fensterteile "Workflow-Konfiguration" und "Ausführung & Ergebnisse" ein- und ausgeblendet werden können, damit jeweils ein Teil die komplette Arbeitsfläche verwenden kann, weil dort viel Text stehen wird. Dies ist für den Benutzer besser. nun zu diesem zentralen modul. ich hätte gern, dass die daten als tabellen dargestellt und bearbeitet werden können. für view, add, modify, delete jeweils icon pro datensatz ganz links und zuoberst im header ein "new item" symbol oder text, mach einen vorschlag. ist es möglich, eine checkbox pro datensatz zu machen, um mehrere elemente auszuwählen und oben an der tabelle icons zu haben für mehrfach delete? die tabelle soll nach allen feldern gefiltert und sortiert werden können kannst du bitte den code so anpassen, dass main.js die seitenmodule im Anhang dynamisch erst dann lädt, wenn die entsprechende seite in der navigation aufgerufen wird? dann bitte main.js modularisieren, sodass dort nur funktionsaufrufe auf sub-module ausgeführt werden. das navigationsmenu nach "navigation.js" auslagern. den aufbau und betrieb des aktuellen workspaces im main.js drin lassen. Der aktuelle Hauptbereich mitt der Auswahl des workspaces, den zugehörigen Agenten etc ist neu ein Objekt, welches in der "mainView" dargestellt werden kann. Auch andere Objekte können in der mainView dargestellt werden und haben jeweils ihre spezifischen Paramter dazu, wie nachfolgend erklärt. im main.js wird ein globales objekt aller elemente erstellt, welche in der navigation enthalten sein sollen und welches die grundlage für alle funktonsaufrufe beinhaltet. damit gibt es dann im index.html keine details mehr zu den navigationen. diese attribute hat das globale objekt: globalState .objects .user .mainView Hier die Spezifikation der Objekte. .objects[...]: hat eine liste von objekten, welche im mainScreen geladen werden können. Diese Attribute pro Objekt bitte gemäss den heutigen js files im anhang sinngemäss übernehmen: - label: Liste des Labelnamen in den verschiedenen sprachen (default, en, fr...) - modulName: string; dieser wird verwendet für die objektklasse "js/modules/{modulname}.js" und für die html-komponente dazu "modules/part-{modulname}.html und für die calls ans backend /api/{modulname}/..." - icon: Icon vor dem Menupunkt - navigationContext: "left" für agents, data, prompts, users, mandates, workspaces ; "top" für sprachauswahl, logout - isVisible (hier wird z.b. users und mandates nur angezeigt, wenn auch die berechtigung dafür besteht) - isActive: Wenn der Menupunkt ausgewählt ist - navigationContext: diese Optionen, wo ein Objekt ins Menu genommen wird: --"nav_left" für agents, data, prompts, users, mandates, workspaces --"nav_top" für sprachauswahl, logout - navigationActionType: Was passiert, wenn auf das Menu geklickt wird. Diese Optionen: --"module": Standard-Menu button. Es wird ein Modul in die mainView geladen. Das Modul wird erst geladen und mit den Daten initiiert, wenn der Menupunkt ausgewählt wird --"group_open": Gruppenheader; Start einer neuen Gruppe; alle nachfolgenden Objekte der Liste sind in dieser Gruppe integriert. Die Gruppe kann im Menu auf- und zugeklappt werden. Initial Gruppe open, alle Menupunkte sichtbar --"group_collapsed": Gruppenheader; Start einer neuen Gruppe; alle nachfolgenden Objekte der Liste sind in dieser Gruppe integriert. Die Gruppe kann im Menu auf- und zugeklappt werden. Initial Gruppe collapsed. .user: Attribute zum aktiven user - mandate_id - user_id - username - full_name - language (default, en, fr, ...) - isAdmin - isSysAdmin - lastWorkspaceId: Id des zuletzt genutzten Workspaces - aktuell "null" - session: aktuell null und nicht verwendet .mainView: enthält immer die aktuellen Attribute, welche die Seite in der mainView nutzen kann - currentWorkspace: objekt des aktuell ausgewählten Workspaces - availableFiles[]: list of objects - availableAgents[]: list of objects - availablePrompts[]: list of objects - currentWorkflowId: id kannst du bitte part-workflow.html und workflow.js mit dem dynamischen Multi-Agent Chat aktualisieren, welcher im backend angepasst wurde und im Ausführungsprotokoll die Details eines laufenden Chats mit aufklappbaren Texten ergänzen. Das Ausführungsprotokoll-Fenster dynamisch in der Grösse anpassbar machen. Css aufräumen und konsolidieren für gemeinsame Klassen mit allen html und js parallel Admin Seite mit CRUD für User Mgmt und Mandate Management, generisch Im Frontend soll im generischen Formular "generic-entity.js" für ein neues Objekt die ID entweder hidden oder schreibgeschützt sein. die ID wird nicht benötigt, sondern wird erst mit dem speichern in der datenbank erstellt. d.h. nach dem speichern in der datenbank werden die daten der entsprechenden tabelle neu geladen. Kannst du mir bitte code struktur und logik das 'agentservice_interface.py' anpsssen und die code struktur zur besseren wartung und weiterenwticklung verbessern: 1. die anbindung der ai-modelle mit den entsprechenden config-daten und den funktionsaufrufen in separate dateien auslagern ("connector_ai_openai","connector_ai_webscraping"). im 'agentservice_interface.py' die connector module bei der initialisierung importieren und vorbereiten. 2. den agenten-chat 'execute_workflow' nicht in der reihenfolge der agents ausführen, sondern als tischrunde der agents.das heisst ein AI moderator moderiert die agenten autonom und ruft anhand der produzierten antworten und der eigenschaften der agentss den jeweils nächsten geeigneten agenten anhand der 'capabilities' auf, nachdem ein agent seine antwort geliefert hat. der initiale prompt mit den zugehörigen files und dem chatverlauf im 'LogEntry' mit den n letzten Datensätzen (n wird aus dem Config file aus der variablen Application.MAX_HISTORY gelesen) wird in ein 'message'-objekt als dictionary transformiert, welches so aussieht: message = { "role": "user", #--> statisch, immer so "content": [ #--> liste der Files { "type": "text", "text": prompt_text }, { "type": content_type, # --> diese funktion integrieren wir später "source": { "type": "base64", "media_type": mime_type, "data": base64_file # --> hier das dateiname der jeweiligen datei } }, { "type": "text", "text": LogEntries # --> hier die LogEinträge als Textpaket } ] } wenn der AI moderator der Meinung ist, dass die aufgabe erfüllt ist, beendet er den workflow. 3. initialisierungsset: beantwortet Anfragen direkt mit dem hinterlegten KI Modell, welche keine spezialisierten Agenten benötigen. Dies ist die Generierung von Text, Code, Strukturen, die Analyse von Files, Graphiken erstellen, etc. (Agent) Organisator: Dieser analysiert den User Prompt und strukturiert die auszuführenden Aufräge sowie die nötigen zu liefernden Resultate (Agent) Entwickler: Dieser entwickelt python code im Auftrag der anderen Agents und führt ihn anschliessend aus (Agent) Webscrape: Ein Agent, welcher webscraping durchführt. Dieser nutzt die Funktion '_scrape_url', um eine Webseite zu scannen und den Inhalt zurückzugeben. Er kann auch den Entwickler beauftragen, einen Code zu generieren, welcher die funktion _scrape_url mit einer logik (z.B. iterativ oder batch-mässig) ausführt (Prompt): Kannst Du mir ein paar initiale Prompts für die folgenden Fragebereiche vorbereiten, welche ausgewählt werden können: . Web Research . Analyse . Protokoll . Design 4. Kannst Du bitte die fehlenden CRUD Methoden in den modulen "workspaces" und "prompts" ergänzen. Ich glaube, es fehlen Post und Delete. 5. Datenbank-Management verbessern: In den zwei Modulen "gateway_interface.py" und "lucydom_interface" finden keine Manipulationen oder Referenzierungen mit ID's statt. Die ID's für einen neuen Datensatz werden nur in "connector_....py" modulen vergeben. Jeder datensatz hat eine unique id. in den modulen "...interface.py" werden keine id's generiert. die abfrage für die id=1 wird ersetzt mit der funktion 'get_initial_id', welche weiter unten erklärt ist. Dazu bitte die Module anpassen und in den Modulen "connector...py" eine system-tabelle ergänzen, welche sich merkt, welche ID der erste datensatz jeder tabelle hat, denn dieser ist der jeweilige system-datensatz. dann eine funktion 'get_initial_id' erfassen, welche in den modulen Modulen "gateway_interface.py" und "lucydom_interface" aufgerufen werden kann, um die id des initialen datensatzes pro tabelle abzufragen. der gateway funktioniert noch nicht ganz. kannst mir bitte die module prüfen und besser stukturieren? Diese anforderungen und das setting der dateien: models.py: die datei umbenennen in "model_lucydom.py" - die class "User", "UserInDB", "Token" in der datei entfernen und in eine separate datei "model_gateway.py" auslagern. - alle datentypen-definitionen sind hier, abschliessend und unabhängig vom datenbanksystem. - alle ID's sind long-Zahlen, keine Texte - bei jeder class und bei jedem attribut einer class ein label ergänzen, was der name des attributes bzw. der class ist, wenn dies in einem formular abgefragt wird. das label soll einen defaultwert haben und pro sprache gesetzt werden können. - alle objekte mandantenfähig machen, d.h. bei jedem Objekt die Attribute "mandate_id" und "user_id" ergänzen. model_gateway.py: - alle datentypen-definitionen sind hier, abschliessend und unabhängig vom datenbanksystem. - alle ID's sind long-Zahlen, keine Texte - bei jeder class und bei jedem attribut einer class ein label ergänzen, was der name des attributes bzw. der class ist, wenn dies in einem formular abgefragt wird. das label soll einen defaultwert haben und pro sprache gesetzt werden können. - Die class "Mandate" mit den Attributen (id,name,language) ergänzen - Bei der class "User" die "id" und "mandate_id" und "language" ergänzen - alle objekte mandantenfähig machen, d.h. bei jedem Objekt die Attribute "mandate_id" und "user_id" ergänzen. database.py aufteilen in 2 files "connector_db_json.py" und "interface_lucydom.py". connector_db_json.py: Ein erster Konnektor von zukünftig weiteren Konnektoren 1. Parameter, welche übergeben werden: - DB_Folder, DB_USER und DB_APIKEY - Kontextparamter für "mandate_id" und "user_id", welche nicht null sein dürfen. - Die aktuelle JSON-Datenbank im Folder DB_Folder einbinden und so übernehmen, wie sie ist. Falls der Folder fehlt, diesen erstellen. 2. Der Konnector "db" wird als Objekt zur verfügung gestellt. 3. Es werden diese generischen Methoden im Objekt "db" zur Verfügung gestellt. jede abfrage filtert automatisch die datensätze auf die Kontextparamter "mandate_id" und "user_id", sofern diese Parameter in einem Datensatz nicht null oder "" sind. - get_tables(optional filterkriterien): liste aller tabellen - get_fields(table, optional filterkriterien): liste aller attribute einer tabelle - get_schema(table, language, optional filterkriterien): objekt aller attribute einer tabelle mit ihrem Datentyp und dem Label in der entsprechenden Sprache. Ohne Sprache Angabe wird der Default Wert als Label genommen - get_recordset(table, optional filterkriterien für fields, optional filterkriterien für records): liefert das entsprechende datenobjekt mit den Datensätzen - record_create(table,json with attributes): ergänzt einen Datensatz im Kontext "mandate_id", alle attribute, welche nicht im "json with attributes" drin sind, werden auf die standardwerte gemäss dem models.py gesetzt - record_delete: löscht einen Datensatz, aber nur wenn es im Kontext "mandate_id" ist, sonst Verweigerung "Not your mandate" - record_modify: ändert einen Datensatz, aber nur wenn er im Kontext "mandate_id" ist, sonst Verweigerung "Not your mandate" interface_lucydom.py: Ein Interface zum Gateway, es werden weitere Interfaces folgen. Das Interface macht dies: 1. Die Datenbank mit diesen Parametern einbinden: - Connector "connector_db_json.py" - Datenbank "/data_lucydom" - Datenmodell "model_lucydom.py" 2. Das Objekt "db" kann nun genutzt werden 3. initialisierung der Datenbank, falls sie nicht existiert, aber nur die minimal nötigen Objekte: Der "Default Workspace" in "workspaces" interface_gateway.py: Ein Interface zum Gateway, es werden weitere Interfaces folgen. Das Interface macht dies: 1. Die Datenbank mit diesen Parametern einbinden: - Connector "connector_db_json.py" - Datenbank "/data_gateway" - Datenmodell "model_gateway.py" 2. Das Objekt "db" kann nun genutzt werden 3. initialisierung der Datenbank, falls sie nicht existiert, aber nur die minimal nötigen Objekte: User "Admin", Mandate "Root" app.py: Die Initialisierung klar strukturieren und die Endpunkte gemäss der neuen Struktur anpassen 1. Teil: Interfaces einbinden. 2. Alle nötigen Initialisierungen: diese sollen in den jeweiligen Interfaces drin sein, ausser die generischen Teile. 3. Alle Access & Security Funktionen auslagern in "auth.py" 4. Alle Token-Endpunkte komplett generisch halten und vereinfachen: - Dort keine Attributdefinitionen oder Feld-Listen reinnehmen. Wenn ein Modell angepasst wird, sollen hier keine Anpassungen nötig sein. - Die Abfragen und exceptions mit Hilfsfunktionen vereinfachen, sodass die Modellierung der Endpunkte für den Programmierer sehr einfach, übersichtlich und klar ist. - Tasks als Kommentare erfassen, was mit all diesen Aenderungen der Endpunkte im Frontend umgebaut werden muss. agent_service.py: Umbenennen in "interface_agentservice.py" - Bei allen Workflow-Endpunkten, welche nur von einem Interface Logik beziehen, die Logik im Interface integrieren und den Code beim Endpunkt vereinfachen. - Nur bei Endpunkten, welche Logik kombiniert von mehreren Interfaces benötigen, die Logik beim Endpunkt integrieren - Ziel soll es sein, dass die Endpunkte-Codestruktur maximal schlank und übersichtlich ist, also auch die Strukturierung und Gruppierung der Endpunkte ---------------------------------- 2026-03-20 frontend_nyla - Admin Mandanten + Mandanten-Wizard: Formular zeigt neben Mandate-Stammdaten (name, label, enabled aus /api/attributes/Mandate) zusätzlich BillingSettings-Felder (Abrechnungsmodell, Startguthaben, Warnschwelle, Benachrichtigungen). Speichern: POST/PUT Mandate + POST /api/billing/admin/settings/{id}. Utility mandateBillingFormMerge.ts, Hook useMandateFormAttributes liefert createFormAttributesWithBilling / formAttributesWithBilling. ---------------------------------- 2026-03-20 useBilling / BillingAdmin - Nach Speichern der Billing-Einstellungen: wenn sich billingModel ändert, werden Konten, Transaktionen und Mandanten-Benutzer neu geladen (unterer Bereich bleibt konsistent). ---------------------------------- 2026-03-20 frontend_nyla FormGenerator - Default input: Anzeige für Zahl-Felder — Wert 0 nicht mehr durch (value || '') unterdrückt (Startguthaben 0 sichtbar). ---------------------------------- 2026-03-20 billing / mandates - BillingModelEnum: UNLIMITED entfernt (nur PREPAY_MANDATE, PREPAY_USER). parseBillingModelFromStoredValue mappt Legacy-DB-Wert UNLIMITED → PREPAY_MANDATE; getSettings persistiert Migration einmalig. - checkBalance ohne Settings: strikt wie PREPAY_MANDATE (leerer Pool). Frontend: UNLIMITED aus UI/Typen entfernt; FormGenerator edit: leere Zahlfelder mit Default/0 füllen; mandate merge: defaultUserCredit immer numerisch 0 default; BillingAdmin Stripe für Mandanten-Admins wenn Settings existieren.