200 lines
14 KiB
Markdown
200 lines
14 KiB
Markdown
<!-- 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)
|