14 KiB
14 KiB
Changelog
v0.4.0 — 2026-09-17 08:22
- Self-hosted all OpenFreeMap map-styling assets (tiles stay remote): new
scripts/download-map-assets.mjsfetches the 5 OFM styles with a browser UA, rewrites each viarewriteStyle, and extracts the font/sprite tarballs intopublic/— 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:STYLESregistry (5 ids + labels),DEFAULT_STYLE_ID = 'fiord', and purerewriteStyle()— glyphshttps://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf→/fonts/{fontstack}/{range}.pbf, spritehttps://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.jsonby default; new top-bar style switcher (<select data-testid="style-switch">, subtle styling inapp/globals.css) listing all 5 styles, persisted to localStorage keyflight-ops-style; SSR-safe — state initializes toDEFAULT_STYLE_IDand 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/airportsis fetched at most once per page life (promise cached in a ref; count set exactly once), and an idempotentensureAirportsLayer()re-adds the source+layer if missing on EVERYstyledataevent (initial load AND after eachsetStyle, 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 duplicateDockerfile: runtime stage now copiespublic/from the build stage (COPY --from=build /app/public ./public) sonext startserves the self-hosted assets;.dockerignorealready 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 --noEmitclean; 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>incomponents/Map.tsx), sourced dynamically frompackage.jsonvia a JSON import — future version bumps show up automatically with no code change; subtle 11px muted style added toapp/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 showsv{version}with the expected value read frompackage.jsonat test time, so it stays in sync automatically - Verified: unit suite 10/10;
tsc --noEmitclean; 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.tspitch curve + the mapmovehandler): the map is now always flat/top-down with standard pan/zoom only; deletedlib/camera.tsand its unit tests - Removed terrain: no more DEM raster source /
map.setTerrain, and the?terrain=0/NEXT_PUBLIC_TERRAINtoggles 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 --noEmitclean; 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/airportsand the circle layer pick it up with no code changes- Verified: unit suite 8/8;
tsc --noEmitclean; e2e suite 3/3 green against the rebuilt compose instance (/api/airportsserves 242 features)
v0.2.8 — 2026-09-16 23:53
data/airports_eu.csv: reduced the dataset to onlylarge_airportrows (dropped all 731 medium_airport + 54 seaplane_base) — the map now shows just the 260 biggest EU airports;/api/airportsand the circle layer pick it up with no code changese2e/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 --noEmitclean; e2e suite 3/3 green against the rebuilt compose instance (/api/airportsserves 260 features)
v0.2.7 — 2026-09-16 23:39
data/airports_eu.csv: removed all 6400small_airportrows from the OurAirports EU export — the map now shows only the 1045 bigger airports (260 large + 731 medium + 54 seaplane_base);/api/airportsand the circle layer pick it up with no code changese2e/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/airportsserves 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.jsonreplaced bybun.lock; no BuildKittype=cachemounts — 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 itDockerfile: newprod-depsstage (bun install --production) so the runtime image carries prod-onlynode_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-runnext 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 localnext devon :3000; deploy current code first withdocker compose up -d --build; browsers run through the shared remote Playwright server at192.168.1.4:3000, and plainnpx playwright testhandles deploy + readiness + remote connectplaywright.config.ts: dropped theE2E_LOCAL=1local-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, withwebServerrunningdocker compose up -d --build;E2E_REMOTE=1and explicitPW_TEST_CONNECT_WS_ENDPOINT/E2E_BASE_URLoverrides 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) —baseURLdefaults to that URL andwebServerrunsdocker compose up -d --build(300 s timeout) instead ofnpm run dev, becausenext devdoes not render for non-loopback clients: Turbopack's HMR WebSocket fails from the remote browsers, so the page loads blankE2E_LOCAL=1keeps the local iteration path (browsers on this box,webServerauto-startsnpm run dev, baseURLlocalhost:3000) as a fallback when the proxy or compose stack is down;E2E_REMOTE=1and explicitPW_TEST_CONNECT_WS_ENDPOINT/E2E_BASE_URLoverrides 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 exportsPW_TEST_CONNECT_WS_ENDPOINTpointing at that box; in this modebaseURLdefaults to this box's LAN IP (http://192.168.1.26:3000) because pages load from the remote browser's perspective, while the localwebServerstill auto-startsnpm run devE2E_LOCAL=1restores fully-local runs (browsers on this box,localhost:3000baseURL) as a fallback when the remote server is down;E2E_REMOTE=1keeps its previous behavior for runners on other machines — skipswebServerentirely and pairs withE2E_BASE_URL; an explicitPW_TEST_CONNECT_WS_ENDPOINTalways 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 — withE2E_REMOTE=1the localwebServerblock is skipped entirely andbaseURLdefaults tohttp://flight.dev.kkhost.pl;E2E_BASE_URLoverridesbaseURLin both modes; with no env vars set behavior is unchanged (localwebServerauto-startsnpm run dev)
v0.2.1 — 2026-09-16 19:49
- Clamp map zoom to the useful gameplay range:
minZoom: 3/maxZoom: 14on the MapLibre options incomponents/Map.tsx(exported asMIN_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 viaexpect.pollon the map instance thatgetZoom()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) runningnext starton port 3000; nooutput: 'standalone'— the runtime stage keeps fullnode_modulesbecause/api/mlworker/[file]reads maplibre-gl dist files from disk at runtime (and/api/airportsreadsdata/*.csv) .dockerignore: node_modules, .next, data/*.db, test-results, *.tsbuildinfo, plus .git/.env/.codenomad and the e2e harness (@playwright/testis 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 serviceflight, no published ports, joins external networkdev_netwith Traefik labels routingHost(flight.dev.kkhost.pl)→ port 3000 (plain HTTP entrypointweb; 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=0orNEXT_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 inlib/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 (
webServerauto-startsnpm run dev; page load + airport count + marker click popup)