# How FSRS Integration Works in the TypeWords Practice Engine: A Deep Dive into Spaced Repetition Scheduling

> Explore the FSRS integration in the TypeWords practice engine. Learn how typing mistake counts become FSRS ratings and optimal review intervals with dedicated React hooks.

- Repository: [Zyronon/TypeWords](https://github.com/zyronon/TypeWords)
- Tags: deep-dive
- Published: 2026-09-03

---

**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`](https://github.com/zyronon/TypeWords/blob/main/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 the `ts-fsrs` library
- **Rating thresholds** – `fsrsEasyLimit`, `fsrsGoodLimit`, and `fsrsHardLimit` define 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`](https://github.com/zyronon/TypeWords/blob/main/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:

```typescript
// 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:

```typescript
// 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:

1. **Performance Capture** – The engine records the number of typing errors for the completed word
2. **Rating Translation** – `getGradeByWrongTimes()` maps the error count to a `Rating` enum using configurable thresholds
3. **Interval Calculation** – `nextCard()` passes the current card and rating to the FSRS algorithm, which computes the next due date
4. **State Persistence** – The updated card metadata flows through [`app/core/stores/practice.ts`](https://github.com/zyronon/TypeWords/blob/main/app/core/stores/practice.ts) and ultimately syncs to Supabase via [`app/core/apis/words.ts`](https://github.com/zyronon/TypeWords/blob/main/app/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:

```typescript
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-fsrs` library installed via npm to handle spaced repetition mathematics
- **Configuration** lives in [`app/core/stores/setting.ts`](https://github.com/zyronon/TypeWords/blob/main/app/core/stores/setting.ts), separating FSRS algorithm parameters from rating threshold policies
- **Rating determination** occurs in `useGetGradeByWrongTimes`, which converts `wrongTimes` counts into FSRS-compliant `Rating` values
- **Scheduling execution** happens inside `useNextCard`, where the `fsrs.next()` method calculates the next `due` date
- **Data persistence** flows through [`app/core/stores/practice.ts`](https://github.com/zyronon/TypeWords/blob/main/app/core/stores/practice.ts) and [`app/core/apis/words.ts`](https://github.com/zyronon/TypeWords/blob/main/app/core/apis/words.ts) to 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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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.