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>
31 lines
895 B
Swift
31 lines
895 B
Swift
import Foundation
|
|
import SwiftData
|
|
|
|
/// Offline-Grade in der lokalen Queue. Wird beim Reconnect der Reihe nach
|
|
/// an `POST /reviews/:cardId/:subIndex/grade` gesendet — mit dem
|
|
/// **originalen** `reviewedAt`-Timestamp, damit der Server-FSRS
|
|
/// korrekt rechnet.
|
|
@Model
|
|
final class PendingGrade {
|
|
@Attribute(.unique) var id: String
|
|
var cardId: String
|
|
var subIndex: Int
|
|
var ratingRaw: String
|
|
var reviewedAt: Date
|
|
var queuedAt: Date
|
|
var lastTryAt: Date?
|
|
var lastError: String?
|
|
|
|
init(cardId: String, subIndex: Int, rating: Rating, reviewedAt: Date) {
|
|
id = "\(cardId)-\(subIndex)-\(reviewedAt.timeIntervalSince1970)"
|
|
self.cardId = cardId
|
|
self.subIndex = subIndex
|
|
ratingRaw = rating.rawValue
|
|
self.reviewedAt = reviewedAt
|
|
queuedAt = .now
|
|
}
|
|
|
|
var rating: Rating? {
|
|
Rating(rawValue: ratingRaw)
|
|
}
|
|
}
|