# Development TTRPG Initiative Tracker — fork with self-hosted backend. Monorepo via npm workspaces. ## Prerequisites - Node.js 22+ - npm 10+ ## Layout ``` / package.json # workspaces root src/ # React frontend (CRA, existing) App.js # ~2935 lines, Firebase direct (M2 abstracts this) server/ # Backend: generic KV doc store (firebase mirror) index.js # REST (doc/coll/batch) + WS bootstrap db.js # SQLite docs table, KV ops, broadcast ws-contract.test.js # adapter vs live backend (Layer 2) shared/ # Pure logic, no I/O (client + server + tests import) turn.js # turn-order state machine turn.characterization.test.js docs/ REWORK_PLAN.md # milestone plan DEVELOPMENT.md # this file ``` ## Setup ```bash git clone git@github.com:keen99/ttrpg-initiative-tracker.git cd ttrpg-initiative-tracker npm install ``` ## Run ### Frontend (dev server) ```bash npm start # http://localhost:3000 ``` Still uses Firebase by default. Set `REACT_APP_FIREBASE_*` in `.env.local` (copy `env.example`). ### Backend (dev) ```bash npm run server:dev # :4001, db: server/data/tracker.sqlite # or direct with env: DB_PATH=/tmp/tracker.sqlite PORT=4001 node server/index.js ``` Smoke check: ```bash curl http://127.0.0.1:4001/health # -> {"ok":true} ``` Frontend not yet wired to backend — that is M2 (storage adapter + WS client). ## Test Three commands: ```bash npm run test:all # runs shared/ + server/ suites in sequence npm run shared:test # turn logic only (shared/ folder) npm run server:test # backend ws-contract (adapter vs live backend) ``` What each runs: | Suite | What | Count | |---|---|---| | `shared/*.test.js` | turn FSM, pure functions | 39 | | `server/*.test.js` | REST + combat flow, in-memory db | 7 | Server tests use `--forceExit` (open WS handles). Tests spin server on random port, in-memory sqlite, tear down per test. ### Local pipeline (pre-push hook) Private repo = no free GitHub Actions. Tests run locally via git hook. `.githooks/pre-push` runs `npm run test:all` before every push. Enable on clone (do once): ```bash git config core.hooksPath .githooks ``` Already configured on this checkout. Skip with: ```bash git push --no-verify ``` Future: when repo goes public, free GH Actions viable. Then add `.github/workflows/ci.yml`. ## Build ```bash npm run build # CRA production build -> build/ ``` Docker build (existing, frontend-only): ```bash docker build -t ttrpg-initiative-tracker . docker run -p 8080:80 --rm ttrpg-initiative-tracker ``` Full-stack docker-compose arrives in M5. ## Backend architecture Server-authoritative. Kills client-side last-write-wins races (root cause of skip bug). ``` Client (browser) Server | | |-- POST /api/.../nextTurn ----->| action | | store.nextTurn(): | | shared.nextTurn(enc) -> patch | | db tx: apply patch | | addLog | | broadcast(change) |<---- WS {change} --------------| push to all subscribers | | Display / tablet | |<---- WS {change} --------------| same push ``` - **SQLite** owns truth. Single writer (server). WAL mode. - **shared/turn.js** = pure logic, ported verbatim from `App.js`. Bugs preserved for M3 characterization, fixed in M4. - **WS** = real-time push (replaces Firebase `onSnapshot`). Client subscribes to a key (`'campaigns'`, `'encounter:id'`, `'activeDisplay'`...), server pushes on change. - **Actions not results.** Client sends "do X", server computes X, persists, broadcasts. No client-side state mutation. ## Storage backend choice Browser sandbox cannot touch filesystem. Cross-device (DM + tablet + player view) requires a real backend owning the DB file. SQLite = single file, docker volume, trivial backup. Postgres deferred until public multiuser exposure. ## Status | Milestone | State | |---|---| | 0 repo/branch | ✅ done | | 1 backend + tests | ✅ done | | 2 frontend WS adapter | ⬜ next | | 3 characterization tests | ⬜ | | 4 skip fix + manual override | ⬜ | | 5 docker compose | ⬜ | | 6 undo rework | ⬜ | | 7 playwright e2e | ⬜ deferred | See `docs/REWORK_PLAN.md` for full plan. ## Git - `origin` = `github.com:keen99/ttrpg-initiative-tracker` (this fork) - `upstream` = `code.draft13.com/robert/ttrpg-initiative-tracker` (friend's Gitea, read-only) - work branch: `rework-backend` (off `main`) ```bash git fetch upstream # pull friend's changes git merge upstream/main # rebase our branch onto his ```