docs(ui): finalize MVP plan structure and de-archaeologize topic docs
MVP web client (Phases 1-30) is complete; reorganize planning + living docs around that. - PLAN.md kept as the staged MVP record (1-30) with a status block + pointers; removed the 31-36 stages, regression scenarios, and deferred-TODO section (moved out); fixed a stale cross-machine plan path. - ui/PLAN-finalize.md (new): active web-finalization plan in 8 stages (visual system, a11y, i18n, error UX, PWA, build hygiene, docs, owner manual-QA loop); absorbs former Phases 33 and 35. - ui/ROADMAP.md (new): post-MVP (Wails, Capacitor, realistic projection, acceptance + regression scenarios) and triaged deferred follow-ups. - ui/docs/README.md (new): grouped topic-doc index. - De-archaeologized all 20 ui/docs topic docs + ui/README.md + ui/core/README.md: stripped Phase-N build history, rewritten as current-state; deferred work now points at ROADMAP.md / PLAN-finalize.md. Docs-only; no code change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+50
-58
@@ -1,10 +1,8 @@
|
||||
# Per-game state store
|
||||
|
||||
This document describes the per-game state owned by the in-game shell
|
||||
layout. Phase 11 introduces the store and uses it for two consumers
|
||||
(the header turn counter and the map view); later phases plug
|
||||
inspector tabs, the order composer, and the calculator on top of the
|
||||
same instance.
|
||||
layout. The store serves the header turn counter, the map view,
|
||||
inspector tabs, the order composer, and the calculator.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
@@ -23,10 +21,9 @@ gameId })`. `init`:
|
||||
2. calls `setGame(gameId)`, which:
|
||||
- reads the per-game wrap-mode preference from `Cache`
|
||||
(`game-prefs / <gameId>/wrap-mode`, default `torus`);
|
||||
- calls `lobby.my.games.list` and finds the game record (the
|
||||
Phase 11 wire schema extension on `GameSummary` adds
|
||||
`current_turn`); if the user is not a member, the store flips
|
||||
to `error`;
|
||||
- calls `lobby.my.games.list` and finds the game record
|
||||
(`GameSummary` carries `current_turn`); if the user is not a
|
||||
member, the store flips to `error`;
|
||||
- calls `user.games.report` for the discovered turn and decodes
|
||||
the FlatBuffers response into a TS-friendly `GameReport` shape.
|
||||
|
||||
@@ -44,24 +41,21 @@ The store exposes:
|
||||
| `wrapMode` | `torus / no-wrap` | per-game preference, persisted via `Cache` |
|
||||
| `error` | `string \| null` | localised error message when `status === "error"` |
|
||||
|
||||
## Phase boundaries
|
||||
## Store extensions
|
||||
|
||||
- Phase 11 surfaces only the planet subset of the report. Later
|
||||
phases extend `GameReport` and `decodeReport` as their slice of
|
||||
the wire lands (ships, fleets, sciences, routes, battles, mail).
|
||||
- Phase 26 splits `currentTurn` from the turn whose snapshot is
|
||||
displayed (`viewedTurn`) and adds `viewTurn(turn)` /
|
||||
`returnToCurrent()` for history navigation. The derived
|
||||
`historyMode` rune flips automatically when `viewedTurn <
|
||||
currentTurn`; the layout passes it to Phase 12's sidebar /
|
||||
bottom-tabs wiring (which hides the order tab) and to
|
||||
`OrderDraftStore.bindClient` (which gates `add` / `remove` /
|
||||
`move`). See "History mode" below for the cache and refresh
|
||||
rules.
|
||||
- Phase 24 replaces the tab-focus refresh with push-event-driven
|
||||
refreshes; the visibility listener stays as a fallback for
|
||||
background tabs that miss a push.
|
||||
- Phase 29 wires the wrap-mode toggle UI on top of `setWrapMode`.
|
||||
`GameReport` and `decodeReport` are extended as each slice of the
|
||||
wire lands (ships, fleets, sciences, routes, battles, mail).
|
||||
`currentTurn` is split from `viewedTurn`, and `viewTurn(turn)` /
|
||||
`returnToCurrent()` handle history navigation. The derived
|
||||
`historyMode` rune flips automatically when `viewedTurn <
|
||||
currentTurn`; the layout passes it to the sidebar / bottom-tabs
|
||||
wiring (which hides the order tab) and to
|
||||
`OrderDraftStore.bindClient` (which gates `add` / `remove` / `move`).
|
||||
See "History mode" below for the cache and refresh rules.
|
||||
Tab-focus refreshes are supplemented by push-event-driven refreshes;
|
||||
the visibility listener stays as a fallback for background tabs that
|
||||
miss a push. The wrap-mode toggle UI is wired on top of
|
||||
`setWrapMode`.
|
||||
|
||||
## Why `current_turn` lives on `GameSummary`
|
||||
|
||||
@@ -79,20 +73,19 @@ Extending `GameSummary` reuses the existing lobby pipeline; the
|
||||
backend already tracks `current_turn` in its runtime projection
|
||||
(`backend/internal/server/handlers_user_lobby_helpers.go`
|
||||
`gameSummaryToWire` reads it from `g.RuntimeSnapshot.CurrentTurn`).
|
||||
|
||||
The wire change touches Phase 8's already-shipped catalogue, but the
|
||||
`current_turn` field defaults to zero on the FB side, so existing
|
||||
The `current_turn` field defaults to zero on the FB side, so existing
|
||||
tests and the dev sandbox flow continue to work unchanged.
|
||||
|
||||
## State binding
|
||||
|
||||
`map/state-binding.ts::reportToWorld(report)` translates a
|
||||
`GameReport` into a renderer-ready `World`. Phase 11 emits one Point
|
||||
`GameReport` into a renderer-ready `World`. It emits one Point
|
||||
primitive per planet across all four kinds (local / other /
|
||||
uninhabited / unidentified). Each kind gets a distinct fill colour,
|
||||
fill alpha, and point radius so the four classes are
|
||||
visually-distinguishable at a glance; later phases will refine the
|
||||
colour palette as the visual language stabilises (Phase 35 polish).
|
||||
visually-distinguishable at a glance; colour-palette refinement is
|
||||
deferred to the finalization plan
|
||||
([../PLAN-finalize.md](../PLAN-finalize.md)).
|
||||
|
||||
The planet engine number is reused as the primitive id so a hit-test
|
||||
result can resolve back to a planet without an extra lookup table.
|
||||
@@ -108,9 +101,9 @@ unchanged), so a no-op refresh does not flicker the canvas.
|
||||
|
||||
In history mode `refresh()` is a no-op — forcing a reload would
|
||||
silently bump the user back onto the current turn while they are
|
||||
intentionally viewing a past one. Push events (Phase 24) still
|
||||
deliver new-turn notifications asynchronously while the user
|
||||
explores history, so the pending-turn toast continues to work.
|
||||
intentionally viewing a past one. Push events still deliver
|
||||
new-turn notifications asynchronously while the user explores
|
||||
history, so the pending-turn toast continues to work.
|
||||
|
||||
`setWrapMode(mode)` writes to `Cache` and updates the rune; the
|
||||
map view's effect picks the change up and re-mounts the renderer
|
||||
@@ -118,20 +111,19 @@ with the new mode.
|
||||
|
||||
## Map visibility toggles
|
||||
|
||||
Phase 29 adds a `mapToggles: MapToggles` rune that drives the
|
||||
gear popover in the map view. Every flag defaults to `true` —
|
||||
including `unreachablePlanets` (showing every planet by default)
|
||||
and `visibleHyperspace` (the fog overlay on by default). The
|
||||
exhaustive shape lives in `src/lib/game-state.svelte.ts`; the
|
||||
gear popover (`src/lib/active-view/map-toggles.svelte`) is a
|
||||
thin view of the rune.
|
||||
A `mapToggles: MapToggles` rune drives the gear popover in the map
|
||||
view. Every flag defaults to `true` — including `unreachablePlanets`
|
||||
(showing every planet by default) and `visibleHyperspace` (the fog
|
||||
overlay on by default). The exhaustive shape lives in
|
||||
`src/lib/game-state.svelte.ts`; the gear popover
|
||||
(`src/lib/active-view/map-toggles.svelte`) is a thin view of the
|
||||
rune.
|
||||
|
||||
`setMapToggle(key, value)` flips one entry in place and
|
||||
persists the whole blob to `Cache` under the
|
||||
`game-map-toggles/{gameId}` key. The blob carries a companion
|
||||
`lastResetTurn` number — the turn at which the toggles were last
|
||||
reset to defaults — so the new-turn reset path (below) can detect
|
||||
a stale blob even across a cross-session gap.
|
||||
`setMapToggle(key, value)` flips one entry in place and persists
|
||||
the whole blob to `Cache` under the `game-map-toggles/{gameId}` key.
|
||||
The blob carries a companion `lastResetTurn` number — the turn at
|
||||
which the toggles were last reset to defaults — so the new-turn reset
|
||||
path (below) can detect a stale blob even across a cross-session gap.
|
||||
|
||||
### New-turn reset
|
||||
|
||||
@@ -156,7 +148,7 @@ The cache namespace and blob shape are documented in
|
||||
|
||||
## History mode
|
||||
|
||||
Phase 26 lets the user step backward through the report timeline
|
||||
The store lets the user step backward through the report timeline
|
||||
without losing the live snapshot. The store keeps two turn runes:
|
||||
|
||||
- `currentTurn` — the server's authoritative latest. Only
|
||||
@@ -170,7 +162,7 @@ The derived `historyMode` rune (`status === "ready" && viewedTurn
|
||||
< currentTurn`) drives every history-aware consumer:
|
||||
|
||||
- the layout passes it to `Sidebar` / `BottomTabs` so the order
|
||||
tab vanishes (Phase 12 prop wiring);
|
||||
tab vanishes;
|
||||
- the layout passes a `getHistoryMode` getter to
|
||||
`OrderDraftStore.bindClient` so `add` / `remove` / `move` are
|
||||
no-ops while the user is looking at a past turn;
|
||||
@@ -179,17 +171,17 @@ The derived `historyMode` rune (`status === "ready" && viewedTurn
|
||||
- the new `HistoryBanner` component renders the sticky "Viewing
|
||||
turn N · read-only" strip when the flag is true.
|
||||
|
||||
`last-viewed-turn` semantics keep their Phase 11 meaning: "the
|
||||
latest turn the user was caught up on". `loadTurn` only writes the
|
||||
cache row when called with `isCurrent === true` (i.e. when the
|
||||
load matches `currentTurn`). Historical excursions are therefore
|
||||
ephemeral: closing the tab and reopening the game resumes on the
|
||||
last caught-up turn, not on the last clicked one.
|
||||
`last-viewed-turn` means "the latest turn the user was caught up
|
||||
on". `loadTurn` only writes the cache row when called with
|
||||
`isCurrent === true` (i.e. when the load matches `currentTurn`).
|
||||
Historical excursions are therefore ephemeral: closing the tab and
|
||||
reopening the game resumes on the last caught-up turn, not on the
|
||||
last clicked one.
|
||||
|
||||
Past-turn reports are cached in the `game-history` namespace
|
||||
(`{gameId}/turn/{N}` → `GameReport`). The cache is written by
|
||||
`loadTurn` on every successful historical fetch and read first by
|
||||
`viewTurn(N)` before falling back to the network. Past turns are
|
||||
immutable, so the cache has no TTL and no eviction in Phase 26.
|
||||
The current-turn snapshot is deliberately *not* cached — it is
|
||||
mutable until the next engine tick.
|
||||
immutable, so the cache has no TTL and no eviction. The current-turn
|
||||
snapshot is deliberately *not* cached — it is mutable until the next
|
||||
engine tick.
|
||||
|
||||
Reference in New Issue
Block a user