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

255 lines
11 KiB
Markdown

# 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<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.
```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.