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

5.4 KiB

AGENTS.md

Guidance for AI coding agents working in this repo. Humans: see 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.

Setup & commands

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.jsel(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.