Files
Flight-Ops/CHANGELOG.md
T

79 lines
14 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.
# Changelog
## v0.4.0 — 2026-09-17 08:22
- Self-hosted all OpenFreeMap map-styling assets (tiles stay remote): new `scripts/download-map-assets.mjs` fetches the 5 OFM styles with a browser UA, rewrites each via `rewriteStyle`, and extracts the font/sprite tarballs into `public/` — committed so the image is self-contained: `public/styles/{liberty,bright,positron,dark,fiord}.json` (0.15 MB), `public/fonts/Noto Sans {Regular,Italic,Bold}/*.pbf` (768 files, 99.76 MB — the font payload), `public/sprites/ofm{.json,.png,@2x.json,@2x.png}` (0.21 MB); total public/ = 100.13 MB
- New shared module `lib/mapStyles.ts`: `STYLES` registry (5 ids + labels), `DEFAULT_STYLE_ID = 'fiord'`, and pure `rewriteStyle()` — glyphs `https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf` → `/fonts/{fontstack}/{range}.pbf`, sprite `https://tiles.openfreemap.org/sprites/ofm_f384/ofm` → `/sprites/ofm`; the vector tile url (`/planet`) and ne2_shaded raster tiles (`/natural_earth/ne2sr/...`) are left remote; every source lacking an attribution gets `"© OpenStreetMap contributors © OpenFreeMap"` (OFM styles ship none; ODbL requires visible OSM credit)
- `components/Map.tsx`: map now loads the local `/styles/fiord.json` by default; new top-bar style switcher (`<select data-testid="style-switch">`, subtle styling in `app/globals.css`) listing all 5 styles, persisted to localStorage key `flight-ops-style`; SSR-safe — state initializes to `DEFAULT_STYLE_ID` and the saved choice is read/applied only in a post-mount effect (no localStorage reads during render/hydration)
- `components/Map.tsx`: airports layer now survives style switches — `/api/airports` is fetched at most once per page life (promise cached in a ref; count set exactly once), and an idempotent `ensureAirportsLayer()` re-adds the source+layer if missing on EVERY `styledata` event (initial load AND after each `setStyle`, which wipes sources/layers not in the new style); the click→popup handler is re-wired with off+on using a stable reference since MapLibre layer-scoped listeners survive setStyle and would otherwise duplicate
- `Dockerfile`: runtime stage now copies `public/` from the build stage (`COPY --from=build /app/public ./public`) so `next start` serves the self-hosted assets; `.dockerignore` already lets public/ through
- Tests: new unit suite `lib/mapStyles.test.ts` (registry shape, fiord default, rewriteStyle against an inline OFM fixture — local glyphs/sprite, remote tiles preserved, attribution added/preserved, input not mutated); `e2e/map.spec.ts`: openfreemap mock simplified to abort ALL requests from that host (only tiles remain there), new test asserting the switcher has 5 options with Fiord default, switching changes the applied style's layer set AND the airports layer re-renders after the switch + exactly one popup on marker click (guards the setStyle-wipes-layers and duplicate-listener regressions), plus a reload-persistence test for the localStorage restore path
- Verified: unit suite 16/16; `tsc --noEmit` clean; e2e suite 6/6 green against the rebuilt compose instance (all styles/fonts/sprites served locally, fiord default, switch + persistence verified in a real browser)
## v0.3.1 — 2026-09-17 06:35
- Header now shows the app version between the "Flight Ops" title and the airport count (`<small>v{version}</small>` in `components/Map.tsx`), sourced dynamically from `package.json` via a JSON import — future version bumps show up automatically with no code change; subtle 11px muted style added to `app/globals.css`
- Tests: new unit suite `components/version.test.ts` (valid semver in package.json + guard that the header sources the version from package.json rather than hardcoding it); `e2e/map.spec.ts`: new test asserting the header shows `v{version}` with the expected value read from `package.json` at test time, so it stays in sync automatically
- Verified: unit suite 10/10; `tsc --noEmit` clean; e2e suite 4/4 green against the rebuilt compose instance
## v0.3.0 — 2026-09-17 00:32
- Removed the zoom-driven camera tilt (`lib/camera.ts` pitch curve + the map `move` handler): the map is now always flat/top-down with standard pan/zoom only; deleted `lib/camera.ts` and its unit tests
- Removed terrain: no more DEM raster source / `map.setTerrain`, and the `?terrain=0` / `NEXT_PUBLIC_TERRAIN` toggles are gone — pure 2D view, no 3D elements
- MAX_ZOOM 14 → 11: at European latitudes (~52°N) z11 ≈ 47 m/px, so the viewport spans ~90–135 km across a typical desktop width (≈100 km on screen); street-level zoom had no gameplay value
- `e2e/map.spec.ts`: APP_URL is now `/` (terrain flag removed), MAX_ZOOM mirror updated to 11; PLAN.md map section updated to flat-2D-only
- Verified: unit suite 8/8; `tsc --noEmit` clean; e2e suite 3/3 green against the rebuilt compose instance (flat view, wheel zoom clamped to [3, 11])
## v0.2.9 — 2026-09-17 00:21
- `data/airports_eu.csv`: multi-airport metro areas reduced to one dot each on the map — Paris→CDG, London→LHR, Moscow→SVO, Milan→MXP, Istanbul→IST, Warsaw→WAW, Brussels→BRU (Rhine-Ruhr keeps both DUS and CGN as distinct cities); dropped 18 satellite airports incl. Orly, Gatwick, Luton, Stansted, Domodedovo, Vnukovo, Zhukovsky, Linate, Caravaggio, Atatürk, Modlin, Charleroi, Weeze, Prestwick, Treviso, Ciampino, Băneasa — 260 → 242 rows; `/api/airports` and the circle layer pick it up with no code changes
- Verified: unit suite 8/8; `tsc --noEmit` clean; e2e suite 3/3 green against the rebuilt compose instance (`/api/airports` serves 242 features)
## v0.2.8 — 2026-09-16 23:53
- `data/airports_eu.csv`: reduced the dataset to only `large_airport` rows (dropped all 731 medium_airport + 54 seaplane_base) — the map now shows just the 260 biggest EU airports; `/api/airports` and the circle layer pick it up with no code changes
- `e2e/map.spec.ts`: marker-click test repointed from ESSV Visby Airport (a medium_airport, now removed) to UKBB Boryspil International Airport — with only large_airport rows left it is the most isolated marker inside the initial zoom-4 viewport: its nearest surviving airport is Chişinău (LUKK), ~126 px away at the initial zoom (Kyiv Zhuliany, geographically closer, was a medium and got cut)
- Verified: unit suite 12/12; `tsc --noEmit` clean; e2e suite 3/3 green against the rebuilt compose instance (`/api/airports` serves 260 features)
## v0.2.7 — 2026-09-16 23:39
- `data/airports_eu.csv`: removed all 6400 `small_airport` rows from the OurAirports EU export — the map now shows only the 1045 bigger airports (260 large + 731 medium + 54 seaplane_base); `/api/airports` and the circle layer pick it up with no code changes
- `e2e/map.spec.ts`: marker-click test repointed from EKRN Bornholm Airport (a small_airport, now removed) to ESSV Visby Airport on Gotland — its nearest remaining airport is Stockholm Skavsta across the Baltic Sea (~59 px away at the initial zoom), so its marker cannot be overlapped by a neighbouring circle
- Verified: unit suite 12/12; e2e suite 3/3 green against the rebuilt compose instance (`/api/airports` serves 1045 features)
## v0.2.6 — 2026-09-16 22:41
- `Dockerfile`: install stages switched from npm to bun (`oven/bun:1.4.2-alpine`, `bun install --frozen-lockfile`); `package-lock.json` replaced by `bun.lock`; no BuildKit `type=cache` mounts — they don't persist across builds on this daemon, and bun install is fast enough (~3 s cold) that cross-build caching isn't worth it
- `Dockerfile`: new `prod-deps` stage (`bun install --production`) so the runtime image carries prod-only `node_modules` — devDeps (typescript, vitest, @types) are no longer shipped; build and runtime stages otherwise unchanged (still node:22-alpine, full `.next`, no standalone output)
- `.dockerignore`: excludes docs/deployment files (`*.md`, `Dockerfile*`, `docker-compose*`) from the build context so changelog-only commits don't invalidate the COPY layer and re-run `next build`
- Verified: full cold build 77 s → 58 s; e2e suite 3/3 green against the rebuilt compose instance
## v0.2.5 — 2026-09-16 21:22
- `AGENTS.md`: new "E2E tests (mandatory)" section codifies the e2e workflow — the compose instance behind Traefik (`https://flight.dev.kkhost.pl`) is always the app under test, never a local `next dev` on :3000; deploy current code first with `docker compose up -d --build`; browsers run through the shared remote Playwright server at `192.168.1.4:3000`, and plain `npx playwright test` handles deploy + readiness + remote connect
- `playwright.config.ts`: dropped the `E2E_LOCAL=1` local-dev path accordingly — no more local :3000 dev instance for e2e; the default (and only) mode is browsers on the shared remote Playwright server against the prod build behind Traefik, with `webServer` running `docker compose up -d --build`; `E2E_REMOTE=1` and explicit `PW_TEST_CONNECT_WS_ENDPOINT`/`E2E_BASE_URL` overrides are unchanged
## v0.2.4 — 2026-09-16 21:06
- `playwright.config.ts`: default e2e mode now targets the prod build behind Traefik (`https://flight.dev.kkhost.pl`) — `baseURL` defaults to that URL and `webServer` runs `docker compose up -d --build` (300 s timeout) instead of `npm run dev`, because `next dev` does not render for non-loopback clients: Turbopack's HMR WebSocket fails from the remote browsers, so the page loads blank
- `E2E_LOCAL=1` keeps the local iteration path (browsers on this box, `webServer` auto-starts `npm run dev`, baseURL `localhost:3000`) as a fallback when the proxy or compose stack is down; `E2E_REMOTE=1` and explicit `PW_TEST_CONNECT_WS_ENDPOINT`/`E2E_BASE_URL` overrides are unchanged
## v0.2.3 — 2026-09-16 20:30
- `playwright.config.ts`: e2e now launches browsers on the shared remote Playwright server (Docker, `ws://192.168.1.4:3000/`) by default — when no env vars are set it exports `PW_TEST_CONNECT_WS_ENDPOINT` pointing at that box; in this mode `baseURL` defaults to this box's LAN IP (`http://192.168.1.26:3000`) because pages load from the remote browser's perspective, while the local `webServer` still auto-starts `npm run dev`
- `E2E_LOCAL=1` restores fully-local runs (browsers on this box, `localhost:3000` baseURL) as a fallback when the remote server is down; `E2E_REMOTE=1` keeps its previous behavior for runners on other machines — skips `webServer` entirely and pairs with `E2E_BASE_URL`; an explicit `PW_TEST_CONNECT_WS_ENDPOINT` always wins over the default
## v0.2.2 — 2026-09-16 20:05
- `playwright.config.ts`: e2e suite can now run on a different machine than the app — with `E2E_REMOTE=1` the local `webServer` block is skipped entirely and `baseURL` defaults to `http://flight.dev.kkhost.pl`; `E2E_BASE_URL` overrides `baseURL` in both modes; with no env vars set behavior is unchanged (local `webServer` auto-starts `npm run dev`)
## v0.2.1 — 2026-09-16 19:49
- Clamp map zoom to the useful gameplay range: `minZoom: 3` / `maxZoom: 14` on the MapLibre options in `components/Map.tsx` (exported as `MIN_ZOOM`/`MAX_ZOOM`) — wheel travel from the Europe overview is now ~10 levels instead of ~16, and street-level zoom past the z12 pitch transition no longer exists
- e2e: new test that wheels well past both limits via `page.mouse.wheel` (basemap tile fetches blocked for speed/determinism; clamping is pure camera behavior) and asserts via `expect.poll` on the map instance that `getZoom()` settles at 14 / 3, i.e. clamped within [3, 14]
## v0.2.0 — 2026-09-16 18:32
- Docker deployment: multi-stage `Dockerfile` (node:22-alpine; deps → build → runtime) running `next start` on port 3000; no `output: 'standalone'` — the runtime stage keeps full `node_modules` because `/api/mlworker/[file]` reads maplibre-gl dist files from disk at runtime (and `/api/airports` reads `data/*.csv`)
- `.dockerignore`: node_modules, .next, data/*.db, test-results, *.tsbuildinfo, plus .git/.env/.codenomad and the e2e harness (`@playwright/test` is a host-global install, not a project dependency — Next's build-time type check would fail on it inside the image)
- `docker-compose.yml`: single service `flight`, no published ports, joins external network `dev_net` with Traefik labels routing `Host(flight.dev.kkhost.pl)` → port 3000 (plain HTTP entrypoint `web`; TLS terminated upstream)
- Verified: container builds and runs; router registered in dev_traefik rawdata; `curl -H "Host: flight.dev.kkhost.pl" http://localhost/` → 200, `/api/airports` → 200 GeoJSON (7445 features), mlworker .mjs files served from node_modules inside the container; unit suite 12/12 and e2e suite 2/2 still green
## v0.1.0 — 2026-09-16 18:24
- Scaffolded Next.js (App Router) + TypeScript project; dev server on port 3000 (`npm run dev`)
- Full-viewport MapLibre GL map with OpenFreeMap "liberty" vector basemap (attribution included via style)
- Zoom-driven camera controller: top-down at zoom ≤ 6, smoothstep tilt to 45° pitch by zoom ≥ 12; pitch is a pure function of zoom so it never fights pan/zoom gestures
- Lite 3D terrain from AWS terrarium raster-DEM tiles (`map.setTerrain`); toggleable off via `?terrain=0` or `NEXT_PUBLIC_TERRAIN=0` (off in e2e for deterministic headless rendering)
- EU airport seed data: OurAirports CSV (official mirror at davidmegginson.github.io/ourairports-data — the legacy ourairports.com/data URL now 404s) filtered to continent=EU with types small/medium/large_airport + seaplane_base → committed as `data/airports_eu.csv` (7445 airports; heliports, closed and balloon sites excluded — rationale documented in `lib/airports.ts`)
- `GET /api/airports`: server-side CSV parse with in-memory cache, returns GeoJSON FeatureCollection (properties: name, icao, lat, lon)
- Client renders airports as a GeoJSON circle layer (no DOM markers); click → popup with name + ICAO; header shows loaded airport count (`data-testid="airport-count"`)
- MapLibre worker served from node_modules via `/api/mlworker/*` route — bundlers break the URL MapLibre derives for its worker, which silently hangs GeoJSON source updates
- Tests: vitest unit suite (CSV parse/filter, GeoJSON build, pitch curve, API handler) + self-contained Playwright e2e suite (`webServer` auto-starts `npm run dev`; page load + airport count + marker click popup)