From d4207bf33df01ec3172185c54d8d1c9a3e024c57 Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Sun, 29 Mar 2026 13:42:32 +0200 Subject: [PATCH] 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) --- src/api/gemini.test.ts | 12 ++++++------ src/api/gemini.ts | 6 +++++- src/storage/db.test.ts | 4 ++++ src/types.ts | 6 ++++++ src/ui/screens.ts | 4 ++++ 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/api/gemini.test.ts b/src/api/gemini.test.ts index 0935e7d..ed00bcc 100644 --- a/src/api/gemini.test.ts +++ b/src/api/gemini.test.ts @@ -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"); diff --git a/src/api/gemini.ts b/src/api/gemini.ts index d8a05ba..8bafda2 100644 --- a/src/api/gemini.ts +++ b/src/api/gemini.ts @@ -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 { 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( diff --git a/src/storage/db.test.ts b/src/storage/db.test.ts index 5c8106e..9cf95da 100644 --- a/src/storage/db.test.ts +++ b/src/storage/db.test.ts @@ -33,6 +33,10 @@ describe("IndexedDB wrapper", () => { sessionDates: [], selectedCharacter: "fee", selectedLayout: "de", + totalCorrect: 0, + totalErrors: 0, + errorKeyCounts: {}, + lastPlayedLevels: [], }; await saveProgress(db, progress); const result = await getProgress(db); diff --git a/src/types.ts b/src/types.ts index f3c7d84..6867192 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,8 +14,14 @@ export interface Progress { sessionDates: string[]; // ISO date strings (days only) selectedCharacter: CompanionType; selectedLayout: KeyboardLayout; + totalCorrect: number; + totalErrors: number; + errorKeyCounts: Record; + lastPlayedLevels: number[]; // last 3 played levels for review mechanic } +export type LessonPhase = "discover" | "practice" | "words"; + // Companion assets store export interface CompanionAssets { characterType: CompanionType; diff --git a/src/ui/screens.ts b/src/ui/screens.ts index 48664b9..a6ab5ed 100644 --- a/src/ui/screens.ts +++ b/src/ui/screens.ts @@ -70,6 +70,10 @@ export function initWelcomeScreen( sessionDates: [], selectedCharacter: selectedCompanion, selectedLayout: selectedLayout, + totalCorrect: 0, + totalErrors: 0, + errorKeyCounts: {}, + lastPlayedLevels: [], }; await saveProgress(db, progress); navigateTo("forest");