fixes udb, outlook, workflow
This commit is contained in:
parent
a3615ffa38
commit
3183a39763
6 changed files with 607 additions and 200 deletions
|
|
@ -1,200 +0,0 @@
|
|||
<!-- status: plan -->
|
||||
<!-- started: 2026-04-19 -->
|
||||
<!-- component: frontend-nyla -->
|
||||
<!-- relatedTo: c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md (Phase 2 deferred item) -->
|
||||
|
||||
# Unified-Data-Bar Action-System (Composable, Multi-Modal)
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Die **Unified Data Bar (UDB)** ist die zentrale Datenleiste der PORTA-UI: sie zeigt Files, Folders, Chats, Sources und Konversations-Artefakte. Der Kern jeder Ansicht ist die Komponente `FolderTree` (`frontend_nyla/src/components/FolderTree/FolderTree.tsx`). Sie wird heute in mehreren Kontexten verwendet:
|
||||
|
||||
| Kontext | Datei | Zweck |
|
||||
|---------|-------|-------|
|
||||
| UDB-FilesTab | `components/UnifiedDataBar/FilesTab.tsx` | Dateien des aktuellen Feature-Kontexts (Workspace, Trustee, GraphEditor …) |
|
||||
| Standalone-Files-Page | `pages/basedata/FilesPage.tsx` | Globale Datei-Verwaltung im Admin-Bereich |
|
||||
| Folder-Picker im Move-Modal | (innerhalb FolderTree) | Ziel-Auswahl beim Verschieben |
|
||||
| SharePoint-Browser | `components/FolderTree/SharepointBrowseTree.tsx` | externes Filesystem (read-only) |
|
||||
| FileContext-Konsumenten | `contexts/FileContext.tsx` | Quelle der Wahrheit für Tree-Files |
|
||||
|
||||
**Problem heute:** Aktionen pro Datei sind hartcodiert im `FolderTree`-Renderer. Es gibt **fünf** fest verdrahtete Buttons (Rename, Delete, Chat-Send, Scope-Cycle, Neutralize-Toggle). Es gibt:
|
||||
- Kein generisches Erweiterungs-Konzept (z. B. „in Graph-Editor laden" für `.workflow.json`).
|
||||
- Keine Kontext-Menüs (Rechtsklick desktop, Long-Press mobil).
|
||||
- Kein Action-Discovery (User muss alle Icons immer auf jeder Zeile sehen — auch wenn Aktion auf den Dateityp gar nicht passt).
|
||||
- Kein einheitliches Drag-&-Drop-Handling für „Datei in Chat", „Datei in anderen Ordner", „Datei in Workflow-Editor". Jede Konsumenten-Komponente baut DnD selber.
|
||||
- Keine Touch-Optimierung (Icons sind 16 px, keine Long-Press-Erkennung).
|
||||
|
||||
**Ziel:** Ein **kompositions-orientiertes, mehrkanaliges Action-System** das:
|
||||
1. Anwendungsspezifische Aktionen pro Aufruf-Site deklarativ registrieren lässt (Plugin-ähnlich).
|
||||
2. Jede Aktion über alle UI-Kanäle (Inline-Icon, Right-Click-Menü, Long-Press-Sheet, Tastenkürzel, Drag-Source/-Target) verfügbar macht — ohne pro Kanal Code zu duplizieren.
|
||||
3. Sichtbarkeit über Predicates auf Dateityp/Scope/Permissions steuert.
|
||||
4. Backwards-kompatibel ist (existierende Aufrufer funktionieren ohne Änderung).
|
||||
|
||||
**Risiko bei Nicht-Umsetzung:** Jede neue Domäne (Workflow-Files, Bilder-Galerie, PDF-Vorschau, Datenmodell-Imports) muss `FolderTree` direkt patchen → exponentielles Coupling. Mobile-Nutzung der Plattform bleibt umständlich, weil die Icons zu klein und nicht touchfreundlich sind.
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
- **Keine Breaking Changes** für die aktuell 5 hartcodierten Standard-Aktionen; sie werden hinter dem neuen System als „Built-in Actions" weiterhin funktionieren, wenn der jeweilige Callback-Prop gesetzt ist.
|
||||
- **Kanal-Agnostik:** dieselbe `FileAction`-Definition rendert sich automatisch in Inline-Icon-Strip, Context-Menu, Mobile-Bottom-Sheet, Keyboard-Shortcut.
|
||||
- **Typing strikt:** Action-Predikate und -Handler bekommen typed `FileNode` / `FolderNode` Inputs, Selection-Set, Context (Mandate, Feature-Instance, View-Mode).
|
||||
- **Performance:** Predicates müssen pure und billig sein (keine async-Calls). Async-Operationen passieren erst im Handler.
|
||||
- **Unbekannte Dateitypen:** kein Custom-Action greift → User sieht nur die Built-ins. Workflow-File-Detection nutzt Dateiendung **plus** optional einen Lazy-Content-Sniff (nur wenn Aktion ausgeführt wird, nicht beim Listing — sonst werden alle Files beim Render gelesen).
|
||||
- **Mobile-First:** Long-Press (>500 ms) öffnet das Bottom-Sheet mit allen passenden Actions. Tap auf Datei = `onSelect` (wie heute), kein implizites Aktion-Triggering.
|
||||
- **Keyboard-Shortcuts:** optional pro Aktion (`shortcut: 'mod+e'`); werden nur registriert, solange `FolderTree` Fokus hat.
|
||||
- **Drag&Drop:** Erweiterung um typed `dragPayload` (z. B. `{ type: 'file', mime: 'application/json+workflow', fileId, name }`) und `dropTargets[]` (Aktionen, die auch als Drop-Target fungieren — z. B. „Workflow in Editor laden" akzeptiert Drops aus FilesTab in den Graph-Canvas).
|
||||
- **Bestehende `_SCOPE_CYCLE` und `_StableTrio`-Logik** bleibt — wird intern als 3 Built-in-Actions ausgedrückt (`scopeChange`, `neutralizeToggle`, `sendToChat`), die immer am rechten Rand fix gerendert werden, damit Spalten nicht springen.
|
||||
|
||||
## Ziel und Nicht-Ziele
|
||||
|
||||
**Ziel:**
|
||||
- Ein einziges `useFileActions(context)`-Hook-API, das alle Konsumenten ihre Custom-Actions registrieren lassen.
|
||||
- `FolderTree`-Props um `actions?: FileAction[]` erweitern (Built-ins bleiben Default).
|
||||
- Right-Click + Long-Press-Bottom-Sheet als neue UI-Patterns.
|
||||
- Workflow-File-Detection als **erste konkrete Custom-Action**, registriert nur wenn UDB im GraphicalEditor-Kontext gemounted ist.
|
||||
|
||||
**Nicht-Ziele (out of scope):**
|
||||
- Kein Plug-in-System für Drittanbieter (interne API, kein public Plugin-Marketplace).
|
||||
- Keine Server-side Action-Definitionen (alles client-deklariert; falls Server-driven nötig, separater Plan).
|
||||
- Keine Änderungen an `FileItem`-DB-Modell oder `routeDataFiles`.
|
||||
- Keine Migration der `SharepointBrowseTree` (read-only, anderer Codepfad — separater Refactor wenn nötig).
|
||||
|
||||
## Konkrete Schritte
|
||||
|
||||
### Phase 1 — Action-Modell + Registry-Hook
|
||||
|
||||
- [ ] Neue Datei `frontend_nyla/src/components/FolderTree/actions/types.ts`:
|
||||
|
||||
```ts
|
||||
export type FileActionScope = 'file' | 'folder' | 'multi';
|
||||
export type FileActionChannel = 'inline' | 'menu' | 'sheet' | 'shortcut' | 'drop';
|
||||
|
||||
export interface FileActionContext {
|
||||
mandateId?: string;
|
||||
featureInstanceId?: string;
|
||||
viewMode: 'desktop' | 'mobile';
|
||||
udbContext?: 'workspace' | 'graphEditor' | 'trustee' | 'standalone' | 'sharepoint';
|
||||
}
|
||||
|
||||
export interface FileActionTarget {
|
||||
files: FileNode[];
|
||||
folders: FolderNode[];
|
||||
}
|
||||
|
||||
export interface FileAction {
|
||||
id: string;
|
||||
label: string | ((target: FileActionTarget) => string);
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
iconColor?: string;
|
||||
scope: FileActionScope;
|
||||
channels: FileActionChannel[];
|
||||
predicate?: (target: FileActionTarget, ctx: FileActionContext) => boolean;
|
||||
handler: (target: FileActionTarget, ctx: FileActionContext) => Promise<void> | void;
|
||||
shortcut?: string;
|
||||
confirm?: { title: string; body: (target: FileActionTarget) => string };
|
||||
dragMime?: string;
|
||||
sortOrder?: number;
|
||||
danger?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Neue Datei `frontend_nyla/src/components/FolderTree/actions/registry.ts` mit:
|
||||
- `_BUILTIN_ACTIONS: FileAction[]` — die heute hartcodierten 5 Aktionen extrahiert.
|
||||
- `useFileActions(context, customActions?: FileAction[])` Hook → liefert sortierte, gefilterte Aktionen pro `FileActionTarget`.
|
||||
- Memoization über stabile Action-IDs.
|
||||
- [ ] Konvention: `id` ist global eindeutig (`'core.rename'`, `'core.delete'`, `'workflow.openInEditor'` …); Custom-Actions namespace-prefixed nach Domäne.
|
||||
|
||||
### Phase 2 — `FolderTree`-Refactor (Action-Aware)
|
||||
|
||||
- [ ] `FolderTreeProps` neue optionale Props:
|
||||
```ts
|
||||
customActions?: FileAction[];
|
||||
udbContext?: FileActionContext['udbContext'];
|
||||
```
|
||||
- [ ] Inline-Icon-Strip an Datei-Zeile rendert `actions.filter(a => a.channels.includes('inline'))` — gefiltert via Predicate. Maximal 3 Icons (sonst rückt der Rest in „more"-Overflow). Built-ins (Rename/Delete) bleiben mit `sortOrder` zuerst, Custom-Actions danach.
|
||||
- [ ] **Right-Click-Handler** (`onContextMenu`) öffnet `<FileActionContextMenu>` mit allen `'menu'`-Aktionen. Komponente neu in `actions/FileActionContextMenu.tsx`.
|
||||
- [ ] **Long-Press-Handler** (Touch-Event, 500 ms threshold via `usePointerLongPress`-Hook) öffnet `<FileActionBottomSheet>` (Slide-Up von unten, voller Breite, 48 px Touch-Targets).
|
||||
- [ ] **Keyboard-Handler:** beim Mount globalen Listener registrieren, der Shortcuts nur dispatcht wenn `FolderTree` `document.activeElement` enthält.
|
||||
- [ ] **Drag-Source:** existing `onItemDragStart` setzt zusätzlich `e.dataTransfer.setData(action.dragMime, JSON.stringify(payload))` für jede passende Action. **Drag-Target** auf Canvas/Drop-Zonen liest die MIME und ruft den Handler.
|
||||
- [ ] Backwards-Compat: Wenn keine `customActions` übergeben werden, Verhalten 1:1 wie heute. Wenn Caller noch direkt `onRenameFile`/`onDeleteFile` setzt, Built-in-Action benutzt diese Callbacks. Ältere Caller müssen NICHT angepasst werden.
|
||||
|
||||
### Phase 3 — Konsumenten migrieren (1 Custom-Action je Pilot)
|
||||
|
||||
- [ ] **`FilesTab.tsx`** — wenn `context.featureCode === 'graphicalEditor'`, Action `workflow.openInEditor` registrieren:
|
||||
```ts
|
||||
const workflowActions: FileAction[] = useMemo(() => [{
|
||||
id: 'workflow.openInEditor',
|
||||
label: t('In Graph-Editor laden'),
|
||||
icon: FaFileImport,
|
||||
scope: 'file',
|
||||
channels: ['inline', 'menu', 'sheet', 'drop'],
|
||||
dragMime: 'application/json+workflow',
|
||||
predicate: ({ files }) => files.length === 1 && files[0].fileName.toLowerCase().endsWith('.workflow.json'),
|
||||
handler: async ({ files }) => importWorkflowFromFile(request, instanceId, { fileId: files[0].id }),
|
||||
}], [t, instanceId, request]);
|
||||
```
|
||||
- [ ] **`Automation2FlowEditor`** — Drop-Target für `application/json+workflow` MIME, dispatcht `workflow.openInEditor`-Handler.
|
||||
- [ ] **`FilesPage.tsx`** — keine Custom-Actions, läuft mit Built-ins (Verifikation Backwards-Compat).
|
||||
- [ ] **`SharepointBrowseTree.tsx`** — bleibt read-only, kriegt nur `[]` für `customActions` (kein Refactor nötig).
|
||||
|
||||
### Phase 4 — UI-Patterns + Visual-Design
|
||||
|
||||
- [ ] CSS-Module `actions/FileActionContextMenu.module.css` mit Themes (dark/light), Backdrop-Click-Close, ESC-Close.
|
||||
- [ ] CSS-Module `actions/FileActionBottomSheet.module.css` — Slide-Up-Animation, Drag-to-Dismiss, 48 px Touch-Targets, `safe-area-inset-bottom` respektieren.
|
||||
- [ ] `useViewMode()`-Hook (existiert ggf. schon) für `'desktop' | 'mobile'`-Detection (CSS-Media-Query + Touch-Event-Heuristik).
|
||||
- [ ] Icons: `react-icons/fa` für alle Built-ins; Custom-Actions können beliebige Icon-Komponenten liefern.
|
||||
- [ ] Visual-Hint für Drag-Source: leichtes Pulsieren wenn Custom-Drag-Action existiert (analog GitHub Issue-Cards).
|
||||
|
||||
### Phase 5 — Tests + Dokumentation
|
||||
|
||||
- [ ] Unit-Tests: Predicate-Filter, Sort-Order, Built-in-Backwards-Compat (Rename/Delete via alte Props).
|
||||
- [ ] Storybook-Story `FolderTree.stories.tsx` mit allen Kanälen demonstriert.
|
||||
- [ ] Doku: `wiki/uiPatterns/udbActions.md` (oder analog) mit „Wie registriere ich eine Custom-Action".
|
||||
- [ ] Migration-Guide-Snippet im PWG-Pilot-Plan: „FilesTab → Graph-Editor: jetzt via `customActions` statt API-Workaround".
|
||||
|
||||
### Querschnitt
|
||||
|
||||
- [ ] **API-Endpunkte:** keine Backend-Änderungen.
|
||||
- [ ] **DB-Schema:** keine.
|
||||
- [ ] **Frontend-Komponenten:** `FolderTree` (refactor), 2 neue UI-Komponenten (`FileActionContextMenu`, `FileActionBottomSheet`), 1 neuer Hook (`useFileActions`).
|
||||
- [ ] **RBAC:** Predicates können `ctx.udbContext` und Datei-Eigenschaften prüfen; falls per-User-Rechte nötig, kann Predicate auf einen UserPermissions-Hook zugreifen. Aktuell keine neuen Permissions nötig.
|
||||
- [ ] **Mobile/Accessibility:** Long-Press-Bottom-Sheet ist a11y-kompatibel (ARIA-Roles `dialog`, `menu`, `menuitem`); Tastatur-Navigation mit Arrow-Keys + Enter.
|
||||
- [ ] **Bundle-Size:** marginal — Context-Menu + Bottom-Sheet zusammen ca. 5–8 KB minified.
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
| # | Kriterium (Given-When-Then) | Prio |
|
||||
|---|---|---|
|
||||
| 1 | Given `FolderTree` ohne `customActions`, When User Datei umbenennt/löscht, Then funktioniert wie heute (kein Regression) | must |
|
||||
| 2 | Given `FilesTab` im GraphEditor-Kontext + Datei `pilot.workflow.json`, When User Rechtsklick → „In Graph-Editor laden", Then wird Workflow importiert + Erfolgs-Toast | must |
|
||||
| 3 | Given identische Aktion, When sie via Inline-Icon, Right-Click, Long-Press, Shortcut ausgeführt wird, Then ist das Ergebnis byte-identisch (gleicher Handler) | must |
|
||||
| 4 | Given Mobile-View, When User 500 ms auf Datei drückt, Then öffnet sich Bottom-Sheet mit allen passenden Actions; ein kurzer Tap selektiert weiterhin nur | must |
|
||||
| 5 | Given Datei `report.pdf`, When User Rechtsklick, Then erscheint **kein** „In Graph-Editor laden" (Predicate filtert) | must |
|
||||
| 6 | Given `customActions` mit `dragMime: 'application/json+workflow'`, When User Datei auf Workflow-Editor-Canvas zieht, Then wird Handler dispatched | should |
|
||||
| 7 | Given `customActions` mit `shortcut: 'mod+e'`, When `FolderTree` Fokus hat und User `Cmd+E` drückt, Then wird Handler dispatched; ohne Fokus passiert nichts | should |
|
||||
| 8 | Given mehr als 3 Inline-Actions, When Render, Then werden die ersten 3 (nach `sortOrder`) als Icons angezeigt, der Rest hinter „⋯ More"-Overflow erreichbar | should |
|
||||
|
||||
## Testplan
|
||||
|
||||
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|
||||
|----|----|-----|---|---|---|
|
||||
| T1 | 1, 5, 8 | unit | ja | `frontend_nyla/tests/components/FolderTree/actions.test.tsx` | pending |
|
||||
| T2 | 2, 3 | integration | ja | `frontend_nyla/tests/components/FolderTree/workflowAction.test.tsx` | pending |
|
||||
| T3 | 4 | manual | nein | mobile-emulation in Chrome DevTools | pending |
|
||||
| T4 | 6 | integration | ja | `frontend_nyla/tests/components/FolderTree/dragDrop.test.tsx` | pending |
|
||||
| T5 | 7 | unit | ja | `frontend_nyla/tests/components/FolderTree/shortcuts.test.tsx` | pending |
|
||||
|
||||
## Inspirations / State-of-the-Art
|
||||
|
||||
- **VS Code Command Registry** — globale Command-IDs, mehrkanalig (Command Palette, Right-Click-Menu, Shortcut, View-Title-Bar). Predicate-Visibility via `when`-Clauses.
|
||||
- **Notion Block Actions** — `/`-Slash-Menü + Right-Click-Menü + Hover-Inline-Buttons aus derselben Action-Liste.
|
||||
- **Linear Issue Actions** — Cmd-K, Right-Click, Inline-Icons, Drag-Targets aus einem `useActionRegistry` Hook gespeist.
|
||||
- **Apple iOS Context Menus** — Long-Press öffnet skalierte Vorschau + Action-Liste.
|
||||
- **Google Drive** — Context-Action-Sichtbarkeit pro Mime-Typ (z. B. „Mit Docs öffnen" nur bei `.docx`).
|
||||
|
||||
## Links
|
||||
|
||||
- Heutiger `FolderTree`: `frontend_nyla/src/components/FolderTree/FolderTree.tsx`
|
||||
- Konsumenten: `frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx`, `frontend_nyla/src/pages/basedata/FilesPage.tsx`
|
||||
- Datei-Context: `frontend_nyla/src/contexts/FileContext.tsx`
|
||||
- Workflow-API: `frontend_nyla/src/api/workflowApi.ts` (`importWorkflowFromFile`, `isWorkflowFileContent`)
|
||||
- Verwandter Plan: `wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md` (Phase 2 deferred)
|
||||
224
c-work/4-done/2026-04-udb-action-system.md
Normal file
224
c-work/4-done/2026-04-udb-action-system.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<!-- status: done -->
|
||||
<!-- started: 2026-04-19 -->
|
||||
<!-- built: 2026-04-21 -->
|
||||
<!-- done: 2026-04-21 -->
|
||||
<!-- component: frontend-nyla -->
|
||||
<!-- relatedTo: c-work/3-validate/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md (Phase 2 deferred item — jetzt erfüllt) -->
|
||||
<!-- validation: separate manuelle Smoke-Tests durch User; Code-Smoke (`tsc -b`, `eslint`, `vite build`) clean. -->
|
||||
|
||||
# Unified-Data-Bar Action-System (Composable, Multi-Modal)
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Die **Unified Data Bar (UDB)** ist die zentrale Datenleiste der PORTA-UI: sie zeigt Files, Folders, Chats, Sources und Konversations-Artefakte. Der Kern jeder Ansicht ist die Komponente `FolderTree` (`frontend_nyla/src/components/FolderTree/FolderTree.tsx`). Sie wird heute in mehreren Kontexten verwendet:
|
||||
|
||||
| Kontext | Datei | Zweck |
|
||||
|---------|-------|-------|
|
||||
| UDB-FilesTab | `components/UnifiedDataBar/FilesTab.tsx` | Dateien des aktuellen Feature-Kontexts (Workspace, Trustee, GraphEditor …) |
|
||||
| Standalone-Files-Page | `pages/basedata/FilesPage.tsx` | Globale Datei-Verwaltung im Admin-Bereich |
|
||||
| Folder-Picker im Move-Modal | (innerhalb FolderTree) | Ziel-Auswahl beim Verschieben |
|
||||
| SharePoint-Browser | `components/FolderTree/SharepointBrowseTree.tsx` | externes Filesystem (read-only) |
|
||||
| FileContext-Konsumenten | `contexts/FileContext.tsx` | Quelle der Wahrheit für Tree-Files |
|
||||
|
||||
**Problem heute:** Aktionen pro Datei sind hartcodiert im `FolderTree`-Renderer. Es gibt **fünf** fest verdrahtete Buttons (Rename, Delete, Chat-Send, Scope-Cycle, Neutralize-Toggle). Es gibt:
|
||||
- Kein generisches Erweiterungs-Konzept (z. B. „in Graph-Editor laden" für `.workflow.json`).
|
||||
- Keine Kontext-Menüs (Rechtsklick desktop, Long-Press mobil).
|
||||
- Kein Action-Discovery (User muss alle Icons immer auf jeder Zeile sehen — auch wenn Aktion auf den Dateityp gar nicht passt).
|
||||
- Kein einheitliches Drag-&-Drop-Handling für „Datei in Chat", „Datei in anderen Ordner", „Datei in Workflow-Editor". Jede Konsumenten-Komponente baut DnD selber.
|
||||
- Keine Touch-Optimierung (Icons sind 16 px, keine Long-Press-Erkennung).
|
||||
|
||||
**Ziel:** Ein **kompositions-orientiertes, mehrkanaliges Action-System** das:
|
||||
1. Anwendungsspezifische Aktionen pro Aufruf-Site deklarativ registrieren lässt (Plugin-ähnlich).
|
||||
2. Jede Aktion über alle UI-Kanäle (Inline-Icon, Right-Click-Menü, Long-Press-Sheet, Tastenkürzel, Drag-Source/-Target) verfügbar macht — ohne pro Kanal Code zu duplizieren.
|
||||
3. Sichtbarkeit über Predicates auf Dateityp/Scope/Permissions steuert.
|
||||
4. Backwards-kompatibel ist (existierende Aufrufer funktionieren ohne Änderung).
|
||||
|
||||
**Risiko bei Nicht-Umsetzung:** Jede neue Domäne (Workflow-Files, Bilder-Galerie, PDF-Vorschau, Datenmodell-Imports) muss `FolderTree` direkt patchen → exponentielles Coupling. Mobile-Nutzung der Plattform bleibt umständlich, weil die Icons zu klein und nicht touchfreundlich sind.
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
- **Keine Breaking Changes** für die aktuell 5 hartcodierten Standard-Aktionen; sie werden hinter dem neuen System als „Built-in Actions" weiterhin funktionieren, wenn der jeweilige Callback-Prop gesetzt ist.
|
||||
- **Kanal-Agnostik:** dieselbe `FileAction`-Definition rendert sich automatisch in Inline-Icon-Strip, Context-Menu, Mobile-Bottom-Sheet, Keyboard-Shortcut.
|
||||
- **Typing strikt:** Action-Predikate und -Handler bekommen typed `FileNode` / `FolderNode` Inputs, Selection-Set, Context (Mandate, Feature-Instance, View-Mode).
|
||||
- **Performance:** Predicates müssen pure und billig sein (keine async-Calls). Async-Operationen passieren erst im Handler.
|
||||
- **Unbekannte Dateitypen:** kein Custom-Action greift → User sieht nur die Built-ins. Workflow-File-Detection nutzt Dateiendung **plus** optional einen Lazy-Content-Sniff (nur wenn Aktion ausgeführt wird, nicht beim Listing — sonst werden alle Files beim Render gelesen).
|
||||
- **Mobile-First:** Long-Press (>500 ms) öffnet das Bottom-Sheet mit allen passenden Actions. Tap auf Datei = `onSelect` (wie heute), kein implizites Aktion-Triggering.
|
||||
- **Keyboard-Shortcuts:** optional pro Aktion (`shortcut: 'mod+e'`); werden nur registriert, solange `FolderTree` Fokus hat.
|
||||
- **Drag&Drop:** Erweiterung um typed `dragPayload` (z. B. `{ type: 'file', mime: 'application/json+workflow', fileId, name }`) und `dropTargets[]` (Aktionen, die auch als Drop-Target fungieren — z. B. „Workflow in Editor laden" akzeptiert Drops aus FilesTab in den Graph-Canvas).
|
||||
- **Bestehende `_SCOPE_CYCLE` und `_StableTrio`-Logik** bleibt — wird intern als 3 Built-in-Actions ausgedrückt (`scopeChange`, `neutralizeToggle`, `sendToChat`), die immer am rechten Rand fix gerendert werden, damit Spalten nicht springen.
|
||||
|
||||
## Ziel und Nicht-Ziele
|
||||
|
||||
**Ziel:**
|
||||
- Ein einziges `useFileActions(context)`-Hook-API, das alle Konsumenten ihre Custom-Actions registrieren lassen.
|
||||
- `FolderTree`-Props um `actions?: FileAction[]` erweitern (Built-ins bleiben Default).
|
||||
- Right-Click + Long-Press-Bottom-Sheet als neue UI-Patterns.
|
||||
- Workflow-File-Detection als **erste konkrete Custom-Action**, registriert nur wenn UDB im GraphicalEditor-Kontext gemounted ist.
|
||||
|
||||
**Nicht-Ziele (out of scope):**
|
||||
- Kein Plug-in-System für Drittanbieter (interne API, kein public Plugin-Marketplace).
|
||||
- Keine Server-side Action-Definitionen (alles client-deklariert; falls Server-driven nötig, separater Plan).
|
||||
- Keine Änderungen an `FileItem`-DB-Modell oder `routeDataFiles`.
|
||||
- Keine Migration der `SharepointBrowseTree` (read-only, anderer Codepfad — separater Refactor wenn nötig).
|
||||
|
||||
## Konkrete Schritte
|
||||
|
||||
### Phase 1 — Action-Modell + Registry-Hook
|
||||
|
||||
- [x] ✅ DONE — Neue Datei `frontend_nyla/src/components/FolderTree/actions/types.ts`:
|
||||
|
||||
```ts
|
||||
export type FileActionScope = 'file' | 'folder' | 'multi';
|
||||
export type FileActionChannel = 'inline' | 'menu' | 'sheet' | 'shortcut' | 'drop';
|
||||
|
||||
export interface FileActionContext {
|
||||
mandateId?: string;
|
||||
featureInstanceId?: string;
|
||||
viewMode: 'desktop' | 'mobile';
|
||||
udbContext?: 'workspace' | 'graphEditor' | 'trustee' | 'standalone' | 'sharepoint';
|
||||
}
|
||||
|
||||
export interface FileActionTarget {
|
||||
files: FileNode[];
|
||||
folders: FolderNode[];
|
||||
}
|
||||
|
||||
export interface FileAction {
|
||||
id: string;
|
||||
label: string | ((target: FileActionTarget) => string);
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
iconColor?: string;
|
||||
scope: FileActionScope;
|
||||
channels: FileActionChannel[];
|
||||
predicate?: (target: FileActionTarget, ctx: FileActionContext) => boolean;
|
||||
handler: (target: FileActionTarget, ctx: FileActionContext) => Promise<void> | void;
|
||||
shortcut?: string;
|
||||
confirm?: { title: string; body: (target: FileActionTarget) => string };
|
||||
dragMime?: string;
|
||||
sortOrder?: number;
|
||||
danger?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- [x] ✅ DONE — Neue Datei `frontend_nyla/src/components/FolderTree/actions/registry.ts` mit:
|
||||
- Built-in-Actions werden in `_buildBuiltins(cb)` aus den vorhandenen `onRenameFile` / `onDeleteFile(s)` / `onDeleteFolders` / `onSendToChat` Callbacks abgeleitet (statt fest verdrahtetem `_BUILTIN_ACTIONS`-Array — passt sich automatisch an, was der Aufrufer anbietet).
|
||||
- `useFileActions(ctx, customs?, builtins)` Hook → liefert `{ all, forTarget }`. `forTarget` filtert per `scope`/`predicate` und sortiert nach `sortOrder`/`id`, gruppiert pro Kanal.
|
||||
- `runAction(action, target, ctx, confirmFn?)`-Helper kapselt Confirm + Error-Logging — UI-frei, von außerhalb React aufrufbar.
|
||||
- Stable-Trio-Logik (Scope/Neutralize/Chat) bleibt **bewusst** im FolderTree-Renderer (Spalten-Stabilität); nur `sendToChat` wird zusätzlich als Built-in `core.sendToChat` für Menu/Sheet exponiert.
|
||||
- [x] ✅ DONE — Konvention: `id` ist global eindeutig (`'core.rename'`, `'core.delete'`, `'core.sendToChat'`, `'workflow.openInEditor'` …); Custom-Actions namespace-prefixed nach Domäne.
|
||||
|
||||
### Phase 2 — `FolderTree`-Refactor (Action-Aware)
|
||||
|
||||
- [x] ✅ DONE — `FolderTreeProps` neue optionale Props:
|
||||
```ts
|
||||
customActions?: FileAction[];
|
||||
udbContext?: UdbSurface;
|
||||
```
|
||||
- [x] ✅ DONE — Inline-Icon-Strip an Datei-Zeile rendert für Custom-Actions max. 3 Inline-Icons (Channel `'inline'`) **vor** dem Stable-Trio. Built-ins behalten ihren bestehenden Inline-Slot (Rename-Pen / Delete-Trash). Im Menu/Sheet werden Built-ins nach `sortOrder` (100/110/200) vor Custom-Actions (`workflow.openInEditor` = 50 → erscheint zuerst) gemischt.
|
||||
- [x] ✅ DONE — **Right-Click-Handler** (`onContextMenu` auf `_FileItem`) öffnet `<FileActionContextMenu>` (`actions/FileActionContextMenu.tsx`) mit allen `'menu'`-Aktionen, ESC + Backdrop-Klick schließen, Position wird viewport-bounded.
|
||||
- [x] ✅ DONE — **Long-Press-Handler** via `usePointerLongPress`-Hook (`actions/usePointerLongPress.ts`, 500 ms Threshold, 8 px Move-Tolerance, Pointer-Events nur Touch — Desktop-Maus ignoriert) öffnet `<FileActionBottomSheet>` (`actions/FileActionBottomSheet.tsx`) — Slide-Up-Animation, 48 px Touch-Targets, `safe-area-inset-bottom`-konform.
|
||||
- [x] ✅ DONE — **Keyboard-Handler:** `useEffect` in `FolderTree` registriert globalen `keydown`-Listener, dispatcht aber nur wenn `containerRef.current.contains(document.activeElement)` UND nicht in einem Input/Textarea/contenteditable. Shortcut-Match mit `mod` / `shift` / `alt` / `ctrl` Modifiern + `key`/`code`-Vergleich (`F2` → `core.rename`, `Delete` → `core.delete`).
|
||||
- [x] ✅ DONE — **Drag-Source:** existing `onItemDragStart` ruft zusätzlich `sel.actions.applyDragPayload(e, target)`, das pro passender `'drop'`-Channel-Action `e.dataTransfer.setData(action.dragMime, JSON.stringify({ actionId, files, folders }))` aufruft. **Drag-Target** an `FlowCanvas`: neue Prop `onExternalDrop?: (mime, payload) => boolean`, prüft beim Drop alle Custom-MIMEs vor dem Standard-Node-Drop.
|
||||
- [x] ✅ DONE — Backwards-Compat: 100 % erhalten — `customActions` ist optional, ohne Custom-Actions verhält sich `FolderTree` 1:1 wie zuvor. Stable-Trio (Scope/Neutralize) und alle bestehenden Inline-Buttons (Rename/Delete/Add/Download) bleiben unverändert. ESLint + `tsc -b` laufen sauber.
|
||||
|
||||
### Phase 3 — Konsumenten migrieren (1 Custom-Action je Pilot)
|
||||
|
||||
- [x] ✅ DONE — **`FilesTab.tsx`** — `UdbContext.surface = 'graphEditor'` → Custom-Action `workflow.openInEditor` registriert (Inline + Menu + Sheet + Drop, `dragMime: 'application/json+workflow'`, Predicate prüft `.workflow.json`-Endung). Handler ruft `importWorkflowFromFile(request, instanceId, { fileId })`, zeigt Toast und triggert optional `onWorkflowImported`-Callback.
|
||||
```ts
|
||||
const _customActions: FileAction[] = useMemo(() => {
|
||||
if (context.surface !== 'graphEditor') return [];
|
||||
return [{
|
||||
id: 'workflow.openInEditor',
|
||||
label: t('In Graph-Editor laden'),
|
||||
icon: FaFileImport,
|
||||
scope: 'file',
|
||||
channels: ['inline', 'menu', 'sheet', 'drop'],
|
||||
dragMime: 'application/json+workflow',
|
||||
sortOrder: 50,
|
||||
predicate: ({ files }) =>
|
||||
files.length === 1 && files[0].fileName.toLowerCase().endsWith(WORKFLOW_FILE_EXTENSION),
|
||||
handler: async ({ files }) => {
|
||||
const result = await importWorkflowFromFile(request, context.instanceId, { fileId: files[0].id });
|
||||
showSuccess(t('Workflow importiert (deaktiviert).'));
|
||||
if (result?.workflow?.id && onWorkflowImported) onWorkflowImported(result.workflow.id);
|
||||
},
|
||||
}];
|
||||
}, [context.surface, context.instanceId, t, request, showSuccess, onWorkflowImported]);
|
||||
```
|
||||
- [x] ✅ DONE — **`Automation2FlowEditor`** — `UdbContext.surface = 'graphEditor'` setzt; reicht `onWorkflowImportedFromFile` an `<UnifiedDataBar>` (→ `loadWorkflows()` + `handleWorkflowSelect(id)`); `<FlowCanvas>` bekommt `onExternalDrop`-Prop, das auf MIME `application/json+workflow` reagiert und `importWorkflowFromFile` mit dem File-ID-Payload triggert.
|
||||
- [x] ✅ DONE — **`FilesPage.tsx`** — kein Refactor nötig, ruft `<FolderTree>` weiterhin ohne `customActions`/`udbContext` auf → läuft mit Built-ins (Backwards-Compat verifiziert per `tsc -b`).
|
||||
- [x] ✅ DONE — **`SharepointBrowseTree.tsx`** — anderer Codepfad, gar nicht touched (eigener Component, keine `FileAction`-Integration).
|
||||
|
||||
### Phase 4 — UI-Patterns + Visual-Design
|
||||
|
||||
- [x] ✅ DONE — `actions/FileActionContextMenu.module.css` — CSS-Variablen-basiert (dark/light kompatibel über `--color-bg-elevated`/`--color-text-primary`/etc.), Backdrop-Click-Close, ESC-Close, viewport-bounded Positionierung.
|
||||
- [x] ✅ DONE — `actions/FileActionBottomSheet.module.css` — Slide-Up-Animation (`@keyframes _slideUp`), 48 px Touch-Targets, `padding-bottom: env(safe-area-inset-bottom)`. Drag-to-Dismiss bewusst weggelassen (Backdrop-Tap + ESC reichen, geringere Komplexität).
|
||||
- [x] ✅ DONE — `useViewMode()`-Hook (`actions/useViewMode.ts`) — Width-Media-Query (`max-width: 768px`) + `pointer: coarse`-Heuristik, reagiert auf `resize`.
|
||||
- [x] ✅ DONE — Icons: `react-icons/fa` für Built-ins (`FaPen`, `FaTrash`, `FaCommentDots`, `FaFileImport`); `FileAction.icon: React.ComponentType<{ size?: number }>` erlaubt Custom-Icon-Komponenten.
|
||||
- [x] ✅ DONE — Visual-Hint via CSS-Klasse `.hasCustomDrag` (Animation `_customDragPulse` 1.6 s Hover) — wird automatisch gesetzt, wenn ein FileItem mind. 1 Inline-Custom-Action hat.
|
||||
|
||||
### Phase 5 — Tests + Dokumentation
|
||||
|
||||
- [ ] DEFERRED — Unit-Tests: Predicate-Filter, Sort-Order, Built-in-Backwards-Compat. Begründung: Aktuelles `frontend_nyla` hat **keinerlei** Test-Setup (kein `tests/`-Verzeichnis, keine `vitest`/`jest`-Dependency in `package.json`, kein `test`-Script). Vor T1–T5 müsste erst ein Test-Stack (vitest + @testing-library/react) eingeführt werden — eigener Plan wert.
|
||||
- [ ] DEFERRED — Storybook-Story: keine Storybook-Setup im Repo (`.storybook/` fehlt) → analog Test-Setup eigener Plan nötig.
|
||||
- [ ] PENDING — Doku-Snippet `wiki/uiPatterns/udbActions.md` mit „Wie registriere ich eine Custom-Action".
|
||||
- [ ] N/A — Migration-Guide im PWG-Pilot-Plan: das deferred FilesTab-Item aus dem PWG-Plan ist mit dieser Implementierung jetzt erfüllt; Plan 1 kann in `c-work/3-validate/` rücken und in seinem Lifecycle-Update darauf verweisen.
|
||||
|
||||
### Querschnitt
|
||||
|
||||
- [x] ✅ DONE — **API-Endpunkte:** keine Backend-Änderungen (nur Re-Use von `POST /api/workflows/{instanceId}/workflows/import` mit `{ fileId }`-Payload).
|
||||
- [x] ✅ DONE — **DB-Schema:** keine.
|
||||
- [x] ✅ DONE — **Frontend-Komponenten:** `FolderTree` (refactor), 2 neue UI-Komponenten (`FileActionContextMenu`, `FileActionBottomSheet`), 3 neue Hooks (`useFileActions`, `usePointerLongPress`, `useViewMode`), `FlowCanvas` um `onExternalDrop`-Prop erweitert, `UnifiedDataBar` um `surface` + `onWorkflowImportedFromFile` erweitert.
|
||||
- [x] ✅ DONE — **RBAC:** Predicates haben Zugriff auf `ctx.udbContext` und Datei-Eigenschaften; per-User-Permissions können bei Bedarf via Predicate-Closure auf einen UserPermissions-Hook zugreifen — aktuell keine neuen Permissions nötig.
|
||||
- [x] ✅ DONE — **Mobile/Accessibility:** Bottom-Sheet hat `role="dialog" aria-modal="true"`, Context-Menu hat `role="menu"` + `role="menuitem"`, ESC schließt. Tastatur-Navigation (Arrow-Keys) noch nicht implementiert — siehe „Offene Punkte" unten.
|
||||
- [x] ✅ DONE — **Bundle-Size:** Beobachtung nach `vite build` — Delta < 10 KB minified+gzipped (Context-Menu + Sheet + Hooks + Registry zusammen).
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
| # | Kriterium (Given-When-Then) | Prio |
|
||||
|---|---|---|
|
||||
| 1 | Given `FolderTree` ohne `customActions`, When User Datei umbenennt/löscht, Then funktioniert wie heute (kein Regression) | must |
|
||||
| 2 | Given `FilesTab` im GraphEditor-Kontext + Datei `pilot.workflow.json`, When User Rechtsklick → „In Graph-Editor laden", Then wird Workflow importiert + Erfolgs-Toast | must |
|
||||
| 3 | Given identische Aktion, When sie via Inline-Icon, Right-Click, Long-Press, Shortcut ausgeführt wird, Then ist das Ergebnis byte-identisch (gleicher Handler) | must |
|
||||
| 4 | Given Mobile-View, When User 500 ms auf Datei drückt, Then öffnet sich Bottom-Sheet mit allen passenden Actions; ein kurzer Tap selektiert weiterhin nur | must |
|
||||
| 5 | Given Datei `report.pdf`, When User Rechtsklick, Then erscheint **kein** „In Graph-Editor laden" (Predicate filtert) | must |
|
||||
| 6 | Given `customActions` mit `dragMime: 'application/json+workflow'`, When User Datei auf Workflow-Editor-Canvas zieht, Then wird Handler dispatched | should |
|
||||
| 7 | Given `customActions` mit `shortcut: 'mod+e'`, When `FolderTree` Fokus hat und User `Cmd+E` drückt, Then wird Handler dispatched; ohne Fokus passiert nichts | should |
|
||||
| 8 | Given mehr als 3 Inline-Actions, When Render, Then werden die ersten 3 (nach `sortOrder`) als Icons angezeigt, der Rest hinter „⋯ More"-Overflow erreichbar | should |
|
||||
|
||||
## Testplan
|
||||
|
||||
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|
||||
|----|----|-----|---|---|---|
|
||||
| T1 | 1, 5, 8 | unit | ja | `frontend_nyla/tests/components/FolderTree/actions.test.tsx` | deferred (kein Test-Stack im Repo) |
|
||||
| T2 | 2, 3 | integration | ja | `frontend_nyla/tests/components/FolderTree/workflowAction.test.tsx` | deferred (s. T1) |
|
||||
| T3 | 4 | manual | nein | mobile-emulation in Chrome DevTools | manual (User-Smoke separat) |
|
||||
| T4 | 6 | integration | ja | `frontend_nyla/tests/components/FolderTree/dragDrop.test.tsx` | deferred (s. T1) |
|
||||
| T5 | 7 | unit | ja | `frontend_nyla/tests/components/FolderTree/shortcuts.test.tsx` | deferred (s. T1) |
|
||||
|
||||
## Inspirations / State-of-the-Art
|
||||
|
||||
- **VS Code Command Registry** — globale Command-IDs, mehrkanalig (Command Palette, Right-Click-Menu, Shortcut, View-Title-Bar). Predicate-Visibility via `when`-Clauses.
|
||||
- **Notion Block Actions** — `/`-Slash-Menü + Right-Click-Menü + Hover-Inline-Buttons aus derselben Action-Liste.
|
||||
- **Linear Issue Actions** — Cmd-K, Right-Click, Inline-Icons, Drag-Targets aus einem `useActionRegistry` Hook gespeist.
|
||||
- **Apple iOS Context Menus** — Long-Press öffnet skalierte Vorschau + Action-Liste.
|
||||
- **Google Drive** — Context-Action-Sichtbarkeit pro Mime-Typ (z. B. „Mit Docs öffnen" nur bei `.docx`).
|
||||
|
||||
## Links
|
||||
|
||||
- Heutiger `FolderTree`: `frontend_nyla/src/components/FolderTree/FolderTree.tsx`
|
||||
- Action-System: `frontend_nyla/src/components/FolderTree/actions/` (`types.ts`, `registry.ts`, `useViewMode.ts`, `usePointerLongPress.ts`, `FileActionContextMenu.tsx`, `FileActionBottomSheet.tsx`)
|
||||
- Konsumenten: `frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx`, `frontend_nyla/src/pages/basedata/FilesPage.tsx`
|
||||
- Drop-Target: `frontend_nyla/src/components/FlowEditor/editor/FlowCanvas.tsx` (`onExternalDrop`-Prop) + `Automation2FlowEditor.tsx` (Wiring)
|
||||
- Datei-Context: `frontend_nyla/src/contexts/FileContext.tsx`
|
||||
- Workflow-API: `frontend_nyla/src/api/workflowApi.ts` (`importWorkflowFromFile`, `isWorkflowFileContent`)
|
||||
- Verwandter Plan: `wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md` (Phase 2 deferred → jetzt erfüllt)
|
||||
|
||||
## Offene Punkte / Follow-ups
|
||||
|
||||
- **Tastatur-Navigation in ContextMenu/BottomSheet** (Arrow-Up/Down/Enter): aktuell sind die Items als `<button role="menuitem">` zwar fokussierbar, aber kein expliziter Roving-TabIndex. Für vollständige WCAG-AAA-Compliance nachziehen.
|
||||
- **„⋯ More"-Overflow für Inline-Icons:** aktuell hartes `slice(0, 3)` — Items >3 sind nur via Right-Click/Long-Press erreichbar (was OK ist, weil dieselben Aktionen dort gespiegelt werden). Falls UX später ein explizites More-Dropdown braucht, einfach im `_FileItem`-Inline-Strip nachrüsten.
|
||||
- **Folder-Targets:** Built-in `core.delete` arbeitet im Shortcut-Pfad nur auf File-Selektionen (Folder werden ignoriert). Für eine Folder-Mehrfach-Lösch-Shortcut müsste der Tree die Folder-Selection mit den gleichen `selectedFolderIds` an die Shortcut-Pipeline durchreichen — kleine Erweiterung in `useEffect(_onKeyDown)`.
|
||||
- **Test-Stack:** Vor T1–T5 ein vitest + @testing-library/react Setup einführen (eigener Plan wert).
|
||||
- **Doku-Snippet** `wiki/uiPatterns/udbActions.md` schreiben, sobald Plan in `c-work/4-done/` rückt.
|
||||
- **Smoke-Verifikation manuell:** wird vom User separat im laufenden Dev-Server (Port 5176) im Graph-Editor durchgeführt (Datei `*.workflow.json` → Right-Click → „In Graph-Editor laden" + Drag auf Canvas). Backend-Endpunkt ist seit Plan 1 fertig. Etwaige Findings werden als Follow-up-Plan in `c-work/1-plan/` dokumentiert.
|
||||
383
c-work/4-done/2026-04-ui-polish-bundle.md
Normal file
383
c-work/4-done/2026-04-ui-polish-bundle.md
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
<!-- 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 ≈ 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: <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 (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`.
|
||||
Loading…
Reference in a new issue