Default `--self remove` strippt `self.` aus @autoclosure-Calls (Logger.info)
und Closure-Captures, was Swift-6-strict-concurrency dann als "implicit
use of self in closure" rejected. Default `redundantSendable` strippt
`Sendable` von Codable-DTOs, die über actor-Grenzen wandern müssen.
Beide Regeln aus. Zusätzlich Lauf über alle Files: harmlose Whitespace-/
Trailing-Comma-/Optional-Init-Normalisierung in 5 Files. `self.` und
`Sendable` bleiben überall erhalten. Build grün.
Hintergrund: η-0-Lauf hat das aktiv gemacht und Submit-DTOs zerschossen,
die ich dann von Hand revertieren musste. Dieser Commit verhindert die
Wiederholung in η-1+.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CookieBridge ist immer noch Skeleton (no-op currentAccessToken), aber
das Cookie-Setup wird vor ζ-3 scharf. Zwei Korrekturen:
1. Domain `.mana.how` ist tatsächlich korrekt für app.zitare.com:
der Cookie wird vom WebView an XHR-Ziele auf .mana.how mitgesendet
(auth.mana.how/refresh), unabhängig vom Source-Page-Host. Gleicher
Flow wie im Web-Client. Vorheriger „bricht cross-domain"-Kommentar
war falsch.
2. SameSite=Lax → "None" — mana-auth setzt für Cross-Subdomain-SSO
sameSite='none' (better-auth.config.ts:320). Ohne None wird der
Cookie bei Cross-Origin-POST nicht versendet. Foundation hat keine
.sameSiteNone-Konstante, akzeptiert aber Roh-Strings für
.sameSitePolicy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native-Konstanten ziehen mit dem Web-Cutover zu .zitare.com-
Subdomains nach. Universal-Link-AASA-Liste enthält jetzt zitare.com
+ app.zitare.com (zitare.mana.how raus). webBaseURL ist jetzt das
echte publicWebURL (zitare.com), wie ursprünglich geplant.
CookieBridge bleibt Skeleton — die `.mana.how`-Cookie-Domain-
Strategie greift nicht für `.zitare.com`. Hinweis im Kommentar
gesetzt, Update vor ζ-3 nötig.
Code-Only. Erst nach nächstem Native-Build/TestFlight-Upload live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Parallel zu mukke-native: 11 server-side error codes aus zitare-api/
routes/quotes.ts werden zur Build-Zeit in alle 5 Locales kompiliert
und via Bundle.localizedString(forKey:) im SubmitQuoteView-Error-
Banner gelesen.
Codes: author_not_found, author_resolution_failed, duplicate,
invalid_json, invalid_status, no_changes, not_found, not_open,
revision_not_found, unauthorized, validation_failed.
iOS 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>
Überspringt die App-Verschlüsselungs-Abfrage in App Store Connect
bei jedem TestFlight-Upload. Entspricht der "Keinen der oben
genannten Algorithmen"-Wahl, weil die App nur HTTPS nutzt und
damit unter US-Export-Recht exempt ist.
In project.yml mitgepflegt, damit xcodegen den Key nicht beim
nächsten Regen wegblässt.
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>
- accessoryInline: kompakte Zitat-Vorschau (max 40 Zeichen) + Autor
- accessoryRectangular: Zitat 2 Zeilen + Autor
- widgetURL zitare://heute pro Family
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>
Tag 1: Hybrid SwiftUI + WKWebView, ζ-0 + ζ-1 + Teile ζ-2.
Tag 2: dreilagiger weißer Flash beim App-Start gefixt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
System-Launch-Screen war default-weiß (UILaunchScreen leer), WKWebView
malt vor first paint opaque weiß, und das HTML selbst hat keinen
Background bevor das CSS-Bundle lädt. Alle drei Layer jetzt auf den
Paper-Theme-Background gesetzt — sRGB-konvertiert aus `--color-background`
in mana/packages/themes/paper.css.
- LaunchBackground.colorset mit Light + Dark Variante
- project.yml: UILaunchScreen.UIColorName: LaunchBackground
- WKWebView: isOpaque=false + clear backgrounds (iOS), drawsBackground=false (macOS)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: in TestFlight-Build wirkte Account-Tab dunkel, aber
Lesen + Erkunden hell (oder umgekehrt, je nachdem was im Web-
localStorage stand). Inkonsistent, weil:
- AccountView (SwiftUI) nutzt ZitareTheme.dynamic() — folgt System
- WebView las localStorage['zitare-mode'], das nur über den
Theme-Toggle-Button im Web-Header gesetzt wurde — den wir aber
nativ ausgeblendet haben → kein User-Steuerpfad
Fix: neuer User-Script `syncDarkMode` injiziert at document.start:
- liest prefers-color-scheme via matchMedia
- schreibt localStorage['zitare-mode'] = 'dark' / removes
- togglet die `.dark`-Class auf <html>
- bleibt aktiv via matchMedia-change-Listener für Live-Switches
Reihenfolge in WebView-Config: syncDarkMode VOR hideWebHeader,
damit das Theme richtig ist bevor Header-CSS rendert.
Build 3 für nächsten TestFlight-Upload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- WebShellView füllt Window-Höhe explizit
(.frame(maxWidth: .infinity, maxHeight: .infinity)) — sonst hatte
WKWebView auf Mac einen Rest-Spalt, der den Web-Footer abschnitt.
- RootView TabBar-Hintergrund: Paper-Background auf Window-Toolbar
(macOS-only via #if os(macOS), windowToolbar-Placement existiert
unter iOS nicht). Title-Bar passt jetzt zum Content statt System-Grau.
- AccountView Header: SF Symbol "quote.opening" durch eine einzelne,
zentrierte Öffnungs-Anführung in Georgia-Bold ersetzt. Die SF-
Doppelglyphe war asymmetrisch positioniert, sieht jetzt sauber aus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Z + zwei Akzent-Quotes lieferten ein unsauber gerendertes PNG
(Z fehlte aufgrund Font-Bounds-Mathematik bei großem Pointsize).
Reduziert auf eine zentrierte Öffnungs-Anführung („) — ikonisch
auch bei 40×40, klar erkennbar als Quotes-App.
Platzhalter — vor Launch durch Designer-Icon ersetzen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apple Validator hatte drei Fehler geworfen:
- Missing 120x120 (iPhone) und 152x152 (iPad)
- Missing Info.plist key CFBundleIconName
Root-Cause: AppIcon.appiconset hatte keinen filename gesetzt → keine
PNG-Variants im Bundle. Plus: bei GENERATE_INFOPLIST_FILE=NO injiziert
Xcode CFBundleIconName nicht automatisch, das muss explizit in die
plist.
Fixes:
- scripts/make-appicon.swift erzeugt 1024×1024-PNG-Platzhalter in
paper-Theme-Farben (Sienna-Background, dunkles Z, zwei
Anführungszeichen-Akzente) analog cards-native
- Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
verlinkt AppIcon-1024.png für light / dark / tinted (3 Appearances)
- project.yml setzt CFBundleIconName: AppIcon im Info.plist-Root
Archive-Verifikation:
$ /usr/libexec/PlistBuddy -c "Print :CFBundleIconName" Info.plist
→ AppIcon
$ ls ZitareNative.app | grep AppIcon
→ AppIcon, AppIcon60x60@2x.png (=120×120), AppIcon76x76@2x~ipad.png
(=152×152)
Platzhalter — vor produktivem App-Store-Launch durch designtes Icon
ersetzen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- WebShellScripts.hideWebHeader: drei Selektor-Strategien gestapelt
(data-app-nav-Marker / strukturell via :has(a.brand) / positionell
via :first-of-type), damit Klassen-Renames in zitare-web das Hide
nicht still brechen
- project.yml entitlements: applinks:zitare.mana.how als zweite
Universal-Link-Domain, solange zitare.com-DNS-Zone fehlt. Beide
Hosts liefern dasselbe AASA-File. Nach Cloudflare-Cut kann der
Eintrag bleiben — schadet nicht
- Live-E2E verifiziert: simctl openurl https://zitare.mana.how/q/...
→ App empfängt Deep-Link, WebShell lädt die Quote-Page, native
TabBar bleibt, Web-Header bleibt versteckt
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>