Bare HTTP + WebSocket bootstrap (health check route, ws upgrade, connection logging) with matchmaking/ and rooms/ reserved but unimplemented. No matchmaking logic yet. Also adds root README.md (project overview) and AGENTS.md (architecture map, code style, git workflow).
3.4 KiB
AGENTS.md
Context and working agreements for anyone (human or agent) changing this
repo. See README.md for what the product is; this file is about how we
build it.
Architecture
frontend/ (Vite + TypeScript, no UI framework — plain DOM) is
layered bottom-up; each layer only imports from the ones below it:
types.ts shared domain types (Token, CategoryState, ThemeDefinition, ...)
data/ static content: theme palettes, code samples
engine/ pure, DOM-free utilities: tokenizer, color math, particles, sound
game/ canvas rendering, scoring, panel/result DOM views, ThemeGuessGame
preview/ the pre-round "flash the real theme" renderer
ui/ composable DOM widgets (theme grid, preview countdown flow)
main.ts composition root — wires the above to the page, nothing else
New game logic goes in engine/ if it doesn't touch the DOM, game/ if
it does. Don't reach for React/Vue/etc.: the canvas rendering and the
handful of DOM widgets don't need a framework, and adding one is a
bigger discussion than a drive-by PR.
backend/ is currently a bare HTTP+WebSocket bootstrap
(src/index.js + src/ws/connectionHandler.js) with src/matchmaking/
and src/rooms/ reserved but empty (see the README.md in each). Plain
Node with ESM ("type": "module"), no framework, no TypeScript — keep it
that way until there's an actual reason to add one.
Code style
- Frontend TypeScript runs under
strict. Don't widen types to work around an error without understanding why it's there. - Small static string-keyed lookup tables are
Record<K, V>, notSet/Map— reach forSet/Maponly for dynamic membership, non-string keys, or when you need.size/iteration order/.clear(). - Don't wrap a single expression in a named function just to name it, unless the name is a stable public API, a callback whose identity matters, or documents a non-obvious formula. Inline the rest.
- A property literally named
constructorin an object literal typed asRecord<string, V>gets its value type silently widened by TS (seeengine/tokenizer.ts'stoLookup) — build such tables from an array viaObject.fromEntriesinstead of a literal when a key might collide.
Git
- Trunk is
master; keep it green (typechecks, builds, game loads). - One short-lived branch per change, prefixed by intent:
feat/…,fix/…,refactor/…,chore/…,docs/…. - Commit messages follow Conventional Commits:
type(scope): summary— e.g.refactor(frontend): split game into modules,feat(backend): add matchmaking queue. Scope is usuallyfrontend,backend, or a package-relative area (engine,ui,ws, ...). - Squash-merge feature branches into
master; delete the branch after. Keep history readable — no "wip", "fix typo", "asdf" commits onmaster. - Frontend and backend evolve independently; a PR touching only one should only bump/discuss that package's version and deps.
Verification expectations
- Frontend change:
npm run typecheck(orbuild) infrontend/, plus an actual smoke test (open the dev server, play a round) for anything touching rendering, input, or scoring — type-checking alone doesn't catch a canvas drawn one line too high. - Backend change: whatever's implemented must actually boot
(
npm start) and the touched transport (HTTP route / WS message) must be exercised manually or with a script; there's no test suite yet.