# CLAUDE.md — zitare-native repo Guidance für Claude Code in diesem Repository. > **Wenn du gerade neu bist:** lies zuerst [`PLAN.md`](PLAN.md) und > [`docs/NATIVE_LIFT_PLAN.md`](docs/NATIVE_LIFT_PLAN.md). Das ältere > Hybrid-Playbook in `../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md` > ist seit 2026-05-22 **überholt** — nur als historische Referenz lesen. ## Was dieses Repo ist **Zitare Native** — native SwiftUI-Universal-App (iOS / iPadOS / macOS) für **Zitare**, den öffentlichen Zitat-Korpus des Vereins **mana e.V.** ``` HTTPS ┌──────────────────┐ zitare.com ◄──────────── │ zitare-native │ pure SwiftUI (Snapshot + AASA) │ (this repo) │ SwiftData (voller Korpus) │ ev.mana.zitare │ WidgetKit + ShareExt + Spotlight api.zitare.com ◄──────────── │ │ SafariView (nur Legal) (Read + Submit) │ │ └──────────────────┘ ``` ## Status **Phase η-0 — De-Hybrid (2026-05-22, in Arbeit).** Hybrid-Strategie (WKWebView für Read-Surfaces) wurde aufgegeben. Plan + Phasen-Übersicht in [`docs/NATIVE_LIFT_PLAN.md`](docs/NATIVE_LIFT_PLAN.md). Status-Spur in [`PLAN.md`](PLAN.md). ## Leitprinzip: pure-native + Offline-first Das Verteilungs-USP-Argument trägt allein nicht; die App soll auch Funktion + Lese-Komfort eigenständig liefern, ohne den Browser-Schwester- Pfad. Native bringt: 1. **Voller Korpus offline** in SwiftData (Snapshot-Sync `index-full.json`). 2. **Lokale FTS** + Server-Fallback bei seltenen Queries. 3. **Home-Screen-Widget** „Zitat des Tages". 4. **ShareExtension** als Ziel für markierten Text. 5. **Spotlight-Index** für system-weite Volltext-Suche. 6. **Native Submit** mit ManaAuthUI. 7. **SafariView** nur für statische Verein-Recht-Seiten (Impressum, Datenschutz, Lizenz) — kein `WKWebView` im Binary. ## Architektonische Invarianten (η) Beschlossen 2026-05-22. Nicht ohne explizite Diskussion antasten. 1. **Pure SwiftUI für alle Surfaces.** Kein `WKWebView` im App-Binary. `ManaWebShell`-Dep ist raus aus `project.yml`. 2. **Offline-first Lesen.** SwiftData ist Primary-Store, API ist Fallback bei Cache-Miss + Sync-Quelle. Flugmodus-Test ist Akzeptanz- Kriterium ab η-2. 3. **Quote-Korpus ist Snapshot.** Beim Launch + Background-Refresh wird `index-full.json` gepullt, ETag-aware, in SwiftData persistiert, per App-Group an Widget + ShareExt + Spotlight durchgereicht. 4. **Server gewinnt bei Schema-Konflikt.** SwiftData-Modelle sind eine abgeleitete Spiegelung des Drizzle-Schemas in `zitare/apps/api/`. 5. **Search lokal-first.** Lokale FTS aus SwiftData; bei < 3 Treffern oder leerem Result Server-Fallback (`GET /api/v1/quotes?q=`). 6. **Legal-Seiten via SafariView.** Impressum / Datenschutz / Lizenz öffnen `SFSafariViewController`. Das ist die **einzige** zugelassene Browser-Brücke und nur für statisches Verein-Recht-Material. 7. **Schreiben bleibt Submit-only in v1.** Edit / Moderation / Flags- Verwaltung kommen NICHT in die App — bleiben Web. 8. **Universal-Links auf `zitare.com` lösen ins native Surface.** AASA bleibt, `onContinueUserActivity` routet auf SwiftUI-Views, nicht in einen WebView-Tab. 9. **mana-auth via ManaCore + ManaAuthUI.** Submit + Konto nutzen `AuthClient` und die fertigen Views aus ManaAuthUI. Keine eigene Auth, keine Cookie-Bridge mehr (entfällt mit `WKWebView`). 10. **Universal-Link-Domain: `zitare.com`** (+ `app.zitare.com` als AASA-Redundanz). AASA auf `https://zitare.com/.well-known/apple-app-site-association`. 11. **Theme: `paper`-Variant default.** Werte aus `@mana/themes/paper`, lokal als `ZitareTheme.swift` nachgebaut. 12. **Bundle-ID `ev.mana.zitare`.** Reverse-Domain mana-ev.ch. App-Display-Name: „Zitare". Category: `public.app-category.reference`. 13. **Web gewinnt bei Konflikt im Datenmodell.** Funktion wandert zuerst in `zitare/apps/zitare/` oder `zitare/apps/api/`, dann ins native Surface hier — kein einseitiger Native-Vorlauf, der das Web abhängt. ## Konventionen - **Swift 6.0**, Strict Concurrency komplett - **iOS 18 / iPadOS 18 / macOS 15** Minimum - **SwiftUI** als einziges UI-Framework, `WKWebView` via `UIViewRepresentable`/`NSViewRepresentable` die einzige Bridge - **XcodeGen** als SOT: `project.yml` definiert Targets, Info.plist, Entitlements. `.xcodeproj`, generierte Info.plist und Entitlements sind **nicht** im Git - **SwiftFormat** mit `.swiftformat` - **SwiftLint** mit `.swiftlint.yml` - **Logging:** App-Subsystem `ev.mana.zitare` via `Sources/Core/Telemetry/Log.swift` - **Persistenz:** SwiftData für Snapshot-Cache + PendingSubmission- Queue, JWT im Keychain (über ManaCore) - **Lokalisierung:** DE primary, EN fallback via `Localizable.xcstrings` ## Zitare-API-Wire-Format Wire-Format gegen `https://zitare-api.mana.how/api/v1/*`. Quelle der Wahrheit: `../zitare/apps/api/src/routes/*.ts`. Bei neuem DTO: 1. Path + Method gegen den Hono-Handler prüfen 2. Response-Schema (`zod`) gegen `Codable`-Struct mappen 3. snake_case via `CodingKeys`, optionale Felder explizit `Optional` 4. Test-Fixture aus echtem Server-Response in `Tests/UnitTests/` ## Snapshot-Vertrag `https://zitare.com/index-min.json` ist der **lokale Korpus-Spiegel**. Heute Build-Output (`zitare/apps/zitare/src/content/index-min.json`), ausgeliefert als statische Datei. ```json { "generatedAt": "2026-05-08T20:48:48.795Z", "count": 11, "quotes": [ { "slug": "...", "authorSlug": "...", "language": "de", "themeSlugs": [...], "regionSlugs": [...], ... } ] } ``` Native-Konsumenten: `SnapshotSync` (App), `DailyQuoteWidget` (WidgetExtension via App-Group), `SpotlightIndexer` (App), `Submit`- View (Auto-Complete für Author/Theme). ## Repo-Layout ``` zitare-native/ ├── project.yml XcodeGen-Manifest (SOT) ├── PLAN.md Phasen-Tracking ├── CLAUDE.md dieses File ├── README.md ├── .swiftformat, .swiftlint.yml, .gitignore ├── Sources/ │ ├── App/ ZitareNativeApp (@main), RootView │ ├── Features/ │ │ ├── WebShell/ WebShellView (WKWebView-Wrapper, ζ-1) │ │ ├── Submit/ SubmitQuoteView (ζ-3) │ │ ├── Account/ AccountView (ζ-0 stub) │ │ └── Settings/ SettingsView (ζ-5) │ ├── Core/ │ │ ├── Auth/ AppConfig (ManaAppConfig-Provider) │ │ ├── API/ ZitareAPI (Quote-DTOs, Submit, Share) │ │ ├── Snapshot/ SnapshotSync, SnapshotStore (ζ-2) │ │ ├── Spotlight/ SpotlightIndexer (ζ-4) │ │ ├── Telemetry/ Log (OSLog, ev.mana.zitare) │ │ └── Theme/ ZitareTheme (paper-Werte) │ ├── Widgets/ WidgetKit-Extension (ζ-2) │ ├── ShareExtension/ „An Zitare schicken" (ζ-4) │ └── Resources/ │ ├── Assets.xcassets AppIcon, AccentColor │ ├── Localizable.xcstrings │ ├── Info.plist (XcodeGen-generiert, gitignored) │ └── ZitareNative.entitlements (generiert, gitignored) ├── Tests/ │ ├── UnitTests/ │ └── UITests/ └── docs/ ``` ## Wichtige Cross-Repo-Doks - `../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md` — vollständiger Phasen-Plan und Architektur-Begründungen - `../mana/docs/MANA_SWIFT.md` — Plattform-SOT für alle nativen Apps - `../mana/docs/MANA_AUTH_FEDERATION.md` — Auth-Protokoll, Cookie-SSO - `../mana/docs/COMPLIANCE.md` — Telemetrie/Auth/Bezahl-Regeln plus Plattform-Lock-In-Diskurs (gilt auch native) - `../zitare/CLAUDE.md` — Web-App-Konventionen - `../zitare/STATUS.md` — Web-Phasenstand (Funktions-Referenz) - `../zitare/app-manifest.json` — Föderations-Vertrag (Shares / Accepts / Link-Patterns) - `../mana-swift-core/CLAUDE.md` — ManaCore + ManaTokens - `../mana-swift-ui/CLAUDE.md` — ManaAuthUI + ManaAuthGate ## Lokal entwickeln **Pre-Requisites:** - Xcode 16+ - `brew install xcodegen swiftformat swiftlint` - `../mana-swift-core/` und `../mana-swift-ui/` als Schwester- Verzeichnisse (Package-Dependency via `path:`) **Workflow:** ```bash xcodegen generate open ZitareNative.xcodeproj ``` **Vor jedem Commit:** ```bash swiftformat Sources Widgets ShareExtension Tests swiftlint --strict ``` ## Phasen-Disziplin Jede Phase aus dem Lift-Plan hat ein verifizierbares Erfolgskriterium. Nicht in die nächste Phase reinarbeiten, bevor die vorherige abgeschlossen ist. Vollständige Phasen-Tabelle + Erfolgs-Kriterien in [`docs/NATIVE_LIFT_PLAN.md`](docs/NATIVE_LIFT_PLAN.md). Übersicht: - ζ-0..ζ-2: erledigt unter Hybrid-Annahme (Setup, WebShell, Snapshot- Stub + Widget-Code). WebShell entfällt jetzt; Widget + Snapshot- Code bleiben, werden in η-1 erweitert. - η-0: De-Hybrid (ManaWebShell raus, RootView vier native Tabs) (**JETZT**) - η-1: Snapshot v2 + volles SwiftData-Schema (Cross-Repo-Blocker: `zitare/apps/zitare/` muss `index-full.json` + 7 Stammdaten-JSONs liefern) - η-2: Read-Core nativ (Heute, Quote-Detail, Author-Detail) - η-3: Read-Browse (Source / Theme / Place / Role / Epoch / Language) - η-4: Explore + Filter - η-5: Search (lokal-first + Server-Fallback) - η-6: Account voll nativ - η-7: Legal-Sheets via SafariView - η-8: Submit / ShareExt / Spotlight an Snapshot v2 anpassen - η-9: Polish (iPad-Split, Accessibility, macOS-Layout) - η-10: TestFlight Bei Phasen-Wechsel: PLAN.md aktualisieren + Memory-Eintrag `project_zitare_native_dehybrid.md` nachziehen. ## Don't do - **Kein `WKWebView` im App-Binary.** SafariView (SFSafariViewController) ist die einzige zugelassene Browser-Brücke, und nur für statisches Verein-Recht (Impressum, Datenschutz, Lizenz). Wenn du WKWebView brauchen würdest, baust du es als native View nach. - **Kein eigener FSRS-Port, kein eigener Pagefind-Klon.** Existiert beides nicht für Zitare. Lokale FTS in η-5 ist SwiftData-Predicate. - **Keine Push-Notification-Pipeline.** Widget reicht. - **Keine externen UI-Libs / kein Sentry / kein Crash-Reporting- SaaS.** OSLog only (Compliance). - **Kein Edit / Moderation / Admin in der App.** Web-Surface (`app.zitare.com`) bleibt SOT für schreibende Pfade jenseits von Submit. Wer das braucht, öffnet Safari.