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:
+71
-10
@@ -48,13 +48,18 @@ The desktop sidebar hosts three tools:
|
||||
| Inspector | `lib/sidebar/inspector-tab.svelte` | Phase 13 / 19 |
|
||||
| Order | `lib/sidebar/order-tab.svelte` | Phase 12 / 14 |
|
||||
|
||||
The sidebar's selected-tab state is a `$state` rune inside
|
||||
`lib/sidebar/sidebar.svelte`. The component is mounted by the layout
|
||||
at `routes/games/[id]/+layout.svelte`, and SvelteKit keeps that
|
||||
layout instance alive while the user navigates between child routes
|
||||
(`/games/:id/map` → `/games/:id/report` → …). The rune therefore
|
||||
survives every active-view switch automatically, with no URL coupling
|
||||
needed.
|
||||
The selected-tab state is a `$state` rune in
|
||||
`routes/games/[id]/+layout.svelte`, bound into
|
||||
`lib/sidebar/sidebar.svelte` via `$bindable()`. The layout owns the
|
||||
rune so external events — Phase 13's planet click, future similar
|
||||
flows — can drive the active tab from outside the sidebar without
|
||||
plumbing callbacks. The component is mounted by the layout, and
|
||||
SvelteKit keeps that layout instance alive while the user navigates
|
||||
between child routes (`/games/:id/map` → `/games/:id/report` → …),
|
||||
so the rune survives every active-view switch automatically with no
|
||||
URL coupling needed. The URL seed and the history-mode reset
|
||||
described below still live inside the sidebar — they mutate the
|
||||
bindable in place; the layout sees the change through the binding.
|
||||
|
||||
A `?sidebar=calc|calculator|inspector|order` URL param is read once
|
||||
on mount and seeds the initial tab. Later phases that want to land
|
||||
@@ -95,9 +100,9 @@ Three discrete CSS modes matched to the IA section diagrams:
|
||||
view-menu trigger swaps to a hamburger icon (☰) that opens the
|
||||
drop-down as a full-width drawer below the header.
|
||||
|
||||
Inspector is intentionally unreachable on mobile in Phase 10. Per the
|
||||
IA section the mobile inspector is a bottom-sheet raised by tapping a
|
||||
map object, and that mechanism waits for Phase 13.
|
||||
On mobile the bottom tab row does not include `Inspector`. The
|
||||
inspector content is reached by tapping a map object instead, which
|
||||
raises a bottom-sheet — see [Planet selection](#planet-selection-phase-13).
|
||||
|
||||
## Mobile bottom-tabs and tool overlay
|
||||
|
||||
@@ -132,6 +137,62 @@ back-stack mechanism. Phase 34 lands the back-stack alongside its
|
||||
first user (multi-turn projection, range circles in the ship-class
|
||||
designer).
|
||||
|
||||
## Planet selection (Phase 13)
|
||||
|
||||
The map view turns into the entry point for the inspector by
|
||||
translating a renderer click into a planet selection. The flow:
|
||||
|
||||
1. The renderer (`src/map/render.ts`) exposes `onClick(cb)` next to
|
||||
the existing `hitAt(cursor)`. It is built on `pixi-viewport`'s
|
||||
`clicked` event, which already differentiates a click from a
|
||||
pan-drag, so a click handler will not race the pan plugin.
|
||||
2. `lib/active-view/map.svelte` wires that callback after a successful
|
||||
`mountRenderer`. On a click it asks the renderer for the hit
|
||||
primitive, looks the planet up by `number` in the live
|
||||
`GameStateStore.report`, and calls `SelectionStore.selectPlanet(number)`.
|
||||
3. `SelectionStore` (`lib/selection.svelte.ts`) is a runes store
|
||||
instantiated by the layout and exposed via Svelte context under
|
||||
`SELECTION_CONTEXT_KEY`. It carries a discriminated union — Phase
|
||||
13 only models `{ kind: "planet"; id: number }`; Phase 19 widens
|
||||
it for ship groups. Selection is in-memory only: it survives the
|
||||
layout's lifetime (active-view switches inside `/games/:id/*`)
|
||||
but does not persist across reloads — that contrast with the
|
||||
order draft is intentional.
|
||||
4. The layout watches the selection rune and, on the null → planet
|
||||
transition, flips its bound `activeTab` to `inspector` and
|
||||
`sidebarOpen` to `true`. Desktop already has the sidebar pinned;
|
||||
tablet needs the drawer to surface; mobile is unaffected by the
|
||||
tab rune because the sidebar is CSS-hidden there.
|
||||
5. `lib/sidebar/inspector-tab.svelte` and
|
||||
`lib/inspectors/planet-sheet.svelte` both read the selection
|
||||
store, resolve it against the live report, and either render
|
||||
`lib/inspectors/planet.svelte` or fall back to the empty state.
|
||||
A selection that points at a planet missing from the current
|
||||
report (visibility lost between turns) collapses to the empty
|
||||
state instead of holding stale rows.
|
||||
|
||||
The mobile bottom-sheet is mounted alongside `<BottomTabs />` in the
|
||||
layout. Its visibility is conditional on `effectiveTool === "map"` so
|
||||
it does not stack on top of the calc / order overlays. Phase 13 ships
|
||||
the minimal dismissal surface: a close button (`✕`) that calls
|
||||
`SelectionStore.clear()`. Tap-outside and swipe-down dismissal from
|
||||
the IA section are deferred to Phase 35 polish. A click that lands on
|
||||
empty space is a no-op — selection is mutated only by an explicit
|
||||
planet click or by the close button.
|
||||
|
||||
The planet inspector itself is a presentational component: it takes
|
||||
a `ReportPlanet` snapshot as a prop and renders the documented field
|
||||
set per planet kind. The wrapper in `api/game-state.ts` exposes every
|
||||
field the FBS schema carries (`industryStockpile` for `capital`,
|
||||
`materialsStockpile` for `material`, `industry`, `population`,
|
||||
`colonists`, `production`, `freeIndustry`, plus `owner` for `other`).
|
||||
Fields the FBS table does not project for a given kind read as `null`
|
||||
and the inspector simply omits the row.
|
||||
|
||||
The selected-planet visual on the map (a ring or halo) is **not**
|
||||
shipped in Phase 13. It rolls into Phase 35 polish together with the
|
||||
sheet's swipe-to-dismiss gesture.
|
||||
|
||||
## Auth gate
|
||||
|
||||
The root `+layout.svelte` redirects `anonymous → /login` for any
|
||||
|
||||
Reference in New Issue
Block a user