Rebuild flashcard study screen from mockup, with real backend-backed stats
Study screen now matches the provided mockup's layout: a "Current Deck" panel with a circular mastery-progress ring, and a "Spaced Repetition" panel (New/Due/Learning/Review counts, streak, daily goal) on either side of the card. Kept the app's real global sidebar rather than the mockup's own nav, per direction. Mastery/Streak/Daily Goal weren't tracked anywhere, so this adds real backend support rather than showing fabricated numbers: - Mastered = graduated to review state AND FSRS stability >= 21 days (FlashcardScheduleStore.MasteryStabilityDaysThreshold), stricter than the plain Review-state count - CurrentStreakDays = consecutive UTC days with a review, computed from review-log timestamps in the new pure FlashcardScheduling .ComputeStreakDays (unit tested, habit-streak semantics: doesn't reset to 0 the instant a new day starts before today's first review) - DailyGoal/ReviewsToday reuse the account's existing ReviewsPerDay setting rather than inventing a new one Also removes the exit "X" button in favor of the same Escape/pause menu the question-player flow uses (main.gd's shared app_menu, with an "Exit Study Session" action), and adds a new circular_progress.gd reusable ring control plus a real "Options" menu (bury/suspend the current card) wired to schedule endpoints that had no UI before.
This commit is contained in:
@@ -38,4 +38,68 @@ public sealed class FlashcardSchedulingTests
|
||||
{
|
||||
Assert.Equal(expected, FlashcardScheduling.ResolveDailyBudget(globalLimit, deckOverride, consumedToday));
|
||||
}
|
||||
|
||||
private static readonly DateTime Today = new(2026, 3, 10, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_NoReviews_IsZero()
|
||||
{
|
||||
Assert.Equal(0, FlashcardScheduling.ComputeStreakDays([], Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_ReviewedTodayOnly_IsOne()
|
||||
{
|
||||
var timestamps = new List<DateTime> { Today.AddHours(9) };
|
||||
|
||||
Assert.Equal(1, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_ThreeConsecutiveDaysEndingToday_IsThree()
|
||||
{
|
||||
var timestamps = new List<DateTime>
|
||||
{
|
||||
Today.AddDays(-2).AddHours(8), Today.AddDays(-1).AddHours(20), Today.AddHours(7)
|
||||
};
|
||||
|
||||
Assert.Equal(3, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_NotYetReviewedToday_StillCountsThroughYesterday()
|
||||
{
|
||||
// The streak shouldn't visibly reset to 0 the instant a new calendar day begins, before
|
||||
// the user has had a chance to study today.
|
||||
var timestamps = new List<DateTime> { Today.AddDays(-2).AddHours(8), Today.AddDays(-1).AddHours(20) };
|
||||
|
||||
Assert.Equal(2, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_GapBeforeYesterday_BreaksTheStreak()
|
||||
{
|
||||
var timestamps = new List<DateTime>
|
||||
{
|
||||
Today.AddDays(-10).AddHours(8), Today.AddDays(-1).AddHours(20), Today.AddHours(7)
|
||||
};
|
||||
|
||||
Assert.Equal(2, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_MissedYesterdayAndToday_IsZero()
|
||||
{
|
||||
var timestamps = new List<DateTime> { Today.AddDays(-3).AddHours(8) };
|
||||
|
||||
Assert.Equal(0, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeStreakDays_MultipleReviewsSameDay_CountOnceTowardTheStreak()
|
||||
{
|
||||
var timestamps = new List<DateTime> { Today.AddHours(7), Today.AddHours(8), Today.AddHours(9) };
|
||||
|
||||
Assert.Equal(1, FlashcardScheduling.ComputeStreakDays(timestamps, Today));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user