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:
+64
-80
@@ -25,29 +25,28 @@ during a connectivity hiccup keeps every line the player typed. A
|
||||
remote-first composer that reflects the gateway's pending-orders
|
||||
queue would force a sync on every keystroke.
|
||||
|
||||
Phase 14 lands the submit pipeline with batch semantics: every
|
||||
entry the user has marked `valid` is collected into one signed
|
||||
`user.games.order` request. The engine validates and stores the
|
||||
order, returning per-command `cmdApplied` / `cmdErrorCode` in the
|
||||
response body. The gateway re-encodes that JSON into the FBS
|
||||
`UserGamesOrderResponse` envelope (with `commands: [CommandItem]`
|
||||
populated), and `submitOrder` rejoins the verdict to each draft
|
||||
entry by `cmdId`. Successfully applied entries stay visible in
|
||||
the draft (the player keeps composing until turn cutoff);
|
||||
rejected entries stay until the player edits or removes them.
|
||||
The submit pipeline uses batch semantics: every entry the user has
|
||||
marked `valid` is collected into one signed `user.games.order`
|
||||
request. The engine validates and stores the order, returning
|
||||
per-command `cmdApplied` / `cmdErrorCode` in the response body. The
|
||||
gateway re-encodes that JSON into the FBS `UserGamesOrderResponse`
|
||||
envelope (with `commands: [CommandItem]` populated), and `submitOrder`
|
||||
rejoins the verdict to each draft entry by `cmdId`. Successfully
|
||||
applied entries stay visible in the draft (the player keeps composing
|
||||
until turn cutoff); rejected entries stay until the player edits or
|
||||
removes them.
|
||||
|
||||
Phase 25 layers a transport-level policy on top of this baseline
|
||||
without changing the batch semantics. The submit pipeline now
|
||||
goes through `OrderQueue` (see
|
||||
[`sync-protocol.md`](sync-protocol.md)): the queue holds the
|
||||
submit while the browser is offline, classifies
|
||||
`turn_already_closed` and `game_paused` server replies into
|
||||
matching banners on the order tab, and exits the loop on the
|
||||
sticky states so a stream of mutations does not re-elicit the
|
||||
same gateway reply. Recovery from a `conflict` or `paused`
|
||||
banner happens on the next `game.turn.ready` push frame via
|
||||
`OrderDraftStore.resetForNewTurn`, which clears the local draft
|
||||
and re-hydrates from the server for the new turn.
|
||||
A transport-level policy layers on top of the batch baseline without
|
||||
changing the batch semantics. The submit pipeline goes through
|
||||
`OrderQueue` (see [`sync-protocol.md`](sync-protocol.md)): the queue
|
||||
holds the submit while the browser is offline, classifies
|
||||
`turn_already_closed` and `game_paused` server replies into matching
|
||||
banners on the order tab, and exits the loop on the sticky states so
|
||||
a stream of mutations does not re-elicit the same gateway reply.
|
||||
Recovery from a `conflict` or `paused` banner happens on the next
|
||||
`game.turn.ready` push frame via `OrderDraftStore.resetForNewTurn`,
|
||||
which clears the local draft and re-hydrates from the server for the
|
||||
new turn.
|
||||
|
||||
## Local-validation invariant
|
||||
|
||||
@@ -58,10 +57,9 @@ pipeline refuses to drain a draft that contains any `invalid`
|
||||
entries. The validation step is per-command and pure — it consults
|
||||
the current `GameStateStore` snapshot only, never the network.
|
||||
|
||||
Phase 14's `planetRename` is the first variant that exercises the
|
||||
`draft → valid | invalid` transition. The validator
|
||||
(`lib/util/entity-name.ts`) is a TS port of
|
||||
`pkg/util/string.go.ValidateTypeName`, exercised on every render
|
||||
The `planetRename` variant exercises the `draft → valid | invalid`
|
||||
transition. The validator (`lib/util/entity-name.ts`) is a TS port
|
||||
of `pkg/util/string.go.ValidateTypeName`, exercised on every render
|
||||
in the inline editor and re-run by the store on every `add`. The
|
||||
submit pipeline filters the draft to `valid` entries only — any
|
||||
`invalid` row blocks the Submit button.
|
||||
@@ -79,13 +77,12 @@ draft ──validate──▶ valid ──submit──▶ submitting ──ack
|
||||
Transitions:
|
||||
|
||||
- **`draft → valid` / `draft → invalid`**: local validation. May
|
||||
re-run when the underlying `GameStateStore` snapshot changes
|
||||
(Phase 14+).
|
||||
re-run when the underlying `GameStateStore` snapshot changes.
|
||||
- **`valid → submitting`**: the submit pipeline picks the entry off
|
||||
the draft and sends it to the gateway.
|
||||
- **`submitting → applied` / `submitting → rejected`**: the gateway
|
||||
responded; the entry is no longer in flight.
|
||||
- **`submitting → conflict`** (Phase 25): the gateway returned
|
||||
- **`submitting → conflict`**: the gateway returned
|
||||
`resultCode = "turn_already_closed"`. The order tab surfaces a
|
||||
banner above the command list. Any subsequent mutation
|
||||
re-validates the conflict row back to `valid` / `invalid`; a
|
||||
@@ -94,10 +91,10 @@ Transitions:
|
||||
[`sync-protocol.md`](sync-protocol.md) for the full state
|
||||
table and recovery paths.
|
||||
|
||||
Phase 14 lands the local validators (`draft → valid | invalid`),
|
||||
the submit pipeline (`valid → submitting → applied | rejected`),
|
||||
and the optimistic overlay that shows the player's intent on the
|
||||
map and inspector while the order is in flight.
|
||||
Local validators (`draft → valid | invalid`), the submit pipeline
|
||||
(`valid → submitting → applied | rejected`), and the optimistic
|
||||
overlay that shows the player's intent on the map and inspector while
|
||||
the order is in flight are all implemented.
|
||||
|
||||
Statuses are runtime-only — they are not persisted alongside the
|
||||
commands themselves. On every `init` the store re-runs
|
||||
@@ -110,9 +107,7 @@ stored value).
|
||||
|
||||
## Discriminated union shape
|
||||
|
||||
`OrderCommand` is a discriminated union on the `kind` field. Phase
|
||||
12 shipped the skeleton with a single content-free variant; Phase
|
||||
14 added the first real one and Phase 15 added the second:
|
||||
`OrderCommand` is a discriminated union on the `kind` field:
|
||||
|
||||
```ts
|
||||
interface PlaceholderCommand {
|
||||
@@ -148,9 +143,9 @@ The `id` field is the canonical identifier the store uses for
|
||||
remove and reorder; later variants must keep `id: string` so the
|
||||
store API stays uniform. The whole draft round-trips through
|
||||
IndexedDB structured clone, so every variant must use only
|
||||
JSON-friendly value types. Phase 14 lands `planetRename` together
|
||||
with the inline editor in `lib/inspectors/planet.svelte`, the
|
||||
local validator (`lib/util/entity-name.ts`, parity with
|
||||
JSON-friendly value types. `planetRename` ships with the inline
|
||||
editor in `lib/inspectors/planet.svelte`, the local validator
|
||||
(`lib/util/entity-name.ts`, parity with
|
||||
`pkg/util/string.go.ValidateTypeName`), and the submit pipeline.
|
||||
|
||||
`setProductionType` is the wire-mirror of the engine's
|
||||
@@ -164,10 +159,10 @@ optimistic overlay rewrites `planet.production` using
|
||||
mirrors the engine's `Cache.PlanetProductionDisplayName` so the
|
||||
overlay stays byte-equal with the next server report.
|
||||
|
||||
### Collapse-by-target rule (Phase 15)
|
||||
### Collapse-by-target rule
|
||||
|
||||
`setProductionType` is the first variant to carry a
|
||||
collapse-by-target rule. `OrderDraftStore.add` enforces it:
|
||||
`setProductionType` carries a collapse-by-target rule.
|
||||
`OrderDraftStore.add` enforces it:
|
||||
when the incoming command's `kind` is `"setProductionType"` it
|
||||
drops every prior `setProductionType` entry with the same
|
||||
`planetNumber` (and the matching keys from `statuses`) before
|
||||
@@ -187,9 +182,7 @@ coexist — the rules apply within a `kind`, not across.
|
||||
`OrderDraftStore` lives in
|
||||
[`../frontend/src/sync/order-draft.svelte.ts`](../frontend/src/sync/order-draft.svelte.ts).
|
||||
The class is a Svelte 5 runes store, so the file extension is
|
||||
`.svelte.ts` (the original PLAN.md artifact line listed `.ts` —
|
||||
the deviation is documented inline in `PLAN.md`'s Phase 12
|
||||
"Decisions" subsection).
|
||||
`.svelte.ts`.
|
||||
|
||||
Lifecycle:
|
||||
|
||||
@@ -212,9 +205,8 @@ Layout integration mirrors `GameStateStore`:
|
||||
- Exposed through the `ORDER_DRAFT_CONTEXT_KEY` Svelte context.
|
||||
- Disposed in the layout's `onDestroy`.
|
||||
|
||||
The order tab consumes the store via
|
||||
`getContext(ORDER_DRAFT_CONTEXT_KEY)`; Phase 14's planet inspector
|
||||
will use the same key to push a new command.
|
||||
The order tab and the planet inspector both consume the store via
|
||||
`getContext(ORDER_DRAFT_CONTEXT_KEY)` to push new commands.
|
||||
|
||||
## Submit pipeline
|
||||
|
||||
@@ -224,9 +216,9 @@ will use the same key to push a new command.
|
||||
`markSubmitting(ids)` so each row reads `submitting`, then
|
||||
posts the snapshot through `submitOrder`.
|
||||
2. `submitOrder` builds the FBS `UserGamesOrder` request (game_id,
|
||||
`updatedAt = 0` in Phase 14, every command encoded as a
|
||||
`CommandItem` with the typed payload union) and signs it via
|
||||
the existing `executeCommand` orchestration.
|
||||
`updatedAt`, every command encoded as a `CommandItem` with the
|
||||
typed payload union) and signs it via the existing
|
||||
`executeCommand` orchestration.
|
||||
3. The engine validates, stores, and answers `202 Accepted` with
|
||||
the stored order body — `game_id`, `updatedAt`, plus each
|
||||
command echoed with `cmdApplied` and (on rejection)
|
||||
@@ -252,8 +244,7 @@ in-flight entries back to `valid` so the operator can retry.
|
||||
`applyOrderOverlay(report, commands, statuses)` (in
|
||||
`api/game-state.ts`) returns a copy of the server `GameReport`
|
||||
with every command in `applied` or `submitting` status projected
|
||||
on top. Phase 14 understands `planetRename` only; future phases
|
||||
extend the switch.
|
||||
on top.
|
||||
|
||||
The overlay has its own context (`RENDERED_REPORT_CONTEXT_KEY`,
|
||||
`lib/rendered-report.svelte.ts`) — the in-game shell layout owns
|
||||
@@ -288,9 +279,7 @@ Cache row layout:
|
||||
| -------------- | ------------------ | ---------------- |
|
||||
| `order-drafts` | `{gameId}/draft` | `OrderCommand[]` |
|
||||
|
||||
The store writes the full draft on every mutation. Phase 25 may
|
||||
profile the submit pipeline and batch into a microtask if write
|
||||
amplification becomes a problem; until then the deterministic
|
||||
The store writes the full draft on every mutation. The deterministic
|
||||
write-on-every-mutation model is what tests assert and what the
|
||||
layout relies on for crash safety.
|
||||
|
||||
@@ -300,14 +289,12 @@ order composer uses the namespace.
|
||||
|
||||
## History mode wiring
|
||||
|
||||
Phase 26 implements history mode: the user can step back through
|
||||
past turns and see the report as it was. The IA section specifies
|
||||
that the Order tab is hidden when history mode is active — the
|
||||
player is browsing an immutable snapshot, and composing commands
|
||||
History mode lets the user step back through past turns and see the
|
||||
report as it was. The Order tab is hidden when history mode is active
|
||||
— the player is browsing an immutable snapshot, and composing commands
|
||||
against it would be confusing.
|
||||
|
||||
Phase 12 wires the flag end-to-end as a prop. The layout owns the
|
||||
flag and passes it to:
|
||||
The layout owns the `historyMode` flag and passes it to:
|
||||
|
||||
- `Sidebar` as `historyMode`. The sidebar forwards it to its
|
||||
`TabBar` as `hideOrder`. The Order entry is filtered out of the
|
||||
@@ -318,17 +305,16 @@ flag and passes it to:
|
||||
- `BottomTabs` as `hideOrder`. The mobile bottom-tab `Order`
|
||||
button is suppressed when true.
|
||||
|
||||
Phase 26 turns the constant into a derived value driven by
|
||||
`GameStateStore.historyMode` (`viewedTurn < currentTurn` while
|
||||
`status === "ready"`). The same getter is also passed into
|
||||
`OrderDraftStore.bindClient` as `getHistoryMode`, which short-
|
||||
circuits the `add` / `remove` / `move` mutations to a no-op while
|
||||
the flag is true. This makes every Phase 14–22 inspector affordance
|
||||
that calls `orderDraft.add(...)` inert in history mode without
|
||||
per-component edits — the gate lives in the one chokepoint that
|
||||
all callers go through. The conflict / paused banners and the
|
||||
in-flight sync pipeline are untouched: they describe state that
|
||||
exists independently of the user's current view.
|
||||
`historyMode` is a derived value driven by `GameStateStore.historyMode`
|
||||
(`viewedTurn < currentTurn` while `status === "ready"`). The same
|
||||
getter is also passed into `OrderDraftStore.bindClient` as
|
||||
`getHistoryMode`, which short-circuits the `add` / `remove` / `move`
|
||||
mutations to a no-op while the flag is true. This makes every
|
||||
inspector affordance that calls `orderDraft.add(...)` inert in history
|
||||
mode without per-component edits — the gate lives in the one
|
||||
chokepoint that all callers go through. The conflict / paused banners
|
||||
and the in-flight sync pipeline are untouched: they describe state
|
||||
that exists independently of the user's current view.
|
||||
|
||||
The store itself stays alive across history-mode round-trips so
|
||||
the draft survives the toggle. The `RenderedReportSource` overlay
|
||||
@@ -346,13 +332,11 @@ the chrome.
|
||||
|
||||
## Testing
|
||||
|
||||
Phase 12 + Phase 14 test artifacts:
|
||||
|
||||
- [`../frontend/tests/order-draft.test.ts`](../frontend/tests/order-draft.test.ts)
|
||||
— Vitest unit tests for the store. Drives `OrderDraftStore`
|
||||
directly with `IDBCache` over `fake-indexeddb`. Covers init,
|
||||
add, remove, move, per-game isolation, mutations-before-init,
|
||||
dispose hygiene, the Phase 14 status machine
|
||||
dispose hygiene, the status machine
|
||||
(`validate` / `markSubmitting` / `applyResults` /
|
||||
`revertSubmittingToValid`), and the
|
||||
`hydrateFromServer` cache-miss fallback.
|
||||
@@ -375,12 +359,12 @@ Phase 12 + Phase 14 test artifacts:
|
||||
— Vitest component tests for the rename action and the inline
|
||||
editor's local validation.
|
||||
- [`../frontend/tests/e2e/order-composer.spec.ts`](../frontend/tests/e2e/order-composer.spec.ts)
|
||||
— Playwright spec for the Phase 12 skeleton (seed three
|
||||
— Playwright spec for the order composer skeleton (seed three
|
||||
commands, reload, persistence).
|
||||
- [`../frontend/tests/e2e/rename-planet.spec.ts`](../frontend/tests/e2e/rename-planet.spec.ts)
|
||||
— Phase 14 end-to-end: select a planet, rename, submit, observe
|
||||
the overlay-applied name on the inspector + map, reload, and
|
||||
see the rename hydrated from `user.games.order.get`.
|
||||
— End-to-end: select a planet, rename, submit, observe the
|
||||
overlay-applied name on the inspector + map, reload, and see the
|
||||
rename hydrated from `user.games.order.get`.
|
||||
|
||||
The `__galaxyDebug.seedOrderDraft(gameId, commands)` and
|
||||
`__galaxyDebug.clearOrderDraft(gameId)` helpers in
|
||||
|
||||
Reference in New Issue
Block a user