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
+10 -7
View File
@@ -1,17 +1,18 @@
<!--
Active-view router for the per-entity tables. Phase 17 lights up
the ship-classes table; Phase 21 lights up the sciences table; the
remaining slugs (planets, ship-groups, fleets, races) keep the
Phase 10 stub copy until their respective phases land. The wrapper
preserves `data-testid="active-view-table"` and
`data-entity={entity}` for every branch (each leaf component
mirrors them) so the navigation e2e specs (`game-shell.spec.ts`,
`view-menu`) keep matching.
the ship-classes table; Phase 21 lights up the sciences table;
Phase 22 lights up the races table; the remaining slugs (planets,
ship-groups, fleets) keep the Phase 10 stub copy until their
respective phases land. The wrapper preserves
`data-testid="active-view-table"` and `data-entity={entity}` for
every branch (each leaf component mirrors them) so the navigation
e2e specs (`game-shell.spec.ts`, `view-menu`) keep matching.
-->
<script lang="ts">
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
import TableShipClasses from "./table-ship-classes.svelte";
import TableSciences from "./table-sciences.svelte";
import TableRaces from "./table-races.svelte";
type Props = { entity: string };
let { entity }: Props = $props();
@@ -26,6 +27,8 @@ mirrors them) so the navigation e2e specs (`game-shell.spec.ts`,
<TableShipClasses />
{:else if entity === "sciences"}
<TableSciences />
{:else if entity === "races"}
<TableRaces />
{:else}
<section
class="active-view"