Files
History/CHANGELOG.md
T
kamilkrzywda d5285ce28d
CI / build + unit + e2e (pull_request) Successful in 1m21s
Add sub-national regional dataset + generator (v0.11.0)
Fixes #16
2026-10-12 00:33:11 +00:00

14 KiB
Raw Blame History

Changelog

All notable changes to History are listed here, newest first.

v0.11.0 — 2026-10-12 00:31

  • Sub-national region dataset + generator (#16, foundation only — no app behavior change). A new dependency-free scripts/generate-regions.mjs (run via npm run generate:regions) splits each of the 177 countries in public/countries.geo.json into adaptive ~100–200 km sub-national regions on a regular lat/lon grid clipped to the country (Sutherland–Hodgman), and writes the committed public/regions.geo.json (2,597 regions, ~755 KB). Each region is id'd <ISO_A3>-<nn> (nn = 1..k in row-major order, capped at 36 per country) with properties = { name, parent }, so downstream work can map any region back to its parent nation. Antimeridian countries are handled three ways: small island groups (Fiji) stay a single whole-country region; large ones (Russia) are tiled in shifted [0,360) longitude space with output rings split at ±180°; and the full-360° Antarctic wrap ring becomes two hemispheric wedges. The output is fully deterministic — the same input yields a byte-identical file (verified by sha256).
  • Validation test — new self-contained scripts/regions.test.ts (picked up by npm test) reads the two GeoJSON files once each and asserts: the file is a well-formed FeatureCollection; every region is a valid feature whose id matches ^[A-Z]{3}-\d+$ and whose parent is a real country id; every country has 1–36 regions (≥2 for any country larger than 50,000 km²); every region has a sane area (≥ 200 km², with a microstate exemption, and < 5,000,000 km²); and each country's regions sum to 45–108% of its own area (the measured range is ~[64%, 105%] — Antarctica's wedges under-fill, Norway's high-latitude fjord coast over-fills by 0.1%). No change to the running app, the LLM contract, or the map — this is purely the dataset + tooling that later PRs for #16 build on.
  • Fixes #16.

v0.10.0 — 2026-10-11 21:25

  • Fix world map style (#2) — made the choropleth fill fully opaque (fill-opacity 0.85 → 1.0) and darkened the Natural Earth terrain raster (raster-opacity 0.5 → 0.15), so the raster's flat water tint and fine 1:10m coastline no longer bleed through countries and compete with the borders.
  • Strengthened country border lines — border width 0.7 → 1.25, and the per-region stroke alphas raised (owned 0.10 → 0.30, unowned 0.06 → 0.14) so the vector borders read clearly as the dominant edge, with owned regions still more prominent than unowned ones.
  • Fixes #2.

v0.9.0 — 2026-10-11 20:57

  • Reduced turn latency (prompt-level): capped recentEvents in the turn prompt from the last 10 to the last 5 events, and added a HARD CONSTRAINT asking the reasoning model to keep its internal analysis brief before emitting the JSON. This shortens the chain-of-thought (the 170–223s turn driver) and lowers the risk of exhausting the 4000-token output budget before the answer (the original 502 failure mode). Reasoning stays enabled (reasoning_effort: low); the deterministic stub, the TurnResult contract, max_tokens, and reasoning_effort are all unchanged.

v0.8.0 — 2026-10-11 20:29

  • Remove a saved game (#20) — every saved game now has a delete control: a trash button on its card in the home screen's "Saved games" section opens a confirmation dialog ("Delete this game?" showing the scenario/turn/date) with Cancel and a destructive Delete. The new DELETE /api/games/:id route removes the game's row from SQLite and returns { deleted: true } (404 if the id is unknown).
  • Redirect on a missing/deleted game (#20) — loading a non-existent game id no longer leaves you stranded. A deep link to a missing game already fell back to home, and now a game that disappears while its tab is open (deleted from another tab or the same session) is pushed home too: the server broadcasts an SSE error event on delete, and the client's useSse gains an onError handler that returns the tab to the home screen (matching the existing "game not found" connect behavior).
  • PresetPicker gained an onDelete prop, a per-card data-game-id attribute, the delete button, and the confirm dialog; api.deleteGame(id), Db.delete(id), and the shared DeleteGameResponse type were added to support it.
  • Unit + e2e tests — new Db.delete test (removes by id; reports absence for unknown / already-gone ids) and two e2e specs: (a) deleting a saved game from the home screen (open → cancel leaves it; confirm removes the card, the response is {deleted:true}, and a follow-up GET is 404), and (b) deleting an open game (the live tab is pushed home via the SSE error, and a fresh deep-link to the deleted id lands on home).

v0.7.0 — 2026-10-11 19:17

  • Clicking a map region now switches the selected dossier to that region's owning nation instead of opening a popup (#7). The closeable click popup is gone; a hover tooltip (owner + key stats) is kept. The manual "Inspect" selector still works and stays in sync — both it and a map click drive the same dossier target.
  • Fixed: a clicked owned region no longer reports "no owner" (#6). Root cause: MapLibre repacks a GeoJSON source's top-level id (the ISO_A3 string) into a numeric index when it stores the source, so the feature a click/hover handler receives has a number for id — the old regions[f.id] lookup resolved to nothing, and the popup read "No owner" even for owned regions (the choropleth fill still worked because it is stamped from the original id before setData). Fix: applyRegions now stamps the stable ISO_A3 code into properties.regionId (and ownerId), which survives into the rendered features, and both the click and hover handlers resolve the owning nation from that code via the authoritative RegionMap — never from the repacked feature.id.
  • Unowned regions (no entry in the RegionMap) do not change the dossier when clicked — the current target is left in place (a deliberate, non-crashing choice rather than a special "unowned" dossier state).
  • New shared helper regionOwner(regions, isoA3) in @shared/protocol (the pure, single source of truth for "ISO_A3 → owner record").
  • Unit tests — new regionOwner tests (owned → owner record, unowned → undefined, empty/missing → undefined) plus a #6 regression guard that pins the exact MapLibre-repacked feature shape (numeric id, ISO_A3 in properties.regionId) and asserts resolution via properties.regionId works while the old feature.id path fails (44 tests, up from 39).

v0.6.0 — 2026-10-11 18:27

  • Advisor fetches now show a "thinking" indicator (mirroring the turn's "Simulating…" hint) while the model responds, and the Ask-advisor button disables during the fetch; the top-bar status shows "Advisor thinking…" in the meantime. Fixes #15.

v0.5.0 — 2026-10-11 17:36

  • HUD revamp — removed the "Map" window (the full-screen map is directly interactive via scroll/trackpad; no floating zoom control, and the now-dead MapApi/onMapReady map plumbing is removed from WorldMap); the top bar is now a fixed, non-movable element pinned to top-center a few px from the top (a new pinned mode on FloatingWindow — viewport-centered, no drag, no persisted position, always above other windows); and the dossier / event-feed / action panels can each be docked into a left or right full-height pane (a new dock/onDockChange mode on FloatingWindow, with small left/right/float controls in each title bar, choice persisted in localStorage and reproduced on reload; the default "float" reproduces the current first-run layout exactly). The legend stays a small floating widget. Fixes #4, Fixes #5.
  • FloatingWindow — new optional props: pinned (top-center fixed bar), dock ('left'/'right' full-height columns) and onDockChange (renders the dock controls and reports the mode), plus dataTestId (a data-testid on the window root for e2e). anchor is now optional (defaults to top-center).
  • New data-testids — top-bar (pinned header), panel-dossier / panel-feed / panel-action (per-window roots), and dock-left / dock-right / dock-float (per-window dock controls).
  • New localStorage key — history.floatwin.v1.dock (per-id dock mode); mirrors the existing history.floatwin.v1.collapsed.
  • E2E — new "HUD revamp" suite: asserts the Map window and its zoom buttons are gone, the top bar is pinned and horizontally centered a few px from the top (and doesn't move under a drag attempt), the dossier docks to the left edge (full-height) and the feed to the right edge, and a dock choice survives a page reload — all deterministic under LLM_STUB=1 (11 tests, up from 9).

v0.4.0 — 2026-10-11 16:02

  • Fix: queued items disappearing from the pending list (#10). Root cause: SSE state events carried no freshness information, and the client's onState/onTurn handlers applied any incoming snapshot unconditionally. A stale snapshot — e.g. the SSE on-connect resync taken just before an add was saved, or an EventSource auto-reconnect — could arrive after the add's REST response and clobber it, so "add one: nothing happens, add another: both appear". Fix: every mutation now bumps a monotonic GameState.version (persisted in SQLite, started at 1 on create, bumped by queueRoundItem, the new removeRoundItem, and applyTurnResult; legacy games without the field are treated as version 0), and the client routes every state ingestion (SSE events, add/remove/jump responses, game loads) through a single "never go backwards" rule: a different game id is always adopted, the same game only when the incoming version is not older than the current one (compared inside the functional setGame updater, so it is stale-closure-proof).
  • New: remove button for queued items (#11). Each pending-queue row in "Your move" has a small trash-icon button that opens a shadcn Dialog confirmation (item quoted, Cancel / destructive Remove, dark command-center styling). Confirming calls the new POST /api/games/:id/queue/remove (body { index }) — same fast save → broadcast → respond order as the add routes, no LLM, no turn advance; invalid indices get a 400. The client api.removeQueueItem(id, index) is version-gated like every other state update, so the list can never regress after a removal.
  • Unit tests — new removeRoundItem tests (valid removal + version bump, out-of-bounds/non-integer → null), version-bump assertions for queueRoundItem/applyTurnResult/stub turn, a legacy-no-version case, and version round-trip in db.test.ts (39 tests, up from 35).
  • E2E — new "queue add is never clobbered by a stale SSE snapshot" test (one add appears immediately, still there after a 1s grace period for late SSE events, version strictly increases across adds, queue survives a full page reload) and a "remove with confirmation" test (cancel keeps the item, confirm removes it, no turn advance, version persisted) — all deterministic under LLM_STUB=1.

v0.3.0 — 2026-10-11 15:29

  • Deep-linking — the active game's id now lives in the URL (#/game/<id>), so a link opens that exact game and two tabs can run two different games; a bad or missing id falls back to the home screen. Fixes #3.

v0.2.0 — 2026-10-11 14:24

  • Batched round model — replaced the single-action/turn with a queued-round model. Actions and diplomatic messages accumulate in game.pending (no LLM call, no turn advance); a single "Jump forward" button commits the whole batch in one LLM turn. This fixes the 502 reliability problem by making each LLM call do one coherent batch of work instead of one tiny fragment.
  • New routes — POST /api/games/:id/action and POST /api/games/:id/diplomacy (fast, no LLM); POST /api/games/:id/jump (the single LLM call); POST /api/games/:id/turn removed.
  • New client API functions — api.addAction(id, text), api.addDiplomacy(id, to, message), api.jump(id); api.turn(...) removed.
  • ActionPanel rewrite — Action tab: textarea + small "Add action" button. Diplomacy tab: target select + textarea + small "Add to round" button. Advisor tab: input + "Ask advisor" button. Below the tabs: a visible pending-queue list (⚡ for action, 💬 for diplomacy with target name, truncated text, count badge) and a large "Jump forward" primary button that shows the pending count and disables while a jump is in flight.
  • App.tsx — runAddAction / runAddDiplomacy handlers (no busy state); runJump (with busy); the "Your move" window's title bar now shows a pending-count badge chip instead of the old jump button.
  • Deterministic stub updated — deterministicTurn now emits one event per queued item (action → military/success, diplomacy → diplomatic/partial) instead of one event per turn; still sets player military to clamp(cur+5) and allies with the first other nation.
  • Unit tests — rewrote engine.test.ts with batch-aware tests (7 new tests, 35 total up from 28); updated db.test.ts and map.test.ts fixtures to include pending: [].
  • E2E — rewrote e2e/sim.spec.ts to the new flow: queue action → assert pending list → queue diplomacy → assert pending list → jump → assert turn+1, date+2, military 80→85, two events, pending cleared, ally relation.
  • AGENTS.md — added Gitea-issue step to the change process; updated Layout and Conventions to reflect the batched-round model.

v0.1.0 — 2026-10-11 08:02

  • Added a Gitea Actions CI pipeline (.gitea/workflows/ci.yml: typecheck → lint → unit → build → deterministic Playwright e2e). E2e runs against the app's own dev webServer (LLM_STUB=1) — no dind/docker.
  • Committed package-lock.json so CI uses a reproducible npm ci.
  • Switched to the mandatory PR-based workflow: feature branch → PR to master → CI green → auto-merge → tag + manual deploy (documented in AGENTS.md).
  • Versioned the repo at 0.1.0 (package.json) and added CHANGELOG.md, .env (gitignored) and .env.example.
  • This change is the first one landed through the new PR process (dogfooding it).