wiki/c-work/4-done/2026-05-formgenerator-tree-and-folder-recovery.md

40 KiB

FormGeneratorTree (NEU) + Recovery der persistenten Folder/File-Funktionen

Beschreibung und Kontext

In den Commits c8e9304 (2026-04-29), e7a79a3 und 7c05cb0 (beide 2026-04-30, zusammen -1263 / +897) wurde FolderTree aus

  • frontend_nyla/src/pages/basedata/FilesPage.tsx
  • frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx

entfernt und durch FormGeneratorTable mit groupingConfig ersetzt. Begleitend wurden die Folder-Operationen aus FileContext.tsx und useFiles.ts reduziert, FileInfo.folderId aus fileApi.ts und das Pydantic-Feld FileItem.folderId aus gateway/modules/datamodels/datamodelFiles.py entfernt. Im Repo liegt das Archiviertes Skript gateway/modules/migrations/_archive/migrate_folders_to_groups.py, das die FileFolder-Tabelle + FileItem.folderId-Spalte in einen JSON-Tree (TableGrouping.rootGroups, Context files/list) ueberfuehren kann -- per Default --dry-run, ohne begleitende Alembic-Migration zum DROP-COLUMN / DROP-TABLE.

Datenbank-Stand: FileFolder und FileItem.folderId sind in der DB unveraendert vorhanden. Recovery laeuft als reine Pydantic-/Routen-/FE-Wieder- verdrahtung -- kein neues Schema fuer diesen Track.

Beobachtete funktionale Luecken (nach dem Wechsel zu Table-only)

  • Folders koennen nicht mehr neu erstellt werden (UI-Affordance entfernt).
  • Pro-File / pro-Folder Scope-Icons + Neutralize-Toggle fehlen (patchGroupScope / patchGroupNeutralize sind Bulk-Operationen auf Group- Items und ersetzen die Per-Item-UI nicht).
  • Multiselect / Multidelete mit Cascade ueber gemischte Folders+Files-Auswahl ist nicht abgebildet.
  • DnD-Protokoll inkompatibel: application/group-id, application/group-file-ids und application/tree-items mit type: 'file' | 'group' waren auf Tabellen-Gruppierungen optimiert; WorkspaceInput.TreeItemDrop musste mitziehen.
  • Mandanten-geteilte Folder-Strukturen lassen sich mit per-User-TableGrouping nicht abbilden.

Tree-Fragmentierung im Frontend (zusaetzlicher Treiber)

Heute existieren mindestens acht parallele Tree-Implementierungen:

