wiki/c-work/4-done/2026-04-ui-polish-bundle.md
2026-06-02 09:42:12 +02:00

28 KiB
Raw Blame History

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:

  • platform-core/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.
  • ui-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:#'###.001'444'555.67
  • M:012
  • L:0.0004.556
  • R:CHF #'###.00CHF 1'234.50
  • R:#'###.00 CHF1'234.50 CHF
  • R:b4.5 GB
  • R:0.0 kg12.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

@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.)
    • ui-nyla/src/components/UnifiedDataBar/SourcesTab.tsx (Thema 7: Border-Left auf Z.1414 erzeugt 3px-Versatz; aktive Wildcard-Row braucht kompensierenden negativen marginLeft)
    • platform-core/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 ui-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 platform-core/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 (platform-core/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 ui-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=TrueDELETE /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 platform-core/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 enYes / — / 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 ui-nyla/src/utils/applyFrontendFormat.test.ts pending
T2 1,2,3 api ja platform-core/tests/test_attributeUtils_format.py pending
T3 3 api ja platform-core/tests/test_i18nModel_formatLabels.py pending
T4 4,5 e2e nein (manuell) UDB → Chat Flow pending
T5 6 api ja platform-core/tests/test_routeStore_automation.py pending
T6 7 unit ja ui-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 platform-core/tests/test_outlookAdapter_reply.py (Mock Graph) pending
T10 13 api ja platform-core/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 platform-core/tests/test_outlookAdapter_moveDelete.py (Mock Graph) pending
T14 20,21 api ja platform-core/tests/test_outlookAdapter_folderResolve.py pending
T15 22 api ja platform-core/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 platform-core/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.

  • 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

  • Code-Implementierung aller 14 Workitems (P1P11c)
  • Manueller End-to-End-Test durch User (UDB → Chat, Outlook-Agent, Modal-Forms, Format-Hints, Trustee-Tabellen-Header)
  • 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: ui-nyla/src/pages/views/trustee/TrusteeDataTablesView.tsx.