Commit graph

14 commits

Author SHA1 Message Date
Till JS
58eb2807c7 fix(auth): CookieBridge SameSite=None + Cross-Domain-Kommentar
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>
2026-05-20 15:09:23 +02:00
Till JS
a4ea32b637 feat: app.zitare.com + api.zitare.com URLs für Cutover 2026-05-20
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>
2026-05-20 14:34:03 +02:00
Till JS
c6127a2d31 ζ-3.6: Drop-Notification-Banner für Submission-Conflicts
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>
2026-05-19 16:49:17 +02:00
Till JS
53f8043a2d ζ-3.5b: Pending-Queue-UI + NWPathMonitor-Reconnect-Flush
Pending-Queue-UI:
- AccountView.pendingQueueCard listet alle wartenden Submissions
  mit Text-Preview (120c), Author, createdAt, retryCount, lastError
- "Jetzt versuchen"-Button triggert tryFlush(api:)
- Trash-Icon pro Row löscht einzeln aus der Queue
- Pull-to-Refresh aktualisiert beide Listen
- Card-View nur sichtbar wenn Queue-Tiefe > 0

ReachabilityWatcher:
- NWPathMonitor erkennt Reconnect-Flanke (offline → online)
- Bei Reconnect: SubmissionQueue.tryFlush auf @MainActor
- Filtert reine Wifi↔Mobil-Switches raus (nur "wieder erreichbar"
  zählt)
- Lebt im App-Root, startet nach Launch via .task

Files:
- Sources/Core/Submit/ReachabilityWatcher.swift (neu)
- Sources/App/ZitareNativeApp.swift: reachability.start in .task
- Sources/Features/Account/AccountView.swift: pendingQueueCard

iOS + macOS BUILD SUCCEEDED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:26:55 +02:00
Till JS
61927d27a3 ζ-3.5: Offline-Submit-Queue mit SwiftData + Auto-Retry
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>
2026-05-19 16:22:52 +02:00
Till JS
127c81b74c ζ-3: SubmitQuoteView nativ (Form + authGate + POST /quotes)
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>
2026-05-19 15:49:51 +02:00
Till JS
7ba8684074 fix(account): Anmelden-Button + ManaAuthGate-Wiring
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>
2026-05-19 15:28:08 +02:00
Till JS
99f81fcb78 feat(auth): Cross-App-SSO via shared Keychain-Group ev.mana.session
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>
2026-05-18 16:32:10 +02:00
Till JS
30e371b9d7 refactor(log): Log.swift auf ManaAppLog (mana-swift-core v1.7.0) + appGroup-Konsolidierung
Audit V4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:38:58 +02:00
Till JS
4b00c4ecdf refactor: Migration auf ManaWebShell + ManaTheme.paper aus mana-swift-* v1.6.0/v0.6.0
ManaWebShell aus mana-swift-ui v0.6.0 ersetzt den lokalen
`Sources/Features/WebShell/`-Ordner. WebShellCoordinator, WebShell-
View, WebShellScripts geloescht (~430 LOC). CookieBridge bleibt
lokal (App-spezifischer Cookie-SSO-Pfad fuer .mana.how), wandert
nach `Sources/Core/WebShell/CookieBridge.swift`.

`RootView.makeWebShellConfig()` baut Config mit Host-Whitelist
`zitare.com` + `www.zitare.com` + `*.mana.how`, ZitareTheme-Hints,
`syncDarkMode(localStorageKey: "zitare-mode")` und `hideElements`
fuer den zitare-web-Header.

ZitareTheme forwarded auf ManaTheme.paper aus mana-swift-core
v1.6.0 (~90 LOC weg, paper-Werte jetzt single-source in
`mana/packages/themes/src/variants/paper.css`).

AppConfig.userAgent als plattform-spezifischer Helper hinzu.

20/20 Unit-Tests gruen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:13:16 +02:00
Till JS
e139a382d8 fix(auth): keychainAccessGroup explizit auf TeamID.BundleID
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>
2026-05-17 18:20:47 +02:00
Till
c89d48c6f6 ζ-2 native: SwiftData-Snapshot-Cache + DailyQuoteWidget
- SnapshotModels.swift: CachedQuote (slug-unique, themes/regions
  als CSV), SnapshotMeta (singleton mit lastSyncedAt + totalCount),
  SnapshotContainer.make() mit App-Group-Store-URL (Fallback auf
  App-Container für Dev ohne Apple-Dev-Portal-Setup)
- SnapshotSync (actor) mit injectable Loader für Tests: refresh /
  refreshIfStale / tryRefresh (fail-soft). Re-konsolidiert beim Pull
  (Update + Insert + Delete entzogene Slugs). 24h-Staleness-Default.
- DailyQuoteWidget: Hash-of-Day-Picker aus SwiftData, drei Sizes,
  Mitternacht-Refresh-Policy, Placeholder bei leerem Store. Widget-
  Target zieht SnapshotModels.swift mit (project.yml).
- ZitareNativeApp triggert SnapshotSync.tryRefresh() bei Launch +
  WidgetCenter.reloadAllTimelines() danach.
- AppConfig.snapshotURL = webBaseURL/index-min.json (Web-Endpoint
  noch nicht live, fail-soft).
- DeepLinkRouter Substring-Guard fix (`/t` statt `/t/` im
  Prefix-Array, sonst greift hasPrefix("/t//") nicht).
- 22 Tests grün (6 AppConfig + 11 DeepLinkRouter + 3 SnapshotSync +
  1 UI + 1 Widget-Compile-Smoke), swiftlint 0 violations in 22 Files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:16:05 +02:00
Till
75b5e7113f ζ-1: WebShellView + Universal-Link-Routing
- 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>
2026-05-14 12:56:05 +02:00
Till
0bd59ed148 ζ-0 Setup: Repo-Skelett, iOS-Build grün, Healthz live
- 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>
2026-05-14 12:15:22 +02:00