pageta-native/Sources/Core/EventSync/EventStates.swift
Till JS d4a388b4ed fix(ui): π-11 Polish — Reader-Padding, Cookie-Wall-Banner, Lese-Liste-Sections, Feed-Reader
Bug-Fixes:
- ArticleDetailView: ScrollView.contentMargins(.bottom, 60) — die
  iOS-18+-floating Tab-Bar verdeckte sonst die letzten Zeilen
- Empty-State-Copy entstaubt (kein „kommt mit π-6" mehr)
- Consent-Wall-Warning: ArticleExtractPreview decodet jetzt
  `warning`, persistiert via ArticleSavedPayload + ArticleState,
  Reader rendert Banner mit „Original im Browser öffnen"-Link
  wenn warning == "probable_consent_wall"
- AsyncImage-Placeholder von paper-cream auf Color.secondary.opacity(0.15)
  — sah im Dark-Mode wie helles Quadrat aus
- ArticleStatus.reading.label: „Lese" → „Liest" (Imperativ → Verb)

Lese-Liste-Umbau (Tills Feedback):
- Statt 4-Tab-Picker jetzt ein Single-Scroll mit Sections:
  Lesend → Ungelesen → Gelesen. Sticky-Header via List/Section.
- Archiv ist eigene Seite (ArchivedListView), erreichbar via
  Toolbar-Icon rechts. Aus dem Haupt-Scroll raus, damit's nicht
  permanent stört.
- Restore-Action im Archiv (Swipe → Wiederherstellen → status=unread)

Feed-Polish:
- FeedItemRow ist jetzt tappbar — Tap pusht in FeedItemReaderView
  (rendert FeedItem.content direkt aus Pool ohne Save-Roundtrip).
  Toolbar-Action „In Lese-Liste speichern" macht Save explizit.
- Swipe-Action „In Lese-Liste" als Quick-Save direkt in der Row.

ArticleSummarySection: Empty-State jetzt kompakter Pill-Trigger
statt großer Card mit leerem Header. Volle Card entsteht erst nach
Generieren (oder bei Error/Loading).

Toolbar: ellipsis.circle → ellipsis (iOS 18+ rendert Toolbar-Items
in Glass-Capsules, .circle ergab doppelten Kreis).

iOS-Sim BUILD SUCCEEDED, 38/38 Tests grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 17:33:25 +02:00

110 lines
2.7 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
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: "",
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
)
}
}