How FSRS Integration Works in the TypeWords Practice Engine: A Deep Dive into Spaced Repetition Scheduling
The TypeWords practice engine leverages the ts-fsrs library to convert typing mistake counts into FSRS ratings and calculate optimal review intervals through dedicated React hooks that separate user policy from algorithmic scheduling.
The FSRS (Free Spaced Repetition Scheduler) algorithm powers the adaptive learning system in TypeWords, an open-source typing practice application. This integration determines when words should reappear based on performance, ensuring difficult words surface more frequently while mastered words fade into longer intervals. Understanding how FSRS integration works in TypeWords reveals a clean architecture that isolates configuration, rating logic, and persistence into distinct layers.
Configuring FSRS Parameters in TypeWords
All FSRS tuning values live in the centralized settings store located at app/core/stores/setting.ts. This file acts as the single source of truth for both the mathematical parameters and the user-facing thresholds that control rating boundaries.
The store contains two critical configuration objects:
fsrsParameters– Holds the complete set of algorithmic constants including initial interval, ease factors, and decay rates used by thets-fsrslibrary- Rating thresholds –
fsrsEasyLimit,fsrsGoodLimit, andfsrsHardLimitdefine the upper bounds of wrong attempts that map to each Rating enum value (Easy, Good, Hard, and Again)
Separating these values from the scheduling logic allows users to adjust spaced repetition behavior through the settings UI without modifying core engine code.
Translating Mistakes into FSRS Ratings
When a user finishes typing a word, the engine counts the total mistakes (wrongTimes). The hook useGetGradeByWrongTimes in app/core/hooks/fsrs.ts converts this raw error count into a standardized Rating value that the FSRS algorithm understands.
The useGetGradeByWrongTimes Hook
This hook reads the threshold limits from the settings store and applies conditional logic to determine the appropriate rating:
// src/app/core/hooks/fsrs.ts
export function useGetGradeByWrongTimes() {
const store = useSettingStore()
function getGradeByWrongTimes(wrongTimes?: number): Rating {
if (wrongTimes !== undefined) {
if (wrongTimes <= store.fsrsEasyLimit) return Rating.Easy
else if (wrongTimes <= store.fsrsGoodLimit) return Rating.Good
else if (wrongTimes <= store.fsrsHardLimit) return Rating.Hard
else return Rating.Again
}
return Rating.Easy
}
return { getGradeByWrongTimes }
}
The function returns Rating.Easy for perfect or near-perfect attempts, escalating through Good and Hard as errors accumulate, and defaulting to Rating.Again for words exceeding the hard limit.
Computing the Next Review Date
Once the rating is determined, the useNextCard hook instantiates the FSRS class and requests the next scheduled interval. This hook encapsulates the algorithmic execution while remaining agnostic to how the rating was derived.
Scheduling with useNextCard
The hook creates a fresh FSRS instance using the stored parameters and invokes the next() method to generate a new card state:
// src/app/core/hooks/fsrs.ts
export function useNextCard() {
const store = useSettingStore()
const fsrs = new FSRS(store.fsrsParameters) // <-- FSRS instance
function nextCard(card: CardInput | Card, grade: Grade): Card {
return fsrs.next(card, new Date(), grade).card // <-- schedule next review
}
return { nextCard }
}
The next() method returns a Card object containing a recalculated due date based on the card's current stability, difficulty, and the provided rating. This separation allows the same scheduling logic to handle both existing cards and newly created CardInput objects.
The Complete FSRS Workflow in Practice
The integration follows a predictable pipeline that bridges user interaction with persistent storage:
- Performance Capture – The engine records the number of typing errors for the completed word
- Rating Translation –
getGradeByWrongTimes()maps the error count to aRatingenum using configurable thresholds - Interval Calculation –
nextCard()passes the current card and rating to the FSRS algorithm, which computes the next due date - State Persistence – The updated card metadata flows through
app/core/stores/practice.tsand ultimately syncs to Supabase viaapp/core/apis/words.ts, ensuring the schedule survives page reloads
This architecture decouples policy (how many mistakes constitute "Hard") from algorithm (how to calculate the next interval), enabling fine-tuned control over the learning curve.
Practical Implementation Example
To schedule a word after user input:
import { useNextCard } from '@/app/core/hooks/fsrs'
import type { CardInput } from 'ts-fsrs'
const { nextCard } = useNextCard()
const currentCard: CardInput = {
id: 'word-123',
due: new Date(),
stability: 0,
difficulty: 0,
elapsed_days: 0,
reps: 0,
lapses: 0,
state: 0,
}
// User earned an Easy rating
const newCard = nextCard(currentCard, Rating.Easy)
// newCard.due contains the calculated review date
Summary
- FSRS integration in TypeWords relies on the
ts-fsrslibrary installed via npm to handle spaced repetition mathematics - Configuration lives in
app/core/stores/setting.ts, separating FSRS algorithm parameters from rating threshold policies - Rating determination occurs in
useGetGradeByWrongTimes, which convertswrongTimescounts into FSRS-compliantRatingvalues - Scheduling execution happens inside
useNextCard, where thefsrs.next()method calculates the nextduedate - Data persistence flows through
app/core/stores/practice.tsandapp/core/apis/words.tsto Supabase, maintaining state across sessions
Frequently Asked Questions
What library does TypeWords use for FSRS implementation?
TypeWords implements FSRS through the ts-fsrs TypeScript library, which provides the FSRS class and Rating enum used in app/core/hooks/fsrs.ts. This library handles the algorithmic complexity of spaced repetition scheduling while exposing a clean API for calculating next review dates based on card metadata and user grades.
How does TypeWords convert typing errors into FSRS ratings?
The application counts mistakes during a typing session and passes the wrongTimes value to getGradeByWrongTimes(). This function compares the count against three configurable limits (fsrsEasyLimit, fsrsGoodLimit, fsrsHardLimit) stored in the settings store. Depending on which threshold the count falls under, it returns Rating.Easy, Rating.Good, Rating.Hard, or Rating.Again to represent the user's performance.
Where are FSRS parameters stored in the TypeWords codebase?
All FSRS-related configuration resides in app/core/stores/setting.ts. This includes the fsrsParameters object containing algorithmic constants like initial intervals and decay rates, plus the rating threshold limits that define how many errors map to each difficulty level. Centralizing these values allows the settings UI to adjust spaced repetition behavior without touching the scheduling logic.
How does the scheduled card data persist between sessions?
After FSRS calculates the next due date, the updated Card object flows through app/core/stores/practice.ts, which manages the local state of the current practice session. The changes then propagate to app/core/apis/words.ts, which handles API communication with the Supabase backend. This ensures that the due field and updated stability metrics survive page reloads and remain synchronized across devices.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →