No description
Find a file
lila 0cc643e308 feat: update extractor for all 5 languages, update import for multi-language
- Extract.ts now processes all 5 language files, filters non-English
  entries by lang_code, skips translation extraction for non-English
  (no translations in source files)
- Import.ts now imports all 5 language output files, uses language
  field from ExtractedSense instead of hardcoding en
- Sample limit hardcoded to 500 entries per language for development
2026-05-05 18:46:32 +02:00
.forgejo/workflows chore: add husky pre-commit and pre-push hooks 2026-04-30 01:15:14 +02:00
.husky chore: add husky pre-commit and pre-push hooks 2026-04-30 01:15:14 +02:00
apps feat: migrate production schema from OMW to Kaikki flat vocabulary model 2026-05-05 17:39:25 +02:00
data-pipeline feat: update extractor for all 5 languages, update import for multi-language 2026-05-05 18:46:32 +02:00
documentation docs: rewrite data-pipeline.md for Kaikki migration 2026-05-05 17:14:48 +02:00
packages feat: migrate production schema from OMW to Kaikki flat vocabulary model 2026-05-05 17:39:25 +02:00
.dockerignore infra: add Docker Compose setup for local development 2026-03-25 18:56:04 +01:00
.env.example feat: add email/password auth backend + forgot/reset password routes 2026-04-30 18:30:20 +02:00
.gitignore feat: add Kaikki extraction and import scripts for stage 1 2026-05-05 18:11:53 +02:00
.prettierignore chore: configure root eslint with react and tanstack router rules 2026-03-21 19:32:38 +01:00
.prettierrc chore: configure prettier with ignore rules and format scripts + running format 2026-03-20 18:37:38 +01:00
Caddyfile feat(caddy): add security headers for frontend 2026-04-23 21:45:35 +02:00
docker-compose.yml updated docker pipeline to include database migrations, added dummy table to verify the pipeline works 2026-04-23 09:19:57 +02:00
eslint.config.mjs feat: add db schema, init, and vitest config 2026-05-03 17:56:29 +02:00
mise.toml setting up python env, download word data 2026-03-26 11:41:46 +01:00
package.json feat: add db schema, init, and vitest config 2026-05-03 17:56:29 +02:00
pnpm-lock.yaml feat: add db schema, init, and vitest config 2026-05-03 17:56:29 +02:00
pnpm-workspace.yaml adding the data-pipeline to ts and pnpm workspaces 2026-04-20 09:05:27 +02:00
README.md formatting + adding issues 2026-04-28 16:39:36 +02:00
tsconfig.base.json formatting 2026-03-21 19:33:07 +01:00
tsconfig.json formatting 2026-04-28 13:18:18 +02:00
vitest.config.ts chore: configure vitest with project-based setup and coverage 2026-03-20 19:25:00 +01:00

lila

Learn words. Beat friends.

lila is a vocabulary trainer built around a Duolingo-style quiz loop: a word appears in one language, you pick the correct translation from four choices. It supports singleplayer and real-time multiplayer, and is designed to work across multiple language pairs without schema changes.

Live at lilastudy.com.


Stack

Layer Technology
Monorepo pnpm workspaces
Frontend React 18, Vite, TypeScript
Routing TanStack Router
Server state TanStack Query
Styling Tailwind CSS
Backend Node.js, Express, TypeScript
Database PostgreSQL + Drizzle ORM
Validation Zod (shared schemas)
Auth Better Auth (Google + GitHub)
Realtime WebSockets (ws library)
Testing Vitest, supertest
Deployment Docker Compose, Caddy, Hetzner VPS
CI/CD Forgejo Actions

Repository Structure

lila/
├── apps/
│   ├── api/        — Express backend
│   └── web/        — React frontend
├── packages/
│   ├── shared/     — Zod schemas and types shared between frontend and backend
│   └── db/         — Drizzle schema, migrations, models, seeding scripts
├── scripts/        — Python scripts for vocabulary data extraction
└── documentation/  — Project docs

packages/shared is the contract between frontend and backend. All request/response shapes are defined there as Zod schemas and never duplicated.


Architecture

Requests flow through a strict layered architecture:

HTTP Request → Router → Controller → Service → Model → Database

Each layer only talks to the layer directly below it. Controllers handle HTTP only. Services contain business logic only. Models contain database queries only. All database code lives in packages/db — the API never imports Drizzle directly for queries.


Data Model

Words are modelled as language-neutral concepts (terms) with per-language translations. Adding a new language requires no schema changes — only new rows. CEFR levels (A1C2) are stored per translation for difficulty filtering.

Core tables: terms, translations, term_glosses, decks, deck_terms Auth tables (managed by Better Auth): user, session, account, verification

Vocabulary data is sourced from WordNet and the Open Multilingual Wordnet (OMW).


API

POST /api/v1/game/start     — start a quiz session (auth required)
POST /api/v1/game/answer    — submit an answer (auth required)
GET  /api/v1/health         — health check (public)
ALL  /api/auth/*            — Better Auth handlers (public)

The correct answer is never sent to the frontend — all evaluation happens server-side.


Multiplayer

Rooms are created via REST, then managed over WebSockets. Messages are typed via a Zod discriminated union. The host starts the game; all players answer simultaneously with a 15-second server-enforced timer. Room state is held in-memory (Valkey deferred).


Infrastructure

Internet → Caddy (HTTPS)
            ├── lilastudy.com      → web (nginx, static files)
            ├── api.lilastudy.com  → api (Express)
            └── git.lilastudy.com  → Forgejo (git + registry)

Deployed on a Hetzner VPS (Debian 13, ARM64). Images are built cross-compiled for ARM64 and pushed to the Forgejo container registry. CI/CD runs via Forgejo Actions on push to main. Daily database backups are synced to the dev laptop via rsync.

See documentation/deployment.md for the full infrastructure setup.


Local Development

Prerequisites

  • Node.js 20+
  • pnpm 9+
  • Docker + Docker Compose

Setup

# Install dependencies
pnpm install

# Create your local env file (used by docker compose + the API)
cp .env.example .env

# Start local services (PostgreSQL, Valkey)
docker compose up -d

# Build shared packages
pnpm --filter @lila/shared build
pnpm --filter @lila/db build

# Run migrations and seed data
pnpm --filter @lila/db migrate
pnpm --filter @lila/db seed

# Start dev servers
pnpm dev

The API runs on http://localhost:3000 and the frontend on http://localhost:5173.


Testing

# All tests
pnpm test

# API only
pnpm --filter api test

# Frontend only
pnpm --filter web test

Roadmap

Phase Description Status
0 Foundation — monorepo, tooling, dev environment
1 Vocabulary data pipeline + REST API
2 Singleplayer quiz UI
3 Auth (Google + GitHub)
4 Multiplayer lobby (WebSockets)
5 Multiplayer game (real-time, server timer)
6 Production deployment + CI/CD
7 Hardening (rate limiting, error boundaries, monitoring, accessibility) 🔄

See documentation/roadmap.md for task-level detail.