Add flashcards feature with spaced repetition scheduling

Adds a full flashcards system: FSRS-based scheduling, cloze/basic
card types, CSV import/export, media storage, and study UI, wired
into navigation and the dashboard alongside existing question flows.
This commit is contained in:
hz
2026-07-21 15:08:52 -05:00
parent 29b89dc404
commit 48a8c8361d
41 changed files with 4458 additions and 8 deletions
+51
View File
@@ -0,0 +1,51 @@
extends SceneTree
# Exercises FlashcardService's offline-review-queue bookkeeping without touching the network
# or user:// disk (mirrors the load(...).new() pattern used by review_queue_session_test.gd -
# the instance is never added to the tree, so _ready() never runs and the real HTTPRequest
# nodes/autoload wiring are never touched).
var failures := 0
func _initialize() -> void:
var service = load("res://scripts/autoload/flashcard_service.gd").new()
var uuid: String = service._new_uuid()
_assert(uuid.length() == 36, "a client review id should be a standard 36-character UUID")
var segments := uuid.split("-")
_assert(segments.size() == 5, "a UUID should have five dash-separated segments")
_assert(segments[2].begins_with("4"), "the generated UUID should be version 4")
_assert("89ab".contains(segments[3][0]), "the generated UUID should have a valid RFC 4122 variant nibble")
service.active_account_key = "account-a"
service.outbox = [
{"account": "account-b", "payload": {"cardId": "other-account-card"}, "retry_count": 0},
{"account": "account-a", "payload": {"cardId": "card-1"}, "retry_count": 0}
]
_assert(service.has_pending_reviews(), "a queued review for the active account should be reported as pending")
_assert(service._next_account_item_index() == 1,
"outbox lookup should skip items queued under a different account (offline queue recovery must not leak across accounts)")
service.active_account_key = "account-c"
_assert(not service.has_pending_reviews(),
"switching to an account with no queued reviews should report nothing pending")
service.active_account_key = "account-a"
service.outbox[1]["active"] = true
_assert(service._active_item_index() == 1, "the in-flight retry item should be discoverable while marked active")
var iso: String = service.current_study_day_start_utc()
_assert(iso.ends_with("Z"), "the study day boundary must be expressed as a UTC instant")
_assert(Time.get_unix_time_from_datetime_string(iso.trim_suffix("Z")) > 0,
"the study day boundary string should parse back into a valid datetime")
service.free()
quit(1 if failures > 0 else 0)
func _assert(condition: bool, message: String) -> void:
if condition:
return
failures += 1
push_error(message)