# Flashcards A separate spaced-repetition system alongside the missed-question review queue. Users author basic and cloze flashcards (or generate a basic card from a resolved question), organize them in flat decks, and study them on an FSRS schedule. ## Scheduling Reviews are scored by [FSRS.Core](https://www.nuget.org/packages/FSRS.Core) 1.0.7, wrapped behind `IFlashcardScheduler` (`api/QuestionsApi/Services/Flashcards/FlashcardScheduler.cs`) so the rest of the API never touches the library directly. Pinned, non-configurable defaults: - Desired retention: 90% - Maximum interval: 36,500 days - Interval fuzzing: disabled, so a review's outcome is a deterministic function of stored state (needed for idempotent replay from the offline outbox, and for golden-vector testing) - Algorithm/parameter version is recorded per card (`FlashcardSchedule.AlgorithmVersion`) so a future FSRS upgrade or personalized-parameter feature can be introduced without losing the ability to tell which cards were scheduled under which version There is no per-user parameter optimization yet; `FlashcardScheduleStore` stores complete before/after snapshots on every `FlashcardReviewLog` entry specifically so that a future optimizer can be trained from full history without a data migration. `api/QuestionsApi.Tests/FlashcardSchedulerTests.cs` validates the wrapper against the official FSRS formulas (initial stability/difficulty per rating, and the interval-equals-stability identity that holds at exactly 90% desired retention) computed from the library's own shipped default parameters, rather than against hand-copied constants - this stays correct even if FSRS.Core's defaults are retrained upstream. ## Data model Six user-owned Mongo collections, all indexed on `(UserId, ...)`: - `FlashcardDecks` - flat (no nesting); every user has exactly one `IsDefault` deck, created lazily on first use. - `FlashcardNotes` - authored content (`Prompt`/`Answer`/`Extra`/`Tags`/`Images`). Cloze notes store their `{{cN::answer::hint}}` markup in `Prompt`. - `FlashcardSchedules` - one row per generated card, keyed by `(NoteId, Variant)`. Basic notes always have variant `0`; cloze notes get one row per distinct cloze number found in the prompt. Kept in a separate collection from notes so editing a note's content never discards its review history. - `FlashcardReviewLogs` - immutable, keyed by the client-generated review UUID for idempotent submission; carries full before/after schedule snapshots. - `FlashcardMediaAssets` - user-uploaded images (PNG/JPEG/WebP, 5 MB/4-per-note limits). - `FlashcardSettings` - one row per user (global new/review daily limits). Attaching a question's own image to a flashcard does not copy bytes: `FlashcardImageReference` has a `Kind` (`"question"` vs `"owned"`) so the client can resolve `"question"` references against the existing `/api/questions/media/{id}` endpoint instead of duplicating the asset. Sibling cloze cards are buried (`FlashcardSchedule.BuriedUntilUtc`) until the next local study day whenever any one sibling is reviewed. Daily new/review limits reset at the same boundary. That boundary is supplied by the client as `studyDayStartUtc` on study/review requests (the local calendar day at session start, expressed as a UTC instant) rather than computed server-side, per the spec's client-local-day requirement. ## API All endpoints live under `/api/flashcards` (`FlashcardsController.cs`) and require `USER`/`SUPER` auth. Deck/note mutations use the same optimistic-concurrency (`ExpectedRevision` + 409 Conflict) pattern as the rest of the API. Review submission is idempotent on `ClientReviewId`; undo only allows reverting the single most recent review on a card (it refuses if the card's revision has moved since). ## Import/export CSV is UTF-8, text-only, headers `stable_id,deck,type,prompt,answer,extra,tags, source_question_external_id` (tags semicolon-separated); it omits images, schedule state, and review history and reports those omissions via the `X-Flashcard-Export-Omissions` response header. App JSON is full-fidelity (decks, settings, notes, schedules, review logs, base64 images) and merges on import by the note's stable UUID. Both formats go through a create/update/skip/error preview (`FlashcardImportPreviews`, TTL-expired after an hour) before `import/commit` applies it; commit never silently drops a row - everything is created, updated, skipped as an exact-content duplicate, or reported as a row-level error. Limits: 25 MB and 10,000 notes per import. The Godot client (`scripts/autoload/flashcard_service.gd`) picks a file, uploads it to the matching preview endpoint as `multipart/form-data`, and shows the create/update/skip/error counts before letting the user commit or cancel. Export streams the response body and saves it client side. File selection and download both branch on `OS.has_feature("web")`: on web, a hidden ``/`FileReader` pair (bridged back into GDScript via `JavaScriptBridge.create_callback`) and an anchor-click data-URL download stand in for a native picker/save dialog; on desktop, a `FileDialog` and the OS downloads folder are used instead. ## Deployment No new secrets. `FlashcardsController`/stores register the same way as the rest of the API (`Program.cs`, `AddSingleton`). The Godot client resolves its API base URL from `step3/flashcards_api_url` in `project.godot`, following the same same-origin-on-web resolution as every other autoload service (`api_endpoint_config.gd`).