zitare-native/CLAUDE.md
Till JS 1d770123f5 η-0: De-Hybrid — WKWebView raus, native Tabs mit Platzhaltern
Lift von Hybrid (WKWebView für Lesen/Erkunden) auf fully-native ist
beschlossen. Diese Phase entfernt die WebShell-Infrastruktur; das volle
native Read-Surface folgt in η-2..η-5 nach docs/NATIVE_LIFT_PLAN.md.

- ManaWebShell-Dep raus aus project.yml
- Sources/Core/WebShell/CookieBridge.swift gelöscht
- RootView auf vier native Tabs (Lesen + Erkunden = Platzhalter,
  Submit + Konto unverändert nativ)
- DocComments in DeepLinkRouter / AppConfig / Account / Settings von
  WebView-Verweisen befreit
- CLAUDE.md Invarianten von Hybrid auf η umgestellt (13 Invarianten,
  pure SwiftUI + Offline-first + SafariView-Ausnahme für Legal)
- PLAN.md auf η-0 + Phasenübersicht η-0..η-10
- AppConfigTests.test_keychainService_matchesSharedGroup auf
  ManaSharedKeychainGroup aktualisiert (war drift seit Cross-App-SSO)

Verifikation:
- xcodebuild iOS-Simulator iPhone 16e: BUILD SUCCEEDED
- nm ZitareNative | grep WKWebView: 0 Referenzen
- otool -L: kein WebKit-Framework-Link
- 20/20 Tests grün

Cross-Repo-Follow-up (η-1 Blocker):
- zitare/apps/zitare/ muss index-full.json + 7 Stammdaten-JSONs liefern
- zitare/apps/api/ Volltext-Search-Endpoint bestätigen/ergänzen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:32:05 +02:00

11 KiB

CLAUDE.md — zitare-native repo

Guidance für Claude Code in diesem Repository.

Wenn du gerade neu bist: lies zuerst PLAN.md und 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. Status-Spur in 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<T>
  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.

{
  "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:

xcodegen generate
open ZitareNative.xcodeproj

Vor jedem Commit:

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.

Ü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.