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>
255 lines
11 KiB
Markdown
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.
|