# Development How to run Siftlode locally and pass the gate. Conventions for the backend code itself live in [`backend/app/README.md`](../backend/app/README.md). ## Node One major, everywhere. `.nvmrc` pins it; 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: ```bash 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: ```bash cd frontend && npm install && npm run dev # http://localhost:5173, proxying the API on :8080 ``` The dev server also serves the **design lab** at `http://localhost:5173/design-lab`: the open design decisions and the chosen patterns, rendered with the app's real CSS. It never reaches a production build (see [`DESIGN.md`](DESIGN.md)). ## The gate A change is done when this is green, from the repo root: ```bash bash scripts/check.sh # every lane bash scripts/check.sh lint # the static lanes only (no tests, build or audits) — the quick loop ``` It runs every lane even after one fails and names each red one at the end. What it runs, and why each lane runs where it does, is in the script's comments; `frontend/src/lib/gates.contract.test.ts` holds its lines, so a lane dropped or narrowed there fails a test. What each gate deliberately does NOT check — and why — is the [gate register](gates.md). There is no CI: the gate runs here, and a release runs it (plus e2e and the CVE scan) before anything is merged. The lanes, for running one by hand: **Backend** ```bash # lint + format check (ruff is pinned in backend/requirements-dev.txt) python -m ruff check --config backend/ruff.toml backend scripts python -m ruff format --check --config backend/ruff.toml backend scripts # type check — in the test image (requirements.lock does not install on Windows); new errors only, # the pre-existing ones are in backend/.basedpyright/baseline.json docker compose -f backend/docker-compose.test.yml run --rm --no-deps --build test \ python -m basedpyright --project pyrightconfig.json # tests — pure-logic + the DB-backed integration lane, against an ephemeral Postgres, ending with the # coverage total. 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 lock against the advisory databases, at any severity docker compose -f backend/docker-compose.test.yml run --rm --no-deps test \ pip-audit -r requirements.lock --no-deps --disable-pip ``` A missing database or ffmpeg is a hard error, not a skip — so the gate can't print green while the DB-backed tests were quietly not run (`SIFTLODE_TEST_REQUIRE_DB=0` is the explicit "pure-logic only" run for a machine without Postgres). 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/`) ```bash node ../scripts/check-node-modules.mjs # node_modules == package-lock.json (else: npm ci) npm run typecheck # tsc x3 (app / node / e2e) npm run lint # eslint (typed rules included; warnings fail) npm run format:check # prettier npm run knip # unused files/exports/deps — and unused by what SHIPS (--production) npm run test # vitest (incl. the compiler, size/complexity and gate-register contracts) npx vite build # the production bundle (devOnlyGuard fails it on dev-only code) npm audit --omit=dev --audit-level=high # what ships to the browser, at high or above ``` Coverage is reported, never enforced: `npm run test -- --coverage`, and the pytest lane's last line. The Playwright suite (`npm run e2e`) runs against the `:8080` stack built from your tree; the release requires it. Before an image is pushed, it is scanned with Trivy (HIGH/CRITICAL with a fix fails; exceptions in `.trivyignore.yaml`, each with a reason and an expiry). ## 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.