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
@@ -27,6 +27,7 @@ import { fetchOrder } from "./order-load";
import type { CommandStatus, OrderCommand } from "./order-types";
import { submitOrder } from "./submit";
import { validateEntityName } from "$lib/util/entity-name";
import { validateShipClass } from "$lib/util/ship-class-validation";
const NAMESPACE = "order-drafts";
const draftKey = (gameId: string): string => `${gameId}/draft`;
@@ -487,6 +488,31 @@ function validateCommand(cmd: OrderCommand): CommandStatus {
// which the inspector enforces by only mounting the
// component on `kind === "local"`.
return "valid";
case "createShipClass":
// Mirrors `pkg/calc/validator.go.ValidateShipTypeValues`
// plus the entity-name rules. The duplicate-name check is
// the designer's responsibility (it sees the live overlay
// list); here the validator runs without `existingNames`
// so a draft that was valid at creation time does not flip
// to invalid just because another `createShipClass` for
// the same name landed in the draft afterwards — both
// rows ride out the wire and the engine arbitrates.
return validateShipClass({
name: cmd.name,
drive: cmd.drive,
armament: cmd.armament,
weapons: cmd.weapons,
shields: cmd.shields,
cargo: cmd.cargo,
}).ok
? "valid"
: "invalid";
case "removeShipClass":
// `removeShipClass` carries only the name; the engine
// checks that the class exists and is not referenced by
// active production / ship groups. Local validation only
// guards the name shape.
return validateEntityName(cmd.name).ok ? "valid" : "invalid";
case "placeholder":
// Phase 12 placeholder entries are content-free and never
// transition out of `draft` — they are not submittable.