ui/phase-22: races table with stance toggle and vote slot

Adds the Races View in the in-game shell. The table lists every
non-extinct other race with tech levels (percent), totals,
planets, votes received, and a per-row WAR | PEACE segmented
control. A single vote-recipient slot above the table queues a
`CommandRaceVote`; per-row buttons queue `CommandRaceRelation`.
Both commands flow through the existing order draft store with
collapse-by-acceptor (stance) and singleton (vote) rules.

`GameReport` widens with `races`, `myVotes`, `myVoteFor`; the
decoder walks `report.player[]` once for the richer projection.
The optimistic overlay flips stance and vote target immediately;
`votesReceived`, `myVotes`, and the alliance summary stay
server-authoritative — alliance grouping and the 2/3 victory
check are tallied on the server at turn cutoff and explicitly
not surfaced client-side (`rules.txt` keeps foreign races'
outgoing vote targets private).

Includes Vitest component coverage of stance and vote
collapse rules + a Playwright e2e that drives both commands
through the dispatcher route and verifies the gateway saw the
expected `CommandRaceRelation` / `CommandRaceVote` payloads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-11 01:52:23 +02:00
parent 7a7f2e4b98
commit 9111dd955a
18 changed files with 1714 additions and 47 deletions
+13 -5
View File
@@ -1,13 +1,15 @@
// EMPTY_SHIP_GROUPS supplies empty arrays for the ancillary report
// fields added in Phase 19 (ship-groups + fleets) and Phase 21
// (sciences). Test fixtures spread it into their report objects so
// the fixture body still focuses on the fields under test, without
// forcing every spec to enumerate the full GameReport surface.
// EMPTY_SHIP_GROUPS supplies empty arrays / zero defaults for the
// ancillary report fields added in Phase 19 (ship-groups + fleets),
// Phase 21 (sciences), and Phase 22 (races / diplomacy / voting).
// Test fixtures spread it into their report objects so the fixture
// body still focuses on the fields under test, without forcing
// every spec to enumerate the full GameReport surface.
import type {
ReportIncomingShipGroup,
ReportLocalFleet,
ReportLocalShipGroup,
ReportOtherRace,
ReportOtherShipGroup,
ReportUnidentifiedShipGroup,
ScienceSummary,
@@ -21,6 +23,9 @@ export const EMPTY_SHIP_GROUPS: {
localFleets: ReportLocalFleet[];
otherRaces: string[];
localScience: ScienceSummary[];
races: ReportOtherRace[];
myVotes: number;
myVoteFor: string;
} = {
localShipGroups: [],
otherShipGroups: [],
@@ -29,4 +34,7 @@ export const EMPTY_SHIP_GROUPS: {
localFleets: [],
otherRaces: [],
localScience: [],
races: [],
myVotes: 0,
myVoteFor: "",
};