diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-02 10:41:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-02 10:41:12 +0900 |
| commit | 8f1a08fefee3a8e928baec741c830a88a4cd7200 (patch) | |
| tree | 19101c992c19e283e4fa30abafcd58cfeb401cc9 /src/server | |
| parent | 90b06b22e1e468cd19358536919a38ab6377fd23 (diff) | |
| download | kioku-8f1a08fefee3a8e928baec741c830a88a4cd7200.tar.gz kioku-8f1a08fefee3a8e928baec741c830a88a4cd7200.tar.zst kioku-8f1a08fefee3a8e928baec741c830a88a4cd7200.zip | |
feat(study): submit reviews offline via IndexedDB
Move FSRS scheduling to a shared module so the client can compute next
card state without contacting the server. StudyPage now writes the
updated card and review log straight to IndexedDB and lets the existing
sync engine push them on reconnect, instead of POSTing to
/api/decks/:deckId/study/:cardId. Online sessions still trigger a sync
immediately so server-side aggregates stay fresh; offline sessions
accumulate in pendingCountAtom until the next online tick.
The legacy study POST route is preserved for backwards compatibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/routes/study.ts | 56 |
1 files changed, 13 insertions, 43 deletions
diff --git a/src/server/routes/study.ts b/src/server/routes/study.ts index 0f42f93..97ab5f4 100644 --- a/src/server/routes/study.ts +++ b/src/server/routes/study.ts @@ -1,12 +1,7 @@ import { zValidator } from "@hono/zod-validator"; import { Hono } from "hono"; -import { - type Card as FSRSCard, - type State as FSRSState, - fsrs, - type Grade, -} from "ts-fsrs"; import { z } from "zod"; +import { computeNextSchedule } from "../../shared/fsrs.js"; import { authMiddleware, Errors, getAuthUser } from "../middleware/index.js"; import { type CardRepository, @@ -33,8 +28,6 @@ const cardIdParamSchema = z.object({ cardId: z.uuid(), }); -const f = fsrs({ enable_fuzz: true }); - export function createStudyRouter(deps: StudyDependencies) { const { cardRepo, deckRepo, reviewLogRepo } = deps; @@ -79,42 +72,19 @@ export function createStudyRouter(deps: StudyDependencies) { const now = new Date(); - // Convert our card to FSRS card format - const fsrsCard: FSRSCard = { - due: card.due, - stability: card.stability, - difficulty: card.difficulty, - elapsed_days: card.elapsedDays, - scheduled_days: card.scheduledDays, - reps: card.reps, - lapses: card.lapses, - state: card.state as FSRSState, - last_review: card.lastReview ?? undefined, - learning_steps: 0, - }; - - // Schedule the card with the given rating - const result = f.next(fsrsCard, now, rating as Grade); - - // Calculate elapsed days for review log - const elapsedDays = card.lastReview - ? Math.round( - (now.getTime() - card.lastReview.getTime()) / - (1000 * 60 * 60 * 24), - ) - : 0; + const next = computeNextSchedule(card, rating, now); // Update the card with new FSRS values const updatedCard = await cardRepo.updateFSRSFields(cardId, deckId, { - state: result.card.state, - due: result.card.due, - stability: result.card.stability, - difficulty: result.card.difficulty, - elapsedDays: result.card.elapsed_days, - scheduledDays: result.card.scheduled_days, - reps: result.card.reps, - lapses: result.card.lapses, - lastReview: now, + state: next.state, + due: next.due, + stability: next.stability, + difficulty: next.difficulty, + elapsedDays: next.elapsedDays, + scheduledDays: next.scheduledDays, + reps: next.reps, + lapses: next.lapses, + lastReview: next.lastReview, }); // Create review log @@ -123,8 +93,8 @@ export function createStudyRouter(deps: StudyDependencies) { userId: user.id, rating, state: card.state, - scheduledDays: result.card.scheduled_days, - elapsedDays, + scheduledDays: next.scheduledDays, + elapsedDays: next.reviewElapsedDays, durationMs: durationMs ?? null, }); |
