Files
Emberfall/HANDOFF.md
T
2026-10-23 01:06:48 +11:00

102 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF.md — Session Handoff (OVERWRITE at the end of every phase or when context is getting long)
> **Read order for a new session:** `NOTES.md` → `STATUS.md` → this file → then work.
> If this file disagrees with the code, trust the code and fix this file.
## 1. Where we are
- **Last completed step:** 10.9 — Allocation UI: unspent points display with + buttons per stat on dashboard stats panel — TICKED in STATUS.md
- **Next step:** Phase 10.10 — POST /api/character/rest endpoint + dashboard button
- **Updated:** 2026-10-23
- **Was the last session cut off mid-task?** No
## 2. Verified state (run these first)
| Command | Expected |
|---|---|
| `npm install` | succeeds |
| `npm run migrate` | applies 001–006 / "nothing to do" |
| `npm test` | **222 passing, 0 failing** (85 unit + 137 integration incl. 8 allocation UI tests) |
| `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear |
| `npm start` + `curl localhost:3000/` | 200 HTML landing page |
| Open `/game.html` logged in → check dashboard for allocation UI | Shows unspent points with + buttons when > 0, hidden when 0 |
## 3. What exists now (files that matter)
- `public/game.html` — Added `#allocation-section` div (hidden by default) with unspent points display, alloc-rows container, alloc-total, alloc-apply-btn.
- `public/js/game.js` — Added 5 allocation functions: `buildAllocationRows`, `handleAllocIncrement`, `updateAllocState`, `handleAllocApply`, `initAllocationUI`. Calls `initAllocationUI` from `loadCharacter`. Apply button listener attached in init.
- `public/css/main.css` — Added allocation UI styles: `#allocation-section`, `.alloc-row`, `.alloc-btn`, `.btn-alloc-apply`.
- `tests/integration/step-10.9.test.js` — 8 tests (5 HTML/JS structure + 2 API flow + 1 full flow).
- `src/services/characterService.js` — `allocatePoints` function (from 10.8).
- `src/routes/character.js` — `handleAllocate` handler (from 10.8).
## 4. Half-finished / fragile things
- No leaderboard UI yet (stub page exists; real UI in 10.17).
- HP never regenerates naturally; only level-up restores it.
- No restart-persistence test for sessions.
- `npm run seed` script in package.json points to nonexistent `scripts/seed.js`.
- Equipment bonuses not yet passed into combat (Issue #5 in STATUS.md).
- Starter gear documented but not auto-created on character creation.
- `GET /api/zones` documented in API.md but not implemented (Issue #2).
- Mobile nav uses emoji icons (⚔️ 🏕️ 🎒) — verify they render acceptably on all platforms.
- **10.10 next:** Rest endpoint — costs level*5 gold, heals to full max HP.
## 5. Decisions & deviations from the docs
- D-004..D-007 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock).
- **D-014:** Mobile bottom nav uses emoji icons (⚔️ Hunt / 🏕️ Trip / 🎒 Items) instead of full text labels.
- **D-015:** Desktop tabs hidden via CSS below 700px; mobile nav shown via CSS; JS `syncMobileNav()` keeps active state in sync.
- **D-016:** `applyXpGain` in `src/game/leveling.js` is the single shared function for XP gain logic, used by both `huntService` and `expeditionService`.
- **D-017:** Landing page `public/index.html` uses `credentials: 'include'` in fetch to `/api/auth/me` to check session state; Play button shown only when `hasHero` is true.
- **D-018:** Link-checker test skips API endpoints (`/api/...`) since they're not static files.
- **D-019:** Rate limiters use `process.env.NODE_ENV === 'test' ? 100 : 100` for test env (relaxed to avoid test interference; prod limit same as test).
- **D-020:** `allocatePoints` in `leveling.js` accepts non-negative integers (0 allowed for unallocated stats) — changed from "positive integer" to support partial allocation via `{str, agi, int, vit}` where unmentioned stats default to 0.
- **D-021:** Allocation UI uses `container.replaceChildren()` instead of `innerHTML = ''` to avoid innerHTML entirely (per NOTES.md constraint).
## 6. Gotchas learned
- `npm test` must be `node --test` with NO path/glob; files must be named `*.test.js`.
- better-sqlite3-session-store ignores `expired.clear: false`; that's why the timer is unref'd.
- Statement caches are per-DB-handle (WeakMap) in huntService; copy that pattern for new services.
- Verify test COUNT, not just "all passing".
- Node.js `fetch` doesn't persist cookies across calls — extract `Set-Cookie` header and pass manually in integration tests.
- SQL `SELECT id` from inventory returns the auto-increment row id; rename with `AS inventory_id` when you also need `item_id`.
- Transaction callback args must match the call order exactly. (This is the 10.1 bug — double-check param/call order in every new transaction.)
- Error responses from route-level `AppError`s do NOT include `data` field.
- `setNow` affects ALL `now()` calls including those in service logic.
- Shop API returns `{ ok: true, data: { stock: [...], character: {...} } }` — field is `stock`, not `stocks`.
- Shop stock items have flat fields (`itemId`, `buyPrice`), not nested (`item.id`).
- New characters have NO inventory items (starter gear not auto-created yet).
- `GET /api/zones` documented in API.md but not implemented.
- Hero profile endpoint is PUBLIC — no `requireAuth` middleware.
- Profile page is public: no auth.js guard needed; uses ?name= query param for lookup.
- Rate limiters in tests: set `NODE_ENV=test` to get relaxed limits; otherwise tests fail with 429.
- `seed-dev.js` must check `NODE_ENV=production` before any `require()` that loads config (config throws on placeholder SECRET).
- Express `sendFile` rejects `../` path segments — use `path.resolve()` + `path.join()` for absolute paths.
- `express.static` sends its own 404 responses; catch-all must be placed BEFORE static middleware.
- Character profile GET returns `{ ok: true, data: { character, derived, energy } }` — fields nested under `character`, `derived`, `energy`.
- Register returns 201 (Created), not 200.
- Character creation returns 201 (Created), not 200.
- Expedition start uses `defId` in request body (not `expeditionId`).
- Expedition claim uses `expeditionId` in request body.
- Expedition start response returns `data.expedition.id` — capture this for claim.
- `src/utils/time` exports `{ now, setNow, resetNow }` directly — `const time = require(...)`, not `const { time }`.
- Inventory sell endpoint (`POST /api/shop/sell`) only accepts `material`-type items.
- `handleListInventory` in `src/routes/inventory.js` — parameter name must be `res`, not `_res`.
- Character profile response has `energy` at `data.energy.current`, NOT `data.character.energy`.
- **10.3:** `expeditionService.js` import changed from `const { now, setNow } = require('../utils/time')` to `const time = require('../utils/time')` — the destructured `now` variable was shadowed by the local `opts.now ?? time.now()` pattern.
- **10.3:** `_opts` cannot be sent via HTTP (functions don't serialize to JSON); test the rng injectable via direct service call instead.
- **10.4:** `req.session.username` must be set in both `handleRegister` and `handleLogin` — register was the gap. `/me` reads it, so without it username is always empty string.
- **10.5:** Link-checker test path: `__dirname` is `tests/integration/`, so `../../public` resolves correctly. API endpoints (`/api/...`) must be excluded since they're not static files.
- **10.6:** Rate limiters add `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers by default in express-rate-limit — use these to verify the limiter is real in tests.
- **10.6:** Global `/api` limiter must be mounted BEFORE auth routes so it covers them; auth-specific limiters are stricter and will trigger first.
- **10.6:** `max: 100` in test env (not 1000) to avoid interference with other tests that also login. The 429 shape test uses a mini-app with `max: 10` to validate the exact JSON body.
- **10.7:** Express JSON parser throws `SyntaxError` with `status: 400` for malformed JSON and `PayloadTooLargeError` with `status: 413` for oversized body — catch these in the error handler before the ZodError check.
- **10.7:** `req.path.startsWith('/api/')` is more reliable than `req.accepts('html')` for deciding JSON vs HTML on unknown paths — Node.js fetch defaults to accepting HTML.
- **10.7:** Session store callbacks (regenerate, destroy) must use `next(err)` not `throw` — `throw` inside a callback is not caught by Express's error handler and can crash the process.
- **10.8:** `leveling.js`'s `allocatePoints` always returns `remainingPoints: 0` — the service must compute `character.unspent_points - total` itself.
- **10.8:** zod `.strictObject` with optional fields needs `.refine()` to require at least one field present.
- **10.8:** For HTTP integration tests that need DB state setup, use direct service calls to set up state, then test the HTTP endpoint.
- **10.9:** Allocation UI must use `textContent`, never `innerHTML` with dynamic data — use `replaceChildren()` instead of `innerHTML = ''` to clear containers.
- **10.9:** `initAllocationUI` called from `loadCharacter` ensures UI stays in sync after any character data change (allocation, hunt, level-up).
## 7. Next-step briefing
- **Next step: 10.10** — Rest endpoint: `POST /api/character/rest` costs `level * 5` gold, heals to full max HP. Errors: `NOTHING_TO_HEAL` (422) if HP is already full, `INSUFFICIENT_GOLD` (422). One transaction. Add a "Rest (N gold)" button to the dashboard.
- Read API.md §Rest and BALANCE.md §Rest for the spec.
- The endpoint is similar to allocate (auth, zod validation, transaction, returns updated character + derived).