fix: v0.10.1 — dispatch pickers independent per accepted contract (placeholder default, no first-eligible auto-fallback; stale-select fix via explicit placeholder option)

This commit is contained in:
2026-09-18 06:48:55 +00:00
parent cf811dcf4d
commit 4c06ed26ff
5 changed files with 175 additions and 7 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## v0.10.1 — 2026-09-18 06:36
- **Bugfix (reported): dispatch picker selections appeared shared across accepted contracts** — selecting a plane in one contract's dispatch picker made every other accepted contract's picker show that same plane until the page was refreshed. Root cause confirmed by code audit of `components/RightPane.tsx`: NOT a single shared state variable and NOT missing list keys (each row already had its own `useState` inside `DispatchControls` and stable `key={contract.id}`) — it was the "effective selection" fallback, which auto-tracked the **first eligible plane** in every row whenever no explicit pick existed: with one grounded plane all pickers displayed that same plane by default, so any selection looked like it propagated to the other rows; additionally there was no placeholder `<option value="">`, so once a pick became ineligible (e.g. after dispatching the only eligible plane from another contract) `value=''` matched no option and the browser kept showing the stale previously-selected option until a DOM refresh
- **Fix**: each accepted-contract row keeps its own local selection in the per-row `DispatchControls` component, now defaulting to an explicit **"Select a plane…" placeholder** (`<option value="">`) instead of preselecting the first eligible plane — rows are visibly independent and the Dispatch button stays disabled until a valid plane is chosen (prevents accidental dispatch on the wrong contract); eligibility unchanged (only planes 'available' AND at `contract.originIcao` enabled, others disabled with reason) and a poll that makes the current pick ineligible drops it back to the placeholder; stable `key={contract.id}` kept on all open + accepted rows. Pure selection rule extracted as exported `dispatchSelection(planes, contract, selectedId)` for unit testing
- **Audit of all RightPane selects**: market buy-model / sell-plane / refuel-plane pickers are single pickers each with their own independent `useState` — no shared state found, left unchanged (their first-entry fallbacks are intentional preview defaults)
- Tests: unit — new `components/right-pane.test.ts` for the extracted rule (placeholder default = NO auto-selection of first eligible plane; user pick honored while eligible; drops to placeholder when a poll makes it ineligible via flight or relocation; unknown id → placeholder). e2e — new regression test in `e2e/right-pane.spec.ts`: two hook-created EDDF contracts accepted through the UI, both dispatch pickers assert placeholder default + disabled Dispatch buttons, selecting plane-starter in contract A's picker asserts contract B's picker STILL shows the placeholder (the reported bug), then dispatching A asserts 'flying' in /api/state + in-flight indicator while B remains accepted with its independent placeholder picker; live instance restored to scale 1 + unpaused
- Verified: unit suite 166 → 171 green; `tsc --noEmit` clean; e2e 24 → 25 green against the rebuilt compose instance
## v0.10.0 — 2026-09-17 19:16
- **Right Jobs & Market pane** (`components/RightPane.tsx`, `data-testid="right-pane"`) replaces the v0.9.0 placeholder in `app/page.tsx` (PLAN.md "Right pane"): open contracts render as rows — origin → destination, reward (€), cargo type × count, deadline relative to the sim clock ("in 14h 30m") — each with an Accept button (`accept-<id>`); accepted contracts form a separate group where grounded ones get dispatch controls (plane picker enabling only planes that are 'available' AND at the contract origin; others disabled with the reason, e.g. "in flight" / "at EDDF") and in-flight ones show an "in flight" badge instead
- **Market section**: buy plane (model picker from the snapshot catalog + price preview), sell plane (grounded-plane picker + payout preview = sellFraction × price), refuel (partial-tank grounded-plane picker + cost preview = (maxFuel − fuel) × fuelPricePerL) — all previews computed client-side from the new snapshot fields, no extra endpoints