ui/phase-19: read-only ship-group inspector + sheet + tab dispatch
Closes Phase 19's UI surface. The inspector dispatches on the
selection variant: local / other groups render class, count, the
four tech levels, mass, cargo (type + amount when loaded),
location (planet name on-orbit, from/to/distance in hyperspace),
and — for local groups only — fleet membership + state. Incoming
groups surface origin / destination / distance / speed and the
inline ETA = ceil(distance / speed); zero speed collapses to the
designer's existing "—" placeholder. Unidentified groups render
just the (x, y) coordinates and the no-data hint, mirroring the
unidentified planet treatment.
Layout / inspector-tab plumbing:
- inspector-tab.svelte derives selectedShipGroup against the
rendered report and mounts <ShipGroup /> when the planet
branch doesn't match. Stale refs (an index that no longer
resolves after a turn refresh) collapse cleanly to the empty
state.
- +layout.svelte mounts <ShipGroupSheet /> alongside the
existing planet sheet on mobile; both share the
`effectiveTool === "map"` guard and clear-on-close.
i18n: en + ru both grow ~30 keys under
`game.inspector.ship_group.*`. Adding a key to one without the
other is a TS error (TranslationKey is `keyof typeof en`), so the
Russian mirror stays mandatory.
Tests:
- inspector-ship-group.test.ts exercises every variant —
on-planet local, in-hyperspace local, cargo-loaded local,
foreign, incoming with ETA, incoming with zero speed,
unidentified, plus the missing-planet `#NN` fallback.
- tests/e2e/inspector-ship-group.spec.ts is a smoke spec that
drives the DEV-only synthetic-report loader from /lobby
through navigation to /games/synthetic-XXX/map.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,12 @@ 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.
|
||||
|
||||
Phase 19 widens the dispatch: a `kind === "shipGroup"` selection
|
||||
resolves against the matching report array and mounts the read-only
|
||||
ship-group inspector. Unresolvable refs (e.g. the chosen index has
|
||||
fallen out of the new turn's report) cleanly collapse to the empty
|
||||
state — same fallback as a stale planet selection.
|
||||
|
||||
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.
|
||||
@@ -23,6 +29,9 @@ from the Phase 10 stub.
|
||||
type RenderedReportSource,
|
||||
} from "$lib/rendered-report.svelte";
|
||||
import Planet from "$lib/inspectors/planet.svelte";
|
||||
import ShipGroup, {
|
||||
type ShipGroupSelection,
|
||||
} from "$lib/inspectors/ship-group.svelte";
|
||||
|
||||
const renderedReport = getContext<RenderedReportSource | undefined>(
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
@@ -38,6 +47,38 @@ from the Phase 10 stub.
|
||||
if (report === undefined || report === null) return null;
|
||||
return report.planets.find((p) => p.number === sel.id) ?? null;
|
||||
});
|
||||
const selectedShipGroup: ShipGroupSelection | null = $derived.by(() => {
|
||||
const sel = selection?.selected;
|
||||
if (sel === undefined || sel === null || sel.kind !== "shipGroup") {
|
||||
return null;
|
||||
}
|
||||
const report = renderedReport?.report;
|
||||
if (report === undefined || report === null) return null;
|
||||
const ref = sel.ref;
|
||||
switch (ref.variant) {
|
||||
case "local": {
|
||||
const group = report.localShipGroups.find((g) => g.id === ref.id);
|
||||
if (group === undefined) return null;
|
||||
return { variant: "local", group };
|
||||
}
|
||||
case "other": {
|
||||
const group = report.otherShipGroups[ref.index];
|
||||
if (group === undefined) return null;
|
||||
return { variant: "other", group };
|
||||
}
|
||||
case "incoming": {
|
||||
const group = report.incomingShipGroups[ref.index];
|
||||
if (group === undefined) return null;
|
||||
return { variant: "incoming", group };
|
||||
}
|
||||
case "unidentified": {
|
||||
const group = report.unidentifiedShipGroups[ref.index];
|
||||
if (group === undefined) return null;
|
||||
return { variant: "unidentified", group };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const localShipClass = $derived(
|
||||
renderedReport?.report?.localShipClass ?? [],
|
||||
);
|
||||
@@ -61,6 +102,8 @@ from the Phase 10 stub.
|
||||
{mapHeight}
|
||||
{localPlayerDrive}
|
||||
/>
|
||||
{:else if selectedShipGroup !== null}
|
||||
<ShipGroup selection={selectedShipGroup} planets={allPlanets} />
|
||||
{:else}
|
||||
<h3>{i18n.t("game.sidebar.tab.inspector")}</h3>
|
||||
<p>{i18n.t("game.sidebar.empty.inspector")}</p>
|
||||
|
||||
Reference in New Issue
Block a user