feat(ui): F8-05 — game-mode chrome cleanup + inspector compact rows (#48)
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Waiting to run

Drains six F8 polish items (parent #43) in one feature:

а) Chrome cleanup
- п.6 — remove the AccountMenu (settings/sessions/theme/language/logout
  ∼ rudimentary in-game) and replace it with a single icon-button
  light/dark theme toggle. The toggle flips an in-memory `theme.override`;
  game-shell unmount calls `theme.clearOverride()` so the lobby (and
  any re-entry) re-projects the persisted lobby choice.
- п.8 — remove the wrap-scrolling radio from the map gear popover. The
  per-game `wrapMode` store and the renderer's no-wrap path stay in
  place for a future engine-side topology feature; only the UI surface
  is dropped (wrap is a server-side concept, not a per-session UI
  affordance).

б) Inspector compact rows (single idiom: select + ✓ apply / ✗ cancel,
or contextual edit/remove/add)
- п.13 — planet name is now click-to-edit: clicking the name opens an
  inline `<input>` + ✓ confirm icon; Escape cancels; the explicit
  Rename action button and Cancel button are gone.
- п.14 — production becomes one row: primary `<select>` picks
  industry/materials/research/ship, conditional secondary `<select>`
  picks the target (tech / science / ship class) for research and
  ship contexts. Apply is gated until row state differs from the
  planet's current effective production; auto-submit-on-click is
  replaced by the apply-gate.
- п.16 — cargo routes collapse to one row: a single dropdown
  (COL/CAP/MAT/EMP plus a placeholder that absorbs the old section
  title) and contextual action buttons (add / edit + remove) to the
  right. After a successful pick or remove the dropdown stays on the
  type the user just acted on.
- п.32 — stationed ship groups hoist the race column into a dropdown
  above the table. The dropdown seeds with the player's own race when
  local groups are stationed here, otherwise the first race
  alphabetically; rendered only when more than one race is in orbit.
  The race column is dropped in both single- and multi-race modes —
  the dropdown's value already names the active race.

Tests: unit and Playwright e2e updated for every changed test-id and
flow; new coverage added for `theme.override`, the in-game toggle, the
apply-gate behaviour, and the stationed-race dropdown. i18n keys for
the removed menu items, the wrap radios, the cargo title, and the
explicit `rename.cancel` are dropped from both locales; new
`game.shell.theme_toggle.*`, `production.main/target.*`,
`production.apply/cancel`, `cargo.placeholder`, and
`ship_groups.race_filter.aria` keys land.

