- project.yml mit Bundle ev.mana.zitare + Widget + ShareExt-Targets - ManaSwiftCore (ManaCore + ManaTokens) + ManaSwiftUI (ManaAuthUI) als Package-Dependencies via path: - Pure SwiftUI für Native-Surfaces, WKWebView nur für Lese-Tabs (Hybrid-Sonderfall vs cards/memoro/manaspur, dokumentiert im Playbook ZITARE_NATIVE_GREENFIELD.md) - Theme: paper-Variant aus @mana/themes - ZitareAPI.healthCheck via direct URLSession (öffentlicher Endpoint, kein AuthenticatedTransport-Gate) - 6/6 AppConfigTests + 1/1 UI-Smoke grün auf iPhone 16e Simulator - Live: zitare-api.mana.how/healthz → HTTP/2 200 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
233 lines
9.2 KiB
Markdown
233 lines
9.2 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
|
|
> [`../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<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 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.
|