diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f82dee..445f94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.32.0 — 2026-09-20 23:47 +- **Queue via fly-to and Relocate; dedicated queue-add input removed (ISS-053).** The flight queue no longer has its own entry point in the On board block (`components/LeftPane.tsx`): the per-cargo "Fly to …" button is now enabled for ANY plane state, and posts to `/api/planes/{id}/queue` instead of `/fly` when the plane is `flying` (grounded → `/fly`, unchanged); Relocate's plane list widened from grounded-only to ALL planes (`relocatable = state.planes`; Refuel keeps its own `grounded` filter), and `relocate-go` posts to `/queue` when the SELECTED plane is flying, else `/fly`. The queue-add select + "Add to queue" button (and the shared `queueAddDestIcao` state) are deleted; leg rows + cancel buttons unchanged. No server or OpenAPI changes — both endpoints already existed +- **E2E updates.** `e2e/queue.spec.ts` test 1 now adds its queued leg via the Relocate section (selects plane-starter, which is flying at that point → `/queue`) instead of the removed queue-add control; `e2e/two-planes.spec.ts` "per-plane queues are independent" adds the starter's second leg via API POST `/api/planes/plane-starter/queue` (the starter is grounded there, so fly-to would start rather than queue). All `queue-leg-*` / `queue-cancel-*` assertions unchanged + ## v0.31.0 — 2026-09-20 21:23 - **Two-plane Load is one-click again (ISS-055).** The reported "can't load cargo with two planes at the same airport" was UX, not a server bug: `LoadControls` (`components/RightPane.tsx`) rendered a plane picker for ≥2 eligible planes whose Load button stayed DISABLED until a pick existed, while the single-plane path is one-click. The multi-plane path now PRESELECTS the first eligible plane (`effective = … ?? eligible[0] ?? null`; the select already bound to `effective?.id`), so Load is enabled and one-click whenever ≥1 plane qualifies — the dropdown still allows picking a different plane - **LeftPane queue testids scoped by plane id (ISS-055).** With two on-board blocks in the DOM the unscoped ids duplicated: `queue-leg-{i}` → `queue-leg-{plane.id}-{i}`, `queue-cancel-{i}` → `queue-cancel-{plane.id}-{i}`, plus the add control `queue-add-dest`/`queue-add` → `queue-add-dest-{plane.id}`/`queue-add-{plane.id}` (`components/LeftPane.tsx`). The shared pane-level destination pick is unchanged. `e2e/queue.spec.ts` locators updated to the scoped form (single-plane fleet, id `plane-starter`) diff --git a/components/LeftPane.tsx b/components/LeftPane.tsx index b67a431..5a95fe0 100644 --- a/components/LeftPane.tsx +++ b/components/LeftPane.tsx @@ -106,10 +106,6 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh // a sensible default (first grounded plane with fuel below max). const [refuelPlaneId, setRefuelPlaneId] = useState(null); - // ISS-043 (v0.26.0): flight-queue "add" destination — one shared pick across every on-board - // block's queue-add select (the e2e fleet is a single plane; per-block state would be dead weight). - const [queueAddDestIcao, setQueueAddDestIcao] = useState(null); - // ISS-010 (v0.18.0): one-time /api/airports fetch per page life — destination options for the // Relocate section, sorted by name so e2e selection is stable. Same promise-ref pattern as Map.tsx; // on failure airportOptions stays null and the Relocate button simply remains disabled. Moved here @@ -209,17 +205,20 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh ? state.planes.filter((p) => p.status === 'flying' || cargoWeightFor(state, p.id) > 0 || (p.queue?.length ?? 0) > 0) : []; - // ISS-023 (v0.21.0): Relocate — any GROUNDED plane can be repositioned to any airport via the - // existing fly endpoint (pure repositioning, no package needed since v0.12.0). Moved here from - // the right-pane market row; with state null both fall back to null and the section isn't rendered. - const grounded = state ? state.planes.filter((p) => p.status === 'available') : []; - const relocatePlane = grounded.find((p) => p.id === relocatePlaneId) ?? grounded[0] ?? null; + // ISS-023 (v0.21.0): Relocate — any plane can be repositioned to any airport via the existing fly + // endpoint (pure repositioning, no package needed since v0.12.0). Moved here from the right-pane + // market row; with state null both fall back to null and the section isn't rendered. + // ISS-053 (v0.32.0): widened from grounded-only to ALL planes — a flying plane's relocation is + // queued instead of started immediately (see relocate-go below). `grounded` stays for Refuel. + const relocatable = state ? state.planes : []; + const relocatePlane = relocatable.find((p) => p.id === relocatePlaneId) ?? relocatable[0] ?? null; const relocateDest = airportOptions === null ? null : airportOptions.find((a) => a.icao === relocateDestIcao) ?? airportOptions[0] ?? null; // ISS-035 (v0.23.0): Refuel-to-full — any GROUNDED plane with fuel below max can be topped up via the // existing refuel endpoint (no body → server defaults to mode 'full'). Moved here from the right-pane - // market row; reuses `grounded` above, and with state null falls back to null. + // market row; grounded-only, and with state null falls back to null. + const grounded = relocatable.filter((p) => p.status === 'available'); const refuelable = grounded.filter((p) => p.fuel < p.maxFuel); const refuelPlane = (state?.planes.find((p) => p.id === refuelPlaneId && p.status === 'available' && p.fuel < p.maxFuel)) ?? @@ -422,8 +421,10 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh type="button" data-testid={`fly-to-${pkg.id}`} className="pane-btn onboard-fly-btn" - disabled={actionsBusy || plane.status !== 'available'} - onClick={() => void runAction(`/api/planes/${plane.id}/fly`, { destIcao: pkg.destIcao })} + disabled={actionsBusy} + // ISS-053 (v0.32.0): a flying plane can't start another leg — the click queues it instead, + // so "Fly to …" works from any state and the queue needs no dedicated input. + onClick={() => void runAction(`/api/planes/${plane.id}/${plane.status === 'flying' ? 'queue' : 'fly'}`, { destIcao: pkg.destIcao })} > Fly to {pkg.destName} @@ -462,8 +463,9 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh ); })} - {/* ISS-043 (v0.26.0): flight queue — the not-yet-started legs with cancel, plus an - add control reusing this pane's already-fetched airport options (no second fetch). */} + {/* ISS-043 (v0.26.0): flight queue — the not-yet-started legs with cancel. + ISS-053 (v0.32.0): the dedicated add input is gone — fly-to and Relocate post to + /queue while a plane is flying, so no separate entry point is needed. */}
Queue {/* ISS-055 (v0.31.0): testids scoped by plane id — with two on-board blocks the @@ -484,32 +486,6 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh
))} -
- - -
); @@ -521,7 +497,7 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh everything else about our planes, between On board and Time. */} {/* ISS-027 (v0.22.0): hovering the section previews the planned relocation on the map — dashed trace from the selected plane's airport to the destination + a ring on the target. - With no grounded plane or no loaded airport options there is nothing to preview → null. */} + With no planes or no loaded airport options there is nothing to preview → null. */}
onHover(null)} >

Relocate

- {/* ISS-010 (v0.18.0): relocate a grounded plane — the fly endpoint already allows pure - repositioning, this row just exposes it for planes with nothing on board. */} + {/* ISS-010 (v0.18.0): relocate a plane — the fly endpoint already allows pure + repositioning, this row just exposes it for planes with nothing on board. + ISS-053 (v0.32.0): lists ALL planes; a flying one's relocation is queued below. */}