dex/AGENTS.md
chris 3a54cdcc53 Add a Vitest unit suite for the pure-logic modules
31 tests in test/, run in a plain Node env with a tiny localStorage shim
(no jsdom — its Node-22.4 transitive-dep breakage isn't worth working
around for logic tests):

- type-chart: modern effectiveness, dual-type stacking, and the era rules
  (no Fairy pre-6, Steel resists Ghost/Dark pre-6, the Gen 1 Ghost→Psychic
  and Bug↔Poison quirks).
- damage-calc: nature multipliers, the Gen 3+ stat formula (Garchomp
  reference values), STAB/effectiveness/immunity, pre/post-Gen-6 crit.
- savedex: a synthetic Gen 1 SRAM (bitfields + checksum) round-trips;
  checksum-mismatch and size guards.
- selection: normalizeSelection v2→v3 migration, per-game isolation,
  global favourite, stats vs statsNational.
- pokedex-resolver: prettify, dex resolution against a mini snapshot.

`npm test` wired into CI after format:check.

Also: docker-compose host port 8080 → 4753 (README updated).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
2026-09-10 11:33:27 -04:00

105 lines
5.4 KiB
Markdown

# AGENTS.md
Guidance for AI coding agents working in this repo. Humans: see
[CONTRIBUTING.md](CONTRIBUTING.md) — this file is the short, machine-facing
version of the same rules.
## What this is
Pocketdex — an offline-first Pokédex / team builder / save-file tracker.
**Vanilla JS, no UI framework.** Vite 6 + `vite-plugin-pwa` (Workbox,
injectManifest). All data comes from [PokéAPI](https://pokeapi.co/).
## Setup & commands
```bash
npm install
npm run snapshot # build src/data/snapshot.json from PokéAPI (git-ignored; required before first build/dev)
npm run dev # dev server, http://localhost:5173
npm run build # runs `snapshot` (prebuild) then builds to dist/
npm run preview # serve the production build — REQUIRED to test the service worker / offline / install
npm test # Vitest (pure-logic unit tests in test/)
npm run format # Prettier, write
```
- Node **20+**.
- CI runs three checks, all must pass: `npm run format:check` (Prettier),
`npm test` (Vitest — pure-logic unit tests in `test/`), and
`npm run build` (esbuild transforms every module, catching syntax and
import errors). Run `npm run format` before finishing. No ESLint.
- If you change type-chart / damage-calc / savedex / selection logic,
update or add a test in `test/`.
- `npm run snapshot -- --force` rebuilds the snapshot even if it's fresh.
## Project structure
| Path | What |
| ---------------------------- | --------------------------------------------------------------------------- |
| `src/main.js` | boot: theme, nav, router, SW registration, install prompt |
| `src/router.js` | hash router; `routes` array; each view is `() => Promise<Node>` |
| `src/views/*.js` | one file per route (`DexGrid`, `PokemonDetail`, `TeamView`, …) |
| `src/components/*.js` | reusable DOM pieces (`Card`, `Sprite`, `TypeChip`, `CompareTable`, …) |
| `src/store/*.js` | `createStore()` state, persisted to `localStorage` under `pdx.*` |
| `src/data/*.js` | snapshot loader, pokedex resolver, lazy PokéAPI client, type chart, natures |
| `src/lib/*.js` | app-agnostic helpers (`dom`, `anim`, `damage-calc`, `savedex`, `swipe`, …) |
| `scripts/build-snapshot.mjs` | the only thing that calls PokéAPI at build time |
| `test/*.test.js` | Vitest unit tests (pure logic only) |
| `src/styles/tokens.css` | palette, type colours, the five themes |
| `src/styles/layout.css` | everything else |
## Conventions
- **Build DOM with `el()`** from `src/lib/dom.js``el(tag, props, ...children)`.
No template-string HTML, no `innerHTML`, except the deliberate `html:` prop
for trusted inline SVG.
- **Stores**: `createStore(key, initial)``{ get, set, subscribe, replace }`.
`set(fn)` **replaces** state (it does not merge) — spread `...s` yourself.
In views, `subscribe()` and call the returned unsub in
`onTeardown(viewNode, off)`.
- **Everything is game-aware.** The selected game lives in
`settings.get().versionGroup`. If you touch typings, effectiveness,
learnsets or evolution, honour that game's generation: `typesForGen()`,
`multiplier(atk, def, gen)`, PokéAPI `past_values`.
- **Tracking is per game.** `src/store/selection.js`: `entry(id)` /
`toggle(id, field)` / `stats(ids)` default to the selected game;
`favorite` and `note` are global. Don't reintroduce a single global
caught set.
- Respect `prefersReducedMotion()` (from `src/store/settings.js`) for any
animation.
- Formatting is Prettier (`npm run format`). Otherwise match the
surrounding file; keep comments about _why_.
- No new runtime dependencies. Prefer adding to the snapshot over a new
runtime fetch.
## Adding things
- **Route** — new `src/views/Foo.js` exporting `async function Foo()`
returning a node; add one entry to `routes` in `src/router.js`. Add a
`skeleton` if it does async work before first paint.
- **Setting** — key + default in `src/store/settings.js` (and `applyTheme()`
if it affects the document), a field in `src/views/SettingsView.js`, and
add it to the export payload in `exportData()` if it should survive a
backup.
- **Snapshot field** — add it in `scripts/build-snapshot.mjs`, run
`npm run snapshot -- --force`, read it via `loadSnapshot()`. Keep the
snapshot small; it's precached on every install.
## Do not
- Commit `dist/` or `src/data/snapshot.json` (both git-ignored).
- Commit Pokémon sprites, artwork, audio, ROMs or save files. All imagery
is fetched at runtime from PokéAPI.
- Add analytics, telemetry, ads, tracking, or "sign in" flows.
- Add a framework or a build step that isn't Vite.
- Break offline-first: the grid, search, filters and game switching must
work with no network.
## Verifying a change
`npm run format`, `npm test`, `npm run build` — all must pass. Then
sanity-check by hand: the dex grid (filters, sort, catching from a card),
switching games (numbers + typings update), a detail page (all tabs, form
switch), and `npm run preview` offline (DevTools → Network → Offline →
reload). Check light and dark
theme and a mobile-width viewport.