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
+64 -24
View File
@@ -2391,44 +2391,84 @@ Targeted tests:
via the Research sub-row, delete it
(`tests/e2e/sciences.spec.ts`).
## Phase 22. Races View — War/Peace Toggle and Votes
## ~~Phase 22. Races View — War/Peace Toggle and Votes~~
Status: pending.
Status: done.
Goal: list other races with their visible stats, expose war/peace
toggle and the voting UI.
Goal: list other races with their visible stats, expose the war/peace
toggle, and the voting UI.
Artifacts:
- `ui/frontend/src/routes/games/[id]/table/races/+page.svelte` table
with one row per race, including name, tech levels, total
population, total production, planet count, war-or-peace from this
race's perspective, votes received. The race list itself is read
from `GameReport.otherRaces` (introduced in Phase 20 for the
ship-group transfer-to-race picker); the table view widens the
per-race shape (tech / population / production / planet count /
votes / relation) by walking `report.player[]` directly when those
fields are needed
- per-row toggle for declaring war or peace (adds
`SetDiplomaticStance` command)
- voting control: a single slot for `give my votes to <race>` (adds
`SetVoteRecipient` command)
- alliance summary panel showing the current vote graph and any
alliance reaching ≥ 2/3 of total votes
- `ui/frontend/src/lib/active-view/table-races.svelte` table mounted
by the dispatcher in
`ui/frontend/src/lib/active-view/table.svelte` (same pattern as
Phase 21's sciences table). One row per non-extinct other race
carrying name, tech levels (drive / weapons / shields / cargo as
percent), total population, total production (engine `industry`),
planet count, votes received, and the local player's stance
toward that race. The richer per-race projection
(`GameReport.races: ReportOtherRace[]`) is decoded in
`ui/frontend/src/api/game-state.ts` by walking `report.player[]`
once and surfacing the row alongside the existing `otherRaces:
string[]` (which keeps backing the ship-group transfer picker from
Phase 20)
- per-row segmented `WAR | PEACE` control. The active stance is
highlighted (`aria-pressed=true` + contrast colour); the inactive
button queues `setDiplomaticStance` (engine `CommandRaceRelation`).
The displayed stance is the local player's relation toward the
named race (`rules.txt` "(R) Ваше отношение к указанной расе, но
не наоборот") — not the other way round
- voting control: a single `<select>` populated with `races[].name`,
changing it queues `setVoteRecipient` (engine `CommandRaceVote`).
Disabled when the local player is the only non-extinct race. A
read-only `myVotes` total renders next to the picker
- explanatory note in the page header: alliance grouping and the 2/3
victory check are tallied on the server at turn cutoff and are
NOT projected on the client. The report carries each race's votes
received (`Player.votes`) and the local player's outgoing vote
(`Report.vote_for`), but foreign races' outgoing votes are
intentionally private, so a client-side vote graph would be
partial. The acceptance criterion "vote counts match server state
byte-for-byte" forbids a local recomputation
Cross-stack notes:
- No backend / wire changes. `CommandRaceRelation`,
`CommandRaceVote`, `Player.relation`, `Player.votes`,
`Report.votes`, and `Report.vote_for` already carry every datum
this stage needs
- TS draft store
(`ui/frontend/src/sync/order-draft.svelte.ts`) gains two collapse
rules: `setDiplomaticStance` collapses by `acceptor` (one stance
intent per opponent); `setVoteRecipient` collapses singleton (a
single outgoing vote slot per `rules.txt:1066`)
- The optimistic overlay (`applyOrderOverlay`) flips
`races[i].relation` and `myVoteFor` immediately so the controls
reflect the queued intent without waiting for the auto-sync
round-trip. `votesReceived`, `myVotes`, and the alliance state
stay server-authoritative
Dependencies: Phase 14.
Acceptance criteria:
- the user can toggle war / peace and change vote recipient;
- the alliance summary updates after a server roundtrip;
- vote counts match server state byte-for-byte.
- the per-row stance and the "I vote for" picker reflect the
queued intent immediately (optimistic overlay) and resolve to
`applied` in the sidebar order tab after the auto-sync round-trip;
- vote counts match server state byte-for-byte (no client tally).
Targeted tests:
- Vitest component tests for the alliance summary on canonical fixtures
(chain of votes, fork, win condition);
- Playwright e2e: change diplomatic stance and vote, submit, confirm.
- Vitest component test
(`ui/frontend/tests/table-races.test.ts`) covering: render rows
from a canonical fixture, filter, sort flip, stance click +
collapse-by-acceptor, vote pick + singleton collapse, empty state;
- Playwright e2e (`ui/frontend/tests/e2e/races.spec.ts`): open the
races table, toggle one row's stance, change the vote recipient,
observe both commands as `applied` in the sidebar order tab and
verify the decoded gateway payload.
## Phase 23. Reports View — Current Turn Sections