From dae354410f58f31fb0ff042ecf829a4a3246e026 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Tue, 22 Sep 2026 15:39:04 +0000 Subject: [PATCH] Add favicon: white airplane on pastel blue as app/icon.svg (ISS-122) --- CHANGELOG.md | 4 ++++ app/icon.svg | 4 ++++ e2e/panes.spec.ts | 12 ++++++++++++ package.json | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 app/icon.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b97f03..b669695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.55.0 — 2026-09-22 15:38 +- **Favicon (ISS-122).** The app shipped with no favicon at all (`app/layout.tsx` set only title/description; no `app/icon.svg`, no `public/favicon.ico`). New hand-written single-file `app/icon.svg` — a bold white top-down airplane silhouette (fuselage + swept wings + tail, one straight-line path) on a pastel blue (#93c5fd) rounded square, 64×64 viewBox. Next App Router's file convention auto-serves it at `/icon.svg` and injects `` into the head; no layout/metadata changes needed. +- **Tests.** E2E +1 in `e2e/panes.spec.ts`: page load asserts exactly one `head link[rel~="icon"]` pointing at `/icon.svg`, and a direct `GET /icon.svg` returns 200 with content-type `image/svg+xml`. No unit test — static asset. + ## v0.54.0 — 2026-09-22 15:20 - **Long priority jobs are now always finishable (ISS-119).** `generatePackage` in `lib/game/seed.ts` drew a flat 6–72 h expiry window (priority ×0.5 → 3–36 h) independent of leg distance, so some jobs' deadlines were shorter than the flight itself and could never be completed (legs go up to MAX_JOB_LEG_KM = 9000 km; the fastest catalog plane is ~920 km/h ≈ 10 h for a max leg). The drawn window is now clamped UP after special scaling: deadline ≥ now + `travelDurationMs(legKm, FASTEST_CATALOG_SPEED_KMH)` + `EXPIRY_BUFFER_MS` (flat 1 h), reusing the existing `travelDurationMs` from `lib/sim/movement.ts`. New constant `FASTEST_CATALOG_SPEED_KMH` in `lib/game/catalog.ts` — data-driven max of `PLANE_TYPES` speeds, so a job must be finishable with the best available aircraft (NOT the starter's 510 km/h, which would wreck long-job economics); adding a faster plane tightens the floor automatically. `EXPIRY_BUFFER_MS` lives next to the deadline tunables in seed.ts. The window is a single uniform draw, so clamping up covers both requirements: short legs (drawn × scale already above the floor) are byte-identical to before, and when the raw draw falls below the floor the deadline lands exactly on it — which stays ≤ the scaled ceiling for any pair of airports on Earth (max leg ~20000 km → floor ≈ 22.8 h < the 36 h priority ceiling), so no separate ceiling raise is needed. Forced `deadlineOffsetMs` skips the floor, same as it skips special scaling (the e2e expiry hook keeps working). - **Tests.** Unit +5 in `lib/game/seed.test.ts` (new "expiry floor" block): pins the constants (fastest = max of catalog speeds and > starter speed; buffer exactly 1 h); a seeded sweep over 200 unforced packages on the real dataset asserting deadline ≥ now + travel time at max speed + buffer for every one (specials roll naturally, ~15% priority); the farthest within-cap pair in the real dataset (~9000 km) forced with both `priority` and null across 50 seeds each — invariant holds everywhere AND at least one package per special lands exactly on the floor (deterministic for fixed seeds, proving the clamp engages rather than holding vacuously); short legs (EDDF→EHAM ≈ 370 km → floor ≈ 1.4 h < the priority minimum window) pinned byte-identical to a replay of the pre-v0.54.0 draw order (no economy regression); forced deadline offsets still skip the floor. E2E +1 in `e2e/expiry.spec.ts`: the EXISTING `POST /api/test/create-package` hook forces EDDB→KSMF (~9000 km, at MAX_JOB_LEG_KM) with `special: 'priority'`; 10 packages are created and every one asserted to expire ≥ travel time at max speed + buffer after creation (and ≤ the 36 h priority ceiling), then all ignored for cleanup — no new server hooks. diff --git a/app/icon.svg b/app/icon.svg new file mode 100644 index 0000000..76e6ffe --- /dev/null +++ b/app/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/e2e/panes.spec.ts b/e2e/panes.spec.ts index 29680ec..6e82f92 100644 --- a/e2e/panes.spec.ts +++ b/e2e/panes.spec.ts @@ -192,3 +192,15 @@ test('right pane placeholder exists with the Jobs & Market heading', async ({ pa await expect(page.getByTestId('tab-planes-market')).toBeVisible(); await expect(page.getByTestId('tab-jobs')).toHaveAttribute('aria-selected', 'true'); }); + +test('favicon ships as /icon.svg (ISS-122)', async ({ page }) => { + await page.goto('/'); + // App Router file convention: app/icon.svg is auto-served and injected into the head. + const link = page.locator('head link[rel~="icon"]'); + await expect(link).toHaveCount(1); + expect(await link.getAttribute('href')).toBe('/icon.svg'); + + const res = await page.request.get('/icon.svg'); + expect(res.status()).toBe(200); + expect(res.headers()['content-type']).toContain('image/svg+xml'); +}); diff --git a/package.json b/package.json index 11fd2f1..40299ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flight-ops", - "version": "0.54.0", + "version": "0.55.0", "private": true, "scripts": { "dev": "next dev -p 3000",