From b011953087eacad173970c4a84deb585fd79cced Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Thu, 17 Sep 2026 06:37:28 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20v0.3.1=20=E2=80=94=20show=20app=20versi?= =?UTF-8?q?on=20in=20header=20(sourced=20from=20package.json)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ app/globals.css | 5 +++++ components/Map.tsx | 2 ++ components/version.test.ts | 16 ++++++++++++++++ e2e/map.spec.ts | 11 +++++++++++ package.json | 2 +- 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 components/version.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a0a51..95b03ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.3.1 — 2026-09-17 06:35 +- Header now shows the app version between the "Flight Ops" title and the airport count (`v{version}` 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 diff --git a/app/globals.css b/app/globals.css index d99b938..e796eb4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -43,6 +43,11 @@ body { font-weight: 600; } +.app-header small { + font-size: 11px; + color: #94a3b8; +} + [data-testid='airport-count'] { font-size: 12px; color: #cbd5e1; diff --git a/components/Map.tsx b/components/Map.tsx index 5f403e6..4a45c0c 100644 --- a/components/Map.tsx +++ b/components/Map.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import * as maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; +import pkg from '../package.json'; const EUROPE_CENTER: [number, number] = [10, 52]; const INITIAL_ZOOM = 4; @@ -93,6 +94,7 @@ export default function Map() {

Flight Ops

+ v{pkg.version} {airportCount === null ? 'loading airports…' : `${airportCount} EU airports`} diff --git a/components/version.test.ts b/components/version.test.ts new file mode 100644 index 0000000..f581c35 --- /dev/null +++ b/components/version.test.ts @@ -0,0 +1,16 @@ +import { readFileSync } from 'node:fs'; +import pkg from '../package.json'; +import { describe, expect, it } from 'vitest'; + +const mapSource = readFileSync(new URL('./Map.tsx', import.meta.url), 'utf8'); + +describe('app version display', () => { + it('package.json carries a valid semver version (single source of truth)', () => { + expect(pkg.version).toMatch(/^\d+\.\d+\.\d+$/); + }); + + it('the header sources the version from package.json instead of hardcoding it', () => { + expect(mapSource).toContain("from '../package.json'"); + expect(mapSource).toContain('v{pkg.version}'); + }); +}); diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts index c635c11..b61e335 100644 --- a/e2e/map.spec.ts +++ b/e2e/map.spec.ts @@ -1,7 +1,12 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { test, expect, type Page } from '@playwright/test'; const APP_URL = '/'; +// Read at test time so the assertion tracks package.json automatically. +const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')) as { version: string }; + // Must match MIN_ZOOM / MAX_ZOOM exported from components/Map.tsx. const MIN_ZOOM = 3; const MAX_ZOOM = 11; @@ -24,6 +29,12 @@ test('page loads with the map and EU airport count', async ({ page }) => { expect(count).toBeGreaterThan(0); }); +test('header shows the app version sourced from package.json', async ({ page }) => { + await page.goto(APP_URL); + const header = page.getByTestId('app-header'); + await expect(header).toContainText(`v${pkg.version}`); +}); + test('clicking an airport marker opens a popup with name and ICAO', async ({ page }) => { await page.goto(APP_URL); await waitForAirportCount(page); diff --git a/package.json b/package.json index 6d92182..bd9650d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flight-ops", - "version": "0.3.0", + "version": "0.3.1", "private": true, "scripts": { "dev": "next dev -p 3000",