Adds the second end-to-end command (`setProductionType`) with a
collapse-by-`planetNumber` rule on the order draft, the segmented
production-controls component on the planet inspector, the FBS
encoder/decoder pair for `CommandPlanetProduce`, and the
`localShipClass` projection on `GameReport`. Forecast number is
deferred and tracked in the new `ui/docs/calc-bridge.md`.
Replaces the manual Submit button with an auto-sync pipeline driven
by `OrderDraftStore`: every successful add / remove / move
coalesces a `submitOrder` call so the engine always mirrors the
local draft. Removing the last command sends an empty cmd[] PUT —
the engine, repo, and rest model now accept that as a valid
"player cleared their draft" state.
`hydrateFromServer` is now invoked unconditionally on game boot so
a fresh device picks up the player's stored order, and the local
cache is overwritten by the server's view (server is the source of
truth).
Header replaces the static "race ?" + turn counter with a single
headline string `<race> @ <game>, turn <n>`, sourced from the
engine's Report.race + the lobby's GameSummary.gameName + the live
turn number, with a `?` fallback while any piece is loading.
Tests:
- engine: empty PUT round-trips, repo round-trips empty Commands
- order-draft: auto-sync sends full draft on every mutation,
rejected response surfaces error sync status, rapid mutations
coalesce, server hydration overwrites cache
- order-tab: per-row status flips through the auto-sync lifecycle,
remove → empty cmd[] PUT, rejected → retry button
- inspector overlay: applied + valid + submitting all participate
in the optimistic projection
- header: live race / game / turn rendering with fall-back
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The owner reported two symptoms after pulling the Phase 14 stack:
1. user.games.order.get answered with `unimplemented: message_type
is not routed`. The gateway/backend code was correct, but the
local-dev compose images were stale — `make rebuild` picked up
the new routes table and the symptom went away. To prevent this
class of regression from depending on docker-image freshness,
gateway/internal/backendclient/routes_test.go now asserts that
every authenticated MessageType constant declared in
pkg/model/{user,lobby,order,report} is registered, and verifies
that user.games.order.get specifically resolves to the game
command client.
2. The inspector kept the un-renamed name after a successful submit.
ui/frontend/tests/inspector-overlay.test.ts mounts the inspector
tab against a real OrderDraftStore + a stubbed GameStateStore
and walks the full happy path (add planetRename → markSubmitting
→ applied → simulate refresh) plus the integration scenario
driven through the order-tab Submit button. Both cases pass —
the underlying overlay path is reactive and resilient to a
refresh that returns the un-renamed snapshot. The original
in-browser symptom was the rebuilt-image freshness issue from
point 1; this test pins the reactive contract for future
refactors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>