pageta-native/Sources/Core/EventSync/EventStates.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

115 lines
3 KiB
Swift

import Foundation
// Projected state types — Swift-Spiegel der TypeScript-State-Shapes in
// `apps/web/src/lib/event-sync-api.ts` und der Reducer in
// `mana/packages/shared-schemas/src/pageta/events/*.ts`.
//
// Diese Typen sind das Ergebnis des Reducer-Replays über alle Events
// eines Aggregates. Die UI sieht den Wire-Type `Article`/`Highlight`
// aus `Core/Domain/`; `EventSyncCoordinator` mappt State → Wire.
// MARK: - Article
/// Lese-Listen-Artikel als event-sourced State.
///
/// `content` enthält den Mozilla-Readability-Plain-Text aus dem
/// HTTP-`extract/preview`-Schritt, der weiterhin pageta-api lebt. Alle
/// User-Mutationen (Status/Progress/Favorite/Note/Delete) sind Events.
///
/// `urlHash` wird vom Native-Client als SHA-256-Hex der `originalUrl`
/// berechnet — matched dem Web-`urlHash`, damit Duplicate-Check über
/// alle Clients konsistent ist.
struct ArticleState: Codable, Equatable {
var id: String
var userId: String
var originalUrl: String
var urlHash: String
var title: String
var excerpt: String?
var content: String
/// Archiviertes Readability-HTML (img-srcs → media.mana.how) aus
/// `ArticleMediaArchived`. Native emittiert selbst nie HTML (der
/// Reader ist Plain-Text), spiegelt aber Web-Archive im State.
var htmlContent: String?
var author: String?
var siteName: String?
var imageUrl: String?
var wordCount: Int?
var readingTimeMinutes: Int?
var publishedAt: String?
var status: ArticleStatus
var readingProgress: Double
var isFavorite: Bool
var userNote: String?
var tags: [String]
var savedAt: String
var readAt: String?
var deletedAt: String?
/// Vom Server-Extract gesetzt (`"probable_consent_wall"` etc.).
/// Bleibt im State, damit das UI nach Replay den Banner zeigt.
var warning: String?
static func initial() -> ArticleState {
ArticleState(
id: "",
userId: "",
originalUrl: "",
urlHash: "",
title: "",
excerpt: nil,
content: "",
htmlContent: nil,
author: nil,
siteName: nil,
imageUrl: nil,
wordCount: nil,
readingTimeMinutes: nil,
publishedAt: nil,
status: .unread,
readingProgress: 0,
isFavorite: false,
userNote: nil,
tags: [],
savedAt: "",
readAt: nil,
deletedAt: nil,
warning: nil
)
}
}
// MARK: - Highlight
/// Highlight-State. Web-Client setzt echte Offsets, Native setzt 0/0
/// (Paragraph-Granularität). `text` ist führend für Anzeige + Lookup.
struct HighlightEventState: Codable, Equatable {
var id: String
var userId: 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?
var createdAt: String
var deletedAt: String?
static func initial() -> HighlightEventState {
HighlightEventState(
id: "",
userId: "",
articleId: "",
text: "",
note: nil,
color: .yellow,
startOffset: 0,
endOffset: 0,
contextBefore: nil,
contextAfter: nil,
createdAt: "",
deletedAt: nil
)
}
}