diff --git a/CHANGELOG.md b/CHANGELOG.md index 445f94f..651253c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.33.0 — 2026-09-21 00:45 +- **Merged On board into the Fleet list (ISS-054).** The left pane's separate "On board" section is deleted; each Fleet row (`components/LeftPane.tsx`) is now the single per-plane block — status/fuel/loadout plus, when present, that plane's loaded/expired cargo rows (fly-to / refuel-for-flight / abandon + penalty preview) and its queue legs. The header shows "in flight" + the current leg for flying planes and the location ICAO when grounded; the empty "Queue" heading is no longer rendered (the sub-section renders only with ≥1 queued leg). All per-item testids unchanged (`onboard-package-*`, `fly-to-*`, `refuel-for-*`, `abandon-*`, `abandon-penalty-*`, `plane-leg-*`, `queue-leg-*`, `queue-cancel-*`); the removed one is `onboard-{plane.id}` (use `plane-{id}`). CSS: `.onboard-block .contract-row*` re-targeted to `.plane-row .contract-row*`; no server or OpenAPI changes +- **E2E updates.** The four `onboard-{plane.id}` references now target the fleet rows (`e2e/hover.spec.ts`, `e2e/right-pane.spec.ts`, `e2e/two-planes.spec.ts`); `e2e/panes.spec.ts` no longer asserts an "On board" heading — it asserts the heading is absent from both panes + ## 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 diff --git a/app/globals.css b/app/globals.css index aa8844a..55636d5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -376,17 +376,10 @@ body { font-variant-numeric: tabular-nums; } -/* v0.12.0 — on-board blocks (per plane); moved to the left pane in ISS-021 (v0.20.0) */ -.onboard-block { - background: rgba(30, 41, 59, 0.6); - border-radius: 6px; - padding: 8px; - margin-bottom: 8px; -} - -/* ISS-028 (v0.20.0 part 2) — on-board cargo rows are two lines: a full-width fly-to button, - then refuel + abandon side by side below it; long destination names truncate with an ellipsis. */ -.onboard-block .contract-row { +/* ISS-054 (v0.33.0) — per-plane cargo rows nested inside the Fleet list's plane cards: two lines, + a full-width fly-to button then refuel + abandon side by side below it; long destination names + truncate with an ellipsis. Transparent sub-rows with a top border under the card header. */ +.plane-row .contract-row { background: transparent; padding: 6px 0 0; border-top: 1px solid rgba(148, 163, 184, 0.2); @@ -395,7 +388,7 @@ body { gap: 5px; } -.onboard-block .contract-row .contract-sub { +.plane-row .contract-row .contract-sub { margin-top: 0; /* row spacing comes from the container's gap */ } diff --git a/components/LeftPane.tsx b/components/LeftPane.tsx index 5a95fe0..89b4bf1 100644 --- a/components/LeftPane.tsx +++ b/components/LeftPane.tsx @@ -19,7 +19,7 @@ interface LeftPaneProps { // The shell's single-poll refresh — called after a sim-control POST so the pane updates immediately. onRefresh: () => void; // ISS-021 (v0.20.0): push the full snapshot returned by an action endpoint into the shared shell - // state — needed now that the On board section's refuel/fly/abandon actions live in this pane. + // state — needed now that the per-plane cargo rows' refuel/fly/abandon actions live in this pane. applySnapshot: (snapshot: StateSnapshot) => void; // ISS-002 (v0.15.0): report fleet-row hovers to the shell so the map can highlight the plane. onHover: (target: HoverTarget | null) => void; @@ -92,7 +92,7 @@ export async function retrySimPost( export default function LeftPane({ state, loading, error, onRefresh, applySnapshot, onHover, onError }: LeftPaneProps) { const [busy, setBusy] = useState(false); - // ISS-021 (v0.20.0): shared action runner for the On board section (moved here from RightPane). + // ISS-021 (v0.20.0): shared action runner for the per-plane cargo/queue actions (moved here from RightPane). const { runAction, busy: actionsBusy, actionError } = useGameAction(state, applySnapshot, onError); // ISS-023 (v0.21.0): Relocate selections — moved here from the right-pane market row; same @@ -195,15 +195,8 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh const paused = state?.simState.paused ?? false; const timeScale = state?.simState.timeScale ?? 1; - // On board (v0.12.0, moved from the right pane in ISS-021 v0.20.0): every plane that carries - // cargo or is mid-flight gets a block — its loaded/expired packages with fly-to-destination + - // abandon controls, and flying planes show their current leg. + // Snapshot clock — feeds the deadline/penalty previews in the per-plane cargo rows. const clockMs = state?.simState.clockMs ?? 0; - // ISS-043 (v0.26.0): a plane with queued legs always shows its block too — the queue is the - // reason it's here even when grounded with nothing on board. - const onboard = state - ? state.planes.filter((p) => p.status === 'flying' || cargoWeightFor(state, p.id) > 0 || (p.queue?.length ?? 0) > 0) - : []; // 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 @@ -340,161 +333,143 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh

