feat(03-01): extend Progress type, add LessonPhase, update rate limits

- Progress: added totalCorrect, totalErrors, errorKeyCounts, lastPlayedLevels
- New LessonPhase type: discover | practice | words
- Rate limits: 10 text / 5 image per day (was 1 each)
- Updated screens.ts with new Progress field defaults
- Updated existing tests for new rate limits and Progress shape

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 13:42:32 +02:00
co-authored by Claude Opus 4.6
parent e1fc5eb579
commit d4207bf33d
5 changed files with 25 additions and 7 deletions
+6 -6
View File
@@ -230,9 +230,9 @@ describe("Rate limiting", () => {
expect(canCallImage).toBe(true);
});
it("checkRateLimit returns false when at limit", async () => {
await incrementApiCall(db, "text");
await incrementApiCall(db, "image");
it("checkRateLimit returns false when at limit (10 text, 5 image)", async () => {
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
const canCallText = await checkRateLimit(db, "text");
const canCallImage = await checkRateLimit(db, "image");
@@ -241,7 +241,7 @@ describe("Rate limiting", () => {
});
it("incrementApiCall increments correct counter", async () => {
await incrementApiCall(db, "text");
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
const canCallText = await checkRateLimit(db, "text");
const canCallImage = await checkRateLimit(db, "image");
@@ -251,8 +251,8 @@ describe("Rate limiting", () => {
it("rate limit resets when lastApiCallDate differs from today", async () => {
// Increment to hit limit
await incrementApiCall(db, "text");
await incrementApiCall(db, "image");
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
// Manually set lastApiCallDate to yesterday
const { getSettings, saveSettings } = await import("../storage/db");
+5 -1
View File
@@ -5,6 +5,8 @@ import type { GeminiConfig } from "./config";
const GEMINI_BASE_URL =
"https://generativelanguage.googleapis.com/v1beta/models";
const RETRY_DELAYS = [1000, 2000, 4000];
const TEXT_RATE_LIMIT = 10;
const IMAGE_RATE_LIMIT = 5;
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -159,7 +161,9 @@ export async function checkRateLimit(
await saveSettings(db, settings);
}
return settings.apiCallsToday[type] < 1;
return type === "text"
? settings.apiCallsToday.text < TEXT_RATE_LIMIT
: settings.apiCallsToday.image < IMAGE_RATE_LIMIT;
}
export async function incrementApiCall(