From 4a23c357e567875ce33ffbec3beb1e109924f3ca Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 27 May 2026 13:38:42 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat(ui):=20F8-05=20=E2=80=94=20game-mode?= =?UTF-8?q?=20chrome=20cleanup=20+=20inspector=20compact=20rows=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` + ✓ confirm icon; Escape cancels; the explicit Rename action button and Cancel button are gone. - п.14 — production becomes one row: primary `` 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 --- docs/FUNCTIONAL.md | 8 +- docs/FUNCTIONAL_ru.md | 9 +- ui/docs/design-system.md | 22 +- .../src/lib/active-view/map-toggles.svelte | 71 +-- ui/frontend/src/lib/game/game-shell.svelte | 6 + .../src/lib/header/account-menu.svelte | 191 -------- .../lib/header/game-mode-theme-toggle.svelte | 60 +++ ui/frontend/src/lib/header/header.svelte | 14 +- ui/frontend/src/lib/i18n/locales/en.ts | 26 +- ui/frontend/src/lib/i18n/locales/ru.ts | 26 +- ui/frontend/src/lib/inspectors/planet.svelte | 185 ++++---- .../lib/inspectors/planet/cargo-routes.svelte | 221 ++++----- .../lib/inspectors/planet/production.svelte | 433 ++++++++++-------- .../lib/inspectors/planet/ship-groups.svelte | 115 ++++- ui/frontend/src/lib/theme/theme.svelte.ts | 41 +- ui/frontend/tests/e2e/a11y-keyboard.spec.ts | 20 +- ui/frontend/tests/e2e/cargo-routes.spec.ts | 9 +- ui/frontend/tests/e2e/game-shell.spec.ts | 2 +- ui/frontend/tests/e2e/map-toggles.spec.ts | 44 -- ui/frontend/tests/e2e/order-sync.spec.ts | 2 +- .../tests/e2e/planet-production.spec.ts | 42 +- ui/frontend/tests/e2e/rename-planet.spec.ts | 4 +- ui/frontend/tests/e2e/sciences.spec.ts | 33 +- ui/frontend/tests/game-shell-header.test.ts | 42 +- .../inspector-planet-cargo-routes.test.ts | 157 +++++-- .../tests/inspector-planet-production.test.ts | 232 +++++----- .../inspector-planet-ship-groups.test.ts | 102 ++++- ui/frontend/tests/inspector-planet.test.ts | 21 +- .../tests/map-toggles-component.test.ts | 26 +- ui/frontend/tests/theme.test.ts | 41 ++ 30 files changed, 1173 insertions(+), 1032 deletions(-) delete mode 100644 ui/frontend/src/lib/header/account-menu.svelte create mode 100644 ui/frontend/src/lib/header/game-mode-theme-toggle.svelte diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index bb0e71b..fb3b8a0 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -820,8 +820,12 @@ every change applies within one frame (no Pixi remount): `VisibilityDistance(localPlayerDrive)` circles around LOCAL planets; LOCAL planets are always exempt — the toggle is named after the visible part of the map rather than the - obscured one) plus the torus / no-wrap radio that switches - the renderer mode while preserving the camera centre. + obscured one). The renderer always runs in torus mode; the + earlier torus / no-wrap radio was removed in F8 polish + (issue #48 п.8) because the topology is a server-side concept + rather than a per-session UI affordance. The renderer-side + no-wrap path is retained for the day the engine surfaces a + bounded-plane mode. LOCAL planets are always rendered — they have no toggle. Every other toggle defaults to ON. Hiding a planet cascades onto every diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index 615b423..0429bef 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -840,9 +840,12 @@ Directory-промоушен ([Раздел 5](#5-реестр-названий- объединения окружностей `VisibilityDistance(localPlayerDrive)` вокруг LOCAL-планет; LOCAL-планеты всегда вне фильтра — тоггл назван по видимой - области карты, а не по затемнённой) плюс радиогруппа - «торус / без переноса», переключающая режим рендерера с - сохранением центра камеры. + области карты, а не по затемнённой). Рендерер всегда работает + в торическом режиме; прежняя радиогруппа «торус / без + переноса» была удалена в полишинге F8 (issue #48 п.8), + поскольку топология карты — серверная сущность, а не + per-session UI-настройка. Код-путь без переноса в рендерере + оставлен на день, когда движок выставит режим bounded plane. LOCAL-планеты отрисовываются всегда — для них тоггла нет. Остальные тогглы по умолчанию включены. Скрытие планеты diff --git a/ui/docs/design-system.md b/ui/docs/design-system.md index 264f01a..fcafb7c 100644 --- a/ui/docs/design-system.md +++ b/ui/docs/design-system.md @@ -71,13 +71,24 @@ the colour block in `tokens.css`. `theme.resolved` (`light` | `dark`), and `theme.setChoice(…)`. It persists the choice, applies `data-theme`, and — while the choice is `system` — follows OS theme changes via `matchMedia`. -- The account menu (`account-menu.svelte`) exposes the picker. The - default is `system` (it follows the OS preference); `light` / `dark` - pin a theme. +- The persisted picker lives in the lobby profile screen + ([`screens/profile-screen.svelte`](../frontend/src/lib/screens/profile-screen.svelte)) — + the in-game header is intentionally light on chrome and only carries + the volatile light/dark toggle described below. The default is + `system` (it follows the OS preference); `light` / `dark` pin a theme. +- On top of the persisted choice the store carries an ephemeral + `theme.override` (`null` | `light` | `dark`). `setOverride(…)` + short-circuits `resolved` so the in-game toggle + ([`header/game-mode-theme-toggle.svelte`](../frontend/src/lib/header/game-mode-theme-toggle.svelte)) + can flip the document theme without touching the lobby preference. + The override lives in memory only; the game shell calls + `theme.clearOverride()` on unmount, so leaving the game and re-entering + it re-projects the persisted choice from lobby. The `app.html` guard and the store deliberately duplicate the resolution logic (one runs before modules load, the other after) — keep -them in sync. +them in sync. The ephemeral override is intentionally absent from the +pre-paint guard: it cannot survive a reload, only an in-tab session. ## Conventions @@ -126,4 +137,5 @@ battle-scene palette, both defined in code rather than as tokens), the overlay scrims, and the directional / deliberate drop shadows. The default theme is **`system`** — it follows the OS light/dark -preference; users can pin light or dark via the account-menu picker. +preference; users pin light or dark via the lobby profile screen, and +flip the in-game appearance volatilely through the header theme toggle. diff --git a/ui/frontend/src/lib/active-view/map-toggles.svelte b/ui/frontend/src/lib/active-view/map-toggles.svelte index 4ce09c1..9b74f0c 100644 --- a/ui/frontend/src/lib/active-view/map-toggles.svelte +++ b/ui/frontend/src/lib/active-view/map-toggles.svelte @@ -1,10 +1,15 @@ - - - - - diff --git a/ui/frontend/src/lib/header/game-mode-theme-toggle.svelte b/ui/frontend/src/lib/header/game-mode-theme-toggle.svelte new file mode 100644 index 0000000..3f397d4 --- /dev/null +++ b/ui/frontend/src/lib/header/game-mode-theme-toggle.svelte @@ -0,0 +1,60 @@ + + + + + + diff --git a/ui/frontend/src/lib/header/header.svelte b/ui/frontend/src/lib/header/header.svelte index b1dd0f5..eaf5191 100644 --- a/ui/frontend/src/lib/header/header.svelte +++ b/ui/frontend/src/lib/header/header.svelte @@ -2,9 +2,13 @@ Top header for the in-game shell. Composes the in-game ID strip (race name @ game name) followed by the Phase 26 turn navigator (a `← Turn N →` triplet with a popover of every turn), the view -dropdown / hamburger, and the account menu. The sidebar-toggle slot -to its left appears only on tablet viewports (768–1024 px) and is -wired by `+layout.svelte`. +dropdown / hamburger, and the in-game ephemeral light/dark theme +toggle. The sidebar-toggle slot to its left appears only on tablet +viewports (768–1024 px) and is wired by `+layout.svelte`. + +The persisted theme choice (and the language picker, logout, etc.) +lives in the lobby — the in-game header carries only the ephemeral +toggle for quick visual flips during a session. The race name is read from the engine's `Report.race`, the game name from the lobby's `GameSummary.gameName`. While either piece @@ -24,7 +28,7 @@ absent until Phase 24 wires push-event state. type GameStateStore, } from "$lib/game-state.svelte"; import ViewMenu from "./view-menu.svelte"; - import AccountMenu from "./account-menu.svelte"; + import GameModeThemeToggle from "./game-mode-theme-toggle.svelte"; import TurnNavigator from "./turn-navigator.svelte"; type Props = { @@ -78,7 +82,7 @@ absent until Phase 24 wires push-event state. ⤧ - + diff --git a/ui/frontend/src/lib/i18n/locales/en.ts b/ui/frontend/src/lib/i18n/locales/en.ts index 54cfd74..8107798 100644 --- a/ui/frontend/src/lib/i18n/locales/en.ts +++ b/ui/frontend/src/lib/i18n/locales/en.ts @@ -144,16 +144,9 @@ const en = { "game.shell.menu.close_sidebar": "close sidebar", "game.shell.menu.open_views": "open views menu", "game.shell.menu.close_views": "close views menu", - "game.shell.menu.account": "account", - "game.shell.menu.settings": "settings", - "game.shell.menu.sessions": "sessions", - "game.shell.menu.theme": "theme", - "game.shell.menu.theme_system": "system", - "game.shell.menu.theme_light": "light", - "game.shell.menu.theme_dark": "dark", - "game.shell.menu.language": "language", "game.shell.menu.return_to_lobby": "return to lobby", - "game.shell.menu.logout": "logout", + "game.shell.theme_toggle.to_light": "switch to light theme", + "game.shell.theme_toggle.to_dark": "switch to dark theme", "game.shell.coming_soon": "coming soon", "game.shell.turn.label": "turn {turn}", "game.shell.turn.list_item": "turn #{turn}", @@ -183,9 +176,6 @@ const en = { "game.map.toggles.unidentified_planets": "unidentified planets", "game.map.toggles.unreachable_planets": "show unreachable planets", "game.map.toggles.visible_hyperspace": "visible hyperspace", - "game.map.toggles.wrap.label": "wrap scrolling", - "game.map.toggles.wrap.torus": "torus", - "game.map.toggles.wrap.no_wrap": "no-wrap", "game.view.table": "table", "game.view.table.planets": "planets", "game.view.table.ship_classes": "ship classes", @@ -293,7 +283,6 @@ const en = { "game.inspector.planet.action.rename": "rename", "game.inspector.planet.rename.title": "rename planet", "game.inspector.planet.rename.confirm": "save", - "game.inspector.planet.rename.cancel": "cancel", "game.inspector.planet.rename.invalid.empty": "name cannot be empty", "game.inspector.planet.rename.invalid.too_long": "name is too long (30 characters max)", "game.inspector.planet.rename.invalid.starts_with_special": "name cannot start with a special character", @@ -302,6 +291,8 @@ const en = { "game.inspector.planet.rename.invalid.whitespace": "name cannot contain spaces", "game.inspector.planet.rename.invalid.disallowed_character": "name contains disallowed characters", "game.inspector.planet.production.title": "production", + "game.inspector.planet.production.main.aria": "production type", + "game.inspector.planet.production.main.placeholder": "(production)", "game.inspector.planet.production.option.industry": "industry", "game.inspector.planet.production.option.materials": "materials", "game.inspector.planet.production.option.research": "research", @@ -310,8 +301,14 @@ const en = { "game.inspector.planet.production.research.weapons": "weapons", "game.inspector.planet.production.research.shields": "shields", "game.inspector.planet.production.research.cargo": "cargo", + "game.inspector.planet.production.target.research.aria": "research target", + "game.inspector.planet.production.target.research.placeholder": "(tech or science)", + "game.inspector.planet.production.target.ship.aria": "ship class", + "game.inspector.planet.production.target.ship.placeholder": "(ship class)", "game.inspector.planet.production.ship.no_classes": "no ship classes designed yet", - "game.inspector.planet.cargo.title": "cargo routes", + "game.inspector.planet.production.apply": "apply production change", + "game.inspector.planet.production.cancel": "discard production change", + "game.inspector.planet.cargo.placeholder": "cargo routes", "game.inspector.planet.cargo.slot.col": "colonists", "game.inspector.planet.cargo.slot.cap": "industry", "game.inspector.planet.cargo.slot.mat": "materials", @@ -602,6 +599,7 @@ const en = { "game.inspector.ship_group.action.invalid.level": "level must be in ({current}, {max}]", "game.inspector.ship_group.action.invalid.fleet_name": "fleet name does not match the entity-name rules", + "game.inspector.planet.ship_groups.race_filter.aria": "stationed race", "game.inspector.planet.ship_groups.title": "stationed ship groups", "game.inspector.planet.ship_groups.row.count": "{count} ships", "game.inspector.planet.ship_groups.row.mass": "mass {mass}", diff --git a/ui/frontend/src/lib/i18n/locales/ru.ts b/ui/frontend/src/lib/i18n/locales/ru.ts index f9ddca3..b2fb2de 100644 --- a/ui/frontend/src/lib/i18n/locales/ru.ts +++ b/ui/frontend/src/lib/i18n/locales/ru.ts @@ -145,16 +145,9 @@ const ru: Record = { "game.shell.menu.close_sidebar": "закрыть боковую панель", "game.shell.menu.open_views": "открыть меню видов", "game.shell.menu.close_views": "закрыть меню видов", - "game.shell.menu.account": "аккаунт", - "game.shell.menu.settings": "настройки", - "game.shell.menu.sessions": "сессии", - "game.shell.menu.theme": "тема", - "game.shell.menu.theme_system": "системная", - "game.shell.menu.theme_light": "светлая", - "game.shell.menu.theme_dark": "тёмная", - "game.shell.menu.language": "язык", "game.shell.menu.return_to_lobby": "вернуться в лобби", - "game.shell.menu.logout": "выйти", + "game.shell.theme_toggle.to_light": "переключить на светлую тему", + "game.shell.theme_toggle.to_dark": "переключить на тёмную тему", "game.shell.coming_soon": "скоро будет", "game.shell.turn.label": "ход {turn}", "game.shell.turn.list_item": "ход #{turn}", @@ -184,9 +177,6 @@ const ru: Record = { "game.map.toggles.unidentified_planets": "неопознанные планеты", "game.map.toggles.unreachable_planets": "показывать недостижимые планеты", "game.map.toggles.visible_hyperspace": "видимое гиперпространство", - "game.map.toggles.wrap.label": "перенос карты", - "game.map.toggles.wrap.torus": "тор", - "game.map.toggles.wrap.no_wrap": "без переноса", "game.view.table": "таблица", "game.view.table.planets": "планеты", "game.view.table.ship_classes": "классы кораблей", @@ -294,7 +284,6 @@ const ru: Record = { "game.inspector.planet.action.rename": "переименовать", "game.inspector.planet.rename.title": "переименование планеты", "game.inspector.planet.rename.confirm": "сохранить", - "game.inspector.planet.rename.cancel": "отмена", "game.inspector.planet.rename.invalid.empty": "имя не может быть пустым", "game.inspector.planet.rename.invalid.too_long": "имя слишком длинное (максимум 30 символов)", "game.inspector.planet.rename.invalid.starts_with_special": "имя не может начинаться со спецсимвола", @@ -303,6 +292,8 @@ const ru: Record = { "game.inspector.planet.rename.invalid.whitespace": "имя не может содержать пробелы", "game.inspector.planet.rename.invalid.disallowed_character": "имя содержит недопустимые символы", "game.inspector.planet.production.title": "производство", + "game.inspector.planet.production.main.aria": "тип производства", + "game.inspector.planet.production.main.placeholder": "(производство)", "game.inspector.planet.production.option.industry": "промышленность", "game.inspector.planet.production.option.materials": "сырьё", "game.inspector.planet.production.option.research": "исследование", @@ -311,8 +302,14 @@ const ru: Record = { "game.inspector.planet.production.research.weapons": "оружие", "game.inspector.planet.production.research.shields": "щиты", "game.inspector.planet.production.research.cargo": "трюм", + "game.inspector.planet.production.target.research.aria": "цель исследования", + "game.inspector.planet.production.target.research.placeholder": "(технология или наука)", + "game.inspector.planet.production.target.ship.aria": "класс корабля", + "game.inspector.planet.production.target.ship.placeholder": "(класс корабля)", "game.inspector.planet.production.ship.no_classes": "классы кораблей ещё не спроектированы", - "game.inspector.planet.cargo.title": "грузовые маршруты", + "game.inspector.planet.production.apply": "применить изменение производства", + "game.inspector.planet.production.cancel": "отменить изменение производства", + "game.inspector.planet.cargo.placeholder": "грузовые маршруты", "game.inspector.planet.cargo.slot.col": "колонисты", "game.inspector.planet.cargo.slot.cap": "промышленность", "game.inspector.planet.cargo.slot.mat": "сырьё", @@ -603,6 +600,7 @@ const ru: Record = { "game.inspector.ship_group.action.invalid.level": "уровень должен быть в ({current}, {max}]", "game.inspector.ship_group.action.invalid.fleet_name": "имя флота не соответствует правилам имён сущностей", + "game.inspector.planet.ship_groups.race_filter.aria": "раса в орбите", "game.inspector.planet.ship_groups.title": "корабли на орбите", "game.inspector.planet.ship_groups.row.count": "{count} кораблей", "game.inspector.planet.ship_groups.row.mass": "масса {mass}", diff --git a/ui/frontend/src/lib/inspectors/planet.svelte b/ui/frontend/src/lib/inspectors/planet.svelte index 1c2073f..79415de 100644 --- a/ui/frontend/src/lib/inspectors/planet.svelte +++ b/ui/frontend/src/lib/inspectors/planet.svelte @@ -1,16 +1,20 @@
-

- {i18n.t("game.inspector.planet.cargo.title")} -

-
- {#each CARGO_LOAD_TYPE_VALUES as loadType (loadType)} - {@const entry = slotEntries[loadType]} - {@const slug = loadType.toLowerCase()} -
-
+
+ + + {#if selected !== ""} + {#if activeEntry === null} + + {:else} + + → {destinationName(activeEntry.destinationPlanetNumber)} + + + + {/if} + {/if} +
+ {#if pendingSlot !== null}
- {:else if reach > 0 && reachableSet().size === 0} + {:else if selected !== "" && reach > 0 && reachableSet().size === 0}