A dataset-driven spaced-repetition and practice app for learning Vocabulary.
Each dataset declared in backend/data/datasets.json becomes its own deck:
- Practice - Endless, no-SRS stages
- SRS - Lessons + stage-based reviews
Simple-SRS-Demo.mp4
Monorepo with three projects:
backendExpress + better-sqlite3 API and SRS scheduler (port3000)frontendReact + Vite SPA (dev on5173; built and served by the backend on3000)e2ePlaywright end-to-end suite (boots backend + frontend on3100/5174)
- Node.js
- npm
# Use the pinned Node version
nvm usenpm ci
npm --prefix backend ci
npm --prefix frontend ci
npm --prefix e2e ciStart both servers together from the repo root:
npm startStart just one of them:
npm run backend
npm run frontendPorts default to backend: 3000, frontend: 5173. Override with the BACKEND_PORT, FRONTEND_PORT env vars:
BACKEND_PORT=3100 FRONTEND_PORT=5174 npm startBuild the frontend, then run the backend (which also serves the frontend app)
cd frontend && npm run build
cd backend && npm startThe app is fully driven by backend/data/datasets.json (the registry), which points at one JSON file per dataset.
Each entry carries the deck's metadata (name, mode, badge, description); the backend seeds the DB from these on start:
[
{
"id": "hangul",
"name": "Hangul Alphabet",
"file": "korean/hangul.json",
"mode": "practice",
"type": "character",
"badge": "Practice",
"description": "Practice recognizing Hangul endlessly! Grind for as long as you like."
},
{
"id": "korean-words-6000",
"name": "Korean Words",
"file": "korean/korean-words-6000.json",
"mode": "srs",
"type": "vocabulary",
"badge": "SRS",
"description": "Learn the most common Korean words. Spaced repetition with stages."
}
]mode: "practice"- endless grind, no SRS.mode: "srs"- lessons + stage-based reviews, with per-stage stats.
The app comes with the following decks:
| Deck | Language | Mode | Type |
|---|---|---|---|
| Hangul Alphabet | Korean | Practice | character |
| Korean Words | Korean | SRS | vocabulary |
| Hiragana | Japanese | Practice | character |
| Katakana | Japanese | Practice | character |
| WaniKani Radicals | Japanese | SRS | character |
| WaniKani Kanji | Japanese | SRS | character |
| WaniKani Vocabulary | Japanese | SRS | vocabulary |
Those decks are generated by build scripts in backend/scripts/, grouped by language:
- Korean (
scripts/korean/) - Hangul alphabet and the common Korean words list. - Japanese (
scripts/japanese/) - Hiragana, Katakana, and WaniKani.
Each script writes a deck JSON file, registers it in backend/data/datasets.json, and downloads any audio. The decks are seeded in the DB automatically on start. See the backend README for run instructions and sources.
- Keyboard shortcuts are configurable via Vite env vars (see frontend README and
frontend/.env.example). - Formatting uses Prettier:
npm run formatformats the whole monorepo. Useformat:backend,format:frontend,format:e2e,format:rootfor just one scope. - A Husky pre-commit hook runs
npm run formaton every commit.
Run the whole suite (backend + e2e) with npm test.
Unit tests for the Express API, grading, and SRS scheduling:
npm run test:backendA Playwright suite (e2e/) exercises the frontend against a fresh, self-contained fixture.
It covers the dashboard, practice (grading, tally, input clearing, Enter-to-advance), self-graded cards (accept/reject + audio), and the word lesson + review flows - all data-driven from datasets.json.
npm --prefix e2e ci
# Run only once
npm --prefix e2e run install-browsers
npm run test:e2e