# 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 > [`../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md`](../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md). > Dieses CLAUDE.md ist die Konventions- und Cross-Repo-Referenz. ## 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 │ WKWebView (Lesen) (statisch, public) │ (this repo) │ SwiftUI (Submit) │ ev.mana.zitare │ WidgetKit zitare-api ◄──────────── │ │ SwiftData (Snapshot-Cache) zitare-api.mana.how │ │ CoreSpotlight └──────────────────┘ ``` ## Status **Phase ζ-0 — Setup.** Repo-Skelett, `project.yml`, leerer Build im Simulator. Phasen ζ-1 bis ζ-7 in [`../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md`](../mana/docs/playbooks/ZITARE_NATIVE_GREENFIELD.md). ## Leitprinzip: Verteilungs-USP, nicht Funktions-USP Anders als die anderen drei nativen Apps (cards/memoro/manaspur) gibt es bei Zitare **keinen Hardware-Vorteil** gegenüber dem Browser. Die Web-App ist mobile-responsive, statisch prerendered, hat Pagefind- client-Suche. Native bringt: 1. **Home-Screen-Widget** „Zitat des Tages" 2. **ShareExtension** als Ziel für markierten Text 3. **Spotlight-Index** für system-weite Suche 4. **Native Submit-View** mit ManaAuthUI Alles andere (Lesen, Filtern, Search, Edit, Moderation) bleibt im `WKWebView` gegen `zitare.com` / `zitare.mana.how`. ## Architektonische Invarianten Beschlossen. Nicht ohne explizite Diskussion antasten. 1. **Hybrid ausnahmsweise.** Lese-Surfaces via `WKWebView`, Native- Surfaces (Widget, ShareExt, Submit, Spotlight) pure SwiftUI. Diese Trennung ist **fest** — keine schleichende Native-Re-Implementation von Read-Routes. 2. **Read-only via Web, Submit via SwiftUI.** Submit ist der einzige schreibende Pfad in v1. Edit, Moderation, History bleiben Web. 3. **Snapshot lokal gespiegelt für Widget + Spotlight.** Beim Launch `https://zitare.com/index-min.json` pullen, in SwiftData persistieren, App-Group `group.ev.mana.zitare` reicht es an Widget + ShareExtension durch. **Nicht** für den WebView-Pfad — der lädt live. 4. **mana-auth via ManaCore + ManaAuthUI.** Submit-Pfad nutzt `AuthClient` und die fertigen Views aus ManaAuthUI. Keine eigene Auth. WebView gegen `zitare.mana.how` bekommt JWT per Cookie- Injection (`mana.access` auf `.mana.how`). 5. **Universal-Link-Domain: `zitare.com`.** AASA auf `https://zitare.com/.well-known/apple-app-site-association`. `zitare.mana.how` ist *kein* applinks-Ziel. 6. **Theme: `paper`-Variant default.** Werte aus `@mana/themes/paper`, lokal als `ZitareTheme.swift` nachgebaut. 7. **Bundle-ID `ev.mana.zitare`.** Reverse-Domain mana-ev.ch. App-Display-Name: „Zitare". Category: `public.app-category.reference`. 8. **Pure SwiftUI für Native-Surfaces.** WKWebView ist die einzige UIKit-Bridge. 9. **Web gewinnt bei Konflikt.** Funktion wandert zuerst in `zitare/apps/zitare/` oder `zitare/apps/api/`, dann ins WebView- bzw. Native-Surface hier. ## 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 Greenfield-Plan hat ein verifizierbares Erfolgskriterium. Nicht in die nächste Phase reinarbeiten, bevor die vorherige abgeschlossen ist: - ζ-0: leerer Build + ManaCore-Login + Healthz-Probe (**JETZT**) - ζ-1: WebShellView + Universal-Links + Cookie-SSO-Bridge - ζ-2: Snapshot-Sync + DailyQuoteWidget auf realem Gerät - ζ-3: Submit-View nativ mit ManaAuthGate - ζ-4: Spotlight + ShareExtension + App Intents - ζ-5: Polish, Theme-Sync, iPad-Split-Layout, Accessibility - ζ-6: App-Store-Submission Bei Phasen-Wechsel: PLAN.md aktualisieren + Memory-Eintrag `project_zitare_native.md` nachziehen (sobald angelegt). ## Don't do - **Keine Native-Re-Implementation der Read-Routes.** Wenn dir die Web-Quote-Ansicht im WebView nicht gefällt, fixe sie in `../zitare/apps/zitare/src/routes/(read)/`. Nicht hier eine zweite bauen. - **Kein eigener FSRS-Port, kein eigener Pagefind-Klon.** Existiert beides nicht für Zitare und soll nicht entstehen. - **Keine Push-Notification-Pipeline.** Widget reicht. - **Keine externen UI-Libs / kein Sentry / kein Crash-Reporting- SaaS.** OSLog only (Compliance). - **Keine offline-Volltext-Funktion im WebView.** Wenn Offline- Lesen Use-Case wird → PWA-Pfad (Option A in `zitare/CLAUDE.md`), nicht hier.