Development Guide
The CLAUDE.md at the repo root has the canonical "run the dev stack" instructions and architectural facts the next session must know — start there. This file expands on the developer workflow and conventions.
Prerequisites
| Tool | Why | Notes |
|---|---|---|
Rust stable (≥ 1.83) |
Backend |
CI uses |
Rust nightly-2026-04-25 |
Frontend |
Pinned in |
|
Frontend |
Auto-installed by toolchain file |
|
Frontend dev server / bundler |
|
|
Frontend |
|
|
Backend |
Only needed when adding/modifying SQL queries — |
|
Postgres + (optionally) Compose stack |
|
|
Mock JWKS server |
Stdlib only |
The pre-commit hook config in .pre-commit-config.yaml runs split clippy hooks per crate so backend (stable) and frontend (nightly+wasm32) check correctly. Run pre-commit install once after cloning.
First-time setup
# 1. Postgres on :5433 (matches DEV_AUTH wiring in scripts)
docker run -d --name shiftit-pg \
-e POSTGRES_USER=shiftit -e POSTGRES_PASSWORD=shiftit -e POSTGRES_DB=shiftit \
-p 5433:5432 postgres:16-alpine
# 2. Apply demo seed (idempotent — see scripts/seed-demo.sql)
docker exec -i shiftit-pg psql -U shiftit -d shiftit < scripts/seed-demo.sql
Daily dev loop
Three components, three terminals (or & in one):
# A. Mock JWKS so JwksCache::new succeeds without a real Zitadel
python3 scripts/mock-jwks.py 9999
# B. Backend with DEV_AUTH bound to one of the seeded users (admin|employee|customer)
./scripts/run-backend.sh admin
# C. Frontend dev server with hot-reload, proxies /api/* to :3010
cd frontend && trunk serve
Endpoints:
To enter the auth-gated UI, paste in the browser console once:
localStorage.setItem('shiftit_token', JSON.stringify('dev')); location.reload();
The frontend stores tokens JSON-encoded via gloo-storage; any non-empty string works in dev because the backend ignores tokens entirely when DEV_AUTH_* env vars are set.
To switch user role mid-session, kill the backend and re-run ./scripts/run-backend.sh employee (or customer).
Build & test
| Action | Command |
|---|---|
Format check |
|
Backend clippy |
|
Frontend clippy |
|
Backend tests |
|
Frontend prod build |
|
Backend prod build |
|
sqlx offline cache rebuild |
|
License-tool tests |
|
Conventions
-
Branch model:
mainonly; no PRs locally — every commit lands onmaindirectly.Cargo.lockis committed. -
Commit style: conventional-ish prefixes (
feat:,feat(frontend):,fix:,docs:,chore:). Rungit log --oneline -20for examples. -
Migrations: new migrations follow Plan 02 conventions — explicit
FOR ALL+WITH CHECKon every RLS policy, table-distinguished policy names (<table>_isolation), and explicit GRANTs toshiftit_app. After adding/modifying SQL, runcargo sqlx prepare. -
Migration discipline: migrations 0001 and 0005 were edited in-place after first apply. If you see
migration X was previously applied but has been modifiedoncargo run, drop+recreate the dev DB:docker exec shiftit-pg psql -U shiftit -d postgres -c \ "DROP DATABASE IF EXISTS shiftit; DROP ROLE IF EXISTS shiftit_app; CREATE DATABASE shiftit;" docker exec -i shiftit-pg psql -U shiftit -d shiftit < scripts/seed-demo.sql -
Adding routes: annotate the handler with
#[utoipa::path], register the path inbackend/src/main.rs::create_router, AND add it to thepaths(...)list inbackend/src/openapi.rs::ApiDoc. The third step is easy to forget — theopenapi_doc_includes_expected_path_counttest catches catastrophic regressions but not single missing entries. -
Tenant-scoped queries: always go through
ctx.rls_transaction(&pool). Never use the pool directly from a handler that’s tenant-scoped; theSET ROLE shiftit_appis on the connection, butapp.tenant_idis set byrls_transactiononly. -
Frontend fetch:
LocalResource::new(fetcher)for reads,Action::new_local(...)for writes.gloo-netfutures are!Sendon wasm32 so the local variants are mandatory. -
No JS in frontend: WASM-only by design. The rust-ui Dialog/Popover/DropdownMenu components emit
<script>tags referring towindow.ScrollLock; we haven’t pulled those shims yet. Don’t use those components until follow-up #8 is resolved.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
|
See migration discipline above — drop+recreate. |
Backend panics on startup with "missing public key" |
Non-saas dev build needs |
|
Check browser console for |
|
|
|
|