Docs synced: `docs/FUNCTIONAL.md` §6.7 + `docs/FUNCTIONAL_ru.md`
mirror drop the torus / no-wrap radio mention; `ui/docs/design-system.md`
documents the lobby-owned persisted picker + the in-game ephemeral
override channel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-27 13:38:42 +02:00
parent 2901ecb21b
commit 4a23c357e5
30 changed files with 1173 additions and 1032 deletions
@@ -10,14 +10,24 @@ best-effort:
typed contract does not carry per-group ownership outside
battle rosters.
Phase 20 makes own-ship rows interactive: clicking a row pivots
Phase 20 made own-ship rows interactive: clicking a row pivots
the inspector to the corresponding ship-group inspector through
the shared `SelectionStore`. The actions panel mounts on top of
the existing ship-group inspector, so the row is the on-planet
entry point for Send / Load / Modernize / etc. Foreign rows stay
non-interactive — there are no actions to drive against another
race's fleet. Phase 21+ will reuse the same row shape inside the
ship-groups table view with an additional `(planet, race)` filter.
race's fleet.
F8-05 (issue #48 п.32) moved the race column into a dropdown
above the table: the previous "race | class | count | mass"
layout overflowed horizontally on narrow viewports. The dropdown
seeds with the player's own race when local groups are stationed
here, otherwise with the first race alphabetically; both cases
are after sorting `availableRaces` alphabetically so the picker
is stable across re-mounts. When a single race is in orbit the
dropdown is hidden — there is nothing to choose — and the table
renders straight through. The race column is dropped in both
modes because the dropdown already names the active race.
-->
<script lang="ts">
import { getContext } from "svelte";
@@ -91,6 +101,55 @@ ship-groups table view with an additional `(planet, race)` filter.
return rows;
});
const ownStationedHere = $derived(
stationedRows.some((r) => r.selectable),
);
const availableRaces = $derived.by(() => {
const set = new Set<string>();
for (const row of stationedRows) set.add(row.race);
return Array.from(set).sort((a, b) => a.localeCompare(b));
});
const ownRaceLabel = $derived(
localRace || i18n.t("game.inspector.planet.ship_groups.race.unknown"),
);
function defaultRace(races: ReadonlyArray<string>): string {
if (races.length === 0) return "";
if (ownStationedHere && races.includes(ownRaceLabel)) return ownRaceLabel;
return races[0]!;
}
let selectedRace = $state<string>("");
$effect(() => {
// Re-seed whenever the inspector switches planets or the
// stationed roster changes (new arrivals after a turn).
// Preserve the player's pick if it is still represented;
// otherwise fall back to the documented default.
void planet.number;
const races = availableRaces;
if (races.length === 0) {
selectedRace = "";
return;
}
if (selectedRace === "" || !races.includes(selectedRace)) {
selectedRace = defaultRace(races);
}
});
const showFilter = $derived(availableRaces.length > 1);
const filteredRows = $derived(
showFilter
? stationedRows.filter((r) => r.race === selectedRace)
: stationedRows,
);
function pickRace(event: Event): void {
selectedRace = (event.target as HTMLSelectElement).value;
}
function selectLocalGroup(groupId: string): void {
if (selection === undefined) return;
selection.selectShipGroup({ variant: "local", id: groupId });
@@ -99,9 +158,26 @@ ship-groups table view with an additional `(planet, race)` filter.
{#if stationedRows.length > 0}
<section class="ship-groups" data-testid="inspector-planet-ship-groups">
<h4>{i18n.t("game.inspector.planet.ship_groups.title")}</h4>
<div class="head">
<h4>{i18n.t("game.inspector.planet.ship_groups.title")}</h4>
{#if showFilter}
<select
class="race-select"
data-testid="inspector-planet-ship-groups-race-filter"
aria-label={i18n.t(
"game.inspector.planet.ship_groups.race_filter.aria",
)}
value={selectedRace}
onchange={pickRace}
>
{#each availableRaces as race (race)}
<option value={race}>{race}</option>
{/each}
</select>
{/if}
</div>
<ul class="rows">
{#each stationedRows as row (row.key)}
{#each filteredRows as row (row.key)}
<li class="row" data-testid="inspector-planet-ship-groups-row">
{#if row.selectable && row.groupId !== null}
{@const groupId = row.groupId}
@@ -111,9 +187,6 @@ ship-groups table view with an additional `(planet, race)` filter.
data-testid="inspector-planet-ship-groups-select"
onclick={() => selectLocalGroup(groupId)}
>
<span class="race" data-testid="inspector-planet-ship-groups-race">
{row.race}
</span>
<span class="class">{row.class}</span>
<span class="count">
{i18n.t("game.inspector.planet.ship_groups.row.count", {
@@ -127,9 +200,6 @@ ship-groups table view with an additional `(planet, race)` filter.
</span>
</button>
{:else}
<span class="race" data-testid="inspector-planet-ship-groups-race">
{row.race}
</span>
<span class="class">{row.class}</span>
<span class="count">
{i18n.t("game.inspector.planet.ship_groups.row.count", {
@@ -154,6 +224,12 @@ ship-groups table view with an additional `(planet, race)` filter.
flex-direction: column;
gap: 0.4rem;
}
.head {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
h4 {
margin: 0;
font-size: 0.85rem;
@@ -161,6 +237,18 @@ ship-groups table view with an additional `(planet, race)` filter.
letter-spacing: 0.05em;
color: var(--color-text-muted);
}
.race-select {
flex: 1 1 auto;
min-width: 0;
font: inherit;
font-size: 0.85rem;
padding: 0.15rem 0.4rem;
background: var(--color-surface-raised);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: 3px;
cursor: pointer;
}
.rows {
list-style: none;
margin: 0;
@@ -177,7 +265,7 @@ ship-groups table view with an additional `(planet, race)` filter.
.row > span,
.row > .select {
display: grid;
grid-template-columns: 1fr 1fr auto auto;
grid-template-columns: 1fr auto auto;
gap: 0.5rem;
}
.select {
@@ -195,9 +283,6 @@ ship-groups table view with an additional `(planet, race)` filter.
border-color: var(--color-border);
background: var(--color-surface-hover);
}
.race {
font-weight: 600;
}
.class {
color: var(--color-text-muted);
}