--- name: Swift iOS App Nachbau overview: Vollstaendiger Implementierungsplan fuer den Nachbau des React-Web-Frontends (frontend_nyla) als native Swift/SwiftUI iOS 18+ App fuer iPhone und iPad. Baut auf dem bestehenden Plan auf, korrigiert Fehler und integriert alle geklaerten Entscheidungen (kein Backend-Aenderungen, kein Voice, kein Push, ASWebAuthenticationSession fuer OAuth, read-only Billing, nativer Code-Editor). todos: - id: phase-0 content: "Phase 0: Xcode-Projekt erstellen, Ordnerstruktur, SPM-Dependencies (Runestone, MarkdownUI), Build-Configs (Dev/Int/Prod), TestFlight Setup" status: pending - id: phase-1 content: "Phase 1: Core Networking Layer -- APIClient (URLSession + Cookies + Headers), SSEClient (POST-basiert, async bytes), CSRFManager" status: pending - id: phase-2 content: "Phase 2: Authentication -- LocalAuth, ASWebAuthenticationSession fuer MS/Google, Keychain, Biometrie, 401-Handler" status: pending - id: phase-3 content: "Phase 3: Domain Models (Mandate, Feature, Instance, Permissions, Pagination) + FeatureStore (@Observable)" status: pending - id: phase-4 content: "Phase 4: App Shell -- NavigationSplitView (iPad/iPhone adaptiv), Backend-driven Sidebar, Dashboard, SF Symbol Mapping" status: pending - id: phase-5 content: "Phase 5: i18n String Catalogs (de/en/fr) + Theme System (System/Light/Dark) + DesignTokens" status: pending - id: phase-6 content: "Phase 6: Shared UI -- FormGeneratorForm + FormGeneratorTable + Report, ContentPreview, ChatMessage, Toast, SearchBar" status: pending - id: phase-7 content: "Phase 7: Core Pages -- Store, GDPR, Basedata (Prompts/Files/Connections), Billing (read-only), Settings" status: pending - id: phase-8 content: "Phase 8: Admin Module -- 16 Admin-Seiten (Mandates, Users, RBAC, Invitations, Wizards, Billing Admin, Logs)" status: pending - id: phase-9 content: "Phase 9: Feature Trustee -- Dashboard, Documents, Positions, Roles, Expense-Import, VisionKit Scan, Accounting" status: pending - id: phase-10 content: "Phase 10: Feature Workspace -- SSE Chat-Streaming, Workflows, Files, Datasources, Pending Edits, KeepAlive" status: pending - id: phase-11 content: "Phase 11: Feature Chatbot -- SSE-Streaming Chat, Threads, Conversations" status: pending - id: phase-12 content: "Phase 12: Feature Teamsbot -- Sessions, WebSocket Bot, Config, MFA, Screenshots" status: pending - id: phase-13 content: "Phase 13: Feature CommCoach -- Coaching Sessions, Text-Streaming, Tasks, Badges, Scores, Export" status: pending - id: phase-14 content: "Phase 14: Feature Automation -- Definitions CRUD, Templates, Execute, Workflow-Management" status: pending - id: phase-15 content: "Phase 15: Feature Automation2 -- Workflows CRUD, Tasks, Execute (ohne visuellen Node-Editor)" status: pending - id: phase-16 content: "Phase 16: Feature CodeEditor -- Runestone Editor, SSE-Stream, Pending Edits, Syntax-Highlighting" status: pending - id: phase-17 content: "Phase 17: Feature RealEstate/PEK -- MapKit Integration, Parcels, Address-Search, BZO, Koordinaten-Transformation" status: pending - id: phase-18 content: "Phase 18: Feature Neutralization -- Config, Neutralize Text/File, Stats" status: pending isProject: false --- # Nyla iOS/iPadOS App -- Aktualisierter Implementierungsplan ## Aenderungen gegenueber dem bestehenden Plan Basierend auf der vollstaendigen Code-Analyse und den Klaerungen: - **Korrektur Auth**: Backend-Popup-Flow (window.open) wird durch `ASWebAuthenticationSession` ersetzt -- KEIN MSAL iOS SDK noetig, da das Backend die OAuth-Flows selbst handled - **Entfernt**: Voice-Features (Phase ueberall vereinfacht), Push Notifications (Phase 8 entfaellt), Automation2 visueller Node-Editor, Stripe Checkout - **Hinzugefuegt**: Detailliertes FormGenerator-Mapping, Workspace-Layout nach Apple HIG, nativer Code-Editor mit Runestone - **Korrektur Billing**: Nur read-only (Balance, Transaktionen, Statistiken) -- kein Checkout - **iOS 18+**: Erlaubt Nutzung aller modernen SwiftUI APIs - **Team**: 2-3 Entwickler, parallelisierbare Feature-Module --- ## Architektur-Ueberblick ```mermaid graph TD subgraph presentation [SwiftUI Views] Views[Screens und Components] FormGen[FormGenerator SwiftUI] ChatUI[Chat und Streaming UI] end subgraph viewmodels [ViewModels] VM["@Observable ViewModels"] end subgraph repositories [Repositories] Repo[Repository Protokolle] end subgraph networking [Networking Layer] APIClient[APIClient URLSession] SSEClient[SSEClient async bytes] CSRFMgr[CSRFManager] CookieStore[HTTPCookieStorage] end subgraph backend [Gateway FastAPI] API["/api/* Endpoints"] end Views --> VM FormGen --> VM ChatUI --> VM VM --> Repo Repo --> APIClient Repo --> SSEClient APIClient --> CSRFMgr APIClient --> CookieStore APIClient --> API SSEClient --> API ``` ### Technische Entscheidungen (aktualisiert) - **Plattform**: iOS 18+ / iPadOS 18+ (iPhone + iPad mit adaptivem Layout) - **UI-Framework**: SwiftUI (rein, kein UIKit ausser wo zwingend noetig) - **Architektur**: MVVM + Repository Pattern - **Networking**: URLSession + async/await + Codable - **SSE**: Custom Client auf `URLSession.bytes(for:)` Basis (POST-basiert, nicht EventSource) - **Auth**: ASWebAuthenticationSession fuer MS/Google OAuth, Local Login via API, Keychain fuer Token-Storage - **State**: `@Observable` (Observation Framework) - **Navigation**: `NavigationSplitView` (iPad 2-3 Spalten) + `NavigationStack` (iPhone auto-collapse) - **DI**: SwiftUI `@Environment` - **Packages**: SPM -- Runestone (Code-Editor), ggf. MarkdownUI - **Karten**: MapKit (SwiftUI) - **Charts**: Swift Charts Framework - **i18n**: String Catalogs (`.xcstrings`) fuer de/en/fr - **Persistenz**: Keychain (Secrets), UserDefaults (Preferences) - **Distribution**: TestFlight ### Projektstruktur ``` NylaApp/ NylaApp.swift Config/ AppConfig.swift # API URLs per Build Config Environment.swift # Dev/Int/Prod Core/ Networking/ APIClient.swift # Zentraler HTTP-Client (= api.ts) APIError.swift APIEndpoints.swift SSEClient.swift # Server-Sent Events (= sseClient.ts) CSRFManager.swift # CSRF Token (= csrfUtils.ts) RequestContext.swift # X-Mandate-Id, X-Instance-Id Auth/ AuthManager.swift # Zentrale Auth-Logik LocalAuthService.swift # Username/Password OAuthService.swift # ASWebAuthenticationSession (MS + Google) KeychainService.swift Navigation/ NavigationStore.swift # Backend-driven Nav State AppRouter.swift # Root Navigation Coordinator Localization/ Localizable.xcstrings LanguageManager.swift Theme/ ThemeManager.swift DesignTokens.swift Domain/ Models/ # Codable Structs Repositories/ # Protokolle Data/ API/ # 21 API-Module (= src/api/*.ts) Repositories/ # Implementierungen Features/ # Feature-Module (je Ordner) Dashboard/ Store/ Settings/ GDPR/ Basedata/ Billing/ Admin/ Trustee/ Workspace/ Chatbot/ Teamsbot/ CommCoach/ Automation/ Automation2/ RealEstate/ Neutralization/ Shared/ Components/ FormGenerator/ # Dynamische Formulare ContentPreview/ ChatMessage/ AccessRules/ SearchBar/ LoadingView/ ErrorView/ EmptyStateView/ Extensions/ Utilities/ Resources/ Assets.xcassets ``` --- ## Phase 0: Projekt-Setup (1-2 Tage) - Xcode-Projekt erstellen (iOS 18+, SwiftUI App Lifecycle, iPhone + iPad) - Ordnerstruktur gemaess obigem Schema - SPM Dependencies: - **Runestone** (Code-Editor mit TreeSitter) -- fuer Phase 17 - **MarkdownUI** (oder Apple's native AttributedString) -- fuer Chat-Rendering - Alle anderen Features nutzen System-Frameworks (MapKit, Charts, PDFKit) - Build-Konfigurationen (`.xcconfig`): **Dev** / **Int** / **Prod** - `API_BASE_URL`: `http://localhost:8000` / INT-URL / PROD-URL - Analog zu [frontend_nyla/config/config.ts](frontend_nyla/config/config.ts) (`VITE_API_BASE_URL`) - TestFlight: App ID, Provisioning Profile, Code Signing - SwiftLint Konfiguration - Imlementiere unter zuhilfenahme der rules unter /.cursor --- ## Phase 1: Core Networking Layer (3-5 Tage) ### APIClient.swift -- Equivalent zu [frontend_nyla/src/api.ts](frontend_nyla/src/api.ts) Zentrale Anforderungen aus der Web-Analyse: - **Cookie-basierte Auth**: `URLSessionConfiguration.default` mit `httpCookieStorage = .shared` -- httpOnly Cookies werden automatisch gesendet (`withCredentials: true` Equivalent) - **Bearer Token**: Aus Keychain lesen, als `Authorization: Bearer {token}` Header setzen (nur wenn vorhanden, analog Zeile ~100-110 in `api.ts`) - **Kontext-Headers**: `X-Mandate-Id` und `X-Instance-Id` aus dem aktuellen NavigationStore (analog `getContextFromUrl()` in `api.ts` das die URL parst -- in Swift kommt der Context aus dem Navigation State, nicht aus einer URL) - **CSRF**: Fuer POST/PUT/PATCH/DELETE automatisch `X-CSRF-Token` Header setzen - **401 Handler**: Auth-State clearen und zur Login-Ansicht navigieren (analog `api.ts` Response Interceptor) - **429 Handler**: Rate-Limit Warning anzeigen - **Error Parsing**: FastAPI `detail` kann String oder Array sein -- beide Faelle behandeln ```swift // Konzept APIClient @Observable class APIClient { func get(_ path: String, query: [String: String]?) async throws -> T func post(_ path: String, body: Encodable?) async throws -> T func put(_ path: String, body: Encodable?) async throws -> T func delete(_ path: String) async throws func upload(_ path: String, fileData: Data, fileName: String, fields: [String: String]?) async throws -> T } ``` ### SSEClient.swift -- Equivalent zu [frontend_nyla/src/utils/sseClient.ts](frontend_nyla/src/utils/sseClient.ts) **Kritisch**: Das Web nutzt `fetch(POST)` mit `ReadableStream` -- NICHT standard EventSource (GET). In Swift: ```swift func startStream(url: URL, body: Encodable, onEvent: @escaping (SSEEvent) -> Void) async throws { var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = try JSONEncoder().encode(body) // + Auth headers, CSRF, Content-Type let (bytes, response) = try await URLSession.shared.bytes(for: request) for try await line in bytes.lines { if line.hasPrefix("data: ") { let json = String(line.dropFirst(6)) let event = try JSONDecoder().decode(SSEEvent.self, from: json.data(using: .utf8)!) onEvent(event) } } } ``` Wird benoetigt fuer: Workspace Chat, Chatbot, CommCoach, Trustee Streaming. ### CSRFManager.swift -- Equivalent zu [frontend_nyla/src/utils/csrfUtils.ts](frontend_nyla/src/utils/csrfUtils.ts) - Token: 16 Hex-Zeichen via `SecRandomCopyBytes` (analog `crypto.getRandomValues`) - Gespeichert im Keychain (nicht UserDefaults -- Sicherheit) - Automatisch bei jedem mutierenden Request angehaengt --- ## Phase 2: Authentication (3-5 Tage) ### Kritische Erkenntnis: Zwei Auth-Patterns koexistieren Die Web-App nutzt **zwei verschiedene** Auth-Mechanismen gleichzeitig: 1. **Local Login**: `POST /api/local/login` (form-encoded) setzt httpOnly Cookies -- danach Cookie-basierte Auth 2. **Microsoft/Google**: Backend-Popup-Flow setzt sowohl Cookies ALS AUCH `localStorage.authToken` + Bearer Header **Fuer die native App (ohne Backend-Aenderungen):** - **Local Login**: Funktioniert direkt -- `POST /api/local/login` setzt Cookies, `URLSession` speichert sie automatisch - **Microsoft OAuth**: `ASWebAuthenticationSession` oeffnet `/api/msft/auth/login` im Safari-Sheet. Das Backend macht den OAuth-Dance und redirected am Ende zurueck. Der Callback-URL-Handler muss die Cookies aus dem Safari-Sheet in die URLSession uebernehmen. **Herausforderung**: ASWebAuthenticationSession teilt Cookies mit Safari, NICHT mit URLSession. Loesung: Nach dem OAuth-Flow `GET /api/msft/me` aufrufen -- wenn Cookies korrekt gesetzt sind, kommt der User zurueck. Falls nicht: `prefersEphemeralWebBrowserSession = false` verwenden, damit Cookies persistent sind. - **Google OAuth**: Gleicher Flow wie Microsoft via `ASWebAuthenticationSession` mit `/api/google/auth/login` ### AuthManager.swift ```swift @Observable class AuthManager { var isAuthenticated = false var currentUser: User? var authAuthority: String? // "local", "msft", "google" func loginLocal(username: String, password: String) async throws func loginMicrosoft(presentingWindow: ASPresentationAnchor) async throws func loginGoogle(presentingWindow: ASPresentationAnchor) async throws func fetchCurrentUser() async throws -> User func logout() async throws } ``` Analog zu: - [frontend_nyla/src/hooks/useAuthentication.ts](frontend_nyla/src/hooks/useAuthentication.ts) -- `useAuth`, `useMsalAuth`, `useGoogleAuth` - [frontend_nyla/src/api/authApi.ts](frontend_nyla/src/api/authApi.ts) -- `loginApi`, `fetchCurrentUserApi`, `logoutApi` ### Login Screen - Username/Password Felder - "Mit Microsoft anmelden" Button -> ASWebAuthenticationSession - "Mit Google anmelden" Button -> ASWebAuthenticationSession - Face ID / Touch ID (wenn zuvor erfolgreich eingeloggt) - Links: Registrierung, Passwort vergessen - Analog zu [frontend_nyla/src/pages/Login.tsx](frontend_nyla/src/pages/Login.tsx) ### Biometrische Auth (Bonus, nicht im Web) - Nach erstem Login: Frage ob Face ID/Touch ID aktiviert werden soll - Credentials verschluesselt im Keychain speichern - Bei App-Start: Biometrie -> Auto-Login --- ## Phase 3: Domain Models + Feature Store (2-3 Tage) ### Zentrale Models Mapping der TypeScript-Types aus [frontend_nyla/src/types/mandate.ts](frontend_nyla/src/types/mandate.ts): ```swift struct I18nLabel: Codable { var de: String; var en: String; var fr: String? func localized(_ lang: String) -> String { switch lang { case "en": return en case "fr": return fr ?? de default: return de } } } enum AccessLevel: String, Codable { case none = "n", my = "m", group = "g", all = "a" } struct TablePermission: Codable { var view: Bool var read, create, update, delete: AccessLevel } struct FieldPermission: Codable { var read: Bool; var write: Bool } struct InstancePermissions: Codable { var tables: [String: TablePermission] var fields: [String: [String: FieldPermission]]? var views: [String: Bool] var isAdmin: Bool? } struct FeatureInstance: Codable, Identifiable { var id: String var featureCode, mandateId, mandateName, instanceLabel: String var userRoles: [String] var permissions: InstancePermissions } struct MandateFeature: Codable { var code: String; var label: I18nLabel; var icon: String var instances: [FeatureInstance] } struct Mandate: Codable, Identifiable { var id, name: String var label, code: String? var features: [MandateFeature] } ``` ### FeatureStore -- Analog zu [frontend_nyla/src/stores/featureStore.tsx](frontend_nyla/src/stores/featureStore.tsx) ```swift @Observable class FeatureStore { var mandates: [Mandate] = [] var isLoading = false var isInitialized = false func loadFeatures() async throws // GET /api/features/my func getMandateById(_ id: String) -> Mandate? func getInstanceById(_ id: String) -> FeatureInstance? } ``` ### Pagination Model -- Analog zum Backend `PaginatedResponse` ```swift struct PaginatedResponse: Codable { var items: [T] var total: Int var page: Int var pageSize: Int var totalPages: Int } struct PaginationParams: Encodable { var page: Int = 1 var pageSize: Int = 25 var search: String? var sort: [SortConfig]? var filters: [String: String]? } ``` --- ## Phase 4: App Shell + Navigation (4-6 Tage) ### Adaptive Layout nach Apple HIG **iPad** (Regular Width): ```mermaid graph LR subgraph splitView [NavigationSplitView] Sidebar[Sidebar: Navigation] Content[Content Area] end Sidebar --> Content ``` - `NavigationSplitView` mit Sidebar + Detail - Sidebar: Backend-driven Navigation (Mandate > Feature > Instance > Views) - Detail: NavigationStack fuer den Content-Bereich **iPhone** (Compact Width): - Automatisches Collapse durch `NavigationSplitView` in `NavigationStack` - Tab-basierte Hauptnavigation fuer schnellen Zugriff auf Dashboard, Settings - Sidebar oeffnet sich als Sheet oder wird zum NavigationStack ### Backend-driven Navigation `GET /api/navigation?language={lang}` liefert den kompletten Navigationsbaum. Analog zu [frontend_nyla/src/hooks/useNavigation.ts](frontend_nyla/src/hooks/useNavigation.ts) und [frontend_nyla/src/components/Navigation/MandateNavigation.tsx](frontend_nyla/src/components/Navigation/MandateNavigation.tsx): - **Static Blocks**: "Meine Sicht" (Dashboard, Store, Settings, etc.), "Administration" (Admin-Seiten mit Subgroups) - **Dynamic Block**: Mandate > Features > Instances > Views (hierarchischer Baum) - Icon-Mapping: Web `react-icons` -> SF Symbols (Mapping-Tabelle erstellen) ### Screen-Routing ```swift enum AppDestination: Hashable { case dashboard case store case settings case gdpr case basedata(BasedataSection) case billing case admin(AdminSection) case feature(mandateId: String, featureCode: String, instanceId: String, view: String) } ``` Analog zum Route-Setup in [frontend_nyla/src/App.tsx](frontend_nyla/src/App.tsx) -- der `VIEW_COMPONENTS` Map in [frontend_nyla/src/pages/FeatureView.tsx](frontend_nyla/src/pages/FeatureView.tsx). ### Feature-View-Dispatcher ```swift @ViewBuilder func featureView(code: String, view: String, instance: FeatureInstance) -> some View { switch (code, view) { case ("trustee", "dashboard"): TrusteeDashboardView(instance: instance) case ("trustee", "documents"): TrusteeDocumentsView(instance: instance) case ("workspace", "dashboard"): WorkspaceView(instance: instance) case ("chatbot", "conversations"): ChatbotConversationsView(instance: instance) // ... alle Mappings default: NotFoundView() } } ``` ### MainLayout-Equivalent Analog zu [frontend_nyla/src/layouts/MainLayout.tsx](frontend_nyla/src/layouts/MainLayout.tsx): - Logo oben in der Sidebar - MandateNavigation als Hauptinhalt der Sidebar - UserSection am unteren Rand der Sidebar (Profilbild, Name, Logout) - FeatureLayout: Breadcrumb Header (Mandate > Feature > Instance) + Role Badge, analog [frontend_nyla/src/layouts/FeatureLayout.tsx](frontend_nyla/src/layouts/FeatureLayout.tsx) ### Dashboard Analog zu [frontend_nyla/src/pages/Dashboard.tsx](frontend_nyla/src/pages/Dashboard.tsx): - Titel "Uebersicht" mit Anzahl Instanzen/Mandate - Sektionen pro Mandate mit Instance-Karten - Jede Karte linkt zur ersten View der Instanz (`instance.views[0].uiPath`) - Bei 0 Instanzen: Navigation zum Store --- ## Phase 5: i18n + Theme (2-3 Tage) ### Internationalisierung - **String Catalogs** (`.xcstrings`) fuer de/en/fr - Alle statischen Strings aus [frontend_nyla/src/locales/de.ts](frontend_nyla/src/locales/de.ts), `en.ts`, `fr.ts` uebernehmen - Dynamische Labels: `I18nLabel.localized(lang)` Helper - `LanguageManager` speichert Praeferenz in UserDefaults, default `de` - Sprachauswahl in Settings analog Web ### Theme - `@AppStorage("theme") var theme: String = "system"` - `.preferredColorScheme()` fuer System-Integration (auto/light/dark) - `DesignTokens`: Farben als `Color` Extensions, Spacing als CGFloat Constants - CSS-Variable-Equivalent: SwiftUI `@Environment(\.colorScheme)` + Custom EnvironmentValues - Mapping der CSS-Variablen aus dem Web (`--primary-color`, `--border-color`, etc.) zu Swift `Color` Assets --- ## Phase 6: Shared UI Components (5-8 Tage) ### FormGenerator (KRITISCH -- wird von fast allen Features genutzt) Analog zu [frontend_nyla/src/components/FormGenerator/](frontend_nyla/src/components/FormGenerator/): **Konzept**: Dynamische Formulare und Tabellen basierend auf `AttributeDefinition` Arrays vom Backend (`GET /api/attributes/{entityType}`). #### Attribut-Typ-Mapping (Web -> Swift) Basierend auf [frontend_nyla/src/utils/attributeTypeMapper.ts](frontend_nyla/src/utils/attributeTypeMapper.ts): - `text` / `string` -> `TextField` - `textarea` -> `TextEditor` (mit `minRows`/`maxRows` -> `.frame(minHeight:maxHeight:)`) - `email` -> `TextField` mit `.keyboardType(.emailAddress)` - `url` -> `TextField` mit `.keyboardType(.URL)` - `password` -> `SecureField` - `number` / `integer` / `float` -> `TextField` mit `.keyboardType(.decimalPad)` + NumberFormatter - `select` / `enum` -> `Picker` (Wheel/Menu/Inline je nach Kontext) - `multiselect` -> Multi-Selection List oder Chip-basierte Auswahl - `checkbox` / `boolean` -> `Toggle` - `date` -> `DatePicker(.date)` - `time` -> `DatePicker(.hourAndMinute)` - `timestamp` -> `DatePicker` (date + time) - `multilingual` -> Custom View mit 3 TextFields (de/en/fr) und Sprach-Tabs - `file` -> File-Picker Button + Vorschau - `readonly` -> `Text` (nicht editierbar) #### FormGeneratorForm (Formular-Ansicht) ```swift struct FormGeneratorForm: View { let entityType: String let mode: FormMode // .create, .edit, .view @Binding var data: [String: AnyCodable] var attributes: [AttributeDefinition]? // optional, sonst von API laden var onSubmit: (([String: AnyCodable]) async throws -> Void)? var filterFields: [String]? var customValidator: (([String: AnyCodable], [AttributeDefinition]) -> [String: String])? } ``` - Laedt Attribute von `GET /api/attributes/{entityType}` wenn nicht uebergeben - Sortiert nach `order` (default 999) - Filtert nach `visible`, `editableOnCreate`/`editableOnUpdate` - Options-Loading: Wenn `options` ein String ist (API-Pfad), parallele GETs mit `{instanceId}` Replacement #### FormGeneratorTable (Tabellen-Ansicht) Analog zu [frontend_nyla/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx](frontend_nyla/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx): - **Server-seitige** Pagination, Sortierung, Filterung (kein Client-Side!) - Debounced Suche (300ms) -> `paginationParams.search` - Multi-Column Sort (asc -> desc -> entfernt) - Filter-Dropdowns pro Spalte (Werte von `{apiEndpoint}/filter-values?column=...`) - Pagination Controls: First/Prev/Next/Last + Seitenzahlen - Row Actions: Edit, Delete, Download, Custom Actions - Inline Editing fuer unterstuetzte Typen - Group-By Support mit Custom Group Renderer **Auf iOS/iPad**: `List` oder `LazyVStack` statt HTML-Table. Auf iPad breites Layout, auf iPhone Card-basiert oder horizontales Scrollen. #### FormGeneratorReport (Statistik-Ansicht) - Sektionen: KPI Grid, Bar, Line, Area, Pie Charts, Tabellen - Toolbar: Periodenfilter, Datumsbereich - Swift Charts fuer alle Chart-Typen (Recharts-Equivalent) ### ContentPreview - PDF: `PDFKitView` (UIViewRepresentable mit PDFKit) - Bilder: AsyncImage - JSON: Syntax-Highlighting - HTML: WKWebView (Mini) ### ChatMessage Components Analog zu [frontend_nyla/src/components/UiComponents/Messages/](frontend_nyla/src/components/UiComponents/Messages/): - User vs. Assistant Bubbles - Markdown-Rendering (MarkdownUI oder AttributedString) - Code-Blocks mit Syntax-Highlighting - File-Attachments (Karten mit Icon + Name) - Streaming-Indicator (Typing Animation) - Tool-Activity Anzeige - Auto-Scroll zum neuesten Eintrag ### Weitere Shared Components - **SearchBar**: Debounced Suchfeld - **LoadingView**: Spinner/Skeleton - **ErrorView**: Fehlermeldung mit Retry-Button - **EmptyStateView**: Illustration + Text + Action - **Toast**: Analog [frontend_nyla/src/contexts/ToastContext.tsx](frontend_nyla/src/contexts/ToastContext.tsx) -- Types: success (5s), error (8s), warning (6s), info (5s) --- ## Phase 7: Core Pages (5-7 Tage) ### Store (Feature Marketplace) - `GET /api/store/features` -> Feature-Liste als Karten - `POST /api/store/activate` / `deactivate` - Analog [frontend_nyla/src/pages/Store.tsx](frontend_nyla/src/pages/Store.tsx) ### GDPR - `GET /api/user/me/data-export`, `/data-portability` - `DELETE /api/user/me/` (mit Bestaetigung) - Analog [frontend_nyla/src/pages/GDPR.tsx](frontend_nyla/src/pages/GDPR.tsx) ### Basedata -- Prompts - FormGeneratorTable + FormGeneratorForm fuer CRUD auf `/api/prompts` - Analog [frontend_nyla/src/pages/PromptsPage.tsx](frontend_nyla/src/pages/PromptsPage.tsx) ### Basedata -- Files - `GET /api/files/list`, Upload, Download, Preview - Ordnerstruktur: `GET /api/files/folders` - Upload: `UIDocumentPickerViewController` (via UIViewControllerRepresentable) - Vorschau: QuickLook Framework - Batch-Delete, Move, Folder-Management - Analog [frontend_nyla/src/pages/FilesPage.tsx](frontend_nyla/src/pages/FilesPage.tsx) ### Basedata -- Connections - FormGeneratorTable + FormGeneratorForm fuer CRUD auf `/api/connections/` - Connect/Disconnect Aktionen - Analog [frontend_nyla/src/pages/ConnectionsPage.tsx](frontend_nyla/src/pages/ConnectionsPage.tsx) ### Billing (Read-Only) - `GET /api/billing/balance` -> Saldo-Anzeige - `GET /api/billing/transactions` -> Transaktionsliste - `GET /api/billing/statistics/{period}` -> Charts mit Swift Charts - KEIN Checkout/Stripe in der App - Analog [frontend_nyla/src/pages/billing/BillingDataView.tsx](frontend_nyla/src/pages/billing/BillingDataView.tsx) ### Settings - Theme-Toggle (System/Light/Dark) - Sprachauswahl (de/en/fr) - Profil-Bearbeitung (via `/api/users/{userId}`) - Passwort aendern (`POST /api/users/change-password`) - Analog [frontend_nyla/src/pages/Settings.tsx](frontend_nyla/src/pages/Settings.tsx) --- ## Phase 8: Admin Module (5-7 Tage) Alle Admin-Seiten nutzen den FormGenerator intensiv. Analog zu [frontend_nyla/src/pages/admin/](frontend_nyla/src/pages/admin/): - **Mandates**: CRUD `/api/mandates/` + User-Zuweisung - **Users**: CRUD `/api/users/` + Password-Link senden - **User-Mandates**: `/api/mandates/{id}/users` -- Zuweisungen verwalten - **Access Hub**: `/api/rbac/permissions`, `/api/rbac/rules` -- Regel-Editor - **Feature Instances**: `/api/features/instances` -- CRUD + Sync-Roles - **Feature Roles**: `/api/features/templates/roles` -- Template-Rollen - **Feature Users**: `/api/features/instances/{id}/users` -- Benutzer pro Instanz - **Invitations**: CRUD `/api/invitations/` + Token-Validierung - **Mandate Roles**: `/api/rbac/roles` -- Rollen-Verwaltung - **Role Permissions**: `/api/rbac/rules/by-role/{roleId}` -- Matrix-Ansicht - **User Access Overview**: `/api/admin/user-access-overview/` -- Read-Only Uebersicht - **Billing Admin**: `/api/billing/admin/` -- Read-Only (Accounts, Transactions, Settings) - **Subscriptions Admin**: `/api/subscription/admin/all` -- Uebersicht - **Automation Events**: `/api/admin/automation-events` -- Liste + Sync - **Logs**: `/api/admin/logs` -- Systemlogs mit Download - **Mandate Wizard**: Kombination von Mandate erstellen + Features zuweisen + Benutzer einladen - **Invitation Wizard**: Gesteuerter Einladungs-Flow --- ## Phase 9: Feature Trustee (5-7 Tage) API-Basis: `/api/trustee/{instanceId}/` Views (aus [frontend_nyla/src/pages/views/trustee/](frontend_nyla/src/pages/views/trustee/)): - **Dashboard**: Uebersicht ueber Organisationen, Vertraege, Positionen - **Documents**: CRUD + Upload (`POST .../documents/upload`) + Download - **Positions**: CRUD mit verschachtelten Position-Documents - **Instance-Roles**: Rollenverwaltung pro Instanz - **Expense-Import**: CSV/Excel Import von Belegen (Automation-basiert) - **Scan-Upload**: Dokument-Scan -> auf iOS: `VNDocumentCameraViewController` (VisionKit) fuer native Scan-Funktion (besser als Web!) - **Accounting Settings**: Connector-Konfiguration, Sync, Chart of Accounts Besonderheiten: - Viele Options-Endpoints fuer Dropdowns (`.../organisations/options`, `.../roles/options`, etc.) - Hierarchische Daten: Organisation > Contract > Document > Position - Scan-Upload ist auf iOS BESSER als im Web dank VisionKit --- ## Phase 10: Feature Workspace (5-7 Tage) API-Basis: `/api/workspace/{instanceId}/` **Haupt-View**: AI Chat mit SSE-Streaming ### Workspace Layout (Apple HIG Best Practice) **iPad** (Regular Width) -- `NavigationSplitView` mit 3 Spalten: - **Sidebar**: Conversations/Workflows Liste (analog linke Spalte im Web) - **Content**: Chat-Bereich mit Nachrichten + Input-Feld - **Detail (Inspector)**: Activity Panel, File Preview, Pending Edits (als `.inspector` Modifier oder dritte Spalte) **iPhone** (Compact Width) -- Automatischer Collapse: - Chat ist Primary View - Conversations-Liste als Back-Navigation - Activity/Files als Toolbar-Button -> Sheet/Overlay ### SSE Chat-Streaming Analog zu [frontend_nyla/src/pages/views/workspace/useWorkspace.ts](frontend_nyla/src/pages/views/workspace/useWorkspace.ts): - `POST /api/workspace/{instanceId}/start/stream` mit JSON Body: - `prompt`, `fileIds`, `dataSourceIds`, `featureDataSourceIds`, `userLanguage`, `workflowId`, `allowedProviders` - Event-Types aus dem Stream: `message`, `chunk`, `status`, `tool_activity`, `agent_progress`, `agent_summary`, `fileCreated`, `dataSourceAccess`, `workflowUpdated`, `complete`, `stopped`, `error` - Stop: `POST .../stop/{workflowId}` ### Weitere Workspace-Features (ohne Voice) - Workflows CRUD: List, Create, Patch, Delete - Messages: `GET .../workflows/{id}/messages` - Files + Folders: Browse, Upload, Preview - Datasources: CRUD + Feature-Datasources - Pending Edits: Accept/Reject (einzeln und alle) - Settings: General + RAG-Statistiken ### WorkspaceKeepAlive-Equivalent Im Web bleibt die Workspace-Komponente gemounted (analog [frontend_nyla/src/pages/views/workspace/WorkspaceKeepAlive.tsx](frontend_nyla/src/pages/views/workspace/WorkspaceKeepAlive.tsx)). In Swift: `@Observable WorkspaceViewModel` wird im Environment gehalten und ueberlebt Navigation-Wechsel. Der SSE-Stream bleibt aktiv solange die Instanz ausgewaehlt ist. --- ## Phase 11: Feature Chatbot (3-5 Tage) API-Basis: `/api/chatbot/{instanceId}/` Views: - **Conversations**: Thread-Liste + Chat-Stream - **Settings**: Placeholder im Web, aber Endpoints existieren Technisch identisch zum Workspace-Chat-Pattern: - `POST .../start/stream` -> SSE - `POST .../stop/{workflowId}` - Threads: List, Delete - Verwendet dieselben ChatMessage-Components aus Phase 6 --- ## Phase 12: Feature Teamsbot (3-5 Tage, OHNE Voice) API-Basis: `/api/teamsbot/{instanceId}/` Views: - **Dashboard**: Uebersicht Sessions + Config - **Sessions**: Session-Liste, Session-Details mit Stream - **Settings**: Config, System Bots, User Account Technisch: - SSE fuer Session-Stream (`GET .../sessions/{sessionId}/stream`) - Config/Settings CRUD - Screenshots anzeigen (`GET .../sessions/{sessionId}/screenshots`) - **WebSocket** (`/bot/ws/{sessionId}`) fuer Live-Bot-Interaktion -- `URLSessionWebSocketTask` - MFA-Support fuer Session-Authentifizierung --- ## Phase 13: Feature CommCoach (4-6 Tage, OHNE Audio-Streaming) API-Basis: `/api/commcoach/{instanceId}/` Views: - **Dashboard**: Kontext-Uebersicht, aktive Sessions, Fortschritt - **Coaching**: Session starten, Nachrichten-Stream (SSE), Tasks - **Dossier**: Export, Score-History, Badges - **Settings**: Personas, Documents Technisch: - Contexts CRUD (Create, Archive, Activate) - Sessions: Start, Complete, Cancel - Message-Stream: SSE fuer Coaching-Nachrichten - Tasks: CRUD + Status-Updates - Badges + Scores: Visualisierung mit Swift Charts - Export als PDF/Download **Hinweis**: Audio-Streaming (Mikrofon -> Backend) wird in dieser Version uebersprungen. Text-basiertes Coaching ist voll funktional. --- ## Phase 14: Feature Automation (3-5 Tage) API-Basis: `/api/automations/` Views: - **Definitions**: Automation-Definitionen CRUD + Execute + Duplicate - **Templates**: Template-Verwaltung Technisch: - FormGeneratorTable fuer Definitionen/Templates - Execute mit Status-Tracking - Workflow-Management: List, Get, Status, Logs, Messages - Actions-Endpoint fuer verfuegbare Aktionen --- ## Phase 15: Feature Automation2 (2-3 Tage, OHNE visuellen Editor) API-Basis: `/api/automation2/{instanceId}/` Views: - **Workflows**: Liste aller Workflows + CRUD - **Workflow-Tasks**: Offene Tasks mit Complete-Aktion **Kein visueller Node-Editor** -- nur Listen-basierte Verwaltung: - Workflows: List, Create, Update, Delete - Workflow Runs: `GET .../workflows/{id}/runs` - Execute: `POST .../execute` - Tasks: List + Complete - Node-Types und Connections als Reference-Info --- ## Phase 16: Feature CodeEditor (3-5 Tage) API-Basis: Nutzt Workspace/Automation2 API-Patterns Views: - **Editor**: Code-Anzeige mit Syntax-Highlighting (Runestone) - **Workflows**: Workflow-Liste Technisch: - **Runestone** (SPM Package) fuer nativen Code-Editor mit: - TreeSitter-basiertes Syntax-Highlighting - Zeilennummern - Theme-Integration (Light/Dark) - SSE-Stream fuer Code-Generierung - Pending Edits: Accept/Reject wie im Workspace - File-Content anzeigen: `GET .../files/{fileId}/content` --- ## Phase 17: Feature RealEstate/PEK (5-7 Tage) API-Basis: `/api/realestate/{instanceId}/` Views: - **Dashboard (Map)**: Karten-Visualisierung mit Parzellen - **Instance-Roles**: Rollen-Verwaltung Technisch: - **MapKit** (SwiftUI `Map` View): - Parzellen als Polygone auf der Karte - Parcel-Selection durch Tap - Adjacent Parcels Highlight - Cluster-Ansicht fuer viele Parzellen - Address-Autocomplete: `GET /api/realestate/address/autocomplete` + optionales MKLocalSearchCompleter - Projects + Parcels CRUD - BZO Information: Bauvorschriften anzeigen - WFS (Web Feature Service): Parcel-Geometrie laden - Selection Summary **Hinweis**: Leaflet (Web) -> MapKit (iOS) erfordert Koordinaten-Transformation. `proj4` im Web wird durch MapKit's native Projektionen ersetzt. Falls Swiss-spezifische Koordinatensysteme (LV95/LV03) benoetigt werden, braucht es einen Converter. --- ## Phase 18: Feature Neutralization (2-3 Tage) API-Basis: `/api/neutralization/` Views: - **Dashboard/Playground** (gleiche View): Text eingeben -> neutralisieren/aufloesen Technisch: - Config: `GET/POST /api/neutralization/config` - Neutralize Text: `POST .../neutralize-text` - Resolve Text: `POST .../resolve-text` - Neutralize File: `POST .../neutralize-file` (File-Upload) - Stats: `GET .../stats` - Attributes: `GET .../attributes` --- ## API-Header-Konvention (fuer alle Requests) Jeder Request muss folgende Header senden (analog [frontend_nyla/src/api.ts](frontend_nyla/src/api.ts)): - `Authorization: Bearer {token}` -- aus Keychain, wenn JWT vorhanden - `X-Mandate-Id: {mandateId}` -- aus NavigationStore, bei Feature-Seiten - `X-Instance-Id: {instanceId}` -- aus NavigationStore, bei Feature-Seiten - `X-CSRF-Token: {token}` -- aus CSRFManager, bei POST/PUT/PATCH/DELETE - `Content-Type: application/json` -- Standard fuer JSON Bodies - Cookies (httpOnly) -- automatisch via URLSession HTTPCookieStorage --- ## Parallelisierungsstrategie (2-3 Entwickler) ```mermaid gantt title Entwicklungsplan 2-3 Entwickler dateFormat YYYY-MM-DD axisFormat %d.%m section Gemeinsam Phase0_Setup :p0, 2026-04-01, 2d Phase1_Networking :p1, after p0, 5d Phase2_Auth :p2, after p1, 5d Phase3_Models :p3, after p2, 3d section Dev1_Core Phase4_AppShell :p4, after p3, 6d Phase5_i18n_Theme :p5, after p4, 3d Phase7_CorePages :p7, after p5, 7d Phase8_Admin :p8, after p7, 7d section Dev2_Components Phase6_SharedUI :p6, after p3, 8d Phase10_Workspace :p10, after p6, 7d Phase11_Chatbot :p11, after p10, 5d Phase13_CommCoach :p13, after p11, 6d section Dev3_Features Phase9_Trustee :p9, after p6, 7d Phase12_Teamsbot :p12, after p9, 5d Phase14_Automation :p14, after p12, 5d Phase15_Automation2 :p15, after p14, 3d Phase16_CodeEditor :p16, after p15, 5d Phase17_RealEstate :p17, after p16, 7d Phase18_Neutralization :p18, after p17, 3d ``` - **Phase 0-3**: Gemeinsam (Basis fuer alle) - **Ab Phase 4**: Parallel -- Core/Admin, Shared UI/Chat-Features, Trustee/Weitere Features - Features sind nach Phase 6 (Shared Components, besonders FormGenerator) unabhaengig voneinander --- ## Gesamtaufwand-Schaetzung (aktualisiert) - Phase 0: Setup -- 1-2 Tage - Phase 1: Networking -- 3-5 Tage - Phase 2: Authentication -- 3-5 Tage - Phase 3: Domain Models -- 2-3 Tage - Phase 4: App Shell + Navigation -- 4-6 Tage - Phase 5: i18n + Theme -- 2-3 Tage - Phase 6: Shared UI Components -- 5-8 Tage - Phase 7: Core Pages -- 5-7 Tage - Phase 8: Admin -- 5-7 Tage - Phase 9: Trustee -- 5-7 Tage - Phase 10: Workspace -- 5-7 Tage - Phase 11: Chatbot -- 3-5 Tage - Phase 12: Teamsbot -- 3-5 Tage - Phase 13: CommCoach -- 4-6 Tage - Phase 14: Automation -- 3-5 Tage - Phase 15: Automation2 -- 2-3 Tage - Phase 16: CodeEditor -- 3-5 Tage - Phase 17: RealEstate -- 5-7 Tage - Phase 18: Neutralization -- 2-3 Tage - **Gesamt sequentiell**: ~65-100 Tage - **Gesamt mit 3 Devs parallel**: ~35-50 Tage (nach Phase 3) --- ## Risiken und offene Punkte 1. **Auth ohne Backend-Aenderungen**: `ASWebAuthenticationSession` teilt Cookies mit Safari, nicht direkt mit `URLSession`. Falls das Backend nach dem OAuth-Redirect nur httpOnly Cookies setzt (ohne Token im Redirect-URL), muss getestet werden ob die Cookie-Uebernahme funktioniert. Worst case: Backend muss doch einen Token-Parameter im Callback-URL zurueckgeben. 2. **CSRF Validierung**: Das Backend validiert CSRF-Tokens mit einem Hex-Format-Check. Die client-seitige Generierung muss identisch sein (16 Hex-Zeichen). Testen ob das Backend Session-gebundene CSRF-Validierung macht oder nur Format-Check. 3. **SSE ueber POST**: Standard `EventSource` ist GET-only. Das Web nutzt `fetch(POST)` + `ReadableStream`. In Swift: `URLSession.bytes(for:)` mit POST Request -- funktioniert, aber muss getestet werden mit dem spezifischen Backend-Setup. 4. **FormGenerator Komplexitaet**: Der FormGenerator ist das komplexeste Shared Component. Die Tabellen-Ansicht auf iPhone (schmaler Screen) braucht ein alternatives Layout (Cards statt Tabelle). Dies erfordert sorgfaeltiges Responsive Design. 5. **MapKit vs Leaflet**: Falls Swiss-spezifische Koordinatensysteme (LV95/LV03) benoetigt werden, muss ein nativer Koordinaten-Transformer implementiert werden (im Web: `proj4`). 6. **Keine Offline-Faehigkeit**: Bei schlechter Netzwerkverbindung (z.B. auf dem Bau) koennen Ladezustaende frustrierend sein. Empfehlung: Zumindest ein Request-Cache fuer die letzte erfolgreiche Response pro Endpoint. 7. **TestFlight Limitierung**: Max 10'000 Tester, 90-Tage Build-Ablauf. Fuer laengerfristigen Einsatz Enterprise Distribution oder App Store evaluieren.