Voller Lern-Flow mit Web-Parität: fällige Karten via /reviews/due laden, flip + rate (4 Buttons + Haptic), Grades via Offline-Queue ans Server-FSRS schicken. - Card/Review/DueReview DTOs mit snake_case + camelCase-deckId- Sonderfall im embedded card-Subobjekt - CardType-Enum (alle 7 Typen), Rating-Enum mit deutschen Labels - Cloze-Helper 1:1-Port aus cards-domain (extractClusterIds, subIndexCount, clusterId, renderPrompt/Answer, hint) - CardsAPI.dueReviews(deckId:) + gradeReview(cardId,subIndex,rating,reviewedAt) - PendingGrade SwiftData-Model + GradeQueue (FIFO-Drain, originaler Timestamp bleibt, bei Netzfehler in Queue, Retry beim nächsten Drain) - StudySession @Observable State-Machine - CardRenderer für basic, basic-reverse, cloze; Placeholder für image-occlusion/audio-front/typing/multiple-choice (β-3/β-4) - RatingBar mit UIImpactFeedbackGenerator (medium/heavy) - StudySessionView per NavigationLink aus DeckListView - 9 neue Tests (Cloze: 8, Review-Decoding: 3), insgesamt 17 grün Server-authoritative FSRS bleibt — kein ts-fsrs-Port. Endurance-Test auf realem Gerät steht aus (siehe PLAN.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
51 lines
1.6 KiB
Swift
51 lines
1.6 KiB
Swift
import Foundation
|
|
|
|
/// Card-DTO. Wire-Format aus `cards/apps/api/src/lib/dto.ts:toCardDto`
|
|
/// und `cards/packages/cards-domain/src/schemas/card.ts`.
|
|
struct Card: Codable, Identifiable, Hashable, Sendable {
|
|
let id: String
|
|
let deckId: String
|
|
let userId: String
|
|
let type: CardType
|
|
let fields: [String: String]
|
|
let mediaRefs: [String]
|
|
let contentHash: String?
|
|
let createdAt: Date
|
|
let updatedAt: Date
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case deckId = "deck_id"
|
|
case userId = "user_id"
|
|
case type
|
|
case fields
|
|
case mediaRefs = "media_refs"
|
|
case contentHash = "content_hash"
|
|
case createdAt = "created_at"
|
|
case updatedAt = "updated_at"
|
|
}
|
|
}
|
|
|
|
/// Card-Type-Enum. Vollständig aus `CardTypeSchema`. In β-2 rendern
|
|
/// wir nur `basic`, `basic-reverse`, `cloze`. Die anderen Types
|
|
/// kommen in β-3 und β-4 dazu, sind aber jetzt schon decodierbar.
|
|
enum CardType: String, Codable, Sendable, CaseIterable {
|
|
case basic
|
|
case basicReverse = "basic-reverse"
|
|
case cloze
|
|
case imageOcclusion = "image-occlusion"
|
|
case audioFront = "audio-front"
|
|
case typing
|
|
case multipleChoice = "multiple-choice"
|
|
}
|
|
|
|
/// Vereinfachtes Card-Sub-Objekt aus `/reviews/due?deck_id=X`-Response.
|
|
/// Server liefert nur 4 Felder (id, deckId, type, fields) als Drizzle-
|
|
/// Joined-Subset — Achtung: `deckId` hier in **camelCase**, nicht
|
|
/// snake_case wie sonst.
|
|
struct ReviewCard: Codable, Hashable, Sendable {
|
|
let id: String
|
|
let deckId: String
|
|
let type: CardType
|
|
let fields: [String: String]
|
|
}
|