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:
@@ -230,9 +230,9 @@ describe("Rate limiting", () => {
|
|||||||
expect(canCallImage).toBe(true);
|
expect(canCallImage).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("checkRateLimit returns false when at limit", async () => {
|
it("checkRateLimit returns false when at limit (10 text, 5 image)", async () => {
|
||||||
await incrementApiCall(db, "text");
|
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
|
||||||
await incrementApiCall(db, "image");
|
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
|
||||||
|
|
||||||
const canCallText = await checkRateLimit(db, "text");
|
const canCallText = await checkRateLimit(db, "text");
|
||||||
const canCallImage = await checkRateLimit(db, "image");
|
const canCallImage = await checkRateLimit(db, "image");
|
||||||
@@ -241,7 +241,7 @@ describe("Rate limiting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("incrementApiCall increments correct counter", async () => {
|
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 canCallText = await checkRateLimit(db, "text");
|
||||||
const canCallImage = await checkRateLimit(db, "image");
|
const canCallImage = await checkRateLimit(db, "image");
|
||||||
@@ -251,8 +251,8 @@ describe("Rate limiting", () => {
|
|||||||
|
|
||||||
it("rate limit resets when lastApiCallDate differs from today", async () => {
|
it("rate limit resets when lastApiCallDate differs from today", async () => {
|
||||||
// Increment to hit limit
|
// Increment to hit limit
|
||||||
await incrementApiCall(db, "text");
|
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
|
||||||
await incrementApiCall(db, "image");
|
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
|
||||||
|
|
||||||
// Manually set lastApiCallDate to yesterday
|
// Manually set lastApiCallDate to yesterday
|
||||||
const { getSettings, saveSettings } = await import("../storage/db");
|
const { getSettings, saveSettings } = await import("../storage/db");
|
||||||
|
|||||||
+5
-1
@@ -5,6 +5,8 @@ import type { GeminiConfig } from "./config";
|
|||||||
const GEMINI_BASE_URL =
|
const GEMINI_BASE_URL =
|
||||||
"https://generativelanguage.googleapis.com/v1beta/models";
|
"https://generativelanguage.googleapis.com/v1beta/models";
|
||||||
const RETRY_DELAYS = [1000, 2000, 4000];
|
const RETRY_DELAYS = [1000, 2000, 4000];
|
||||||
|
const TEXT_RATE_LIMIT = 10;
|
||||||
|
const IMAGE_RATE_LIMIT = 5;
|
||||||
|
|
||||||
function sleep(ms: number): Promise<void> {
|
function sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
@@ -159,7 +161,9 @@ export async function checkRateLimit(
|
|||||||
await saveSettings(db, settings);
|
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(
|
export async function incrementApiCall(
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ describe("IndexedDB wrapper", () => {
|
|||||||
sessionDates: [],
|
sessionDates: [],
|
||||||
selectedCharacter: "fee",
|
selectedCharacter: "fee",
|
||||||
selectedLayout: "de",
|
selectedLayout: "de",
|
||||||
|
totalCorrect: 0,
|
||||||
|
totalErrors: 0,
|
||||||
|
errorKeyCounts: {},
|
||||||
|
lastPlayedLevels: [],
|
||||||
};
|
};
|
||||||
await saveProgress(db, progress);
|
await saveProgress(db, progress);
|
||||||
const result = await getProgress(db);
|
const result = await getProgress(db);
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ export interface Progress {
|
|||||||
sessionDates: string[]; // ISO date strings (days only)
|
sessionDates: string[]; // ISO date strings (days only)
|
||||||
selectedCharacter: CompanionType;
|
selectedCharacter: CompanionType;
|
||||||
selectedLayout: KeyboardLayout;
|
selectedLayout: KeyboardLayout;
|
||||||
|
totalCorrect: number;
|
||||||
|
totalErrors: number;
|
||||||
|
errorKeyCounts: Record<string, number>;
|
||||||
|
lastPlayedLevels: number[]; // last 3 played levels for review mechanic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LessonPhase = "discover" | "practice" | "words";
|
||||||
|
|
||||||
// Companion assets store
|
// Companion assets store
|
||||||
export interface CompanionAssets {
|
export interface CompanionAssets {
|
||||||
characterType: CompanionType;
|
characterType: CompanionType;
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ export function initWelcomeScreen(
|
|||||||
sessionDates: [],
|
sessionDates: [],
|
||||||
selectedCharacter: selectedCompanion,
|
selectedCharacter: selectedCompanion,
|
||||||
selectedLayout: selectedLayout,
|
selectedLayout: selectedLayout,
|
||||||
|
totalCorrect: 0,
|
||||||
|
totalErrors: 0,
|
||||||
|
errorKeyCounts: {},
|
||||||
|
lastPlayedLevels: [],
|
||||||
};
|
};
|
||||||
await saveProgress(db, progress);
|
await saveProgress(db, progress);
|
||||||
navigateTo("forest");
|
navigateTo("forest");
|
||||||
|
|||||||
Reference in New Issue
Block a user