diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbb9e3..b29f0b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v0.35.0 — 2026-09-21 02:26 +- **Job list shows cargo tonnage instead of count (ISS-066).** The open-package rows in the right pane's Jobs tab (`components/RightPane.tsx`) now show cargo as type + tonnage ("Mail 10.0t") via a new `formatCargo` in `lib/format.ts` — `(kg / 1000).toFixed(1) + 't'`, identical to the loadout display — instead of "type × count"; unit tests added to `lib/format.test.ts`; e2e assertion added to `e2e/right-pane.spec.ts` (the hook-created package is forced to 10 t and its row must contain "10.0t"). No server or OpenAPI changes — `weightKg` was already in the snapshot + ## v0.34.0 — 2026-09-21 01:26 - **Duplicate queue legs rejected with 409 (ISS-065).** `queueLeg` (`lib/game/actions.ts`) now refuses to append a destination that is already in the plane's flight queue ("{icao} is already in the flight queue"), and — while the plane is flying — re-queuing the active leg's destination ("already flying to {icao}"). Previously the same route could be queued any number of times, producing pointless repeated/zero-length hops when landing auto-chains `queue[0]`. No UI or OpenAPI changes (the endpoint already documents error responses) diff --git a/components/RightPane.tsx b/components/RightPane.tsx index a2d0e88..f956815 100644 --- a/components/RightPane.tsx +++ b/components/RightPane.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import type { PackageState, PlaneState, StateSnapshot } from '@/lib/game/state'; import { PLANE_TYPES, SELL_FRACTION } from '@/lib/game/catalog'; import { maxRangeKm } from '@/lib/sim/movement'; -import { formatCash, formatDeadlineRelative } from '@/lib/format'; +import { formatCargo, formatCash, formatDeadlineRelative } from '@/lib/format'; import { useGameAction } from '@/lib/useGameAction'; import type { HoverTarget } from './hoverTypes'; @@ -225,7 +225,8 @@ export default function RightPane({ state, loading, applySnapshot, onHover, onEr
{formatCash(pkg.reward)} - {pkg.cargoType} × {pkg.cargoCount} + {/* ISS-066 (v0.35.0): cargo shows as type + tonnage ("Mail 10.0t"), not "type × count". */} + {formatCargo(pkg.cargoType, pkg.weightKg)} {formatDeadlineRelative(pkg.deadlineSimTime, clockMs)} diff --git a/e2e/right-pane.spec.ts b/e2e/right-pane.spec.ts index edd03fb..2301b96 100644 --- a/e2e/right-pane.spec.ts +++ b/e2e/right-pane.spec.ts @@ -158,9 +158,9 @@ function maxRangeKm(burnPerKm: number, emptyWeightKg: number, maxFuel: number): } // Test hook (FLIGHT_TEST_HOOKS=1): create an open package on a known route. -async function createPackage(page: Page, fromIcao: string, toIcao: string, deadlineOffsetMs?: number): Promise { +async function createPackage(page: Page, fromIcao: string, toIcao: string, deadlineOffsetMs?: number, weightKg?: number): Promise { const created = await page.request.post('/api/test/create-package', { - data: { originIcao: fromIcao, destIcao: toIcao, deadlineOffsetMs }, + data: { originIcao: fromIcao, destIcao: toIcao, deadlineOffsetMs, weightKg }, }); expect(created.status()).toBe(201); return (await created.json()) as PackageState; @@ -213,9 +213,12 @@ test('open packages render with names, reward and deadline; Load is enabled at a // Deterministic target: a hook-created EDDF package — Load is enabled (starter at origin, free slot). const nearest = await nearestToHub(page); - const pkg = await createPackage(page, HUB, nearest.icao); + const pkg = await createPackage(page, HUB, nearest.icao, undefined, 4_000); const row = page.locator(`li[data-testid="open-package-${pkg.id}"]`); await expect(row, 'the hook-created package appears in the list').toBeVisible({ timeout: 15_000 }); + // ISS-066 (v0.35.0): cargo shows as type + tonnage ("… 4.0t"), not "type × count". + // 4 t is within the starter ATR's 4.8 t maxPayloadKg, so Load stays enabled below. + await expect(page.getByTestId(`open-package-cargo-${pkg.id}`)).toContainText('4.0t'); await expect(page.getByTestId(`open-package-reward-${pkg.id}`)).toContainText(eur(pkg.reward)); const loadBtn = page.getByTestId(`load-${pkg.id}`); await expect(loadBtn).toBeEnabled(); diff --git a/lib/format.test.ts b/lib/format.test.ts index a6c4e35..ae99e08 100644 --- a/lib/format.test.ts +++ b/lib/format.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { formatCash, formatDeadlineRelative, formatDuration, formatFuel, formatSimClock } from './format'; +import { formatCargo, formatCash, formatDeadlineRelative, formatDuration, formatFuel, formatSimClock } from './format'; describe('formatCash', () => { it('formats EUR with thousands separators and two decimals', () => { @@ -21,6 +21,15 @@ describe('formatFuel', () => { }); }); +describe('formatCargo', () => { + it('shows cargo type + tonnage with one decimal, like the loadout display', () => { + expect(formatCargo('Mail', 10_000)).toBe('Mail 10.0t'); + // (250 / 1000).toFixed(1) === '0.3' — half rounds up at this value. + expect(formatCargo('Electronics', 250)).toBe('Electronics 0.3t'); + expect(formatCargo('Mail', 40)).toBe('Mail 0.0t'); + }); +}); + describe('formatSimClock', () => { it('formats epoch ms as a UTC wall-clock string with seconds (deterministic across timezones)', () => { expect(formatSimClock(Date.UTC(2026, 8, 17, 14, 5))).toBe('2026-09-17 14:05:00 UTC'); diff --git a/lib/format.ts b/lib/format.ts index d0e4821..20d78bb 100644 --- a/lib/format.ts +++ b/lib/format.ts @@ -11,6 +11,11 @@ export function formatFuel(fuel: number, maxFuel: number): string { return `${Math.round(fuel)} / ${maxFuel} L`; } +// Job-list cargo label: "Mail 10.0t" — type + tonnage, one decimal like the loadout display. +export function formatCargo(cargoType: string, weightKg: number): string { + return `${cargoType} ${(weightKg / 1000).toFixed(1)}t`; +} + // The sim clock is epoch ms (seeded from real time) — shown as a UTC wall-clock with // seconds so the display is deterministic regardless of the viewer's timezone. export function formatSimClock(clockMs: number): string { diff --git a/package.json b/package.json index 9aa47ee..0ff4ad1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flight-ops", - "version": "0.34.0", + "version": "0.35.0", "private": true, "scripts": { "dev": "next dev -p 3000",