No planes.

) : ( )} - {/* ISS-021 (v0.20.0): moved here from the right pane — between Fleet and Time. */} -
-

On board

- {onboard.length === 0 ? ( -

Nothing on board.

- ) : ( - onboard.map((plane) => { - const cargo = state.packages.filter( - (pkg) => pkg.planeId === plane.id && (pkg.status === 'loaded' || pkg.status === 'expired'), - ); - return ( - // ISS-002: hovering the on-board block (including its cargo rows) highlights the plane. - // Handlers live only on the outer div — React's enter/leave don't fire when moving to a - // child, so this covers the whole block without flicker between sub-elements. -
onHover({ kind: 'plane', id: plane.id })} - onMouseLeave={() => onHover(null)} - > -
- {PLANE_TYPES[plane.model]?.name ?? plane.model} - {plane.status === 'flying' ? ( - <> - in flight - {plane.destName && ( - - {plane.locationName} → {plane.destName} - - )} - - ) : ( - {plane.locationIcao} - )} -
- {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 })} - > - -
- - -
-
- {pkg.originName} → {pkg.destName} - {formatCash(pkg.reward)} - {pkg.status === 'expired' ? ( - expired - ) : ( - {formatDeadlineRelative(pkg.deadlineSimTime, clockMs)} - )} -
-
- ); - })} - {/* 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 - unscoped ids duplicated in the DOM and broke Playwright strict mode. */} - {plane.queue.map((icao, i) => ( -
- - {airportOptions?.find((a) => a.icao === icao)?.name ?? icao} ({icao}) - - -
- ))} -
-
- ); - }) - )} -
- {/* ISS-023 (v0.21.0): moved here from the right-pane market row — it belongs with - everything else about our planes, between On board and Time. */} + everything else about our planes, between Fleet 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 planes or no loaded airport options there is nothing to preview → null. */} @@ -562,7 +537,7 @@ export default function LeftPane({ state, loading, error, onRefresh, applySnapsh

Refuel

{/* Moved verbatim from the right-pane Market section — same testids/labels; the per-package - "Refuel" button in On board (mode 'forFlight') is a different feature and stays there. */} + "Refuel" button in the fleet rows (mode 'forFlight') is a different feature and stays there. */}
{refuelable.length === 0 ? ( diff --git a/e2e/hover.spec.ts b/e2e/hover.spec.ts index ccd9a3c..f8a1bf9 100644 --- a/e2e/hover.spec.ts +++ b/e2e/hover.spec.ts @@ -439,7 +439,7 @@ test('hovering the Relocate section previews the planned route and target airpor }).toBe(0); }); -test('hovering an on-board block of a flying plane highlights its route', async ({ page }) => { +test('hovering the fleet row of a flying plane highlights its route', async ({ page }) => { test.setTimeout(600_000); // self-heal + one flight at scale 1000 + fly-home cleanup await flyHomeViaApi(page); @@ -458,8 +458,8 @@ test('hovering an on-board block of a flying plane highlights its route', async expect(fly.status()).toBe(200); await setScale(page, SCALE); - // The on-board block shows the plane in flight once a snapshot with status 'flying' lands. - const block = page.getByTestId('onboard-plane-starter'); + // The fleet row shows the plane in flight once a snapshot with status 'flying' lands (ISS-054). + const block = page.getByTestId('plane-plane-starter'); await expect(block).toContainText(/in flight/i, { timeout: 15_000 }); // Freeze mid-flight so the ~10 s leg can't land under us while we assert (same pattern as diff --git a/e2e/panes.spec.ts b/e2e/panes.spec.ts index e765af1..2665e29 100644 --- a/e2e/panes.spec.ts +++ b/e2e/panes.spec.ts @@ -70,10 +70,9 @@ test('left pane renders wallet, reputation and the starter fleet at Frankfurt', // Sim clock renders (UTC wall-clock of the game time). await expect(page.getByTestId('sim-clock')).toContainText(/UTC/); - // ISS-021 (v0.20.0): the On board section moved to the left pane (between Fleet and Time) — - // its heading renders here… - await expect(page.getByTestId('left-pane').getByRole('heading', { name: 'On board', exact: true })).toBeVisible(); - // …and no longer in the right pane. + // ISS-054 (v0.33.0): On board merged into the Fleet list — its heading is gone from both panes, + // per-plane cargo rows now render inside the fleet rows. + await expect(page.getByTestId('left-pane').getByRole('heading', { name: 'On board', exact: true })).toHaveCount(0); await expect(page.getByTestId('right-pane').getByRole('heading', { name: 'On board', exact: true })).toHaveCount(0); // ISS-035 (v0.23.0): the Refuel-to-full row moved to the left pane next to Relocate — its section diff --git a/e2e/right-pane.spec.ts b/e2e/right-pane.spec.ts index dc1463f..edd03fb 100644 --- a/e2e/right-pane.spec.ts +++ b/e2e/right-pane.spec.ts @@ -268,8 +268,8 @@ test('on-board list: fly-to-destination + abandon with penalty preview; flying p await expect(flyBtn).toBeEnabled(); await flyBtn.click(); - // The on-board block now shows the plane in flight with its current leg… - await expect(page.getByTestId('onboard-plane-starter')).toContainText(/in flight/i, { timeout: 10_000 }); + // The fleet row now shows the plane in flight with its current leg (ISS-054)… + await expect(page.getByTestId('plane-plane-starter')).toContainText(/in flight/i, { timeout: 10_000 }); const leg = page.getByTestId('plane-leg-plane-starter'); await expect(leg).toBeVisible(); await expect(leg).toContainText('→'); diff --git a/e2e/two-planes.spec.ts b/e2e/two-planes.spec.ts index e949cd8..1a75582 100644 --- a/e2e/two-planes.spec.ts +++ b/e2e/two-planes.spec.ts @@ -323,9 +323,9 @@ test('two grounded planes: Load is one-click with a preselected plane; the picke }, { timeout: 15_000, message: 'waiting for the second package to load onto a plane' }) .toBe(secondId); - // Both planes carry cargo simultaneously — both on-board blocks render. - await expect(page.getByTestId(`onboard-${STARTER}`), 'the starter block renders').toBeVisible({ timeout: 15_000 }); - await expect(page.getByTestId(`onboard-${secondId}`), 'the second plane block renders').toBeVisible({ timeout: 15_000 }); + // Both planes carry cargo simultaneously — both fleet rows render their cargo (ISS-054). + await expect(page.getByTestId(`plane-${STARTER}`), 'the starter row renders').toBeVisible({ timeout: 15_000 }); + await expect(page.getByTestId(`plane-${secondId}`), 'the second plane row renders').toBeVisible({ timeout: 15_000 }); }); test('two planes fly in parallel; the last landing slows to 1× and the next dispatch resumes', async ({ page }) => { diff --git a/package.json b/package.json index e0b152d..d92d78e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flight-ops", - "version": "0.32.0", + "version": "0.33.0", "private": true, "scripts": { "dev": "next dev -p 3000",