Files
ttrpg-initiative-tracker/docs/DEVELOPMENT.md
T
david raistrick 9457f48b23 ci: local pre-push hook instead of GH Actions (private repo)
- remove .github/workflows/ci.yml
- add .githooks/pre-push: runs npm run test:all
- git config core.hooksPath .githooks (set)
- docs/DEVELOPMENT.md: document local pipeline

Private repo = no free Actions. Revisit when public.
2026-06-28 17:16:23 -04:00

4.8 KiB

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: Express + ws + better-sqlite3
    index.js            # REST + WS bootstrap
    db.js               # SQLite schema, row mappers
    handlers.js         # action -> shared turn fn -> tx persist -> broadcast
    server.test.js      # integration tests
  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

git clone git@github.com:keen99/ttrpg-initiative-tracker.git
cd ttrpg-initiative-tracker
npm install

Run

Frontend (dev server)

npm start                 # http://localhost:3000

Still uses Firebase by default. Set REACT_APP_FIREBASE_* in .env.local (copy env.example).

Backend (dev)

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:

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:

npm run test:all          # runs shared/ + server/ suites in sequence
npm run shared:test       # turn logic only (shared/ folder)
npm run server:test       # backend REST + combat flow (server/ folder)

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):

git config core.hooksPath .githooks

Already configured on this checkout. Skip with:

git push --no-verify

Future: when repo goes public, free GH Actions viable. Then add .github/workflows/ci.yml.

Build

npm run build             # CRA production build -> build/

Docker build (existing, frontend-only):

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)
git fetch upstream         # pull friend's changes
git merge upstream/main    # rebase our branch onto his