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.tsxfrontend_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/patchGroupNeutralizesind 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-idsundapplication/tree-itemsmittype: 'file' | 'group'waren auf Tabellen-Gruppierungen optimiert;WorkspaceInput.TreeItemDropmusste mitziehen. - Mandanten-geteilte Folder-Strukturen lassen sich mit per-User-
TableGroupingnicht 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
- Recovery der Datei-/Ordner-Funktionen, ohne
FormGeneratorTable.groupingConfigfuer andere Use-Cases zurueckzubauen. - Konsolidierung der Tree-Implementierungen auf eine getestete Komponente -- ein Bugfix-/Feature-Punkt statt acht.
- 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:
- Default-Sichtbarkeit
personal. Jedes neu angelegte Folder/File ist nur fuersysCreatedBy = currentUsersichtbar. - Sharing nur ueber
scope. Andere User sehen ein Objekt, sobald der Eigentuemer den Scope explizit erhoeht (featureInstance/mandate/global;globalsetztALL-Permission voraus, in der Praxis SysAdmin). - Mutation = Owner-only (oder
ALL-Permission). Rename / Move / Delete / Scope-Cycle / Neutralize-Toggle sind fuer Nicht-Eigentuemer 403; die UI blendet die Affordance aus. - Zwei getrennte Trees in jeder Files-Sicht:
- „Eigene“ --
sysCreatedBy = currentUser. Volles CRUD; das Scope-Icon zeigt Sharing-Status an. - „Geteilt mit mir“ --
sysCreatedBy != currentUser, ueberscopesichtbar. 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).
- „Eigene“ --
- Render-Regel. Files erscheinen unter ihrem Folder, wenn der Folder fuer
den Current-User sichtbar ist. Fehlt der Folder-Kontext (
FileItem.scopegeteilt,FileFolder.scopenicht), wird die Datei im Root des passenden Trees mitcontextOrphan-Hint („kein Folder-Kontext geteilt“) gerendert. Aendert der Owner spaeter den Folder-Scope, wandert die Datei automatisch unter den Folder. - 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.
- 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).
- Folder-Neutralize-Vererbung beim Drop = nein. Beim Drop eines Files in
einen Folder mit
neutralize=truewird der Datei-Wert nicht automatisch geaendert; ein Indikator zeigt den Mismatch.
Datentabellen-Sicht (
FormGeneratorTable.groupingConfigauf Files): Status quo. Die Tabelle zeigt weiterhin alle sichtbaren Files (eigene + geteilte) flach; nur die Tree-Sicht trennt Eigene/Geteilt. Falls spaeter gewuenscht, kann einownerFilterergaenzt 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.
FormGeneratorTreeals Komponente infrontend_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.
FolderFileProviderals erste Implementierung; recovered die in7c05cb0entfallenen Funktionen auf Basis Modell A. - Ziel 3. UDB-Tabs
FilesTab,SourcesTab,ChatsTabrendernFormGeneratorTree.FilesTabzeigt zwei Tree-Sektionen (Eigene + Geteilt). - Ziel 4.
FilesPage.tsxSplit-View: links zweiFormGeneratorTree- Sektionen, rechtsFormGeneratorTablemit View-Toggle „Tree-Sicht | Liste-mit-Gruppen-Sicht“. - Nicht-Ziel.
FormGeneratorTable.groupingConfigrebauen oder entfernen. - Nicht-Ziel.
FormGeneratorReportanfassen. - 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.typeauf'file' | 'folder'zuruecksetzen, neuer Standard-MIMEapplication/x-poweron-tree-items - Stage 3:
components/UnifiedDataBar/SourcesTab.tsxundChatsTab.tsxaufFormGeneratorTree(je ein Tree, kein Sharing-Split) - Stage 4 (optional):
SharepointBrowseTree,DataPicker,InstanceHierarchyView,AccessRulesEditor
Gateway
- Recovery:
datamodels/datamodelFiles.py-- neuesFileFolderPydantic- Modell +FileItem.folderId: Optional[str]mitfk_targetaufFileFolder.id - Recovery:
interfaces/interfaceDbApp.py/interfaces/interfaceDbManagement.py--getOwnFolderTree(),getSharedFolderTree(),createFolder(Default-Scopepersonal),renameFolder,moveFolder,deleteFolderCascade,patchFolderScope,patchFolderNeutralize. Mutationen strikt owner-only (oderALL). - 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.pyGroup-Endpunkte (/groups/{groupId}/scope,/neutralize,/download,delete_group) fuerFormGeneratorTable.groupingConfig-Use-Cases - Archivieren:
migrations/migrate_folders_to_groups.py->migrations/_archive/mit kurzem README (audit trail) - Unangetastet:
serviceCenter/.../coreTools/_workspaceTools.pyundmainServiceChat.py(TableGrouping-Pfad bleibt). Bei Bedarf spaeter_listFolders/_listFilesInFolderals 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 + ZielFileFolderlt. 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:FileFolderPydantic-Modell (id, name, parentId, mandateId, featureInstanceId, scope, neutralize, sysCreatedAt/By) mitfrontend_optionsanalogFileItem.scope;i18nModel("Ordner")datamodels/datamodelFiles.py:FileItem.folderId: Optional[str]mitfk_targetaufFileFolder.idroutes/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.Bmigrations/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 analogbuildFilesScopeWhereClausecreateFolder(Default-Scopepersonal)renameFolder,moveFolder,deleteFolderCascade,patchFolderScope,patchFolderNeutralize-- jede Methode mit explizitemsysCreatedBy == currentUser-Guard (oderALL); sonstPermissionError-> 403patchFolderScope: 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/deleteFilesBatchwerfen fuer Nicht-Owner mit Scope-Sichtbarkeit weiterhinPermissionError 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;getSharedFolderTreerespektiert 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/mitFormGeneratorTree.tsx,FormGeneratorTree.module.css,types.ts,providers/FolderFileProvider.tsx,__tests__/ TreeNodeProvider-Interface (siehe Architektur-Abschnitt) intypes.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 - DnDapplication/x-poweron-tree-items; aussharednur lesende Drops, kein Move - Inline-Rename (Doppelklick / F2) -- nur fuerownership='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 analogFormGeneratorTable.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 Routenhooks/useFiles.ts: Folder-Operationen wieder einbauenapi/fileApi.ts:FileInfo.folderId,getFolderTree,createFolder,renameFolder,moveFolder,deleteFolderCascade,moveFilesals API-Wrapperpages/views/workspace/WorkspaceInput.tsx:TreeItemDrop.typeauf'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: zweiFormGeneratorTree- Sektionen viaFolderFileProvider- 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};onSendToChatauf'file' | 'folder'pages/basedata/FilesPage.tsx: Split-View - links zweiFormGeneratorTree-Sektionen (Eigene / Geteilt) mit klar getrennten Headern - rechtsFormGeneratorTablemit Toggle „Tree-Sicht | Liste-mit-Gruppen-Sicht“;groupingConfigbleibt 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.tsxproviders/FeatureDataProvider.tsxPro 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.tsxSourcesTab.tsxUmbauChatsTab.tsxUmbauFeature-FlagManuelle 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+ gesamtercomponents/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-TreeredmineTreeLogic.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 umFormGeneratorTreeergaenzt,FolderTree-Eintrag ersetzt,components/-Ordner-Beschreibung aktualisiertwiki/b-reference/frontend-nyla/formgenerator.md: AbschnittFormGeneratorTree(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 |
Links
- 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.mdaktualisiert (Komponenten-Tabelle, UDB-Beschreibung)b-reference/frontend-nyla/formgenerator.mdaktualisiert (Abschnitt FormGeneratorTree)TOPICS.mdaktualisiert- Dokument verschoben nach
4-done/