Skip to content

Development

Setting up

A bare uv sync is not enough to reach a green suite:

uv sync                  # default and dev groups
npm --prefix webapp ci   # inv test skips the frontend suite without this

inv test skips the frontend suite when webapp/node_modules is missing, and CI does not — a run without npm ci is not a full run. Adding a Python dependency also means uv lock; CI installs with --frozen.

languages.toml is needed only to run the app, and inv dev creates it from languages.example.toml. espeak-ng is needed only to run Piper voices, never by a test.

Commands

Task Command
Lint + format + type-check uv run inv pre
Full test suite (Python + frontend) uv run inv test
Python tests only uv run pytest
Frontend tests only npm --prefix webapp test
Dev server (auto-reload) uv run inv dev
Build the PWA into _static/ uv run inv build-static
List every task uv run inv --list

The uv run prefix is optional inside an activated .venv.

Warning

Never call Ruff directly. inv pre runs ruff, ruff-format, pyrefly and the file-hygiene hooks with the project's own configuration, and it is the only gate that matches CI.

For hot module replacement while working on Vue, run the Vite dev server in a second terminal — it proxies /api to the backend on port 8080:

npm --prefix webapp run dev   # http://127.0.0.1:5173

vite dev does not register a service worker. Test offline and PWA behaviour against a real _static/ build.

Interface language

The PWA's strings live in webapp/src/i18n/{en,ru}.js and are reached through t("key"); the header selector writes the choice to localStorage and the client sends it as Accept-Language on every API call. The backend's own user-facing hints — input validation and unknown-language errors — live in src/echo_words/i18n.py and are resolved per request from that header. Both suites assert that the two catalogues carry exactly the same keys, so a string added in one language cannot silently go missing in the other.

Text the server produces for the shared history and event stream — card statuses and the analysis itself — is not per-client and is not translated this way. The analysis language is ECHOWORDS_TARGET_LANG.

Tests

Every new module or function ships its tests in the same commit. No real network, Anki sync, LLM, or TTS calls in tests — every boundary is faked or mocked. The harness in experiments/ is the sanctioned exception and stays out of CI.

CI runs the Python 3.12/3.13 matrix, the frontend Vitest suite, and Ruff. Test reports are published in Allure.

Specifications

spec/ holds the functional description and the decision records behind the settled choices — spaced repetition, TTS, the LLM backend, the interface, and the deployment host. They are not published to this site. Where spec/ and the code disagree, the code wins.