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:
Ilia Denisov
2026-05-21 23:17:51 +02:00
parent 51865b8cf4
commit a89048f6c5
26 changed files with 836 additions and 929 deletions
+56 -65
View File
@@ -18,50 +18,45 @@ two-line wrapper that mounts the matching content component from
the plan is the file system plus those wrappers — there is no
separate dispatch component.
| URL | Active view component | Phase that fills it |
| ------------------------------------- | ---------------------------------------------- | ----------------------- |
| URL | Active view component | Phase that fills it |
| ------------------------------------------ | ---------------------------------------------- | ----------------------- |
| `/games/:id/map` | `lib/active-view/map.svelte` | Phase 11 |
| `/games/:id/table/:entity` | `lib/active-view/table.svelte` | Phase 11 / 17 / 19 / 22 |
| `/games/:id/report` | `lib/active-view/report.svelte` (see [report-view.md](report-view.md)) | Phase 23 |
| `/games/:id/battle/:battleId?` | `lib/active-view/battle.svelte` | Phase 27 |
| `/games/:id/mail` | `lib/active-view/mail.svelte` | Phase 28 |
| `/games/:id/designer/science/:scienceId?` | `lib/active-view/designer-science.svelte` | Phase 21 |
| URL | Active view component |
| ------------------------------------------ | ---------------------------------------------------------------------- |
| `/games/:id/map` | `lib/active-view/map.svelte` |
| `/games/:id/table/:entity` | `lib/active-view/table.svelte` |
| `/games/:id/report` | `lib/active-view/report.svelte` (see [report-view.md](report-view.md)) |
| `/games/:id/battle/:battleId?` | `lib/active-view/battle.svelte` |
| `/games/:id/mail` | `lib/active-view/mail.svelte` |
| `/games/:id/designer/science/:scienceId?` | `lib/active-view/designer-science.svelte` |
`/games/:id` (no trailing view) redirects to `/games/:id/map`. The
optional `:scienceId?` segment on the science designer route matches
SvelteKit's `[[scienceId]]` syntax — `/designer/science` opens the
empty new-science form, `/designer/science/{name}` opens the named
science. Phase 17/18 originally added a parallel ship-class designer
route; Phase 30 removed it and folded ship-class design into the
sidebar ship-class calculator (`lib/sidebar/calculator-tab.svelte`,
see [calculator-ux.md](calculator-ux.md)), reached from the
ship-classes table and the view/bottom menus.
science. Ship-class design is folded into the sidebar ship-class
calculator (`lib/sidebar/calculator-tab.svelte`, see
[calculator-ux.md](calculator-ux.md)), reached from the ship-classes
table and the view/bottom menus.
The `entity` slug on the table route is kebab-case (`planets`,
`ship-classes`, `ship-groups`, `fleets`, `sciences`, `races`).
`table.svelte` is the active-view router: it dispatches by slug to
the per-entity component (`ship-classes``table-ship-classes.svelte`
in Phase 17; the others fall back to the Phase 10 stub copy until
their respective phases land).
the per-entity component (`ship-classes``table-ship-classes.svelte`;
other entities dispatch to their respective components).
## Sidebar tools and state preservation
The desktop sidebar hosts three tools:
| Tool | Component | Phase that fills it |
| ---------- | -------------------------------------- | -------------------- |
| Calculator | `lib/sidebar/calculator-tab.svelte` | Phase 30 |
| Inspector | `lib/sidebar/inspector-tab.svelte` | Phase 13 / 19 |
| Order | `lib/sidebar/order-tab.svelte` | Phase 12 / 14 |
| Tool | Component |
| ---------- | ----------------------------------- |
| Calculator | `lib/sidebar/calculator-tab.svelte` |
| Inspector | `lib/sidebar/inspector-tab.svelte` |
| Order | `lib/sidebar/order-tab.svelte` |
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
rune so external events — such as a planet click — 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
@@ -70,23 +65,21 @@ 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
the user on a particular tool (for example, Phase 14's first
end-to-end command flow) can set it on navigation.
on mount and seeds the initial tab. Navigation flows that want to
land the user on a particular tool can set this param on navigation.
The Order entry is hidden when the layout's `historyMode` flag is
true. Phase 12 plumbs the flag end-to-end as a prop —
`+layout.svelte` forwards a derived value to `Sidebar`, which
true. `+layout.svelte` forwards a derived value to `Sidebar`, which
forwards `hideOrder` to its `TabBar`; the same flag goes to
`BottomTabs` so the mobile `Order` button is also suppressed. A
`?sidebar=order` URL seed that arrives while the flag is true falls
back to `inspector`, and an `$effect` on the sidebar resets
`activeTab` away from `order` if the flag flips on mid-session.
Phase 26 wires the flag to the live history signal owned by
`GameStateStore`. The derivation lives directly in `+layout.svelte`
The `historyMode` flag is derived from the live history signal owned
by `GameStateStore`. The derivation lives directly in `+layout.svelte`
(`const historyMode = $derived(gameState.historyMode)`) — no
separate `lib/history-mode.ts` module ships, because the layout is
separate `lib/history-mode.ts` module exists, because the layout is
the single consumer and the project's compactness rule rejects a
one-line indirection. The order draft survives the toggle because
`OrderDraftStore` lives one level above the sidebar in the layout
@@ -100,8 +93,7 @@ for the draft-store side of the flow and
## Header turn navigator and history banner
The header replaces the Phase 11 inline `turn N` text with a
`← Turn N →` triplet (`lib/header/turn-navigator.svelte`). The
The header shows a `← Turn N` triplet (`lib/header/turn-navigator.svelte`). The
arrows step `viewedTurn` by ±1 (disabled at boundaries `0` and
`currentTurn`); clicking the middle button opens an absolute
popover (desktop) or a fixed full-width drawer (mobile, ≤ 767.98
@@ -129,9 +121,8 @@ Three discrete CSS modes matched to the IA section diagrams:
toggle in the header right corner. Tapping the toggle slides the
sidebar in as a fixed overlay above the active view; a close
button on the sidebar dismisses it. The full swipe-from-right
gesture in the IA section is deferred to Phase 35 polish — the
click toggle satisfies the "layout switches at 768 px" acceptance
criterion on Phase 10.
gesture is deferred to the finalization plan
([../PLAN-finalize.md](../PLAN-finalize.md)).
- **< 768 px (mobile)** — the sidebar is hidden entirely and the
bottom-tabs row appears at the bottom of the viewport. The
view-menu trigger swaps to a hamburger icon (☰) that opens the
@@ -139,7 +130,7 @@ Three discrete CSS modes matched to the IA section diagrams:
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).
raises a bottom-sheet — see [Planet selection](#planet-selection).
## Mobile bottom-tabs and tool overlay
@@ -157,27 +148,27 @@ The next time the user taps a Calc or Order bottom-tab, the
navigation re-routes them to `/map` and re-applies the overlay.
The `More` button opens a drawer that mirrors the header view-menu
content. The IA section's narrower "More" list (Mail, Battle log,
Tables, History, Settings, Logout) is the polish target for Phase 35
— Phase 10 keeps a single source of truth for destinations.
content. A narrower "More" list (Mail, Battle log, Tables, History,
Settings, Logout) is deferred to the finalization plan
([../PLAN-finalize.md](../PLAN-finalize.md)); the current drawer keeps
a single source of truth for destinations.
## Transient map overlays
Some views can push a transient overlay onto `/map` with a back
affordance. (Phase 30's calculator reach circles are a simpler,
always-on map extra rather than a back-stacked overlay; the transient
back-stack mechanism itself is still a Phase 34 concept.) A transient
overlay clears when the user navigates to any other view via the header
or the bottom-tabs.
affordance. (The calculator reach circles are a simpler, always-on
map extra rather than a back-stacked overlay; the transient
back-stack mechanism is planned — see
[../ROADMAP.md](../ROADMAP.md).) A transient overlay clears when the
user navigates to any other view via the header or the bottom-tabs.
Phase 10 documents this concept but does not implement the
back-stack mechanism. Phase 34 lands the back-stack alongside its
first user (multi-turn projection, range circles in the ship-class
designer).
The back-stack mechanism is not yet implemented; it is planned
alongside its first user (multi-turn projection, range circles in the
ship-class designer) in [../ROADMAP.md](../ROADMAP.md).
## Planet selection (Phase 13)
## Planet selection
The map view turns into the entry point for the inspector by
The map view is 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
@@ -190,9 +181,9 @@ translating a renderer click into a planet selection. The flow:
`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
`SELECTION_CONTEXT_KEY`. It carries a discriminated union —
`{ kind: "planet"; id: number }` for planets and widened 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.
@@ -211,11 +202,11 @@ translating a renderer click into a planet selection. The flow:
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
it does not stack on top of the calc / order overlays. The dismissal
surface is a close button (`✕`) that calls `SelectionStore.clear()`.
Tap-outside and swipe-down dismissal are deferred to the finalization
plan ([../PLAN-finalize.md](../PLAN-finalize.md)). 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
@@ -227,9 +218,9 @@ field the FBS schema carries (`industryStockpile` for `capital`,
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.
The selected-planet visual on the map (a ring or halo) is deferred
to the finalization plan ([../PLAN-finalize.md](../PLAN-finalize.md))
together with the sheet's swipe-to-dismiss gesture.
## Auth gate