# Datei Inhalt Lazy Multiselect Inline-Actions
1 components/FolderTree/FolderTree.tsx Files+Folders (entkoppelt) nein ja ja
2 components/FolderTree/SharepointBrowseTree.tsx SharePoint-Browse ja nein nein
3 components/UnifiedDataBar/SourcesTab.tsx Connections + Feature-Daten ja partiell partiell
4 components/UnifiedDataBar/ChatsTab.tsx Chat-Historien nein nein partiell
5 components/FlowEditor/nodes/shared/DataPicker.tsx Upstream-Outputs nein nein nein
6 components/Navigation/TreeNavigation/* + MandateNavigation.tsx Sidebar nein nein nein
7 pages/admin/InstanceHierarchyView.tsx RBAC-Hierarchie nein nein nein
8 pages/views/redmine/redmineTreeLogic.ts Redmine-Issues nein nein partiell
9 components/AccessRules/AccessRulesEditor.tsx RBAC-Rules nein nein partiell

Geschaeftstreiber

  1. Recovery der Datei-/Ordner-Funktionen, ohne FormGeneratorTable.groupingConfig fuer andere Use-Cases zurueckzubauen.
  2. Konsolidierung der Tree-Implementierungen auf eine getestete Komponente -- ein Bugfix-/Feature-Punkt statt acht.
  3. UDB-Konsistenz: drei feste Tabs (Files/Folders, Sources, Chats), alle als hierarchische Sicht auf persistente Entitaeten.

Architektur

FormGeneratorTree als neues Familienmitglied

Die FormGenerator-Familie wird um eine Tree-Komponente erweitert (parallel zu FormGeneratorTable, FormGeneratorForm, FormGeneratorList, FormGeneratorReport).

Aspekt Bestimmung
Aufgabe Hierarchische, lazy-ladbare Sicht auf persistente Entitaeten
Datenbindung Pro Use-Case ein Provider (siehe Provider-Interface unten)
Spalten Backend-Attribut-Resolution analog resolveColumnTypes; keine hardcoded Cell-Renderer in der Page; Pflicht-Spalte = Tree-Spalte (Indent + Chevron + Icon + Name)
Selektion Multiselect mit Shift/Ctrl/Cmd, kaskadierend (Folder selektieren -> alle eigenen Children); BatchAction[]-API analog Table
DnD Standard-MIME application/x-poweron-tree-items, Payload { id, type, name, providerKey }[]
Nicht zustaendig Report-Rendering (das ist FormGeneratorReport); Forms (FormGeneratorForm); eigene Datenpersistenz (haelt der Provider); Replikation der groupingConfig-Logik aus FormGeneratorTable

Provider-Interface (vorlaeufig):

type Ownership = 'own' | 'shared';

interface TreeNode<T> {
  id: string;
  name: string;
  parentId: string | null;
  ownership: Ownership;
  scope?: 'personal' | 'featureInstance' | 'mandate' | 'global';
  neutralize?: boolean;
  // Parent-Kontext (z.B. Folder) ist fuer den Current-User nicht sichtbar;
  // Knoten wird im Root mit Hint-Icon gerendert.
  contextOrphan?: boolean;
  data?: T;
}

interface TreeNodeProvider<T> {
  rootKey: string;
  loadChildren(parentId: string | null, ownership: Ownership): Promise<TreeNode<T>[]>;
  getAttributes(): Promise<AttributeDefinition[]>;
  // Mutations gelten ausschliesslich fuer ownership='own'.
  canCreate?(parentId: string | null): boolean;
  canRename?(node: TreeNode<T>): boolean;
  canDelete?(node: TreeNode<T>): boolean;
  canMove?(source: TreeNode<T>, target: TreeNode<T> | null): boolean;
  canPatchScope?(node: TreeNode<T>): boolean;
  canPatchNeutralize?(node: TreeNode<T>): boolean;
  createChild?(parentId: string | null, name: string): Promise<TreeNode<T>>;
  renameNode?(id: string, newName: string): Promise<void>;
  deleteNodes?(ids: string[]): Promise<void>;
  moveNodes?(ids: string[], targetParentId: string | null): Promise<void>;
  patchScope?(ids: string[], scope: string, cascadeChildren?: boolean): Promise<void>;
  patchNeutralize?(ids: string[], neutralize: boolean): Promise<void>;
}

Folder/File vs. TableGrouping (zwei orthogonale Konzepte)

Eigenschaft Folder/File (FileFolder, FileItem) Group (TableGrouping)
Identitaet First-class Entity (UUID, audit, RBAC) JSON-Knoten in einem User-spezifischen Tree
Persistenz DB-Zeile JSON-Blob in TableGrouping.rootGroups
Sichtbarkeit Per scope (personal/featureInstance/mandate/global) per-User (userId ist Partition-Key)
RBAC Per-Item Scope, Owner-only-Mutationen keine; nur enthaltene Items haben Scope
Move / Delete Beeinflusst alle berechtigten User Lokal pro User
Use-Case Files/Folders (mandate-shared) Persoenliche Sortier-Sicht auf flachen Listen (Workflow-Runs, Trustee-Positionen, ...)

FormGeneratorTable.groupingConfig bleibt unveraendert. FormGeneratorTree und groupingConfig sind komplementaer; beide Sichten duerfen nebeneinander auf denselben Files leben (siehe FilesPage View-Toggle).

Use-Case-Zuordnung

Use-Case Komponente
Files in Folders (mandate-shared) FormGeneratorTree + FolderFileProvider
External-Source-Browse (kDrive, SharePoint, ...) FormGeneratorTree + ConnectorBrowseProvider
Feature-Daten in UDB (Mandate -> FeatureConnection -> Group/Table -> Record) FormGeneratorTree + FeatureDataProvider
Chat-Historie nach Feature gruppiert FormGeneratorTree + ChatProvider
AccessRules-Editor FormGeneratorTree + RbacRuleProvider
FlowEditor DataPicker (Upstream-Output-Pfade) FormGeneratorTree + DataRefProvider
InstanceHierarchyView (RBAC-Audit) FormGeneratorTree + InstanceHierarchyProvider
Workflow-Runs nach Mandant + Status filtern FormGeneratorTable + groupingConfig (flach, per-User)
Sidebar (MandateNavigation, TreeNavigation) bleibt eigene Komponente -- Routing-Affordance, kein CRUD-Tree

Faustregel: Hat das Item eine eigene DB-Identitaet jenseits des aktuellen Users? Ja -> FormGeneratorTree. Nein -> FormGeneratorTable.groupingConfig.

RBAC- und Sichtbarkeits-Modell (verbindlich)

Die Recovery folgt dem fuer Files bereits implementierten RBAC-Modell (siehe interfaceRbac.buildFilesScopeWhereClause, interfaceDbManagement.updateFile / deleteFile / deleteFilesBatch). Folders bekommen 1:1 dieselbe Mechanik:

  1. Default-Sichtbarkeit personal. Jedes neu angelegte Folder/File ist nur fuer sysCreatedBy = currentUser sichtbar.
  2. Sharing nur ueber scope. Andere User sehen ein Objekt, sobald der Eigentuemer den Scope explizit erhoeht (featureInstance / mandate / global; global setzt ALL-Permission voraus, in der Praxis SysAdmin).
  3. Mutation = Owner-only (oder ALL-Permission). Rename / Move / Delete / Scope-Cycle / Neutralize-Toggle sind fuer Nicht-Eigentuemer 403; die UI blendet die Affordance aus.
  4. Zwei getrennte Trees in jeder Files-Sicht:
    • „Eigene“ -- sysCreatedBy = currentUser. Volles CRUD; das Scope-Icon zeigt Sharing-Status an.
    • „Geteilt mit mir“ -- sysCreatedBy != currentUser, ueber scope sichtbar. Read-only; Scope-/Neutralize nur als Indikator-Icon. DnD aus diesem Tree ist rein lesend (z.B. Drop in Chat-Input); Drop in den eigenen Tree erzeugt keinen Move (kein Ownership-Wechsel).
  5. Render-Regel. Files erscheinen unter ihrem Folder, wenn der Folder fuer den Current-User sichtbar ist. Fehlt der Folder-Kontext (FileItem.scope geteilt, FileFolder.scope nicht), wird die Datei im Root des passenden Trees mit contextOrphan-Hint („kein Folder-Kontext geteilt“) gerendert. Aendert der Owner spaeter den Folder-Scope, wandert die Datei automatisch unter den Folder.
  6. Cascade-Delete nur Owner. Multidelete eines (Sub-)Trees laeuft genau dann, wenn alle Items dem Current-User gehoeren. Gemischte Selektionen (Eigene + Geteilte) blockieren die Bulk-Aktion mit Hinweis.
  7. Folder-Scope-Cascade auf Files = opt-in. Beim Setzen eines Folder- Scopes wird der Owner per Dialog gefragt, ob die enthaltenen Files den Scope erben. Default: nein (Folders und Files behalten unabhaengige Scopes, konsistent mit der heutigen Files-Logik).
  8. Folder-Neutralize-Vererbung beim Drop = nein. Beim Drop eines Files in einen Folder mit neutralize=true wird der Datei-Wert nicht automatisch geaendert; ein Indikator zeigt den Mismatch.

Datentabellen-Sicht (FormGeneratorTable.groupingConfig auf Files): Status quo. Die Tabelle zeigt weiterhin alle sichtbaren Files (eigene + geteilte) flach; nur die Tree-Sicht trennt Eigene/Geteilt. Falls spaeter gewuenscht, kann ein ownerFilter ergaenzt werden -- nicht in diesem Plan.


Entscheidungen (konsolidiert)

# Datum Bereich Entscheidung Begruendung
D1 2026-05-01 Komponenten UDB hat fix drei Tabs (Files, Sources, Chats); alle drei rendern FormGeneratorTree Konsistente UDB-Struktur
D2 2026-05-01 Komponenten FormGenerator-Familie um Tree erweitern; nicht FolderTree v2 Konsolidiert ~8 parallele Tree-Implementierungen
D3 2026-05-01 Komponenten FormGeneratorTable.groupingConfig bleibt unveraendert Gilt fuer flache Daten (Workflow-Runs, Trustee-Positionen)
D4 2026-05-01 Komponenten Berichte = FormGeneratorReport; kein FormGeneratorChart (Namens-Klarstellung) Bestehende Familienkomponente
D5 2026-05-01 Komponenten Sidebar (MandateNavigation / TreeNavigation) bleibt eigene Komponente Routing-Affordance, kein CRUD-Tree
D6 2026-05-01 Persistenz Recovery via Modell A: FileFolder + FileItem.folderId reaktivieren (Pydantic, Routen, FE) Mandanten-geteilte Ordnerstruktur; DB-Schema unveraendert
D7 2026-05-01 Persistenz Keine Schema-Aenderung in dev/int/prod; migrate_folders_to_groups.py wird archiviert (audit trail) Nur API/FE-Wiederverdrahtung
D8 2026-05-01 Persistenz Soft-Delete wird nicht eingefuehrt; Audit-Eintrag pro Mutation reicht Bestehende Pattern-Konsistenz
D9 2026-05-01 Persistenz Owner-Wechsel von Folders/Files ist nicht im Scope (sysCreatedBy immutable) Bestehender Kontrakt
D10 2026-05-01 RBAC Default-Scope personal fuer neue Folders/Files; Sharing nur via Owner-Scope-Patch Konsistent zu Files-RBAC heute
D11 2026-05-01 RBAC Geteilte Folders/Files sind fuer Nicht-Owner read-only (Backend 403, UI keine Affordance) Owner-only-Kontrakt analog updateFile / deleteFile
D12 2026-05-01 RBAC Scope-Auflader: featureInstance-Sichtbarkeit gilt nur, wenn der User Zugriff auf die Feature-Instanz hat (bestehende buildFilesScopeWhereClause-Logik unveraendert) Konsistenz zu heutigem Files-Pfad
D13 2026-05-01 RBAC scope=global ist im Eigenen-Tree des Owners (i.d.R. SysAdmin) sichtbar mit eigenem Indikator; bei allen anderen Usern im „Geteilt mit mir“-Tree Symmetrische Owner-Sicht
D14 2026-05-01 UI-Modell Zwei getrennte Trees pro Files-Sicht: „Eigene“ (CRUD) + „Geteilt mit mir“ (read-only) Saubere Datenstruktur, keine Vermischung
D15 2026-05-01 UI-Modell Render-Regel: File unter Folder, falls Folder sichtbar; sonst Root mit contextOrphan-Hint Vermeidet Phantom-Hierarchien
D16 2026-05-01 UI-Modell „Geteilt mit mir“-Tree sortiert primaer nach Owner-Name, dann Folder-Hierarchie Quelle des Sharings ist die wichtigste Information
D17 2026-05-01 UI-Modell „Geteilt mit mir“-Sektion kollabiert sich, wenn leer UI-Hygiene
D18 2026-05-01 UI-Modell FormGeneratorTable.groupingConfig-Sicht auf Files bleibt flach (Eigene + Geteilte gemischt); kein zusaetzlicher Owner-Filter in diesem Plan Status quo der Tabelle
D19 2026-05-01 Sharing Folder-Scope-Cascade auf enthaltene Files = opt-in (Dialog beim Patch) Vermeidet versehentliche Massen-Sharings
D20 2026-05-01 Sharing Folder-Neutralize-Vererbung beim File-Drop = nein (analog Scope; Indikator zeigt Mismatch) Owner-Entscheidung pro Item
D21 2026-05-01 Sharing Drag aus „Geteilt mit mir“ in eigenen Tree = gesperrt (kein Move, keine implizite Kopie) Ownership-Wechsel ausserhalb Scope; Kopier-Workflow ggf. spaeterer Plan
D22 2026-05-01 Stage 3 SourcesTab und ChatsTab bekommen je einen Tree (kein Eigene/Geteilt-Split) Connections und Chats sind heute personal-aequivalent; kein Sharing-Modell

Ziel und Nicht-Ziele

  • Ziel 1. FormGeneratorTree als Komponente in frontend_nyla/src/components/FormGenerator/FormGeneratorTree/ mit Provider-Interface, Built-in-Features (Multiselect, Cascade, DnD, Inline-Edit, Scope-/Neutralize-Indikatoren) und Backend-Attribut-Resolution.
  • Ziel 2. FolderFileProvider als erste Implementierung; recovered die in 7c05cb0 entfallenen Funktionen auf Basis Modell A.
  • Ziel 3. UDB-Tabs FilesTab, SourcesTab, ChatsTab rendern FormGeneratorTree. FilesTab zeigt zwei Tree-Sektionen (Eigene + Geteilt).
  • Ziel 4. FilesPage.tsx Split-View: links zwei FormGeneratorTree- Sektionen, rechts FormGeneratorTable mit View-Toggle „Tree-Sicht | Liste-mit-Gruppen-Sicht“.
  • Nicht-Ziel. FormGeneratorTable.groupingConfig rebauen oder entfernen.
  • Nicht-Ziel. FormGeneratorReport anfassen.
  • Nicht-Ziel. Sidebar (MandateNavigation / TreeNavigation) migrieren.
  • Nicht-Ziel. Owner-Wechsel oder Soft-Delete fuer Folders/Files einfuehren.

Betroffene Module

Frontend Nyla

  • Neu: components/FormGenerator/FormGeneratorTree/ (FormGeneratorTree.tsx, FormGeneratorTree.module.css, types.ts, __tests__/)
  • Neu: providers/FolderFileProvider.tsx
  • Recovery: components/UnifiedDataBar/FilesTab.tsx (Tree statt Table-mit-Grouping; Eigene + Geteilt)
  • Recovery: pages/basedata/FilesPage.tsx (Split-View Tree+Table)
  • Recovery: contexts/FileContext.tsx, hooks/useFiles.ts (Folder-Operationen zurueck)
  • Recovery: api/fileApi.ts (folderId, Folder-API-Wrapper)
  • Anpassen: pages/views/workspace/WorkspaceInput.tsx -- TreeItemDrop.type auf 'file' | 'folder' zuruecksetzen, neuer Standard-MIME application/x-poweron-tree-items
  • Stage 3: components/UnifiedDataBar/SourcesTab.tsx und ChatsTab.tsx auf FormGeneratorTree (je ein Tree, kein Sharing-Split)
  • Stage 4 (optional): SharepointBrowseTree, DataPicker, InstanceHierarchyView, AccessRulesEditor

Gateway

  • Recovery: datamodels/datamodelFiles.py -- neues FileFolder Pydantic- Modell + FileItem.folderId: Optional[str] mit fk_target auf FileFolder.id
  • Recovery: interfaces/interfaceDbApp.py / interfaces/interfaceDbManagement.py -- getOwnFolderTree(), getSharedFolderTree(), createFolder (Default-Scope personal), renameFolder, moveFolder, deleteFolderCascade, patchFolderScope, patchFolderNeutralize. Mutationen strikt owner-only (oder ALL).
  • Recovery: routes/routeDataFiles.py -- Folder-CRUD-Routen (POST /folders, GET /folders/tree?owner=me|shared, PATCH /folders/{id}, POST /folders/{id}/move, DELETE /folders/{id}?cascade=bool, PATCH /folders/{id}/scope, PATCH /folders/{id}/neutralize)
  • Behalten: routeDataFiles.py Group-Endpunkte (/groups/{groupId}/scope, /neutralize, /download, delete_group) fuer FormGeneratorTable.groupingConfig-Use-Cases
  • Archivieren: migrations/migrate_folders_to_groups.py -> migrations/_archive/ mit kurzem README (audit trail)
  • Unangetastet: serviceCenter/.../coreTools/_workspaceTools.py und mainServiceChat.py (TableGrouping-Pfad bleibt). Bei Bedarf spaeter _listFolders / _listFilesInFolder als zusaetzliche Agent-Tools.

DB

Keine Schema-Aenderung. Optionaler Schema-Sanity-Check in Stage 0.


Umsetzungs-Checkliste (Packets pro Modell)

Jedes Packet ist ein abgeschlossener Auftrag fuer eine LLM-Session. Header pro Packet: Modell | Eingang (Voraussetzung) | Lieferumfang | Checkliste | Abnahme.

Modelle:

  • Composer 2 Fast -- mechanische Wiederherstellung, Pattern-Wiederholung, Boilerplate, Doku gegen vorhandenen Code.
  • Opus 4.6 -- Architektur, RBAC-Korrektheit, Greenfield-API, nicht-triviale Refactors. Bei Unsicherheit hochstufen.

Reihenfolge / Block-Strategie

Stage 0 (optional)
   |
   v
Stage 1.A (BE Boilerplate)  ─┐
Stage 1.B (BE RBAC-Kern)     ├─ Block 1 (Backend in beliebiger Reihenfolge,
Stage 1.C (FE Greenfield)    ─┘  FE kann parallel zu BE)
   |
   v
Stage 2.A (FE Recovery-Boilerplate)
   |
   v
Stage 2.B (FE UI-Komposition)
   |
   v
Stage 2.C (manuelle Verifikation, User)        <-- DONE (2026-05-03)
   |
   v
Stage 3.A (Provider-Refactors)                 <-- DEFERRED
Stage 3.B (Tab-Umbau + Cleanup)                <-- DEFERRED
   |
   v
Stage 4 (Tree-Assessment + Dead-Code-Cleanup) <-- DONE (2026-05-03)
   |
   v
Stage 5 (Doku & Cleanup)                      <-- DONE (2026-05-03)

Empfehlung Merge-Bloecke: Stage 0+1+2 = PR 1 (Recovery -- abgeschlossen). Stage 3 deferred. Stage 4+5 = PR 2 (Cleanup + Doku -- abgeschlossen).


Stage 0 -- Schema-Sanity-Check (optional, < 1 h) -- DONE

Modell Composer 2 Fast
Eingang DB-Zugriff dev/int/prod
Lieferumfang Bestaetigte Schema-Praesenz, kein Code-Output
Abnahme Schriftliche Bestaetigung: FileFolder + FileItem.folderId in allen drei Umgebungen vorhanden; ggf. Zeilenanzahl

Checkliste

  • information_schema-Query: FileFolder-Tabelle vorhanden in dev (poweron_management; int/prod separat durch Team)
  • information_schema-Query: FileItem.folderId-Spalte vorhanden (dev; FK-Verifikation: Spalte + Ziel FileFolder lt. Schema)
  • Optional: SELECT COUNT(*) FROM "FileFolder" und Stichprobe pro Mandant auf Datenkonsistenz

Hinweis (dev): python -m scripts.stage0_filefolder_schema_check im Ordner gateway.

Falls eine Umgebung abweicht: Plan zurueckziehen, Restore-/Migration- Subtask einplanen, bevor Stage 1 startet.


Stage 1.A -- Backend Boilerplate (Modell A reaktivieren) -- DONE

Modell Composer 2 Fast
Eingang Stage 0 OK; pre-7c05cb0-Stand von datamodelFiles.py und routeDataFiles.py per Git als Referenz
Lieferumfang Pydantic-Modelle, Routen-Skelett, Migrations-Skript archiviert
Abnahme App startet; GET /folders/tree?owner=me liefert leere Liste fuer leeren Mandanten ohne Fehler

Checkliste

  • datamodels/datamodelFiles.py: FileFolder Pydantic-Modell (id, name, parentId, mandateId, featureInstanceId, scope, neutralize, sysCreatedAt/By) mit frontend_options analog FileItem.scope; i18nModel("Ordner")
  • datamodels/datamodelFiles.py: FileItem.folderId: Optional[str] mit fk_target auf FileFolder.id
  • routes/routeDataFiles.py: Routen-Skelett (POST /folders, GET /folders/tree?owner=me|shared, PATCH /folders/{id}, POST /folders/{id}/move, DELETE /folders/{id}, PATCH /folders/{id}/scope, PATCH /folders/{id}/neutralize) — GET stub leere Liste; Mutations 501 bis Stage 1.B
  • migrations/migrate_folders_to_groups.py -> migrations/_archive/migrate_folders_to_groups.py + README mit Verweis auf diesen Plan

Stage 1.B -- Backend RBAC-Kern + Audit-Tests -- DONE

Modell Opus 4.6
Eingang Stage 1.A OK; interfaceRbac.buildFilesScopeWhereClause und interfaceDbManagement.updateFile als RBAC-Vorlage
Lieferumfang interfaceDbManagement/interfaceDbApp Folder-Methoden mit Owner-only-Guards; vollstaendige RBAC-Tests; Verifikation des bestehenden Files-Pfads
Abnahme test_folderRbac.py und test_folder_crud.py gruen; manuelle Stichprobe Zwei-User: Owner B kann auf Folder von User A keine Mutationsroute aufrufen (alle 403); contextOrphan-Render in der Tree-API korrekt

Checkliste

  • interfaces/interfaceDbApp.py / interfaceDbManagement.py: getOwnFolderTree(), getSharedFolderTree() mit Scope-Where-Klausel analog buildFilesScopeWhereClause
  • createFolder (Default-Scope personal)
  • renameFolder, moveFolder, deleteFolderCascade, patchFolderScope, patchFolderNeutralize -- jede Methode mit explizitem sysCreatedBy == currentUser-Guard (oder ALL); sonst PermissionError -> 403
  • patchFolderScope: Cascade-auf-Files als optionaler Parameter (D19); ohne expliziten Opt-in keine File-Aenderung
  • Verifikation bestehender Files-RBAC (kein Code-Aenderung, nur Tests): updateFile / deleteFile / deleteFilesBatch werfen fuer Nicht-Owner mit Scope-Sichtbarkeit weiterhin PermissionError
  • tests/unit/routes/test_folder_crud.py: Create / Move / Cascade-Delete / Scope-Patch / Neutralize-Patch (Happy Paths + Cross-Tenant)
  • tests/unit/interfaces/test_folderRbac.py: Zwei-User-Matrix, jede Mutationsroute fuer Nicht-Owner = 403; getSharedFolderTree respektiert Scope; contextOrphan-Fall (File geteilt, Folder nicht)

Stage 1.C -- Frontend FormGeneratorTree Greenfield -- DONE

Modell Opus 4.6
Eingang Architektur-Abschnitt (Provider-Interface) + RBAC-Modell aus diesem Plan; bestehende FolderTree.tsx als UX-Referenz; FormGeneratorTable als Bulk-Action-Pattern-Referenz
Lieferumfang Generische FormGeneratorTree-Komponente + FolderFileProvider + Vitest+RTL-Tests; kann parallel zu Stage 1.A/1.B laufen, da Provider gegen Mocks getestet wird
Abnahme Vitest gruen inkl. Edge-Cases (mixed selection, partial-selected, cascade, contextOrphan); Storybook-Story (oder Smoke-Page) zeigt beide Modi own/shared mit Mock-Provider

Checkliste

  • Ordner components/FormGenerator/FormGeneratorTree/ mit FormGeneratorTree.tsx, FormGeneratorTree.module.css, types.ts, providers/FolderFileProvider.tsx, __tests__/
  • TreeNodeProvider-Interface (siehe Architektur-Abschnitt) in types.ts
  • Built-in Features: - Expand/Collapse mit Tastatur-Navigation - Multiselect mit Shift/Ctrl/Cmd; getrennt zwischen ownership='own' und 'shared'; gemischte Selektion blockiert Bulk-Mutationen - Cascading-Selection (Folder selektieren -> alle eigenen Children), Partial-Selected-Indikator - DnD application/x-poweron-tree-items; aus shared nur lesende Drops, kein Move - Inline-Rename (Doppelklick / F2) -- nur fuer ownership='own' - Scope/Neutralize interaktiv nur fuer Eigene; im geteilten Tree Indikator-Icons - contextOrphan-Render-Regel (Knoten am Tree-Root mit Hint-Icon) - Bulk-Action-Toolbar analog FormGeneratorTable.batchActions; im geteilten Tree maximal Lesen/Download
  • FolderFileProvider.tsx: Referenzimplementierung gegen die in Stage 1.B definierten Folder-API-Routen (kann initial gegen Mock-Server laufen)
  • Vitest+RTL-Tests: Lazy-Load, Multiselect-Cascade, Mixed-Selection- Block, DnD-Payload, Inline-Rename, Bulk-Delete, contextOrphan- Render -- alle gegen Provider-Mock

Stage 2.A -- FE Recovery-Boilerplate -- DONE

Modell Composer 2 Fast
Eingang Stage 1.B + 1.C OK; pre-7c05cb0-Stand von FileContext.tsx, useFiles.ts, fileApi.ts per Git
Lieferumfang Folder-Operationen reaktiviert in Context/Hook/API-Wrapper
Abnahme TypeScript clean; bestehende Files-Tests gruen; FilesPage rendert weiterhin (alter Code-Pfad noch aktiv)

Checkliste

  • contexts/FileContext.tsx: FolderInfo-Typ + handleCreateFolder, handleRenameFolder, handleDeleteFolderCascade, handleMoveFolder, handleMoveFiles, handleDownloadFolder, refreshFolders, treeFileNodes, expandedFolderIds -- gegen die neuen Routen
  • hooks/useFiles.ts: Folder-Operationen wieder einbauen
  • api/fileApi.ts: FileInfo.folderId, getFolderTree, createFolder, renameFolder, moveFolder, deleteFolderCascade, moveFiles als API-Wrapper
  • pages/views/workspace/WorkspaceInput.tsx: TreeItemDrop.type auf 'file' | 'folder' zuruecksetzen

Stage 2.B -- FE UI-Komposition (FilesTab + FilesPage) -- DONE

Modell Opus 4.6
Eingang Stage 2.A OK
Lieferumfang FilesTab mit zwei Tree-Sektionen; FilesPage Split-View mit Toggle Tree/Liste-mit-Gruppen-Sicht
Abnahme Storybook/Smoke der UDB zeigt beide Sektionen, leerer Geteilt-Tree kollabiert; FilesPage-Toggle wechselt korrekt

Checkliste

  • components/UnifiedDataBar/FilesTab.tsx: zwei FormGeneratorTree- Sektionen via FolderFileProvider - Eigene mit Toolbar („Neuer Ordner“, „Multidelete“, „Multimove“, „Download als ZIP“) - Geteilt mit mir read-only, kollabiert wenn leer, Owner-Name als oberste Sortier-Achse (D16/D17) - compact={true}; onSendToChat auf 'file' | 'folder'
  • pages/basedata/FilesPage.tsx: Split-View - links zwei FormGeneratorTree-Sektionen (Eigene / Geteilt) mit klar getrennten Headern - rechts FormGeneratorTable mit Toggle „Tree-Sicht | Liste-mit-Gruppen-Sicht“; groupingConfig bleibt als alternative Sicht - Selektion im geteilten Tree -> Tabellenzeilen read-only

Stage 2.C -- Manuelle Verifikation (User) -- DONE

Modell -- (User)
Eingang Stage 2.B deployed in dev; Test-Mandant mit zwei Usern verfuegbar
Lieferumfang Bestaetigung der Akzeptanzkriterien 1-8b + 10
Abnahme Alle Punkte unten haken; sonst Defekt-Issue oeffnen, zustaendige Stage erneut

Checkliste

  • Neuer Folder anlegen -> Default personal, nur eigener Tree zeigt ihn
  • Scope/Neutralize: Eigene interaktiv, Geteilt nur Indikator (Fix 2026-05-02: FolderFileProvider typeMap fuer Folder- vs File-Endpoints)
  • Multiselect Folder + Files (eigen) cascadiert; gemischt Eigene/Geteilt blockiert Bulk-Mutationen mit Hinweis
  • Multidelete cascadiert nur bei reinem Owner-Set; Confirmation (Fix 2026-05-02: TreeBatchAction.typeFilter, separate Ordner/Dateien-Buttons, window.confirm)
  • DnD: File von Folder A nach Folder B (eigen) funktioniert; Drag aus „Geteilt mit mir“ in eigenen Folder = kein Move
  • DnD: File aus FilesTab in Workspace-Chat-Input dropped korrekt (Standard-MIME erkannt) -- aus beiden Trees lesend zulaessig
  • Cross-User-Sharing: User A setzt Folder-Scope auf mandate -> User B sieht den Folder im „Geteilt mit mir“ (read-only, 403 auf Mutationen via DevTools-Probe) (nicht getestet -- erfordert zwei User im selben Mandant)
  • FilesPage-Toggle: umgebaut zu „Ordner-Sicht“ (Tabelle gefiltert nach selektiertem Ordner) und „Alle Dateien“ (ohne Folder-Filterung). Gruppierung entfernt.

Zusaetzliche Fixes aus Stage 2.C Verifikation (2026-05-02/03):

  • FolderFileProvider: 405-Fix (falscher API-Endpoint /api/files -> /api/files/list)
  • FolderFileProvider: typeMap eingefuehrt -- alle Operationen (rename, delete, move, patchScope, patchNeutralize) verwenden korrekte Endpoints fuer Folder vs File
  • FormGeneratorTree: Refresh-Icon im Section-Header (FaSyncAlt)
  • FormGeneratorTree: Tree-Reload nach Batch-Actions und inline Delete
  • FormGeneratorTree: window.confirm() vor Delete (inline + batch)
  • FilesPage: Tree-Table-Sync (selectedFolderId filtert Tabelle, File-Klick highlighted Tabellenzeile)

Stage 3.A -- Provider-Refactors (Sources + Feature-Daten) -- DEFERRED

Status DEFERRED (2026-05-03)
Modell Opus 4.6
Eingang Stage 1.C OK (FormGeneratorTree stabil)
Lieferumfang ConnectorBrowseProvider und FeatureDataProvider mit Aequivalenz-Garantie zur bisherigen Logik
Abnahme Vitest+RTL-Tests pro Provider gruen; Smoke-Test: alle bisherigen Knoten-Ebenen sind reproduzierbar

Grund fuer Deferral: SourcesTab und ChatsTab enthalten hochgradig domaen-spezifische Tree-Logik (Multi-Level lazy-load ueber Connections, Services, Feature-Daten, Mandate-Gruppierungen, Chat-Filterung), die sich nicht sinnvoll auf die generische TreeNodeProvider-Abstraktion abbilden laesst, ohne die Provider-Interfaces erheblich zu erweitern. Der Aufwand uebersteigt den Nutzen gegenueber dem bestehenden, funktionierenden Code. Kann bei Bedarf spaeter als eigenes Projekt aufgegriffen werden.

Checkliste

  • providers/ConnectorBrowseProvider.tsx
  • providers/FeatureDataProvider.tsx
  • Pro Provider Vitest+RTL-Tests

Stage 3.B -- Tab-Umbau + Cleanup (Feature-Flag) -- DEFERRED

Status DEFERRED (2026-05-03)
Modell Composer 2 Fast
Eingang Stage 3.A OK
Lieferumfang SourcesTab.tsx und ChatsTab.tsx auf FormGeneratorTree; alter Custom-Tree-Code entfernt; Feature-Flag fuer Rollback
Abnahme Mit Flag aus = Status quo; Flag ein = neue Tree-UI; manuelle Verifikation der UDB-Tabs OK

Grund fuer Deferral: Abhaengig von Stage 3.A (s.o.).

Checkliste

  • providers/ChatProvider.tsx
  • SourcesTab.tsx Umbau
  • ChatsTab.tsx Umbau
  • Feature-Flag
  • Manuelle Verifikation

Stage 4 -- Restliche Tree-Migrationen (Assessment + Cleanup) -- DONE

Status DONE (2026-05-03)
Modell Composer 2 Fast
Ergebnis Assessment aller Kandidaten; Dead-Code geloescht; keine weiteren Migrationen sinnvoll

Assessment-Ergebnis

  • FolderTree.tsx + SharepointBrowseTree.tsx + gesamter components/FolderTree/-Ordner: GELOESCHT (Dead Code, keine externen Imports). ESLint-Deprecation-Regel entfernt.
  • DataPicker.tsx: SKIP -- Schema-basierter Automation2-Picker (read-only, in-memory, kein Entity-Tree; TreeNodeProvider-Abstraktion passt nicht)
  • InstanceHierarchyView.tsx: SKIP -- Read-only Admin-Visualisierung (synchrone Props, kein CRUD/DnD/Multiselect; 300 Zeilen, stabil)
  • AccessRulesEditor.tsx: SKIP -- Kein Tree, sondern tabellarischer RBAC-Rule-Editor mit Checkbox-Matrix (774 Zeilen; komplett anderes Paradigma)
  • TreeNavigation.tsx: SKIP -- Sidebar-Navigation mit React Router (NavLink, aktiver Pfad); UI-Navigation, nicht Daten-Tree
  • redmineTreeLogic.ts: SKIP -- Reiner BFS-Algorithmus fuer Redmine-Tickets; kein UI-Komponente

Stage 5 -- Doku & Cleanup -- DONE

Modell Composer 2 Fast
Eingang Stage 2 mindestens gemerged; spaetestens nach Stage 4
Lieferumfang Wiki-Update der kanonischen Stellen + neuer Tree-Topic
Abnahme lastReviewed aktualisiert; wiki/c-work/_CHANGELOG.md pro Stage eine Zeile

Checkliste

  • wiki/b-reference/frontend-nyla/architecture.md: Komponenten-Tabelle um FormGeneratorTree ergaenzt, FolderTree-Eintrag ersetzt, components/-Ordner-Beschreibung aktualisiert
  • wiki/b-reference/frontend-nyla/formgenerator.md: Abschnitt FormGeneratorTree (Provider-Interface, Built-in Features, Vergleichstabelle Tree vs. Table-with-grouping, Props, Verwendung)
  • wiki/TOPICS.md: Neuer Eintrag FormGenerator (Table, Form, Tree, Report)
  • wiki/c-work/_CHANGELOG.md: Stage 4+5 Eintrag
  • Plan-Dokument verschoben nach 4-done/

Eskalations-Regel zwischen Modellen

  • Composer-Packet stoesst auf Entscheidung, die nicht in D1-D22 steht -> stoppen, Plan ergaenzen (Opus), dann zurueck.
  • Composer-Test schlaegt fehl und Fix ist nicht mechanisch -> Diagnose-Loop in Opus, Fix-Patch zurueck zu Composer.
  • Pre-Commit-Hook-Modifikationen (lint/format) bleiben in Composer.

Akzeptanzkriterien

# Kriterium (Given-When-Then) Prio
1 Given ein Mandant mit eigenen Folders + Files, When der User die Files-Page oeffnet, Then die linke Spalte zeigt zwei Sektionen („Eigene“, „Geteilt mit mir“) mit rekursiven Folder-Trees must
2 Given der User klickt im Eigenen-Tree auf „Neuer Ordner“, When er einen Namen bestaetigt, Then der Folder erscheint sofort, ist persistiert mit Default-Scope personal und nur fuer den Ersteller sichtbar must
3 Given ein eigener Folder mit 5 Files, When der User per Multiselect den Folder + 2 weitere Files anwaehlt und „Loeschen“ klickt, Then Confirmation Wirklich 1 Folder (mit 5 Files) und 2 weitere Files loeschen?; nach OK sind alle 8 Items weg must
4 Given ein eigenes File mit scope=personal, When der User auf das Scope-Icon klickt, Then das Icon cyclet personal -> featureInstance -> mandate -> global -> personal; jeder Click persistiert via PATCH must
5 Given ein eigener Folder, When der Owner den Scope auf mandate patcht, Then Dialog fragt „Files mit teilen?“ (Default: nein); bei Ja erben enthaltene Files den Scope, sonst bleibt deren Scope unveraendert must
6 Given der User draggt ein File aus dem FilesTab, When im Workspace-Chat-Input gedropped, Then Standard-MIME application/x-poweron-tree-items wird empfangen; aus beiden Trees lesend zulaessig must
7 Given UDB ist offen, When der User durch Files / Sources / Chats blaettert, Then alle drei Tabs zeigen FormGeneratorTree mit konsistentem Look-and-Feel should
8 Given zwei Mandanten-Mitglieder (User A, User B), When User A einen Folder anlegt (Default personal), Then User B sieht den Folder nicht. Erst nach mandate-Scope-Patch erscheint er bei User B im „Geteilt mit mir“ (read-only, Indikator-Icons, keine Mutations-Affordance) must
8a Given User A teilt eine Datei (scope=mandate), aber den enthaltenden Folder nicht, When User B die Files-Page oeffnet, Then die Datei erscheint im Root des „Geteilt mit mir“-Trees mit contextOrphan-Hint; aendert User A den Folder-Scope, wandert die Datei automatisch unter den Folder should
8b Given User B steht im „Geteilt mit mir“ auf einem Folder von User A, When er Mutationsroute (rename / move / delete / scope-cycle / neutralize-toggle) aufruft, Then Backend antwortet 403 und UI hat keine Affordance must
9 Given FormGeneratorTable.groupingConfig aktiv auf AutomationsDashboardPage, When der User die Page laedt, Then Workflow-Runs sind weiterhin nach Mandant + Status gruppiert must
10 Given FilesPage View-Toggle steht auf „Liste-mit-Gruppen-Sicht“, When der User auf „Tree-Sicht“ und zurueck wechselt, Then beide Sichten zeigen dieselben Files; TableGrouping und FileFolder sind orthogonal sichtbar should
11 Given Drag-Source aus SourcesTab, When auf eine Workflow-Node gedropped, Then Empfaenger sieht denselben application/x-poweron-tree-items-MIME wie aus FilesTab should

Testplan

ID AC Art Automatisiert Repo-Pfad Status
T1 1, 2 api ja gateway/tests/unit/routes/test_folder_crud.py pending
T2 3 api ja gateway/tests/unit/routes/test_folder_cascade_delete.py pending
T3 4, 5 api ja gateway/tests/unit/routes/test_folder_scope_neutralize.py (inkl. Cascade-Opt-in) pending
T4 8, 8a, 8b api ja gateway/tests/unit/interfaces/test_folderRbac.py (zwei User; alle Mutationsrouten 403; Render-Regel inkl. contextOrphan) pending
T5 1-6 ui ja frontend_nyla/src/components/FormGenerator/FormGeneratorTree/__tests__/FormGeneratorTree.test.tsx pending
T6 6, 11 ui ja frontend_nyla/src/components/FormGenerator/FormGeneratorTree/__tests__/dnd.test.tsx pending
T7 1-7, 10 manuell nein -- pending
T8 9 manuell nein -- pending
T9 7 manuell nein -- pending

Risiken

Risiko Wahrscheinlichkeit Impact Mitigation
FileFolder / folderId in einer Umgebung doch entfernt (Abweichung vom Repo-Stand) sehr niedrig hoch Stage 0 Spot-Check; ggf. Restore aus Backup oder Ableitung aus TableGrouping.meta.migratedFromFolderId (One-Shot)
Read-only-Kontrakt fuer Folders nicht durchgaengig (UI versteckt, Backend offen) mittel hoch Pro Mutationsroute expliziter sysCreatedBy == currentUser-Guard; Zwei-User-Tests pro Route in Stage 1
contextOrphan-Render leakt Pfad-/Owner-Information mehr als beabsichtigt niedrig mittel Im geteilten Tree nur Owner-Name + File-Name; keine Folder-Pfade aus dem fremden Tree
_workspaceTools.py (writeFile -> Group-Auto-Add) bricht nach Recovery mittel mittel Code unangetastet; TableGrouping-Pfad bleibt; User kann Files spaeter manuell in Folder verschieben
TableGrouping-Eintrag und FileFolder-Eintrag widersprechen sich (selber File in Group X UND Folder Y) hoch niedrig By design orthogonal; UI zeigt beide Sichten ueber Toggle; keine Konsistenz-Erzwingung
Stage 3 enthuellt subtile Abhaengigkeiten in SourcesTab/ChatsTab hoch mittel Eingetreten: Stage 3 deferred -- domaen-spezifische Logik passt nicht auf generische TreeNodeProvider-Abstraktion
Cascade-Selection-Logik im FormGeneratorTree subtil falsch (z.B. partial-selected) mittel niedrig Umfangreiche Vitest+RTL-Tests in Stage 1 mit Provider-Mock

  • PR: tba (Stage 0+1+2 -- bereit), Stage 3 deferred, Stage 4+5 abgeschlossen
  • Issue: --
  • Vorgaenger-Commits: c8e9304, e7a79a3, 7c05cb0
  • Migrations-Skript (archiviert): gateway/modules/migrations/_archive/migrate_folders_to_groups.py
  • Heutige FolderTree-Komponente (vollstaendig, nicht mehr eingebunden): frontend_nyla/src/components/FolderTree/FolderTree.tsx

Abschluss

  • b-reference/frontend-nyla/architecture.md aktualisiert (Komponenten-Tabelle, UDB-Beschreibung)
  • b-reference/frontend-nyla/formgenerator.md aktualisiert (Abschnitt FormGeneratorTree)
  • TOPICS.md aktualisiert
  • Dokument verschoben nach 4-done/