Auto-Flush hat 4xx-Errors (duplicate, validation_failed, unauthorized)
bisher stillschweigend gedroppt — User offline einreichen, im Web
denselben Text posten, Online gehen → die Native-Submission war weg
ohne Hinweis.
SubmissionQueue:
- struct DropRecord (textPreview, authorName, code, message, droppedAt)
- private(set) var dropNotifications: [DropRecord]
- tryFlush sammelt jetzt einen Pre-Delete-Snapshot in dropNotifications
- consumeDropNotifications() leert die Liste — UI ruft beim
Banner-Quittieren auf
SubmitQuoteView:
- droppedBanner zeigt alle gedroppten Drafts mit Text-Preview +
lokalisierter Error-Message
- "Quittieren"-Button leert nur die UI-State (Server-Drop ist final)
- harvestDropNotifications() läuft nach jedem flushPending
iOS + macOS BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bei Network-Failure landet der Quote-Draft jetzt in einer persistenten
SwiftData-Queue (\`PendingSubmission\`) statt im Error-Banner. Beim
nächsten App-Launch ODER beim Wechsel auf scenePhase.active wird der
Flush automatisch versucht.
Retry-Policy:
- 5xx oder Transport-Failure (NSURLErrorDomain) → in Queue, Retry
- 4xx mit code (validation_failed, duplicate, unauthorized) →
permanenter Fehler, kein Retry (User-Aktion nötig)
- Hard-Limit 50 Retries pro Entry, danach pausiert
App-Group-Store \`submissions.store\` (parallel zu snapshot.store) im
\`group.ev.mana.zitare\`-Container. Fallback auf In-Memory falls
Disk-Init scheitert (App-Group noch nicht aktiviert im Apple-Dev-Portal).
UI-Pieces:
- Pending-Banner zeigt Queue-Tiefe wenn > 0
- Queued-Banner nach erfolgreichem Enqueue
- Form-Reset nach Enqueue (User sieht: "weg, kommt nach")
- onChange(scenePhase) → Auto-Flush bei Foreground
- ZitareNativeApp.task: Flush am Launch
Files:
- Sources/Core/Submit/PendingSubmissionModel.swift (neu, @Model)
- Sources/Core/Submit/SubmissionQueue.swift (neu, @Observable @MainActor)
- Sources/App/ZitareNativeApp.swift: Container-Init + environment-Wiring
- Sources/Features/Submit/SubmitQuoteView.swift: enqueue + flush + banners
iOS + macOS BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native Submit-Flow gegen zitare-api. SwiftUI-Form mit:
- TextEditor mit 10-1000-Zeichen-Validation + Counter
- Sprache (de/en/fr/es/it Picker)
- Author-Name (mandatory)
- Optional Source-Section (Toggle): Titel + Art (book/article/talk/film/other) + Jahr
- CC-BY-SA-4.0-Zustimmung als Pflicht-Toggle
- Submit-Button erst aktiv wenn alle 3 Bedingungen erfüllt
- authGate.require(reason: "submit") öffnet Login-Sheet wenn nötig;
Submit feuert auto nach signedIn
- Error-Banner mit lokalisiertem API-Code (api.error.<code> wird
in xcstrings nachgeschlagen)
- Success-Banner mit Slug + "wartet auf Moderation"-Hinweis
Neu in Submit-Tab als 4. Tab (Lesen / Erkunden / Einreichen / Konto).
- ZitareAPI: submitQuote(_:), QuoteDraft, SubmittedQuote, ZitareAPIError
- SubmitQuoteView ersetzt Placeholder-Stub
- RootView: AppTab.submit ergänzt
Offen: Offline-Queue (PendingSubmission via SwiftData) — bei Network-
Failure bleibt der Draft im Form-State und User retried manuell.
Nicht in ζ-3 abgeschlossen, gehört in ζ-3.5.
Offen: api.error.*-Keys in zitare-native Localizable.xcstrings —
aktuell nur DE-Source. EN/FR/ES/IT folgen separat.
iOS + macOS BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AccountView hatte zwar einen Status-Text aber keinen Login-Button,
und der ManaAuthGate war überhaupt nicht im App-Tree eingebaut —
Guest-Mode-User konnten sich nirgends anmelden.
- ZitareNativeApp: ManaAuthGate(auth:) instantiiert + via environment
durchgereicht
- RootView: .manaBrand(ZitareBrand.manaBrand) +
.manaAuthGate(authGate) { ManaLoginView(…) } für globales
Sign-In-Sheet
- AccountView: authActionCard mit "Mit mana-Konto anmelden" /
"Abmelden" (keepGuestMode: true)
- ZitareBrand neu (paper-Theme-Brücke zu ManaBrandConfig)
- project.yml: platformFilter: iOS für Widget+Share-Extensions
(macOS-Build war pre-existing kaputt mit "embedded iOS content")
iOS + macOS BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migriert die App auf die kanonische shared Keychain-Group
`ManaSharedKeychainGroup` aus mana-swift-core. Alle nativen
mana-e.V.-Apps (memoro, wordeck, nutriphi, herbatrium, zitare,
seepuls, viadocu, manameme, werdrobe, pageta, comicello, moodlit)
teilen damit ihren Auth-Token auf demselben Device — ein Login in
einer App, alle anderen starten direkt im .signedIn-Status.
Wichtig: für echtes Cross-App-Sharing müssen sowohl `keychainService`
als auch `keychainAccessGroup` identisch sein (Keychain-Lookup-Tupel
`(service, account, accessGroup)`) — beide jetzt auf
`ManaSharedKeychainGroup`. Bestehender App-eigener Bucket
(`ev.mana.<app>`) wird beim ersten Login mit dem neuen Token
überschrieben; User in TestFlight-Apps brauchen einen Re-Login.
Voraussetzung Apple-Dev-Portal (Tills manueller Schritt):
- Capability "Keychain Sharing" für die App ID aktivieren
- Group `ev.mana.session` hinzufügen
- Provisioning-Profile neu downloaden (Xcode auto)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: User wurden nach App-Update / längerer App-Pause aus-
geloggt, obwohl Refresh-Token theoretisch noch gültig war. Ursache:
mit `keychainAccessGroup: nil` landet das Token im impliziten
default-bucket; bei TestFlight-Cert-Drift oder Provisioning-
Profile-Wechsel wurde es nach Update für die neue App-Instanz
unzugänglich.
Bestehende Tokens werden via ManaCore v1.5.1 KeychainStore-
Migration-Fallback automatisch in den expliziten Bucket gespiegelt
— kein erzwungener Logout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- WebShellView (UIViewRepresentable + NSViewRepresentable) wrapt
WKWebView, KVO-Observation für Loading/Progress/canGoBack/URL,
Pull-to-Refresh via UIRefreshControl
- WebShellCoordinator (MainActor) hält WKNavigationDelegate +
WKUIDelegate, externe Links via openURL aus dem Environment in
System-Browser, Host-Whitelist auf zitare.com + .mana.how
- RootView refactored: Lesen-Tab lädt webBaseURL/, Erkunden-Tab
/explore. Universal-Links zitare.com/q|a|c/<slug>, /search,
/region/*, /thema/* etc. routen in den passenden Tab,
reloadToken zwingt Re-Navigation auch bei selber URL
- AppConfig.webBaseURL = appBaseURL (zitare.mana.how) bis
Cloudflare-Zone für zitare.com live ist; publicWebURL als
Konstante schon eingetragen
- CookieBridge-Skeleton für mana.access auf .mana.how —
scharfgeschaltet erst in ζ-3 nach Live-Auth-Smoke
- iPhone 16e Simulator: zitare.mana.how lädt, Carl-Spitteler-Quote
rendert, Healthz weiter 200
- 16 Files swiftlint-grün, alle Tests grün
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>