Files
siftlode/docs/development.md
T
peter efe106dfda build: pin one Node major across the dev, CI and build machines
The server gate ran node:22 while the dev gate ran node:24, and the two disagree about
Intl.NumberFormat's compact currency padding and about which realm's Blob a Response hands
back — so the frontend job had been red for 73 of its last 90 runs while the local gate was
green, and nothing said so.

Align everything on 24, with `.nvmrc` as the single source of truth: the CI container, the
prod build stage, `engines`, and `@types/node` (which was two majors ahead of the runtime, so
tsc was green over APIs the shipped engine does not have). The backend job's node only runs
the checkout action, so it tracks the pin as hygiene — node 20 is EOL — but deliberately
stays outside the contract.

The pin is held by a contract test that reads every one of those places AND the engine
running it, so the next drift is a red lane on its first run instead of a CI log nobody reads.

Verified on node:24-bookworm (the CI lane: 228 files / 1552 tests, typecheck, lint,
format:check, knip all exit 0) and on node:24-alpine (the prod build stage, v24.21.0, SPA
built). Each assertion of the new test was mutation-checked red.
2026-09-20 18:06:24 +02:00

2.9 KiB

Development

How to run Siftlode locally and pass the gate. Conventions for the backend code itself live in backend/app/README.md.

Node

One major, everywhere. .nvmrc pins it; the CI container, the Dockerfile's frontend build stage, frontend/package.json's engines and @types/node must all match it — and so must the machine you run the gate on. frontend/src/lib/nodeEngine.contract.test.ts fails the gate the moment any of them drift apart, including when yours is the odd one out. nvm use in the repo root picks it up.

This is a contract, not a preference: tsc and vitest read the engine's own Intl and web APIs, and those change between majors — the same currency format renders $160M on one and $160.0M on the next. A gate that is green on a major you do not ship is green about nothing.

Run it locally

The whole stack (Postgres + API + worker) comes up from the localdev compose file:

docker compose -f docker-compose.localdev.yml up --build

Browse at http://localhost:8080 (Google login works — the OAuth redirect is http://localhost:8080/auth/callback). The API runs alembic upgrade head on startup, so the schema is always current.

For a fast frontend edit loop, run the Vite dev server with HMR against that API:

cd frontend && npm install && npm run dev   # http://localhost:5173, proxying the API on :8080

The gate

A change is done when all of this is green. CI runs the same checks server-side (.gitea/workflows/check.yml), so "main is always green".

Backend

# lint + format check
python -m ruff check --config backend/ruff.toml backend scripts
python -m ruff format --check --config backend/ruff.toml backend scripts

# tests — pure-logic + the DB-backed integration lane, against an ephemeral Postgres.
# The backend tree is bind-mounted, so it always tests your working files.
docker compose -f backend/docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from test
docker compose -f backend/docker-compose.test.yml down -v

The test compose sets SIFTLODE_TEST_REQUIRE_DB=1, which turns a would-be silent skip (no DB, or no ffmpeg) into a hard failure — so the gate can't print green while the DB-backed tests were quietly not run. Tests that need a database take the db fixture (backend/tests/conftest.py); the rest are pure-logic and run without one.

Frontend (from frontend/)

npm run typecheck     # tsc x3 (app / node / e2e)
npm run lint          # eslint
npm run format:check  # prettier
npm run knip          # unused exports/deps
npm run test          # vitest

Branching

Trunk-based: branch off main, keep the branch gate-green, and land the whole change back to main in one reviewed step. Migrations are Alembic revisions under backend/alembic/versions; the API applies them on startup, and the DB-lane rebuilds the schema via alembic upgrade head, so a broken migration chain fails the tests.