theme_guess/AGENTS.md

3.5 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>, not Set/ Map — reach for Set/Map only 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 constructor in an object literal typed as Record<string, V> gets its value type silently widened by TS (see engine/tokenizer.ts's toLookup) — build such tables from an array via Object.fromEntries instead of a literal when a key might collide.

Git

  • Trunk is main; 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 usually frontend, backend, or a package-relative area (engine, ui, ws, ...).
  • Squash-merge feature branches into main; delete the branch after. Keep history readable — no "wip", "fix typo", "asdf" commits on main.
  • Frontend and backend evolve independently; a PR touching only one should only bump/discuss that package's version and deps.
  • Agents: all changes must be committed before reporting the task as finished — no handing back a dirty working tree.

Verification expectations

  • Frontend change: npm run typecheck (or build) in frontend/, 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.