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
5.4 KiB
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 intest/), andnpm run build(esbuild transforms every module, catching syntax and import errors). Runnpm run formatbefore finishing. No ESLint. - If you change type-chart / damage-calc / savedex / selection logic,
update or add a test in
test/. npm run snapshot -- --forcerebuilds 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()fromsrc/lib/dom.js—el(tag, props, ...children). No template-string HTML, noinnerHTML, except the deliberatehtml:prop for trusted inline SVG. - Stores:
createStore(key, initial)→{ get, set, subscribe, replace }.set(fn)replaces state (it does not merge) — spread...syourself. In views,subscribe()and call the returned unsub inonTeardown(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éAPIpast_values. - Tracking is per game.
src/store/selection.js:entry(id)/toggle(id, field)/stats(ids)default to the selected game;favoriteandnoteare global. Don't reintroduce a single global caught set. - Respect
prefersReducedMotion()(fromsrc/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.jsexportingasync function Foo()returning a node; add one entry toroutesinsrc/router.js. Add askeletonif it does async work before first paint. - Setting — key + default in
src/store/settings.js(andapplyTheme()if it affects the document), a field insrc/views/SettingsView.js, and add it to the export payload inexportData()if it should survive a backup. - Snapshot field — add it in
scripts/build-snapshot.mjs, runnpm run snapshot -- --force, read it vialoadSnapshot(). Keep the snapshot small; it's precached on every install.
Do not
- Commit
dist/orsrc/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.