cards-native/Sources/Core/Domain/Card.swift
Till JS 3b861af3fb v0.3.0 — Phase β-2 Study-Loop
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>
2026-05-13 00:16:11 +02:00

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]
}