| name | fsrs-integration |
| description | Implement or extend spaced repetition with ts-fsrs |
| user-invocable | true |
FSRS Integration
Implement or extend spaced repetition in DanskPrep.
Reference: Read .claude/references/fsrs-rules.md first — it covers ts-fsrs configuration, card lifecycle, due queue logic, and TypeScript pitfalls.
Key files
src/lib/fsrs.ts — scheduler init, dbToFsrsCard, fsrsCardToDb, scheduleReview, getSchedulingOptions
src/hooks/useStudy.ts — due queue, reviewCard(), Supabase sync
src/components/study/CardRating.tsx — 4-button rating UI with interval preview
DB <-> FSRS field mapping
{ state, difficulty, stability, due, last_review, reps, lapses }
→ Card { state, difficulty, stability, due: new Date(due), last_review: new Date(last_review) | undefined, reps, lapses, elapsed_days: 0, scheduled_days: 0 }
card.due.toISOString(), card.last_review?.toISOString() ?? null
Review flow (order matters)
- Fetch due cards:
due <= now() ordered by due ASC, limit 50
- Display card, start timer
- User rates (1–4) →
scheduleReview(dbToFsrsCard(row), rating)
UPDATE user_cards SET ...fsrsCardToDb(updatedCard) + increment correct/incorrect/streak
INSERT review_logs with rating, response, was_correct, time_taken_ms
- If
rating === Again: re-append card to end of queue; otherwise remove it
Rating UI rule
Always show the next interval next to each button so users understand the consequence:
Again (5m) | Hard (1d) | Good (4d) | Easy (14d)
Use getSchedulingOptions(card) which returns { again, hard, good, easy } as human-readable strings.
TypeScript pitfall
Rating.Manual = 0 causes type errors. Use numeric literals 1|2|3|4 instead of Rating as key type. Cast f.repeat() result to any when indexing — see fsrs-rules.md.