wiki/c-work/4-done/2026-04-ui-polish-bundle.md
2026-04-21 23:49:43 +02:00

383 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- status: done -->
<!-- started: 2026-04-21 -->
<!-- code-complete: 2026-04-21 -->
<!-- closed: 2026-04-21 -->
<!-- component: gateway | frontend-nyla -->
# 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>:]<FORMAT>
```
`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`) |
| `<format> <UNIT>` | `1'234.00 CHF` | Suffix-Unit, sprach-neutral |
| `<UNIT> <format>` | `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.<Class>.<field>.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/<workspaceContext-resolver>` (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.<Class>.<field>.label[<idx>]`
- [ ] `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.<code>.group:<groupKey>.*` (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": "<folderId>"}`. 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 ≈ 510kB)
- [ ] 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: <Original-Subject>` 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 (P1P11c)
- [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`.