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
@@ -0,0 +1,41 @@
using QuestionsApi.Services.Flashcards;
using Xunit;
namespace QuestionsApi.Tests;
public sealed class FlashcardSchedulingTests
{
[Fact]
public void NextStudyDayStart_IsMidnightOfTheFollowingDay()
{
var studyDayStart = new DateTime(2026, 3, 5, 0, 0, 0, DateTimeKind.Utc);
var next = FlashcardScheduling.NextStudyDayStart(studyDayStart);
Assert.Equal(new DateTime(2026, 3, 6, 0, 0, 0, DateTimeKind.Utc), next);
}
[Fact]
public void NextStudyDayStart_TruncatesTimeOfDayBeforeAdvancing()
{
// A session that started mid-day still buries siblings until the *next calendar day*,
// not exactly 24h later.
var studyDayStart = new DateTime(2026, 3, 5, 14, 30, 0, DateTimeKind.Utc);
var next = FlashcardScheduling.NextStudyDayStart(studyDayStart);
Assert.Equal(new DateTime(2026, 3, 6, 0, 0, 0, DateTimeKind.Utc), next);
}
[Theory]
[InlineData(20, null, 0, 20)]
[InlineData(20, null, 15, 5)]
[InlineData(20, null, 25, 0)]
[InlineData(20, 5, 0, 5)]
[InlineData(20, 0, 0, 0)]
public void ResolveDailyBudget_DeckOverrideWinsOverGlobal_ClampedAtZero(
int globalLimit, int? deckOverride, int consumedToday, int expected)
{
Assert.Equal(expected, FlashcardScheduling.ResolveDailyBudget(globalLimit, deckOverride, consumedToday));
}
}