Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ae30214
feat(shared): gravity auto-computation lib with unit tests
countercheck Apr 10, 2026
fe9c6f0
feat(server): maps table, CRUD API routes, requireAdmin middleware
countercheck Apr 10, 2026
a09828e
feat(client): AdminGuard component and /admin/map-editor route
countercheck Apr 10, 2026
2b34219
feat(client): HexGrid SVG component with honeycomb-grid, pan/zoom, gr…
countercheck Apr 10, 2026
1b51bc4
feat(client): useMapEditor hook — immutable state, all editor modes, …
countercheck Apr 10, 2026
5ee38ea
feat(client): map editor UI — toolbar, body/base pickers, hex inspector
countercheck Apr 10, 2026
36a96a6
feat(client): useMaps hook and save/export flow in map editor
countercheck Apr 10, 2026
fd93e93
feat(server): canonical Triplanetary Inner Solar System map seed
countercheck Apr 11, 2026
fc0fa74
chore: add honeycomb-grid and react-zoom-pan-pinch dependencies
countercheck Apr 11, 2026
2c9f658
ci: add ESLint, typecheck, and GitHub Actions CI workflow
countercheck Apr 11, 2026
e3fa9e8
docs: add phase-1a map editor implementation plan
countercheck Apr 11, 2026
59ff472
Merge branch 'feature/phase-1a-map-editor' into ci/eslint-typecheck-g…
countercheck Apr 11, 2026
2448f8e
Update .github/workflows/ci.yml
countercheck Apr 11, 2026
b8d0ea6
Merge pull request #2 from countercheck/ci/eslint-typecheck-github-ac…
countercheck Apr 11, 2026
ad9d0cc
Update packages/client/src/components/AdminGuard.tsx
countercheck Apr 11, 2026
eb75310
Update packages/client/src/hooks/useMapEditor.ts
countercheck Apr 11, 2026
cac0959
Update packages/server/src/api/middleware/requireAdmin.ts
countercheck Apr 11, 2026
8404a87
chore: add lint+test hooks on edit and pre-commit
countercheck Apr 11, 2026
1cc3cc9
fix(ci): remove explicit pnpm version, read from packageManager field
countercheck Apr 12, 2026
0af0cd0
fix(hooks): propagate eslint exit code so Claude sees lint errors
countercheck Apr 12, 2026
e67cf33
fix: guard lefthook install behind .git check
countercheck Apr 12, 2026
dc1a038
chore: split Claude settings — hooks in settings.json, local permissi…
countercheck Apr 12, 2026
07e3f40
fix(hooks): pass -- before file path to prevent flag injection in eslint
countercheck Apr 12, 2026
ef803d2
fix(hooks): pre-commit tests only shared+client; server tests require…
countercheck Apr 12, 2026
82a240a
Merge pull request #3 from countercheck/ci/eslint-typecheck-github-ac…
countercheck Apr 12, 2026
7fb5a6f
fix(editor): delete hex key on erase/space instead of writing type:space
countercheck Apr 12, 2026
5adf1f1
fix(editor): derive hex size and orientation from data.meta instead o…
countercheck Apr 12, 2026
eb0080b
fix(editor): use data: URL for export download, avoids createObjectUR…
countercheck Apr 12, 2026
8c40b2b
fix(maps): sanitize Content-Disposition filename to prevent header in…
countercheck Apr 12, 2026
25ee134
docs(gravity): fix computeGravity return value docstring
countercheck Apr 12, 2026
5ed4108
fix(editor): use setSearchParams after create so React Router re-comp…
countercheck Apr 12, 2026
3d46bb0
fix(maps): guard useMap query against empty string id
countercheck Apr 12, 2026
49667fe
fix(editor): wire HexInspector Erase button to dedicated eraseHex, no…
countercheck Apr 12, 2026
31e5600
fix(lint): suppress unused destructure var in deleteHex, add setSearc…
countercheck Apr 12, 2026
5a3801a
test: add coverage for session store, auth validation, maps export, a…
countercheck Apr 12, 2026
2df1c39
fix(ci): wait for Postgres before running migrations
countercheck Apr 13, 2026
361f920
fix(ci): bypass turbo for migrations so DATABASE_URL reaches pg Pool
countercheck Apr 13, 2026
b78528f
fix: resolve typecheck errors caught by new pre-commit hook
countercheck Apr 13, 2026
baaae41
ci: run lint+typecheck and test in parallel jobs
countercheck Apr 13, 2026
fc93d66
fix(migrations): register session table migration in Drizzle journal
countercheck Apr 13, 2026
50db229
fix: session+passport middleware must precede routes in test apps
countercheck Apr 13, 2026
5628276
fix: remove extra router nesting in test apps to preserve ctx.params
countercheck Apr 13, 2026
d0547af
fix(client): alias @triplanetary/shared to source in Vite build
countercheck Apr 13, 2026
8fc52aa
fix(tests): scope auth cleanup to own email, fix middleware order, dr…
countercheck Apr 13, 2026
bcabba3
docs: add Decision Log section to CLAUDE.md
countercheck Apr 13, 2026
94a6e65
fix: load dotenv via bootstrap before any module body executes
claude Apr 13, 2026
4714a42
fix: address remaining PR review comments
claude Apr 13, 2026
af827f0
fix: await server.close and assert URL update after POST save
claude Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
{
"permissions": {
"allow": [
"Bash(pnpm --filter @triplanetary/server db:migrate)",
"Bash(DATABASE_URL=postgresql://triplanetary:triplanetary@localhost:5432/triplanetary pnpm db:migrate)",
"Bash(DATABASE_URL=postgresql://triplanetary:triplanetary@localhost:5432/triplanetary ./node_modules/.bin/drizzle-kit migrate)",
"Bash(docker exec:*)",
"Bash(pnpm --filter @triplanetary/server test)",
"Bash(pnpm view:*)",
"Bash(node -e \"import\\('boardgame.io/server'\\).then\\(m => console.log\\(Object.keys\\(m\\)\\)\\)\")",
"Bash(node -e \"import\\('boardgame.io/server'\\).then\\(m => { const s = m.Server\\({ games: [] }\\); console.log\\(Object.keys\\(s\\)\\); }\\)\")",
"Bash(node -e \"const m = require\\('boardgame.io/server'\\); const s = m.Server\\({ games: [] }\\); console.log\\(Object.keys\\(s\\)\\);\")",
"Bash(node -e \"const m = require\\('boardgame.io/server'\\); const s = m.Server\\({ games: [], origins: ['http://localhost:3000'] }\\); console.log\\(s.app.constructor.name\\); console.log\\(typeof s.app.use\\);\")",
"Bash(node -e \"const m = require\\('./packages/server/node_modules/boardgame.io/dist/cjs/server.js'\\); const s = m.Server\\({ games: [], origins: ['http://localhost:3000'] }\\); console.log\\(s.app.constructor.name\\); console.log\\(typeof s.app.use\\); console.log\\(typeof s.router\\);\")",
"Bash(node -e \"const m = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/boardgame.io@0.50.2/node_modules/boardgame.io/dist/cjs/server.js'\\); const s = m.Server\\({ games: [], origins: ['http://localhost:3000'] }\\); console.log\\('app type:', s.app.constructor.name\\); console.log\\('router type:', typeof s.router\\); console.log\\('router keys:', s.router && Object.keys\\(s.router\\).slice\\(0,5\\)\\);\")",
"Bash(timeout 8 tsx src/index.ts)",
"Bash(pnpm --filter @triplanetary/server exec timeout 8 tsx src/index.ts)",
"Bash(curl -s localhost:8000/api/health)",
"Bash(curl -s localhost:8000/games)",
"Bash(node -e \"const p = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/boardgame.io@0.50.2/node_modules/boardgame.io/package.json'\\); console.log\\(JSON.stringify\\(p.exports, null, 2\\)\\)\")",
"Bash(node -e \"const p = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/boardgame.io@0.50.2/node_modules/boardgame.io/package.json'\\); console.log\\(p.main, p.module, p.types\\)\")",
"Read(//private/tmp/**)",
"Bash(node -e \"const f = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/is-generator-function@1.1.2/node_modules/generator-function/index.js'\\); console.log\\(typeof f, f\\(\\)\\)\")",
"Bash(node -e \"const f = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/is-generator-function@1.1.2/node_modules/is-generator-function/index.js'\\); console.log\\(f\\(async function\\(\\){}\\), f\\(function*\\(\\){}\\)\\)\")",
"Bash(node -e \"const cjs = require\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/boardgame.io@0.50.2/node_modules/boardgame.io/dist/cjs/server.js'\\); const s = cjs.Server\\({ games: [], origins: ['http://localhost:3000'] }\\); s.app.use\\(function\\(ctx, next\\) { return next\\(\\); }\\); console.log\\('koa app.use works'\\)\")",
"Bash(node -e ':*)",
"Bash(node -e \"const src = require\\('fs'\\).readFileSync\\('/Users/sam/Code/triplanetary/node_modules/.pnpm/boardgame.io@0.50.2/node_modules/boardgame.io/dist/cjs/server.js', 'utf8'\\); const idx = src.indexOf\\('run\\('\\); console.log\\(src.slice\\(idx, idx+600\\)\\);\")",
"Bash(pnpm add:*)",
"Bash(pnpm --filter @triplanetary/client test)"
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "./scripts/post-edit-lint.sh"
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "./scripts/post-edit-lint.sh"
}
]
},
{
"matcher": "MultiEdit",
"hooks": [
{
"type": "command",
"command": "./scripts/post-edit-lint.sh"
}
]
}
]
}
},
"permissions": {}
}
104 changes: 104 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
lint-typecheck:
name: Lint & Typecheck
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build shared
run: pnpm --filter @triplanetary/shared build

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

