ui/phase-13: planet inspector — read-only

Plumbs the map → inspector pathway: a click on a planet selects it
through the new SelectionStore, the sidebar Inspector tab swaps
its empty-state copy for a per-kind read-only field set, and a
mobile-only bottom-sheet mirrors the same content over the map.
Field projection in api/game-state.ts now surfaces every documented
planet field.
This commit is contained in:
Ilia Denisov
2026-05-09 08:29:03 +02:00
parent a3fdcfe9c5
commit 6364bba6fd
19 changed files with 1440 additions and 75 deletions
+61 -19
View File
@@ -1453,36 +1453,74 @@ on the map; read-only, no actions yet.
Artifacts:
- `ui/frontend/src/lib/sidebar/inspector-tab.svelte` empty state
(`select an object on the map`) and routing per selected-object kind
(`select an object on the map`) and routing per selected-object
kind. The tab reads the selection and game-state stores from
context and hands a resolved `ReportPlanet` to the planet inspector
component.
- `ui/frontend/src/lib/inspectors/planet.svelte` read-only display of
every planet field documented in `docs/FUNCTIONAL.md` §6 and the
[`rules.txt`](../game/rules.txt) planet section: name, coordinates, size, population,
industry, materials stockpile, industry stockpile, colonists,
natural resources, current production type, free production
potential
- map click handler that selects the planet and switches sidebar to
Inspector (or raises bottom-sheet on mobile)
- selection store `ui/frontend/src/lib/selection.ts` holding the
currently selected map object id
every planet field carried by the FBS report and documented in
the `rules.txt` planet section: name, coordinates, size, population,
colonists, industry, industry stockpile (`capital`, `$`), materials
stockpile (`material`, `M`), natural resources, current production
type, free production potential. Per-kind nullable fields collapse
silently — uninhabited and unidentified planets render the smaller
field set the engine carries for them.
- `ui/frontend/src/lib/inspectors/planet-sheet.svelte` mobile-only
bottom-sheet that wraps the same planet component for the < 768 px
breakpoint. Visibility is gated on `effectiveTool === "map"` so the
sheet does not stack with the calc / order overlays.
- `ui/frontend/src/lib/active-view/map.svelte` registers a click
handler against the new `RendererHandle.onClick` (built on
`pixi-viewport`'s `clicked` event), translates the hit into a
planet, and calls `SelectionStore.selectPlanet(number)`.
- `ui/frontend/src/lib/selection.svelte.ts` runes store with the
selected-object union (`{ kind: "planet"; id: number } | null`),
exposed via `setContext` from the in-game shell layout. Lifetime
matches the layout instance — selection survives every active-view
switch but does not persist across reloads.
- `ui/frontend/src/api/game-state.ts` projection extended to surface
every planet field needed by the inspector (`industryStockpile`,
`materialsStockpile`, `industry`, `population`, `colonists`,
`production`, `freeIndustry`, plus the existing `owner`).
- `ui/frontend/src/routes/games/[id]/+layout.svelte` lifts
`activeTab` into a layout-level rune bound into the sidebar, owns
the `SelectionStore`, mounts the bottom-sheet, and runs the
reveal `$effect` that flips the sidebar to the inspector tab and
opens the tablet drawer when a new selection lands.
Dependencies: Phase 11.
Acceptance criteria:
- clicking any visible planet on the map shows its details in the
inspector tab on desktop and bottom-sheet on mobile;
- selection state persists across view switches (per global state-
preservation rule);
inspector tab on desktop and tablet (drawer auto-opens), and in a
bottom-sheet on mobile;
- selection state persists across view switches inside `/games/:id/*`
(per global state-preservation rule); reload starts fresh;
- a click on empty map area is a no-op — selection is cleared only
by the explicit close button (``) on the mobile sheet;
- empty inspector renders the empty-state message when no planet is
selected.
selected;
- mobile dismissal is the close button only; swipe-to-dismiss and
tap-outside-to-dismiss are deferred to Phase 35;
- a selection that no longer matches a visible planet (visibility
lost between turns) collapses to the empty state instead of
showing stale rows;
- selected-planet visual feedback on the map (ring / halo) is
intentionally out of scope and rolls into Phase 35.
Targeted tests:
- Vitest component tests for the planet inspector with fixture data;
- Playwright e2e: click a seeded planet, assert all expected fields are
rendered;
- mobile-viewport Playwright run validating the bottom-sheet
presentation.
- Vitest unit (`tests/selection-store.test.ts`) for the runes store;
- Vitest component (`tests/inspector-planet.test.ts`) for per-kind
field rendering against synthetic `ReportPlanet` fixtures;
- Vitest component (`tests/game-shell-sidebar.test.ts`) extended for
the selection-driven inspector content and the missing-planet
fallback;
- Playwright e2e (`tests/e2e/game-shell-inspector.spec.ts`) clicks a
seeded planet on `chromium-desktop` and asserts the sidebar
inspector content, and on `chromium-mobile-iphone-13` asserts the
bottom-sheet appears and the close button clears it.
## Phase 14. First End-to-End Command — Rename Planet
@@ -2342,6 +2380,10 @@ Artifacts:
- accessibility audit results recorded under `ui/docs/a11y.md`
- keyboard-only navigation paths for lobby, game view, and login
- focus rings, ARIA labels, screen-reader-only text where needed
- mobile bottom-sheet swipe-down dismissal and tap-outside dismissal,
on top of the close button shipped in Phase 13
- selected-planet visual on the map (ring or halo), wired off the
Phase 13 `SelectionStore`
Dependencies: Phase 33.