pageta-native/Sources/Core/EventSync/EventPayloads.swift
Till JS ca4b17b1a3 feat: ArticleMediaArchived-Mirror + archivierte Vorschaubilder (Liste + Reader-Header)
Spiegel von @mana/shared-schemas/pageta@0.1.21 (Web = SOT):
- ArticleMediaArchivedPayload + Reducer-Case (non-destruktiv, if-let
  wie warning); ArticleEnriched clobbert imageUrl nicht mehr
- ArticleState.htmlContent; stateToArticle mappt es (war nil)
- ArticleExtractPreview dekodiert imageUrl (og:image-Hotlink)
- archiveMedia(): POST /articles/archive-media, fire-and-forget nach
  Save/Enrich; NUR die archivierte media.mana.how-URL wird emittiert,
  nie der Hotlink; Delete-Race-Guard wie im Web-Client
- MediaURL.variant (thumb/medium/large, Spiegel von media-url.ts)
- Lese-Liste: 56pt-Thumbnail; Reader-Header: Lead-Image (medium, 200pt)
- 5 neue Reducer-/MediaURL-Tests (54/54 grün, iPhone-17-Simulator)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:52:17 +02:00

172 lines
5.5 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"
/// Archivierungs-Ergebnis (`evt_article_media_archived_v1`): Bilder
/// liegen in mana-media, `imageUrl` zeigt auf die archivierte
/// media.mana.how-URL — nie Hotlinks in Events (Design-Regel, siehe
/// shared-schemas `ArticleMediaArchived`).
static let articleMediaArchived = "ArticleMediaArchived"
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
}
/// Archivierungs-Ergebnis für einen Artikel — Swift-Spiegel von
/// `shared-schemas/pageta/events/article.ts#ArticleMediaArchived`.
/// Non-destruktiv (`?? state`-Semantik im Reducer), damit ein
/// imageUrl-only-Archiv (Native-Pfad ohne HTML) ein früheres
/// Voll-Archiv nicht leert — und umgekehrt. Native emittiert
/// `htmlContent: nil` (der Reader ist Plain-Text); `mediaIds`
/// referenziert die mana-media-Objekte für Cleanup/DSGVO-Zuordnung.
struct ArticleMediaArchivedPayload: Codable {
var articleId: String
var htmlContent: String?
var imageUrl: String?
var mediaIds: [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
}