test:
name: Test
runs-on: ubuntu-latest

services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: triplanetary
POSTGRES_PASSWORD: triplanetary
POSTGRES_DB: triplanetary
ports:
- 5433:5432
options: >-
--health-cmd "pg_isready -U triplanetary -d triplanetary -h localhost"
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
DATABASE_URL: postgresql://triplanetary:triplanetary@localhost:5433/triplanetary
SESSION_SECRET: ci-test-secret
NODE_ENV: test

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build shared
run: pnpm --filter @triplanetary/shared build

- name: Wait for Postgres
run: |
for i in {1..30}; do
(echo > /dev/tcp/localhost/5433) >/dev/null 2>&1 && exit 0
echo "Postgres not ready yet ($i/30)..."
sleep 2
done
echo "Postgres did not become ready in time" >&2
exit 1

- name: Run migrations
run: pnpm --filter @triplanetary/server db:migrate

- name: Test
run: pnpm test

build:
name: Build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
.turbo/
*.tsbuildinfo
.worktrees/
.claude/settings.local.json
49 changes: 49 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,52 @@ pnpm --filter @triplanetary/shared build
- **Phase 2** — Game engine (vector movement, gravity, combat)
- **Phase 3** — Lobby refinements, multiplayer polish
- **Phase 4** — AI bot

