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",