ui/phase-17: ship-class CRUD without calc

Phase 17 lights up the ship-class table and designer active views,
extends the order-draft pipeline with createShipClass and
removeShipClass commands, and projects pending Save/Delete actions
through applyOrderOverlay so the table reflects the player's
intent before auto-sync lands. The plan is corrected in the same
patch: per game/rules.txt, ship classes are designed once and
cannot be edited — the engine has no Update command, so the UI
exposes only Create + Delete.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-09 21:44:21 +02:00
parent 8a236bef14
commit 785c3483f8
23 changed files with 2456 additions and 99 deletions
@@ -78,6 +78,19 @@ function localPlanet(
};
}
function shipClass(
overrides: Partial<ShipClassSummary> & Pick<ShipClassSummary, "name">,
): ShipClassSummary {
return {
drive: 0,
armament: 0,
weapons: 0,
shields: 0,
cargo: 0,
...overrides,
};
}
function mountProduction(
planet: ReportPlanet,
localShipClass: ShipClassSummary[] = [],
@@ -167,8 +180,8 @@ describe("planet inspector — production controls", () => {
test("Build-Ship click on a class emits a SHIP setProductionType command", async () => {
const ui = mountProduction(localPlanet({ number: 7 }), [
{ name: "Scout" },
{ name: "Destroyer" },
shipClass({ name: "Scout" }),
shipClass({ name: "Destroyer" }),
]);
await fireEvent.click(
ui.getByTestId("inspector-planet-production-segment-ship"),
@@ -185,7 +198,7 @@ describe("planet inspector — production controls", () => {
test("re-clicks on the same planet collapse to the latest entry via the store", async () => {
const ui = mountProduction(localPlanet({ number: 7 }), [
{ name: "Scout" },
shipClass({ name: "Scout" }),
]);
await fireEvent.click(
ui.getByTestId("inspector-planet-production-segment-industry"),
@@ -224,7 +237,7 @@ describe("planet inspector — production controls", () => {
for (const tc of cases) {
const ui = mountProduction(
localPlanet({ number: 1, production: tc.production }),
[{ name: "Scout" }],
[shipClass({ name: "Scout" })],
);
const ids: ReadonlyArray<
"industry" | "materials" | "research" | "ship"
@@ -268,7 +281,7 @@ describe("planet inspector — production controls", () => {
test("ship class sub-row matches when production equals a class name", async () => {
const ui = mountProduction(
localPlanet({ number: 1, production: "Scout" }),
[{ name: "Scout" }, { name: "Destroyer" }],
[shipClass({ name: "Scout" }), shipClass({ name: "Destroyer" })],
);
expect(
ui.getByTestId("inspector-planet-production-ship-Scout").classList