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
@@ -1,29 +1,66 @@
<!--
Phase 10 stub for the Inspector sidebar tool. The empty-state copy
matches the IA section verbatim — `select an object on the map` —
so the user understands the intended interaction before Phase 13
wires real planet selection.
Inspector sidebar tool. Reads the per-game `SelectionStore` and the
`GameStateStore` from context (both set by the in-game shell layout).
When a planet selection resolves to a live `ReportPlanet` in the
current report, the tab swaps the empty-state copy for the read-
only planet inspector. A selection that points at a planet missing
from the current report (e.g. visibility lost between turns) falls
back to the empty state instead of holding stale data.
The empty-state copy still matches the IA section verbatim — `select
an object on the map` — so the no-selection experience is unchanged
from the Phase 10 stub.
-->
<script lang="ts">
import { getContext } from "svelte";
import { i18n } from "$lib/i18n/index.svelte";
import {
GAME_STATE_CONTEXT_KEY,
type GameStateStore,
} from "$lib/game-state.svelte";
import {
SELECTION_CONTEXT_KEY,
type SelectionStore,
} from "$lib/selection.svelte";
import Planet from "$lib/inspectors/planet.svelte";
const gameState = getContext<GameStateStore | undefined>(
GAME_STATE_CONTEXT_KEY,
);
const selection = getContext<SelectionStore | undefined>(
SELECTION_CONTEXT_KEY,
);
const selectedPlanet = $derived.by(() => {
const sel = selection?.selected;
if (sel === undefined || sel === null || sel.kind !== "planet") return null;
const report = gameState?.report;
if (report === undefined || report === null) return null;
return report.planets.find((p) => p.number === sel.id) ?? null;
});
</script>
<section class="tool" data-testid="sidebar-tool-inspector">
<h3>{i18n.t("game.sidebar.tab.inspector")}</h3>
<p>{i18n.t("game.sidebar.empty.inspector")}</p>
{#if selectedPlanet !== null}
<Planet planet={selectedPlanet} />
{:else}
<h3>{i18n.t("game.sidebar.tab.inspector")}</h3>
<p>{i18n.t("game.sidebar.empty.inspector")}</p>
{/if}
</section>
<style>
.tool {
padding: 1rem;
font-family: system-ui, sans-serif;
}
.tool h3 {
.tool > h3 {
margin: 0 0 0.5rem;
padding: 1rem 1rem 0;
font-size: 1rem;
}
.tool p {
.tool > p {
margin: 0;
padding: 0 1rem 1rem;
color: #888;
}
</style>