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>
153 lines
4.6 KiB
Swift
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
|
|
}
|