# Bundle: UI-Polish-Sprint Q2 (Format-Hints, UDB-Container, Store, Modal-Audit, Node-Mapping) ## Beschreibung und Kontext Sammelplan für fünf zusammenhängende UX-Issues, die als ein Sprint-Bundle umgesetzt werden, weil sie sich technisch ähneln (Metadaten-getriebenes Rendering) bzw. denselben Audit-Charakter haben. Themen: 1. **Format-Hints für Pydantic-Felder** (`frontend_format` + `frontend_format_labels`) – Default-Rendering wie heute, Override per optionalem String/Liste in `json_schema_extra`. 2. **UDB-Container drag&drop in Chat** – nicht nur einzelne Records/Tabellen, sondern auch Gruppierungs-Knoten (`_GroupFolderView`, `_ParentGroupView`, `_MandateGroupView`). 3. **Store-Seite zeigt `automation`-Feature** zur Aktivierung an. 4. **Modal-Forms schliessen nicht mehr durch Outside-Klick** – Audit über alle Ad-hoc-Modals; `usePrompt` ist primärer Verursacher. 5. **Graph-Editor-Nodes: Field-Mapping verständlich machen** – Ports tragen optional dieselben `frontend_type`/`frontend_format`-Hints; DataPicker zeigt formatierte Preview-Werte. 6. **Outlook: Mail-Management-Tools für Agent + Mail-Limit-Audit** – Adapter erhält `replyToMail`/`replyAllToMail`/`forwardMail` (+ Draft-Varianten), `moveMail`/`copyMail`, `deleteMail`/`archiveMail`, `markMailAsRead`/`markMailAsUnread`, `flagMail` auf Basis von Microsoft Graph; Agent-Toolbox `email` exposed sie. Zusätzlich Investigation, warum trotz `_DEFAULT_MESSAGE_LIMIT=100` der User effektiv nur ~20 Mails sieht. 7. **UDB: Einrückung aktiver Datenobjekte entfernen** – In der UDB werden persönliche Datenquellen-Rows nach Aktivierung (Icon-Klick) optisch leicht eingerückt. Diese Indentation ist visuell verwirrend (suggeriert eine Hierarchieebene, die nicht existiert) und soll weg. Padding zwischen aktiviertem und inaktivem Zustand muss identisch sein. 8. **i18n-Registrierung für Feature-DATA_OBJECTS / RESOURCE_OBJECTS (systemisch)** – Bare-String-Labels in `mainRedmine.py`, `mainTrustee.py` etc. werden in nicht-DE-Sprachen als `[Konfiguration]`, `[Redmine-Tickets (Mirror)]` angezeigt, weil `t()` sie nie zur Build-Time registriert hat. Audit + Fix über alle Features. Business-Treiber: Konsolidierung des UI-Polishs vor PWG-Pilot (siehe `c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md`); inkonsistente Zahlen-/Bool-Darstellungen blockieren Demo-Tauglichkeit. Risiko bei Nicht-Umsetzung: Demos zeigen Roh-Floats wie `4567777788`, Modals werden in Live-Demos versehentlich geschlossen, Anwender verstehen Node-Datenflüsse nicht. ## Fokus und kritische Details - **Default-Verhalten unverändert**: Ohne `frontend_format` rendert das UI exakt wie heute. Reine additive Änderung. Keine Migration, kein Feature-Flag. - **i18n-Pfad**: `frontend_format` ist sprach-neutral (Format-Spec, Sprach-tokens wie `CHF`/`kg`/`MB` zulässig aber nicht übersetzt). `frontend_format_labels` (Liste) wird vom `@i18nModel`-Decorator genauso wie `label` registriert via `t()` und vom Backend bereits übersetzt ausgeliefert. - **`usePrompt`-Fix darf laufende Workflows nicht brechen** – Backdrop-Klick muss überall durch ein bewusstes Cancel ersetzt werden, nicht durch komplettes Submit. - **UDB-Container-Resolver** muss `objectKey`-Globs (Wildcard `*`) auf der Backend-Workspace-Context-Seite verstehen, sonst kommt nichts beim AI-Agent an. - **Graph-Editor Ports**: `PortField` ist heute backend-definiert in `nodeDefinitions/*.py`. Keine Schemamigration, nur Schema-Anreicherung. Fragile Stellen: - `gateway/modules/shared/attributeUtils.py::getAttributesForTable` – die zwei parallelen Pfade (`field_info.extra` vs. `json_schema_extra`) sind heute schon redundant. Neue Felder durchgängig über **beide** Pfade lesen, sonst latenter Bug. - `frontend_nyla/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx::formatCellValue` – wird auch im PDF-Export benutzt (prüfen). Format-Funktion muss reine Funktion sein. ## Ziel und Nicht-Ziele - **Ziel**: Fünf Issues gemeinsam, generische Mechanismen wo möglich (Format-Hints, Container-Glob), spezifische Punkt-Fixes wo nötig (Store-Eintrag, `usePrompt`). - **Ziel**: 100% rückwärtskompatibel — kein Field-/Node-Definition muss angefasst werden, wenn nicht gewünscht. - **Explizit NICHT**: Komplettes RenderHint-Objekt-Schema (initial vorgeschlagen, vom User verworfen — zu komplex). - **Explizit NICHT**: Neue `FrontendType`-Enums wie `CURRENCY`, `PERCENT`, `BYTES`. Stattdessen nur `frontend_format`-String über bestehendem `frontend_type: "number"`/`"float"`/`"integer"`/`"boolean"`. - **Explizit NICHT**: Excel-formula-paritätischer Parser. Nur die im Spec-Block unten definierten Format-Tokens. ## Format-Spec (Thema 1) ### `frontend_format`-Syntax ``` [:] ``` `ALIGN` (optional, default = type-spezifisch: `R` für Zahlen, `L` für Text, `M` für Bool): - `L` = links - `M` = mittig - `R` = rechts `FORMAT`-Tokens: | Token | Beispiel-Output | Bedeutung | |-------|-----------------|-----------| | `#'###.00` | `1'234'567.89` | Schweizer Tausendertrenner mit Apostroph, Nachkommastellen via `.00` | | `0.00` | `1234.57` | Feste Nachkommastellen, kein Tausendertrenner | | `0` | `1235` | Integer-Rundung | | `0.0%` | `12.3%` | Prozent (Multiplikation × 100) | | `0.0e+0` | `1.2e+6` | Scientific | | `b` | `12.3 MB` | Auto-Bytes (nutzt `formatBinaryDataSizeBytes`) | | ` ` | `1'234.00 CHF` | Suffix-Unit, sprach-neutral | | ` ` | `CHF 1'234.00` | Prefix-Unit | Beispiele: - `R:#'###.00` → `1'444'555.67` - `M:0` → `12` - `L:0.000` → `4.556` - `R:CHF #'###.00` → `CHF 1'234.50` - `R:#'###.00 CHF` → `1'234.50 CHF` - `R:b` → `4.5 GB` - `R:0.0 kg` → `12.3 kg` - `R:0.00%` → `45.67%` ### `frontend_format_labels`-Syntax Liste von 2 oder 3 Strings, je nach Typ: - **Boolean** (3 Werte): `["Ja", "—", "Nein"]` für `true / null / false` - **Boolean ohne null** (2 Werte): `["Ja", "Nein"]` (null fällt auf `"—"` zurück) - Unbenutzt für Zahlen. `@i18nModel` registriert jeden Listeneintrag automatisch via `t(label, "table...label[N]", desc)`. ### Beispiel-Anwendung ```python @i18nModel("Mietzinsbestaetigung") class Mietzinsbestaetigung(PowerOnModel): betrag: float = Field( json_schema_extra={ "label": "Bruttomiete", "frontend_type": "number", "frontend_format": "R:#'###.00 CHF", }, ) abrechnungsmonat: int = Field( json_schema_extra={ "label": "Monat", "frontend_type": "integer", "frontend_format": "M:0", }, ) ist_bezahlt: bool = Field( json_schema_extra={ "label": "Bezahlt", "frontend_type": "boolean", "frontend_format_labels": ["Bezahlt", "—", "Offen"], }, ) ``` Ohne diese Hints: heutiges Default-Rendering bleibt. ## Betroffene Module - **Gateway**: - `modules/shared/attributeUtils.py` (Thema 1: durchschleifen `frontend_format`, `frontend_format_labels`) - `modules/shared/i18nRegistry.py` (Thema 1: Decorator scannt `frontend_format_labels`) - `modules/routes/routeStore.py` + Feature-Registry (Thema 3: `automation` als `category="store"`) - `modules/features/` (Thema 2: Glob-Wildcards in `objectKey`) - `modules/features/graphicalEditor/nodeDefinitions/*.py` (Thema 5: optionale `frontendType`+`frontendFormat` an `PortField`) - `modules/connectors/providerMsft/connectorMsft.py` (Thema 6: neue `OutlookAdapter`-Methoden `replyToMail`, `replyAllToMail`, `forwardMail`, `createReplyDraft`, `createReplyAllDraft`) - `modules/serviceCenter/services/serviceAgent/toolboxRegistry.py` (Thema 6: Toolbox `email` exposed neue Tools) - `modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py` oder neues `_emailTools.py` (Thema 6: Tool-Implementierung `replyToMail` etc.) - `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx` (Thema 7: Border-Left auf Z.1414 erzeugt 3px-Versatz; aktive Wildcard-Row braucht kompensierenden negativen `marginLeft`) - `gateway/modules/features/*/main*.py` (Thema 8: alle `DATA_OBJECTS`/`RESOURCE_OBJECTS`-Labels via `t()` registrieren – betrifft mind. `mainRedmine.py`, `mainTrustee.py`, `mainCommcoach.py`, `mainChatbot.py`) - **Frontend**: - `src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx` (Thema 1: `formatCellValue` ruft neuen `applyFrontendFormat`) - `src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx` (Thema 1: gleicher Hook für Read-Only-Anzeige) - `src/utils/applyFrontendFormat.ts` (Thema 1: NEU – zentrale Format-Funktion, nutzt `formatAmount`/`formatBinaryDataSizeBytes`) - `src/components/UnifiedDataBar/SourcesTab.tsx` (Thema 2: Container-Buttons, sendToChat für Group-Views) - `src/pages/Store.tsx` (Thema 3: nichts zu ändern – nur Backend-Config) - `src/hooks/usePrompt.tsx` (Thema 4: Backdrop-`onClick` entfernen) - `src/components/UiComponents/Modal/Modal.tsx` (Thema 4: Audit – ist bereits korrekt) - `src/components/FlowEditor/editor/NodeConfigPanel.tsx` (Thema 5: DataPicker zeigt formatierte Werte) - `src/components/FlowEditor/nodes/*` (Thema 5: Edges/Ports labeln) - **DB-Migration**: nein (alles in-memory Schema) - **Andere**: docs in `b-reference/` aktualisieren ## Entscheidungen | Datum | Entscheidung | Begründung | |-------|-------------|------------| | 2026-04-21 | `frontend_format` als optionaler String, kein RenderHint-Objekt | User-Feedback: Komplexität minimieren, an bestehendem `frontend_*`-Pattern andocken | | 2026-04-21 | `frontend_format_labels` als separate Liste (statt Inline-Tokens im Format-String) | i18n-Sammelmechanismus von `@i18nModel` greift nur auf diskreten Feldern; Inline-Parsing wäre fehleranfällig | | 2026-04-21 | Keine neuen `FrontendType`-Enums | Bestehende Typen (`number`, `integer`, `boolean`) reichen – Format ist orthogonal | | 2026-04-21 | Audit aller Modal-Implementierungen, nicht nur `usePrompt` | User explizit gefordert | ## Umsetzungs-Checkliste ### Thema 1 — Format-Hints - [ ] `attributeUtils.py::getAttributesForTable`: `frontend_format` und `frontend_format_labels` aus `json_schema_extra` UND `field_info.extra` lesen, in `attr_def` ausgeben als `format` und `formatLabels` - [ ] `i18nRegistry.py::i18nModel`: `frontend_format_labels` scannen, jedes Element via `t()` registrieren mit Key `table...label[]` - [ ] `getModelLabels()` ergänzen, sodass übersetzte Format-Labels auf Frontend ausgeliefert werden - [ ] **NEU** `frontend_nyla/src/utils/applyFrontendFormat.ts` mit `applyFrontendFormat(value, format, formatLabels, type, locale)` – pure function - [ ] `FormGeneratorTable::formatCellValue` integrieren (für `cellAlign` ebenfalls nutzen) - [ ] `FormGeneratorForm` integrieren für read-only Anzeige - [ ] Unit-Tests für Format-Parser - [ ] Beispiel-Anwendung in 2-3 bestehenden Models (`Mietzinsbestaetigung`, eine Trustee-DTO) ### Thema 2 — UDB-Container - [ ] `SourcesTab.tsx`: Buttons (chat / scope-cycle / neutralize-toggle) auf `_GroupFolderView`, `_ParentGroupView`, `_MandateGroupView` ergänzen - [ ] Container-`objectKey`-Format definieren: `data.feature..group:.*` (Glob mit `*`) - [ ] Backend-Resolver `workspaceContext` (in `gateway/modules/aiAgent/`): Globs auflösen, Records-Set zurückliefern - [ ] Anzeige im Chat-Context: "Container Mandant Müller AG (12 Records)" statt `null` - [ ] Audit Token-Limits — Container kann gross sein, Warning-UX ### Thema 3 — Store: automation - [ ] In Feature-Registry (`gateway/modules/datamodels/datamodelFeatures.py` o.ä. Bootstrap): `automation` mit `meta.category = "store"`, `enabled = True` - [ ] Optional: Icon/Beschreibung für Store-Card prüfen - [ ] Smoke-Test: Aktivierung in fresh-Mandant funktioniert ### Thema 4 — Modal-Forms (Outside-Click-Fix) - [ ] `usePrompt.tsx`: Backdrop-`onClick={_handleCancel}` **entfernen** (oder optional via Prop `dismissOnBackdrop?: boolean = false`) - [ ] Audit: grep nach `onClick.*Close|onClick.*Cancel` auf Overlay-DIVs in `frontend_nyla/src/` - [ ] Liste der gefundenen Stellen prüfen, jede einzeln korrigieren oder bewusst lassen (z.B. Image-Lightbox: Backdrop schliesst soll bleiben) - [ ] `Modal.tsx`: Default-Behavior in Doku festschreiben (`closeOnOverlayClick=false`) ### Thema 5 — Graph-Editor Field-Mapping - [ ] `PortField`-Schema (Pydantic) erweitern um optional `frontendType`, `frontendFormat`, `frontendFormatLabels` - [ ] `NodeConfigPanel.tsx`: DataPicker-Preview nutzt `applyFrontendFormat` - [ ] Node-Card: zeige Input-Schema-Tag und Output-Schema-Tag inline (bisher nur in Detail-Panel) - [ ] Edges: optional Label "Buha.account → posting.kontoNr" anzeigen wenn Port-Mapping nicht 1:1 - [ ] Neuer `frontendType: "wireOnly"` für Parameter, die ausschliesslich via Wire befüllt werden (verhindert UI-Eingabe) - [ ] 3 Beispiel-Nodes (Trustee, Redmine, Mietzins) mit kompletten Hints versehen ### Thema 6 — Outlook Mail-Management + Mail-Limit-Audit #### Reply / Forward-Tools - [ ] `OutlookAdapter.replyToMail(messageId, body, bodyType="HTML", comment=None)` → `POST /me/messages/{id}/reply` mit Body `{"comment": "..."}` (für inline) oder `{"message": {...}}` (für vollen Override) - [ ] `OutlookAdapter.replyAllToMail(messageId, body, bodyType, comment)` → `POST /me/messages/{id}/replyAll` - [ ] `OutlookAdapter.forwardMail(messageId, to, body, bodyType, comment)` → `POST /me/messages/{id}/forward` - [ ] `OutlookAdapter.createReplyDraft(messageId, body, bodyType)` → `POST /me/messages/{id}/createReply` (gibt Draft-ID zurück) - [ ] `OutlookAdapter.createReplyAllDraft(messageId)` → `POST /me/messages/{id}/createReplyAll` - [ ] `OutlookAdapter.createForwardDraft(messageId, to)` → `POST /me/messages/{id}/createForward` - [ ] Agent-Tools `replyToMail`, `replyAllToMail`, `forwardMail` registrieren in `toolboxRegistry.py` (Toolbox `email`) - [ ] Tool-Parameter: `dataSourceId` ODER `connectionId+service`, `messageId` (aus `browseDataSource`/`searchDataSource`-Pfad zu extrahieren), `body`, `bodyType` (Text/HTML), `draft` (bool, default `false`), optional `additionalRecipients`/`cc` - [ ] Tool-Beschreibung explizit: "Use this when responding to an existing email — preserves Subject (with `AW:`/`RE:`-Präfix), Conversation-Thread und Quoted-Original-Message automatisch" - [ ] Anti-Pattern in `sendMail`-Description einbauen: "DO NOT use `sendMail` to reply to an existing email — use `replyToMail` instead" #### Move / Copy / Delete / Archive - [ ] `OutlookAdapter.moveMail(messageId, destinationFolderId)` → `POST /me/messages/{id}/move` mit `{"destinationId": ""}`. Gibt neue Message-ID im Ziel-Ordner zurück (Graph erstellt eine neue ID). - [ ] `OutlookAdapter.copyMail(messageId, destinationFolderId)` → `POST /me/messages/{id}/copy` - [ ] `OutlookAdapter.deleteMail(messageId, hardDelete=False)`: - `hardDelete=False` (default) → Move in Well-Known-Folder `deleteditems` (entspricht Outlook-Papierkorb-Verhalten) - `hardDelete=True` → `DELETE /me/messages/{id}` (endgültig, ohne Papierkorb) - [ ] `OutlookAdapter.archiveMail(messageId)` → Move in Well-Known-Folder `archive` - [ ] **Folder-Resolution-Helper**: `_resolveFolderId(folderRef: str)` akzeptiert: - Well-Known-Names (`inbox`, `archive`, `deleteditems`, `drafts`, `sentitems`, `junkemail`) - Folder-IDs direkt (Graph-Format, beginnen typisch mit `AAMk...`) - Display-Names (case-insensitive Match, mit Locale-Fallback `Posteingang`/`Archiv`/`Gelöschte Elemente` etc.) — über bestehende `browse("/")`-Logik - Ggf. nested via `/Parent/Child` - [ ] Agent-Tools `moveMail`, `deleteMail`, `archiveMail` (kein separates `copyMail` initial – auf Anfrage) - [ ] Tool-Parameter: `dataSourceId|connectionId+service`, `messageId`, `destinationFolder` (well-known-name oder display-name oder ID), `hardDelete` (für deleteMail) - [ ] **Confirmation-Pattern für destruktive Operationen**: Tool-Description macht klar, dass `deleteMail` mit `hardDelete=true` irreversibel ist; Agent soll explizite User-Bestätigung einholen #### Read-State / Flag-Tools (Bonus, da trivial) - [ ] `OutlookAdapter.markMailAsRead(messageId, isRead=True)` → `PATCH /me/messages/{id}` mit `{"isRead": true|false}` - [ ] `OutlookAdapter.flagMail(messageId, flagStatus)` → `PATCH /me/messages/{id}` mit `{"flag": {"flagStatus": "flagged|complete|notFlagged"}}` - [ ] Tools `markMailAsRead`, `markMailAsUnread`, `flagMail` registrieren #### Folder-Listing als Agent-Tool - [ ] `listMailFolders` Tool, das die bestehende `browse("/")`-Logik wrappt und dem Agent die verfügbaren Ordner-Namen + IDs zur Verfügung stellt – Voraussetzung damit `moveMail` mit Display-Name oder Folder-ID arbeiten kann ohne Raten #### Mail-Limit-Audit - [ ] **Investigation**: Warum sieht User effektiv nur ~20 Mails, obwohl `_DEFAULT_MESSAGE_LIMIT=100`? - Hypothese A: LLM ruft `browseDataSource` mit `limit=20` (durch Tool-Beschreibung suggeriert?) - Hypothese B: Output-Truncation im Agent-Loop (`ToolResult.data` wird auf N chars/zeilen gekürzt) - Hypothese C: System-Prompt enthält Limit-Hinweis - Hypothese D: Frontend-UI für UDB-Browse zeigt nur erste N Records - [ ] In Tool-Description klarstellen: "Default returns up to 100 entries. Use `limit` parameter only if user explicitly wants fewer/more." - [ ] Falls Output-Truncation aktiv: Limit auf min. ~32k chars setzen für Mail-Listings (Subjects sind kurz, 100 Mails ≈ 5–10kB) - [ ] Doku in `b-reference/`: Mail-Browse-Limits dokumentieren ### Thema 7 — UDB Indentation aktive Rows - [ ] **Reproduktion**: Zustand vor/nach Aktivierung eines persönlichen Daten-Objekts (Icon-Klick) per Screenshot festhalten – Pixelversatz dokumentieren - [ ] In `SourcesTab.tsx` Row-Renderer prüfen: alle `paddingLeft`-Berechnungen (`depth * 16 + 4`, `depth * 16 + 8`, statisch `10`/`36`, Sub-Block `(depth + 1) * 16 + 20`) - [ ] Aktiver/Selected-Zustand darf **kein** zusätzliches `paddingLeft`/`marginLeft` bewirken - [ ] Falls visueller Indikator gewünscht: per `borderLeft` mit kompensiertem negativem `marginLeft` lösen, **nie** via Padding-Verschiebung - [ ] Hover-/Drag-Over-/Selected-Zustände bleiben pixelgenau gleich - [ ] Analoges Problem in Feature-Sources-Views (`_GroupFolderView`, `_ParentGroupView`, `_MandateGroupView`) prüfen und konsistent fixen ### Thema 8 — i18n-Registrierung Feature-Catalog-Labels (systemisch) - [ ] **Root-Cause-Doku in Plan**: `t(key)` registriert Keys nur lazily zur Aufruf-Zeit. Wenn ein Label nie via `t()` (zur Modul-Import-Zeit) angefasst wurde, fehlt es in `_REGISTRY` zum Zeitpunkt der Übersetzungs-Sammlung → kein Translation-Eintrag → Fallback `[key]` für nicht-DE Sprachen - [ ] **Audit-Script**: Grep über `gateway/modules/features/*/main*.py` nach Pattern `"label":\s*"[^"]+"` (bare string statt `t(...)`) - [ ] **Fix in `mainRedmine.py`**: alle 5 `DATA_OBJECTS`-Labels (`"Konfiguration"`, `"Redmine-Verbindung"`, `"Redmine-Tickets (Mirror)"`, `"Redmine-Beziehungen (Mirror)"`, `"Alle Redmine-Daten"`) und alle 11 `RESOURCE_OBJECTS`-Labels via `t("…", context="UI")` (oder eigener Context wie `"feature.redmine"`) registrieren - [ ] **Fix in `mainTrustee.py`**: identisch für DATA_OBJECTS (mind. 7 Labels: `"Lokale Daten"`, `"Konfiguration"`, `"Daten aus Buchhaltungssystem"`, `"Position"`, `"Dokument"`, `"Buchhaltungs-Verbindung"`, `"Sync-Protokoll"`) und Page-/Resource-Labels - [ ] **Fix in allen weiteren Features** (`commcoach`, `chatbot`, `realEstate`, `automation`, `workspace`, `graphicalEditor`, `neutralization`) – pro Feature 1 commit - [ ] **Optional**: Decorator/Helper `_dataObject(objectKey, label, **meta)` einführen, der `t()` automatisch ruft. Damit wird der Bug zukünftig unmöglich. Pro Feature umstellen. - [ ] **Translation-Cache neu generieren**: Build/Sync-Skript für i18n-Files ausführen, neue Keys übersetzen lassen - [ ] **Frontend-Verifikation**: UDB in EN/FR umschalten, sicherstellen dass Redmine/Trustee-Container saubere Labels zeigen (keine `[...]`) ### Querschnitt - [ ] RBAC / Permissions: keine Änderung - [ ] Neutralisierung betroffen: nein (Format-Layer arbeitet auf Anzeige-Werten) - [ ] Navigation / Routing: keine Änderung - [ ] Billing-Impact: nein - [ ] DB-Migration: nein ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|---------------------------|------| | 1 | Given ein Float-Feld mit `frontend_format="R:#'###.00 CHF"`, When im FormGeneratorTable angezeigt, Then `1234.567` rendert als `1'234.57 CHF`, rechtsbündig | must | | 2 | Given ein Float-Feld OHNE `frontend_format`, When angezeigt, Then identisches Rendering wie vor dem Patch (Regression-Sicherheit) | must | | 3 | Given ein Bool-Feld mit `frontend_format_labels=["Ja","—","Nein"]`, When Wert `true/null/false`, Then Anzeige zeigt `Ja / — / Nein` (mit `t()`-Übersetzung wenn Sprache `en` → `Yes / — / No`) | must | | 4 | Given UDB zeigt Mandanten-Gruppe mit 5 Records, When User klickt Chat-Icon auf Gruppe, Then Chat-Kontext erhält alle 5 Records | must | | 5 | Given UDB-Container mit 1000+ Records, When User sendet an Chat, Then Warning-Toast bzgl. Token-Limit | should | | 6 | Given Mandant mit `automation`-Feature nicht aktiviert, When `/store` öffnet, Then Card "Automation" sichtbar mit Aktivieren-Button | must | | 7 | Given Feature-Instance-Rename-Dialog offen, When User klickt ausserhalb des Dialogs, Then Dialog bleibt offen | must | | 8 | Audit aller Modals erstellt mit Liste pro Datei + Soll-Verhalten | must | | 9 | Given Graph-Editor-Node mit Output-Port `betrag` (`frontendType=number`, `frontendFormat="R:CHF #'###.00"`), When DataPicker im nächsten Node öffnet, Then Sample-Wert formatiert angezeigt | should | | 10 | Given Node-Card im Editor, When Mouse-Hover auf Input-/Output-Anchor, Then Tooltip zeigt Schema-Name + Beschreibung | should | | 11 | Given Mail im Posteingang, When Agent das Tool `replyToMail` mit `messageId` und `body` aufruft, Then Outlook-Thread enthält neue Antwort als `AW: ` mit korrektem `In-Reply-To`/`Conversation-Id` | must | | 12 | Given Tool `replyToMail` mit `draft=true`, When ausgeführt, Then im Drafts-Folder erscheint Antwort-Entwurf, **nicht** als neue Mail | must | | 13 | Given Posteingang mit 100 Mails, When `browseDataSource` ohne expliziten `limit` aufgerufen, Then Agent erhält tatsächlich ≥100 Subjects (verifiziert via Log/Output-Länge) | must | | 14 | Given Tool-Beschreibung von `sendMail`, When Agent eine bestehende Mail beantworten will, Then Beschreibung steuert ihn explizit auf `replyToMail` (manueller Prompt-Test) | should | | 15 | Given persönliche Daten-Quelle in UDB inaktiv, When Icon-Klick aktiviert sie, Then horizontale Position der Row-Inhalte (Icon, Label) bleibt **pixelgenau identisch** — keine Einrückung | must | | 16 | Given dieselbe Row im Hover-/Selected-/Drag-Over-Zustand, When Zustand wechselt, Then keine horizontale Verschiebung sichtbar | should | | 17 | Given Mail im Posteingang, When Agent `moveMail` mit `destinationFolder="archive"` aufruft, Then Mail liegt im Archiv-Ordner und ist nicht mehr im Posteingang | must | | 18 | Given Mail im Posteingang, When Agent `deleteMail` ohne `hardDelete` aufruft, Then Mail liegt in `deleteditems` (Papierkorb), Recovery möglich | must | | 19 | Given Mail, When Agent `deleteMail` mit `hardDelete=true` aufruft, Then Mail komplett entfernt; vorheriges User-Confirmation-Pattern dokumentiert in Tool-Description | must | | 20 | Given Agent kennt Ordner-Namen nicht, When `listMailFolders` aufgerufen, Then Liste mit Display-Namen + IDs (inkl. Well-Known-Aliases `inbox`/`archive`/`deleteditems`) zurück | should | | 21 | Given Agent ruft `moveMail` mit Display-Name `"Archiv"` (DE) auf, When Locale `en` ist, Then trotzdem korrekter Folder-Resolve über Well-Known-Alias `archive` | should | | 22 | Given Mail ungelesen, When Agent `markMailAsRead` aufruft, Then `isRead=true` in Outlook | should | | 23 | Given UDB in Sprache `en`, When User Redmine-Feature expandet, Then Container-Labels sind übersetzt (kein `[Konfiguration]` o.ä. sichtbar) | must | | 24 | Given Audit-Script über alle `main*.py`, When ausgeführt, Then Output zeigt 0 verbleibende Bare-String-Labels in `DATA_OBJECTS`/`RESOURCE_OBJECTS` | must | | 25 | Given neuer Feature-Entwickler ergänzt ein DATA_OBJECT, When er `_dataObject(...)`-Helper benutzt, Then i18n-Registrierung passiert automatisch ohne Mehraufwand | should | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1,2,3 | unit | ja | `frontend_nyla/src/utils/applyFrontendFormat.test.ts` | pending | | T2 | 1,2,3 | api | ja | `gateway/tests/test_attributeUtils_format.py` | pending | | T3 | 3 | api | ja | `gateway/tests/test_i18nModel_formatLabels.py` | pending | | T4 | 4,5 | e2e | nein (manuell) | UDB → Chat Flow | pending | | T5 | 6 | api | ja | `gateway/tests/test_routeStore_automation.py` | pending | | T6 | 7 | unit | ja | `frontend_nyla/src/hooks/usePrompt.test.tsx` | pending | | T7 | 8 | doc | nein | `wiki/b-reference/modal-audit.md` | pending | | T8 | 9,10 | manuell | nein | Graph-Editor Smoke | pending | | T9 | 11,12 | api | ja | `gateway/tests/test_outlookAdapter_reply.py` (Mock Graph) | pending | | T10 | 13 | api | ja | `gateway/tests/test_browseDataSource_mailLimit.py` | pending | | T11 | 11,12,14 | manuell | nein | Real-Outlook Agent-Chat E2E | pending | | T12 | 15,16 | manuell | nein | UDB Visual-Diff Screenshot vor/nach | pending | | T13 | 17,18,19 | api | ja | `gateway/tests/test_outlookAdapter_moveDelete.py` (Mock Graph) | pending | | T14 | 20,21 | api | ja | `gateway/tests/test_outlookAdapter_folderResolve.py` | pending | | T15 | 22 | api | ja | `gateway/tests/test_outlookAdapter_readState.py` | pending | | T16 | 17,18,19,20 | manuell | nein | Real-Outlook E2E Move/Archive/Delete | pending | | T17 | 23 | manuell | nein | Sprachwechsel UDB-Test (DE → EN/FR) | pending | | T18 | 24 | script | ja | `gateway/tests/test_featureCatalogLabels_i18n.py` (Grep über alle main*.py) | pending | ## Phasen-Planung (Vorschlag) | Phase | Inhalt | Geschätzt | Abhängigkeit | |-------|--------|-----------|--------------| | P1 | Thema 1 Backend (`attributeUtils`, `i18nRegistry`) + Tests T2/T3 | 0.5d | – | | P2 | Thema 1 Frontend (`applyFrontendFormat` + Integration FormGenerator) + T1 | 0.5d | P1 | | P3 | Thema 4 (`usePrompt`-Fix + Modal-Audit) + T6/T7 | 0.5d | – (parallel) | | P4 | Thema 3 (Store Automation) + T5 | 0.25d | – (parallel) | | P5 | Thema 2 (UDB-Container Backend-Resolver + Frontend-Buttons) | 1.0d | – (parallel) | | P6 | Thema 5 (Graph-Editor Port-Hints + DataPicker-Format) | 0.75d | P2 (nutzt `applyFrontendFormat`) | | P7 | Anwendung Format-Hints auf 3+ produktiven Models, Demo-Test | 0.5d | P2 | | P8a | Thema 6 Reply/Forward-Adapter + Tools + T9 | 0.5d | – (parallel) | | P8b | Thema 6 Move/Delete/Archive-Adapter + Folder-Resolver + Tools + T13/T14 | 0.75d | P8a | | P8c | Thema 6 Read-State/Flag/listMailFolders + T15 | 0.25d | P8a | | P9 | Thema 6 Mail-Limit-Investigation + Fix + T10 | 0.5d | P8a | | P10 | Thema 7 UDB-Indentation-Fix + T12 | 0.25d | – (parallel) | | P11a | Thema 8 Bare-String-Audit-Script + T18 | 0.25d | – (parallel) | | P11b | Thema 8 Fix `mainRedmine.py` + `mainTrustee.py` + Translation-Sync | 0.25d | P11a | | P11c | Thema 8 Fix übrige Features (commcoach, chatbot, realEstate, automation, …) | 0.5d | P11a | | P11d | Thema 8 Optional `_dataObject(...)`-Helper-Refactoring | 0.25d | P11b/c | Total: ~7.75 Personentage, parallelisierbar auf ~3 Kalendertage. ## Links - PR: tbd - Issue: `local/notes/issues.txt` (interne TODO-Liste) - Verwandt: `wiki/c-work/4-done/2026-04-generic-graph-editor.md` (Typed Node Handover System – Basis für Thema 5) ## Abschluss - [x] Code-Implementierung aller 14 Workitems (P1–P11c) - [x] Manueller End-to-End-Test durch User (UDB → Chat, Outlook-Agent, Modal-Forms, Format-Hints, Trustee-Tabellen-Header) - [x] Plan in `4-done/` verschoben - [ ] _Follow-up_: `b-reference/data-model-fields.md` mit Format-Spec dokumentieren (separates Doku-Ticket) - [ ] _Follow-up_: `b-reference/modal-audit.md` als Living Document anlegen - [ ] _Follow-up_: `TOPICS.md`-Eintrag „UI-Format-Hints (frontend_format)" ## Erweiterungen während der Umsetzung Während der Implementierung kamen vom User folgende Punkte hinzu, die direkt mit-erledigt wurden: - **Trustee Daten-Tabellen-Header**: Tab-Bar nach UDB-Struktur in 4 Kategorien (Stammdaten / Lokale Daten / Konfiguration / Daten aus Buchhaltungssystem) gruppiert; `(read-only)`-Text durch Lock-Icon (🔒) ersetzt. Tab-Labels 1:1 mit UDB-Labels synchronisiert (z.B. „Kontenplan" statt „Konten (Sync)"). Datei: `frontend_nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx`.