# Navigation API Konzept ## Übersicht Dieses Dokument beschreibt das Konzept für die Navigation-API, welche dem UI alle notwendigen Informationen für das Rendering der Navigation liefert. Die API ist die **Single Source of Truth** für die Navigationsstruktur. ## Grundprinzipien ### 1. Backend liefert, UI rendert - Das **Gateway** bestimmt, welche Navigationselemente ein User sehen darf - Das **UI** rendert nur das, was es vom Gateway erhält - Keine Permission-Logik im UI für Navigation ### 2. Trennung von Daten und Darstellung - Die API liefert **keine Style-Elemente** (Icons, CSS-Klassen, etc.) - Die API liefert **UI-Komponenten-Codes**, die das UI auf seine Komponenten mappt - Verschiedene UIs können dieselben Daten unterschiedlich darstellen ### 3. Nur sichtbare Elemente - Elemente ohne Zugriffsberechtigung werden **nicht** in den Baum aufgenommen - Kein `hasAccess: false` - wenn kein Zugriff, dann nicht im Response ### 4. Fehlertoleranz - Wenn ein Code im UI nicht gemappt werden kann → Fehlertext im Nav-Tree - Synchronisationsprobleme zwischen Gateway und UI werden sofort sichtbar ### 5. Personalisierbare Sortierung - Jedes Element hat eine `order` Nummer für die Sortierung - User können die Reihenfolge anpassen durch User-Settings - Gateway liefert bereits sortiert (Default + User-Override) --- ## API Response Struktur ### Endpoint ``` GET /api/navigation ``` ### Response Format ```json { "language": "de", "blocks": [ { "type": "static", "id": "system", "title": "SYSTEM", "order": 10, "items": [...] }, { "type": "static", "id": "workflows", "title": "WORKFLOWS", "order": 20, "items": [...] }, { "type": "dynamic", "id": "features", "title": "FEATURES", "order": 100, "mandates": [...] } ] } ``` --- ## Block-Typen ### Static Block Für System-Seiten, die nicht an Feature-Instanzen gebunden sind. ```json { "type": "static", "id": "system", "title": "SYSTEM", "order": 10, "items": [ { "uiComponent": "page.system.home", "uiLabel": "Übersicht", "uiPath": "/", "order": 10, "objectKey": "ui.system.home" }, { "uiComponent": "page.system.settings", "uiLabel": "Einstellungen", "uiPath": "/settings", "order": 20, "objectKey": "ui.system.settings" } ] } ``` **Felder pro Item:** | Feld | Beschreibung | |------|--------------| | `uiComponent` | Eindeutiger Code für UI-Mapping (z.B. `page.system.home`) | | `uiLabel` | Anzeigetext (bereits in der gewählten Sprache) | | `uiPath` | URL-Pfad für Navigation | | `order` | Sortierreihenfolge (Default oder User-Override) | | `objectKey` | Vollqualifizierter RBAC-Objektname (für Debugging/Referenz) | ### Dynamic Block Für Feature-Instanzen, gruppiert nach Mandanten. ```json { "type": "dynamic", "id": "features", "title": "FEATURES", "order": 100, "mandates": [ { "id": "5ce04434-c4ce-4269-9861-19ff7ebc2a1d", "uiLabel": "SOHA Treuhand AG", "order": 10, "features": [ { "uiComponent": "feature.trustee", "uiLabel": "Treuhand", "order": 10, "instances": [ { "id": "2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0", "uiLabel": "ValueOn AG 2026", "order": 10, "views": [ { "uiComponent": "page.feature.trustee.dashboard", "uiLabel": "Übersicht", "uiPath": "/mandates/5ce04434.../trustee/2fc48f66.../dashboard", "order": 10, "objectKey": "ui.feature.trustee.dashboard" }, { "uiComponent": "page.feature.trustee.positions", "uiLabel": "Positionen", "uiPath": "/mandates/5ce04434.../trustee/2fc48f66.../positions", "order": 20, "objectKey": "ui.feature.trustee.positions" } ] } ] } ] } ] } ``` **Hinweis:** Der `dynamic` Block ist nur vorhanden, wenn der User mindestens eine Feature-Instanz hat. **Sortierung auf allen Ebenen:** - Mandanten: `mandates[].order` - Features: `features[].order` - Instanzen: `instances[].order` - Views: `views[].order` --- ## User-Personalisierung (Order Override) ### Konzept User können die Reihenfolge der Navigation anpassen. Die Einstellungen werden in den User-Settings gespeichert. ### User Settings Struktur ```json { "userId": "user-123", "settings": { "navOrder": { "page.system.home": 10, "page.system.prompts": 5, "page.admin.users": 100, "feature.trustee": 20, "feature.realestate": 10, "mandate.5ce04434-...": 5, "instance.2fc48f66-...": 1 } } } ``` ### Merge-Logik im Gateway ``` Effektive Order = User-Override ?? Default-Order ?? 50 ``` 1. Gateway lädt Default-Order aus Konfiguration 2. Gateway lädt User-Settings (falls vorhanden) 3. Für jedes Element: User-Order überschreibt Default 4. Sortierung erfolgt im Gateway, UI erhält bereits sortiert ### Sortier-Priorität | Ebene | Key für navOrder | Beispiel | |-------|------------------|----------| | Block | Block-ID | `"system"`, `"workflows"`, `"features"` | | Static Item | uiComponent | `"page.system.home"` | | Mandate | `mandate.{id}` | `"mandate.5ce04434-..."` | | Feature | uiComponent | `"feature.trustee"` | | Instance | `instance.{id}` | `"instance.2fc48f66-..."` | | View | uiComponent | `"page.feature.trustee.dashboard"` | ### API für Order-Update ``` PUT /api/user/settings/navOrder Body: { "page.system.prompts": 5, "feature.trustee": 20 } ``` --- ## Code-Konvention ### Statische Seiten ``` page.system. page.admin. ``` Beispiele: - `page.system.home` - `page.system.settings` - `page.system.prompts` - `page.admin.users` - `page.admin.mandates` ### Feature-Seiten ``` page.feature.. ``` Beispiele: - `page.feature.trustee.dashboard` - `page.feature.trustee.positions` - `page.feature.trustee.instance-roles` - `page.feature.realestate.projects` ### Feature-Codes (für Gruppierung) ``` feature. ``` Beispiele: - `feature.trustee` - `feature.realestate` - `feature.chatworkflow` --- ## ObjectKey-Konvention (RBAC) Die `objectKey` werden für Access Rules verwendet und sind vollqualifiziert: ### UI Objekte ``` ui.system. → System-Seiten ui.admin. → Admin-Seiten ui.feature.. → Feature-Views ``` ### DATA Objekte ``` data.system. → System-Tabellen data.feature..
→ Feature-Tabellen ``` ### RESOURCE Objekte ``` resource.system. → System-Aktionen resource.feature.. → Feature-Aktionen ``` --- ## UI Mapping & Fehlerbehandlung ### UI Code Registry Das UI definiert ein Mapping von Codes zu Komponenten: ```typescript // frontend_nyla/src/config/pageRegistry.ts export const PAGE_REGISTRY: Record = { // Static pages 'page.system.home': { component: HomePage, icon: FaHome, layout: 'default', }, 'page.system.settings': { component: SettingsPage, icon: FaCog, layout: 'default', }, 'page.admin.users': { component: AdminUsersPage, icon: FaUsers, layout: 'admin', }, // Feature pages 'page.feature.trustee.dashboard': { component: TrusteeDashboardView, icon: FaChartBar, layout: 'feature', }, 'page.feature.trustee.positions': { component: TrusteePositionsView, icon: FaListAlt, layout: 'feature', }, // ... }; export const FEATURE_REGISTRY: Record = { 'feature.trustee': { icon: FaBriefcase, color: '#e74c3c', }, 'feature.realestate': { icon: FaBuilding, color: '#3498db', }, }; ``` ### Fehlerbehandlung Wenn ein `uiComponent` Code nicht gemappt werden kann: ```typescript function renderNavItem(item: NavItem) { const pageDef = PAGE_REGISTRY[item.uiComponent]; if (!pageDef) { // Fehler sichtbar machen! return ( ⚠️ Unbekannter Code: {item.uiComponent} ObjectKey: {item.objectKey} ); } return ( {item.uiLabel} ); } ``` **Wichtig:** Fehler werden im Nav-Tree angezeigt, nicht versteckt! So werden Synchronisationsprobleme zwischen Gateway und UI sofort sichtbar. ### Sortierung im UI Das UI sortiert die bereits vom Gateway sortierten Elemente NICHT neu. Die Sortierung erfolgt ausschliesslich im Gateway basierend auf: 1. Default-Order aus Konfiguration 2. User-Override aus Settings (falls vorhanden) ```typescript // UI rendert in der Reihenfolge wie empfangen function renderBlock(block: Block) { // items sind bereits sortiert return block.items.map(item => renderNavItem(item)); } ``` --- ## Implementierungsschritte ### Phase 1: Gateway - Navigation Endpoint 1. **Neuer Endpoint** `/api/navigation` erstellen 2. **Static Blocks** aus `NAVIGATION_SECTIONS` generieren 3. **Dynamic Block** aus Feature-Instanzen generieren 4. **Permission-Filter** anwenden (nur sichtbare Elemente) 5. **uiComponent Codes** gemäß Konvention setzen 6. **Default-Order** für alle Elemente setzen ### Phase 2: Gateway - Access Rules 1. **TEMPLATE_ROLES** auf vollqualifizierte `objectKey` umstellen 2. **Permission-Check** auf `objectKey` umstellen 3. **Migration** bestehender Access Rules (falls nötig) ### Phase 3: Gateway - User Settings 1. **User Settings Modell** erweitern mit `navOrder` 2. **API Endpoint** `PUT /api/user/settings/navOrder` erstellen 3. **Merge-Logik** implementieren (User-Override → Default) 4. **Sortierung** vor Response durchführen ### Phase 4: Frontend 1. **PAGE_REGISTRY** erstellen mit uiComponent → Component Mapping 2. **FEATURE_REGISTRY** erstellen mit Feature-Code → Style Mapping 3. **useNavigation Hook** auf neuen Endpoint umstellen 4. **MandateNavigation** refactoren für neue Datenstruktur 5. **Fehlerhandling** für unbekannte uiComponents implementieren ### Phase 5: Frontend - Drag & Drop (Optional) 1. **Drag & Drop UI** für Nav-Tree Sortierung 2. **API Call** bei Order-Änderung 3. **Optimistic Update** für schnelle UX ### Phase 6: Cleanup 1. **FEATURE_REGISTRY** (alte Version in mandate.ts) entfernen 2. **Hardcodierte Navigation** im Frontend entfernen 3. **Tests** für Synchronisation Gateway ↔ UI --- ## Beispiel: Vollständige Response ```json { "language": "de", "blocks": [ { "type": "static", "id": "system", "title": "SYSTEM", "order": 10, "items": [ { "uiComponent": "page.system.home", "uiLabel": "Übersicht", "uiPath": "/", "order": 10, "objectKey": "ui.system.home" }, { "uiComponent": "page.system.settings", "uiLabel": "Einstellungen", "uiPath": "/settings", "order": 20, "objectKey": "ui.system.settings" } ] }, { "type": "static", "id": "workflows", "title": "WORKFLOWS", "order": 20, "items": [ { "uiComponent": "page.system.playground", "uiLabel": "Chat Playground", "uiPath": "/workflows/playground", "order": 10, "objectKey": "ui.system.playground" }, { "uiComponent": "page.system.chats", "uiLabel": "Chats", "uiPath": "/workflows/list", "order": 20, "objectKey": "ui.system.chats" } ] }, { "type": "static", "id": "basedata", "title": "BASISDATEN", "order": 30, "items": [ { "uiComponent": "page.system.prompts", "uiLabel": "Prompts", "uiPath": "/basedata/prompts", "order": 10, "objectKey": "ui.system.prompts" }, { "uiComponent": "page.system.files", "uiLabel": "Dateien", "uiPath": "/basedata/files", "order": 20, "objectKey": "ui.system.files" } ] }, { "type": "static", "id": "admin", "title": "ADMINISTRATION", "order": 200, "items": [ { "uiComponent": "page.admin.users", "uiLabel": "Benutzer", "uiPath": "/admin/users", "order": 10, "objectKey": "ui.admin.users" }, { "uiComponent": "page.admin.mandates", "uiLabel": "Mandanten", "uiPath": "/admin/mandates", "order": 20, "objectKey": "ui.admin.mandates" } ] }, { "type": "dynamic", "id": "features", "title": "MEINE FEATURES", "order": 100, "mandates": [ { "id": "5ce04434-c4ce-4269-9861-19ff7ebc2a1d", "uiLabel": "SOHA Treuhand AG", "order": 10, "features": [ { "uiComponent": "feature.trustee", "uiLabel": "Treuhand", "order": 10, "instances": [ { "id": "2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0", "uiLabel": "ValueOn AG 2026", "order": 10, "views": [ { "uiComponent": "page.feature.trustee.dashboard", "uiLabel": "Übersicht", "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/dashboard", "order": 10, "objectKey": "ui.feature.trustee.dashboard" }, { "uiComponent": "page.feature.trustee.positions", "uiLabel": "Positionen", "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/positions", "order": 20, "objectKey": "ui.feature.trustee.positions" }, { "uiComponent": "page.feature.trustee.documents", "uiLabel": "Dokumente", "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/documents", "order": 30, "objectKey": "ui.feature.trustee.documents" } ] } ] } ] }, { "id": "abc123-mandate-2", "uiLabel": "Partner AG", "order": 20, "features": [ { "uiComponent": "feature.realestate", "uiLabel": "Immobilien", "order": 10, "instances": [ { "id": "def456-instance", "uiLabel": "Objektstudien ZH", "order": 10, "views": [ { "uiComponent": "page.feature.realestate.dashboard", "uiLabel": "Übersicht", "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/dashboard", "order": 10, "objectKey": "ui.feature.realestate.dashboard" }, { "uiComponent": "page.feature.realestate.projects", "uiLabel": "Projekte", "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/projects", "order": 20, "objectKey": "ui.feature.realestate.projects" } ] } ] } ] } ] } ] } ``` --- ## Vorteile dieses Ansatzes 1. **Single Source of Truth**: Gateway definiert, was sichtbar ist 2. **Keine Permission-Logik im UI**: Alles was kommt, wird gerendert 3. **Flexibles UI**: Verschiedene UIs können dieselben Daten anders darstellen 4. **Fehlertoleranz**: Synchronisationsprobleme werden sofort sichtbar 5. **Saubere Trennung**: Daten (Gateway) vs. Darstellung (UI) 6. **Testbarkeit**: API-Response kann einfach getestet werden 7. **Dokumentation**: Codes und ObjectKeys sind selbstdokumentierend 8. **Personalisierung**: User können Navigation individuell sortieren 9. **Konsistente Sortierung**: Gateway sortiert zentral, UI respektiert die Reihenfolge --- ## Offene Fragen (Beantwortet) 1. Soll der `dynamic` Block vor oder nach den `static` Blocks kommen? → **Diese initiale Reihenfolge: System, ``, workflows, basisdaten, migrate to features, administration** 2. Braucht es eine Versionierung der API für Breaking Changes? → **Nein** 3. Wie werden Feature-spezifische Sub-Navigationen behandelt (z.B. Tabs innerhalb einer View)? → **Tabs innerhalb einer View werden als separate Views behandelt** 4. Soll der Default-Order-Abstand 10 sein (erlaubt Einfügen zwischen Elementen)? → **Ja** 5. Soll die Order-Personalisierung auch für Admins pro Mandate/Feature-Instanz möglich sein (System-Default vs. User-Override)? → **Ja** --- ## Code-Analyse: IST-Zustand ### Gateway (Backend) #### 1. `mainSystem.py` - Statische Navigation ```python # IST: Items haben id, label, path, icon, objectKey # NEU: Brauchen uiComponent, uiLabel, uiPath, order (ohne icon) NAVIGATION_SECTIONS = [ { "id": "system", "order": 10, # ✓ Vorhanden "items": [ { "id": "home", "objectKey": "ui.system.home", # ✓ Korrekt "label": {"en": "Home", "de": "Übersicht"}, "icon": "FaHome", # ✗ Muss entfernt werden "path": "/", }, ] } ] ``` **Änderungen:** - `icon` entfernen (UI mappt selbst) - Item-Level `order` hinzufügen - Felder umbenennen für API Response #### 2. `routeSystem.py` - Navigation Endpoint ```python # IST: GET /api/system/navigation # Gibt zurück: { "sections": [...], "language": "de" } # NEU: GET /api/navigation # Soll zurückgeben: { "blocks": [...], "language": "de" } ``` **Änderungen:** - Neuer kombinierter Endpoint - Static Blocks + Dynamic Block zusammenführen - Response-Struktur gemäss Konzept #### 3. `mainTrustee.py` - Feature Views ```python # IST: TEMPLATE_ROLES verwendet Kurznamen TEMPLATE_ROLES = [ { "roleLabel": "trustee-client", "accessRules": [ {"context": "UI", "item": "dashboard", "view": True}, # ✗ Kurzname! {"context": "UI", "item": "positions", "view": True}, # ✗ Kurzname! ] } ] # ABER: UI_OBJECTS verwendet vollqualifizierte Namen UI_OBJECTS = [ {"objectKey": "ui.feature.trustee.dashboard", ...}, # ✓ Vollständig ] ``` **Inkonsistenz gefunden!** `item` in AccessRules muss `objectKey` entsprechen. #### 4. `routeAdminFeatures.py` - Permission Logic ```python # IST: _deriveViewPermissions ist TRUSTEE-spezifisch! def _deriveViewPermissions(permissions): # Hard-coded: TrusteePosition → positions view # Hard-coded: TrusteeDocument → documents view ``` **Problem:** Nicht generisch, funktioniert nur für Trustee. ### Frontend (UI) #### 1. `MandateNavigation.tsx` ```typescript // IST: Zwei separate Datenquellen const { sections } = useNavigation(); // Static von API const mandates = useMandates(); // Dynamic von API // IST: FEATURE_ICONS definiert im Frontend const FEATURE_ICONS = { trustee: }; ``` **Problem:** Navigation wird im Frontend zusammengebaut, nicht vom Backend. #### 2. `mandate.ts` - FEATURE_REGISTRY ```typescript // IST: Doppelte Definition von Views! export const FEATURE_REGISTRY = { trustee: { views: [ { code: 'dashboard', label: {...}, path: 'dashboard' }, // ✗ Kurzname { code: 'positions', label: {...}, path: 'positions' }, // ✗ Kurzname ] } }; ``` **Duplizierung:** Views sind in `mainTrustee.py` UND `mandate.ts` definiert! --- ## Impact-Analyse ### Phase 1: Gateway - Navigation Endpoint | Datei | Änderungstyp | Aufwand | Breaking | |-------|--------------|---------|----------| | `routeSystem.py` | Neu/Refactor | Hoch | Ja | | `mainSystem.py` | Update | Mittel | Nein | | `routeAdminFeatures.py` | Integrieren | Hoch | Nein | **Konkrete Aufgaben:** 1. Neuen Endpoint `GET /api/navigation` erstellen 2. Static Blocks aus `NAVIGATION_SECTIONS` generieren 3. Dynamic Block aus `_getMyFeatureInstances`-Logik generieren 4. Permission-Filter auf beide anwenden 5. `icon` aus Response entfernen 6. Felder umbenennen: `label→uiLabel`, `path→uiPath`, neu `uiComponent` 7. `order` auf Item-Level hinzufügen ### Phase 2: Gateway - Access Rules Migration | Datei | Änderungstyp | Aufwand | Breaking | |-------|--------------|---------|----------| | `mainTrustee.py` | Update | Mittel | Ja (DB) | | `mainRealEstate.py` | Update | Mittel | Ja (DB) | | Migration Script | Neu | Mittel | - | **Konkrete Aufgaben:** 1. `TEMPLATE_ROLES.item` auf vollqualifizierte ObjectKeys umstellen 2. Migration: Bestehende AccessRules in DB aktualisieren 3. Permission-Check in `_checkUiPermission` anpassen ### Phase 3: Gateway - User Settings | Datei | Änderungstyp | Aufwand | Breaking | |-------|--------------|---------|----------| | `datamodelUam.py` | Erweitern | Gering | Nein | | `routeSystem.py` | Endpoint | Gering | Nein | **Konkrete Aufgaben:** 1. `UserSettings` Tabelle oder JSON-Feld in User 2. `PUT /api/user/settings/navOrder` Endpoint 3. Merge-Logik in Navigation-Endpoint ### Phase 4: Frontend | Datei | Änderungstyp | Aufwand | Breaking | |-------|--------------|---------|----------| | `pageRegistry.ts` | Neu | Hoch | - | | `useNavigation.ts` | Refactor | Mittel | Ja | | `MandateNavigation.tsx` | Refactor | Hoch | Ja | | `mandate.ts` | Cleanup | Gering | Ja | **Konkrete Aufgaben:** 1. `PAGE_REGISTRY` erstellen mit uiComponent → Component Mapping 2. `FEATURE_REGISTRY` mit `feature.*` Codes 3. `useNavigation` für neuen Endpoint anpassen 4. `MandateNavigation` für Blocks-Struktur refactoren 5. Error-Handling für unbekannte uiComponents --- ## Kritische Punkte ### 1. Breaking Changes Die Umstellung erfordert **simultane Änderungen** in Gateway UND Frontend. Empfehlung: - Feature-Flag für neuen Endpoint - Alte Endpoints temporär behalten - Frontend schrittweise migrieren ### 2. Datenbank-Migration Bestehende AccessRules haben `item="dashboard"`, neu soll `item="ui.feature.trustee.dashboard"` sein. ```sql -- Beispiel-Migration UPDATE access_rules SET item = 'ui.feature.trustee.' || item WHERE role_id IN (SELECT id FROM roles WHERE feature_code = 'trustee') AND context = 'UI' AND item IS NOT NULL; ``` ### 3. Performance Navigation mit vielen Instanzen kann langsam werden. Empfehlungen: - Response cachen pro User - Cache invalidieren bei Permission-Änderungen - Lazy-Loading für sehr grosse Deployments prüfen ### 4. Feature-spezifische View-Ableitung `_deriveViewPermissions` ist Trustee-spezifisch. Optionen: - **A)** Jedes Feature definiert seine Ableitungslogik - **B)** Keine Ableitung, nur explizite UI-Rules - **C)** Generische Mapping-Konfiguration pro Feature **Empfehlung:** Option B - Explizite UI-Rules sind klarer und testbarer. --- ## Empfohlene Reihenfolge 1. **Schritt 1:** `PAGE_REGISTRY` im Frontend erstellen (unabhängig) 2. **Schritt 2:** Neuen `/api/navigation` Endpoint mit Feature-Flag 3. **Schritt 3:** Frontend auf neuen Endpoint umstellen 4. **Schritt 4:** AccessRules-Migration 5. **Schritt 5:** User-Settings für Order 6. **Schritt 6:** Alte Endpoints/Code entfernen