pageta-native/Sources/Core/EventSync/EventPayloads.swift
Till JS 6ec9e19dca Geteilte Links local-first speichern statt Login-Gate
Korrigiert den vorigen Ansatz: extract/preview ist server-auth-gated, ein
ausgeloggt geteilter Link konnte deshalb nicht gespeichert werden und
erschien erst nach Login. Statt einen Anmelde-Hinweis zu zeigen, speichert
Pageta den Link jetzt local-first.

- flushPendingShares: kein .signedIn-Gate mehr. saveSharedArticle legt einen
  Stub an (ArticleSaved, Host-Titel, leerer Inhalt) — reiner lokaler Emit,
  funktioniert anonym. Artikel erscheint sofort in der Lese-Liste.
- Neuer Event-Typ ArticleEnriched (+ non-destruktiver Reducer-Case) füllt
  Titel/Inhalt/Metadaten nach, ohne Status/Progress/Favorit/Notiz/Tags zu
  berühren.
- enrichPendingArticles-Sweep: läuft nach Flush, bei App-Active und Login;
  pro Artikel best-effort, gescheiterte Versuche je Session nicht wiederholt.
  claimOnSignIn liftet die anonymen Events beim Login in den Account.
- ShareExtension-Toast wieder ehrlich "Zur Lese-Liste hinzugefügt"; das nun
  überflüssige SharedSessionFlag + der Login-Prompt entfernt.
- Reader zeigt für noch nicht extrahierte Artikel einen "Inhalt wird noch
  geladen"-Hinweis statt einer leeren Seite.

Offener Punkt: Web-Reducer kennt ArticleEnriched noch nicht -> auf Web
bleiben so geteilte Artikel bis zum Web-Follow-up inhaltsleer.

Builds: iOS grün, macOS kompiliert (nur Provisioning blockt Signing).
Tests: 38/38 grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:50:47 +02:00

153 lines
4.6 KiB
Swift

import Foundation
import ManaEventSync
// Typed payloads per event type — Swift-Spiegel der `emit`-Aufrufe in
// `apps/web/src/lib/event-sync-api.ts`. Pageta hat noch kein
// `shared-schemas/pageta/events/*.ts`; der Web-Client ist die SOT.
//
// **Wire-Roundtrip:** Beim Emit: payload-Struct → `JSONValue.from(_:)`
// → `EventEnvelope.payload`. Beim Reduce: `EventEnvelope.payload`
// → `JSONValue.decode(_:)` → payload-Struct.
// MARK: - Event Type Names
/// Stabile Event-Type-Strings. Müssen exakt mit den `eventType`-Werten
/// auf der Web-Seite übereinstimmen — der Server reicht heute alles
/// durch, aber Cross-Client-Replay braucht Konsistenz.
enum EventType {
// article
static let articleSaved = "ArticleSaved"
/// Local-first deferred extraction: `ArticleSaved` darf mit leerem
/// `content` emittiert werden (z.B. ein ausgeloggt aus Safari geteilter
/// Link — `/extract/preview` braucht eine gültige Session). `ArticleEnriched`
/// füllt Titel/Inhalt/Metadaten nach, sobald die Extraktion möglich war,
/// **ohne** User-Felder (Status/Progress/Favorit/Tags/Notiz) zu berühren.
/// **Cross-Client:** Web-Reducer kennt das Event noch nicht (Follow-up) —
/// auf Web bleiben so geteilte Artikel bis dahin inhaltsleer.
static let articleEnriched = "ArticleEnriched"
static let articleStatusChanged = "ArticleStatusChanged"
static let articleProgressUpdated = "ArticleProgressUpdated"
static let articleFavoriteToggled = "ArticleFavoriteToggled"
static let articleNoteUpdated = "ArticleNoteUpdated"
static let articleTagsSet = "ArticleTagsSet"
static let articleWarningFlagged = "ArticleWarningFlagged"
static let articleWarningCleared = "ArticleWarningCleared"
static let articleDeleted = "ArticleDeleted"
// highlight
static let highlightCreated = "HighlightCreated"
static let highlightNoteUpdated = "HighlightNoteUpdated"
static let highlightColorChanged = "HighlightColorChanged"
static let highlightDeleted = "HighlightDeleted"
}
// MARK: - Article payloads
/// Snapshot des Extract-Preview + URL-Metadaten. Spiegelt das `emit`-
/// Payload in `event-sync-api.ts#saveArticleViaEventSync`.
struct ArticleSavedPayload: Codable {
var articleId: String
var originalUrl: String
var urlHash: String
var title: String
var excerpt: String?
var content: String
var author: String?
var siteName: String?
var imageUrl: String?
var wordCount: Int?
var readingTimeMinutes: Int?
var publishedAt: String?
var userNote: String?
// `warning` lebt seit shared-schemas 0.1.11 in einem eigenen
// ArticleWarningFlagged-Event, nicht mehr in der ArticleSaved-
// Payload. ArticleSaved bleibt frozen (`evt_article_saved_v1`).
}
/// Reichert einen zuvor minimal gespeicherten Artikel mit dem
/// Extract-Preview an. Nur Inhalts-/Metadaten-Felder — User-Mutationen
/// bleiben unberührt (das macht den Reducer-Apply non-destruktiv).
struct ArticleEnrichedPayload: Codable {
var articleId: String
var title: String
var excerpt: String?
var content: String
var author: String?
var siteName: String?
var imageUrl: String?
var wordCount: Int?
var readingTimeMinutes: Int?
var publishedAt: String?
/// Consent-Wall-Hinweis vom Extract, falls erkannt.
var warning: String?
}
struct ArticleStatusChangedPayload: Codable {
var articleId: String
var newStatus: ArticleStatus
}
struct ArticleProgressUpdatedPayload: Codable {
var articleId: String
var newProgress: Double
}
struct ArticleFavoriteToggledPayload: Codable {
var articleId: String
var isFavorite: Bool
}
struct ArticleNoteUpdatedPayload: Codable {
var articleId: String
var newNote: String?
}
struct ArticleTagsSetPayload: Codable {
var articleId: String
var tags: [String]
}
/// Setzt eine Soft-Warnung am Artikel. Heute nur
/// `"probable_consent_wall"`. Spiegelt
/// `shared-schemas/pageta/events/article.ts#ArticleWarningFlagged`.
struct ArticleWarningFlaggedPayload: Codable {
var articleId: String
var warning: String
}
/// Entfernt eine vorher gesetzte Warnung („Hinweis ausblenden" im UI).
struct ArticleWarningClearedPayload: Codable {
var articleId: String
}
struct ArticleDeletedPayload: Codable {
var articleId: String
}
// MARK: - Highlight payloads
struct HighlightCreatedPayload: Codable {
var highlightId: String
var articleId: String
var text: String
var note: String?
var color: HighlightColor
var startOffset: Int
var endOffset: Int
var contextBefore: String?
var contextAfter: String?
}
struct HighlightNoteUpdatedPayload: Codable {
var highlightId: String
var newNote: String?
}
struct HighlightColorChangedPayload: Codable {
var highlightId: String
var newColor: HighlightColor
}
struct HighlightDeletedPayload: Codable {
var highlightId: String
}