---

## Decision Log

Append entries here whenever an architectural, convention, or tooling decision is made.

### Single-port Koa server (boardgame.io + custom routes)
boardgame.io's built-in Koa server is the only backend process. Custom `/api/*` routes mount directly on its Koa app rather than a separate server. Avoids a second port and proxying complexity. All custom middleware must be Koa-compatible; boardgame.io's internal routing conventions must be respected.

### CommonJS for `shared` and `server` packages
boardgame.io 0.50.x ships CJS-only with no ESM exports map — any ESM import fails. Do not add `"type": "module"` to `packages/shared` or `packages/server`. `packages/client` is ESM (Vite handles bundling).

### ts-node --transpile-only as server dev runner (not tsx)
tsx v4's ESM loader pollutes the CJS module cache, causing `TypeError: getGeneratorFunction is not a function` in koa-passport. Do not switch back to `tsx watch`. Note: transpile-only skips type checking at dev-server startup — run `pnpm typecheck` separately.

### bodyParser scoped to /api router
`koa-bodyparser` at app-level consumes the request stream before boardgame.io's `co-body` parser runs → `InternalServerError: stream is not readable` on match creation. Scope it to the `/api` Router only.

### PostgreSQL on port 5433
Port 5432 conflicts with a locally installed PostgreSQL. All connection strings and CI service definitions target `:5433`.

### Custom pg session store (not connect-pg-simple)
connect-pg-simple has CJS/ESM resolution issues in this module setup. Hand-rolled store in `session.ts` using plain `pool.query`. Expire column uses `NOW() + ($n * INTERVAL '1 millisecond')` — passing a JS `Date` into a `timestamp WITHOUT TIME ZONE` column strips timezone and causes `expire > NOW()` comparisons to fail.

### Custom migration runner (not drizzle-kit migrate)
drizzle-kit's `migrate` CLI fails with CJS/ESM resolution errors. Migrations are applied via `packages/server/src/db/migrate.ts` (run with `tsx`). New migration files must be manually registered in `meta/_journal.json`. CI calls `pnpm --filter @triplanetary/server db:migrate` (bypasses turbo) so `DATABASE_URL` reaches the subprocess.

### Turbo env passthrough: declare env vars in turbo.json
Turbo v2 does not forward env vars to task subprocesses unless declared under `"env": [...]` in the task config. Any task that reads env vars at runtime must declare them there.

### Vite alias: @triplanetary/shared → TypeScript source
Vite/Rollup cannot statically analyse `__exportStar` from tsc's CJS output — named exports appear missing at bundle time. `vite.config.ts` aliases `@triplanetary/shared` to `../shared/src/index.ts`. The server still imports from the compiled `dist/`.

### Vitest fork isolation for server integration tests
`pool: 'forks'` + `poolOptions: { forks: { isolate: true } }` in `packages/server/vitest.config.ts`. Without this, `pool.end()` in one test file's `afterAll` terminates the shared pg Pool used by concurrently-running test files.

### Per-file email namespacing for server integration tests
Test files run concurrently. A broad `DELETE FROM users` in one file's `beforeAll` races with another file's user registration. Each file owns a unique email prefix and cleans up only its own rows:

| File | Namespace |
|---|---|
| `auth.test.ts` | `test@example.com` |
| `auth-validation.test.ts` | `auth-val-*@test.com` |
| `maps.test.ts` | `mapeditor-*@test.com` |
| `maps-export.test.ts` | `export-test@test.com` |
| `session.test.ts` | `test-session-*` (session table only) |

New integration test files must choose a unique prefix and clean up only that prefix.
Loading
Loading