From f2dbad5e038e2d0a1e1f4ed647a79eeccd437e06 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Sat, 19 Sep 2026 21:47:00 +0000 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20v0.22.0=20=E2=80=94=20ISS-022/025/0?= =?UTF-8?q?27=20hover=20route=20trace=20on=20job=20list,=20on-board=20rows?= =?UTF-8?q?=20and=20relocation=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++ components/LeftPane.tsx | 28 ++++++++- components/Map.tsx | 59 ++++++++++++++++-- components/hoverTypes.ts | 7 ++- e2e/hover.spec.ts | 128 +++++++++++++++++++++++++++++++++++++++ lib/mapData.test.ts | 38 +++++++++++- lib/mapData.ts | 27 +++++++++ package.json | 2 +- 8 files changed, 285 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daee047..9135cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v0.22.0 — 2026-09-19 21:42 +- **Hover route trace on job-list rows (ISS-022).** Hovering an open package in the right pane now draws the ENTIRE great-circle trace between origin and destination, not just the two endpoint rings. Zero pane edits — the rows already sent `{kind:'package', originIcao, destIcao}`; `components/Map.tsx` now derives from/to for any hover target carrying two ICAOs and pushes a new pure builder's output (`hoverRouteGeoJson(fromIcao, toIcao, airportsByIcao)` in `lib/mapData.ts`: one LineString via the existing `greatCircleArc` slerp, empty FeatureCollection when either endpoint is missing from the airport index or the `''` no-hover sentinel) into a NEW dedicated `hover-routes` GeoJSON source + top-most dashed `hover-route` LineLayer (paint: `#facc15` matching the airports-highlight ring, width 4, `line-dasharray [2,2]`). The shared `routes` source can't carry this — it only holds FLYING legs, so an open package's or a grounded relocation's planned route was never in it. The layer is added in `ensureLayers` after the highlight layers so style switches keep it; `applyHoverRef` re-pushes data on every hover +- **Same trace on On-board cargo rows (ISS-025).** Each `onboard-package-{id}` row in the left pane's On board section now reports `{kind:'package', originIcao, destIcao, planeId: plane.id}` on mouseenter — same full trace as job-list rows PLUS the carrying plane's halo stays lit via the new optional `planeId` field (Map uses `h.planeId ?? ''` for the planes/routes-highlight filters). Mouseleave restores the block's plain `{kind:'plane', id}` highlight; leaving the whole block still ends at null because React fires the child's mouseleave before the parent's +- **Relocate panel hover previews the planned flight (ISS-027).** The left pane's Relocate section gains `data-testid="relocate-section"` + enter/leave handlers: with a grounded plane AND destination selected it reports `{kind:'route', fromIcao: relocatePlane.locationIcao, toIcao: relocateDest.icao}` (else null), and Map draws the planned trace AND rings ONLY the target airport in airports-highlight (origin stays unmarked — the destination is what the issue asks to see). `HoverTarget` (`components/hoverTypes.ts`) extends to `{kind:'plane';id} | {kind:'package';originIcao;destIcao;planeId?} | {kind:'route';fromIcao;toIcao}`. Existing semantics unchanged: plane hovers filter the three highlight layers exactly as before, package hovers keep BOTH endpoint rings (now plus the trace) +- Tests: unit — new `describe('hoverRouteGeoJson …')` in `lib/mapData.test.ts`: no-hover sentinel → empty; two known airports → one arc with exact dataset endpoints; reversed-input symmetry; missing from/to ICAO → empty (baseline 283 → 288). e2e (`e2e/hover.spec.ts`) — open-package test extended with hover-route endpoint assertions (line geometry first/last coords compared against `/api/airports` lookups) + trace-clear check; new `hovering an on-board cargo row draws its trace and keeps the plane halo` (load via API, hover the row, assert trace endpoints + planes-highlight for that plane, abandon the package afterwards to leave a clean instance); new `hovering the Relocate section previews the planned route and target airport` (selects starter + LEMD via the existing testids, hovers `[data-testid="relocate-section"]`, asserts trace endpoints + target-only ring). New helpers: `airportLonLat(page, icao)` and `expectHoverRoute(page, fromIcao, toIcao)`; all new/extended assertions call `fitToAirports` first since `queryRenderedFeatures` is viewport-scoped +- Verified: `tsc --noEmit` clean; unit suite green locally (288); `npx playwright test --list` = 41 tests (was 39, +2 new in hover.spec.ts); e2e not run locally by design — CI verifies against an isolated stack + ## v0.21.0 — 2026-09-19 21:01 - **Relocate panel moves to the left pane (ISS-023).** Flying an empty/grounded plane to another airport is "anything with our plane", so it belongs in the left pane, not as a market row on the right. The `.market-row.market-row-relocate` JSX (`relocate-plane` select of grounded planes labeled `{planeName} — {icao}`, `relocate-dest` name-sorted select, `relocate-go` button → `POST /api/planes/{id}/fly {destIcao}`) moved verbatim from `components/RightPane.tsx` into a new "Relocate" `
` in `components/LeftPane.tsx`, inserted between On board and Time. Its state (`relocatePlaneId` / `relocateDestIcao`) and the one-time `/api/airports` fetch (promise-ref pattern, GeoJSON → `{name, icao}[]` sorted by name) moved with it — the relocate-dest select was that fetch's only consumer in RightPane, so nothing else there changed. Busy gating rewired from RightPane's `busy` to LeftPane's own `useGameAction` instance (`actionsBusy`); a grounded-plane filter was added next to the existing `onboard` compute; plane labels reuse the expression already inlined in the Fleet rows. Testids kept byte-identical (`relocate-plane` / `relocate-dest` / `relocate-go`) and the row keeps its `.market-row market-row-relocate` classes (global CSS, works in the 360 px left pane); stale "280 px right pane" comment updated in `app/globals.css`. The map-popup relocation controls (`airport-popup-*`, Map.tsx) are a separate surface fed by the snapshot only — untouched - Tests: e2e — only `relocation.spec.ts` touches these testids and all locators are global, so no locator changes; the first test's title (which mentioned "right pane") and the file header comments now say left-pane Relocate section. No unit-test changes (baseline 283) diff --git a/components/LeftPane.tsx b/components/LeftPane.tsx index de4d211..c6b6538 100644 --- a/components/LeftPane.tsx +++ b/components/LeftPane.tsx @@ -385,7 +385,17 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh {cargo.map((pkg) => { const penalty = abandonPenalty(pkg, clockMs); return ( -
+ // ISS-025 (v0.22.0): hovering a loaded package draws its full origin→dest trace on the map — + // same as job-list rows — and keeps this plane's halo lit via planeId. Leaving the row restores + // the block's plain plane highlight; leaving the whole block still ends at null (the child's + // mouseleave fires before the parent's). +
onHover({ kind: 'package', originIcao: pkg.originIcao, destIcao: pkg.destIcao, planeId: plane.id })} + onMouseLeave={() => onHover({ kind: 'plane', id: plane.id })} + >