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
+14 -382
View File
@@ -10,11 +10,18 @@ libraries (iOS/Android), and embedded directly in Wails (desktop). All
network I/O lives on the TypeScript side via ConnectRPC, so the Go
module is a pure compute boundary on every platform.
> **Status — MVP complete.** Phases 130 below (the full web client) are
> done. Remaining work is tracked outside this file: the active web
> finalization pass (visual system, accessibility, localisation, error
> UX, PWA, docs) in [PLAN-finalize.md](PLAN-finalize.md), and the
> deferred platform wrappers (Wails desktop, Capacitor mobile), the
> realistic multi-turn projection, and the cross-platform acceptance
> pass in [ROADMAP.md](ROADMAP.md). This file is retained as the staged
> record of how the MVP was built.
The existing Fyne client in `client/` is deprecated and is not modified
or imported by the new code. The strategy and rationale behind these
choices live in the plan file at
`/Users/id/.claude/plans/buzzing-questing-fountain.md`; the architectural
overview is mirrored into `ui/README.md` as part of Phase 1.
or imported by the new code. The architectural overview is mirrored into
`ui/README.md`.
Each phase ends with a runnable artifact. The visual progression is:
empty page → navigation skeleton → stubbed views → live data → real
@@ -26,7 +33,9 @@ plan can be adjusted with at most one phase of rework.
## Summary
This plan breaks implementation into 36 small reviewable phases. Each
This plan staged the MVP web client as 30 small reviewable phases (130,
all complete; the finalization pass and post-MVP work live in
[PLAN-finalize.md](PLAN-finalize.md) and [ROADMAP.md](ROADMAP.md)). Each
phase has a single primary goal, clear deliverables, explicit
dependencies, acceptance criteria, and focused tests. Tests live
alongside the code added in the phase; a phase is not closed until its
@@ -3408,380 +3417,3 @@ Note: the WASM artefact `ui/frontend/static/core.wasm` must be rebuilt
(`make wasm`, needs TinyGo) for the new bridge functions to be present
at runtime and in the Playwright suite; Vitest injects a fake `Core`
and does not need the rebuild.
## Phase 31. Wails Desktop Wrapper
Status: pending. Re-evaluate Wails v2 vs v3 at phase start.
Goal: build a native desktop app for macOS, Windows, and Linux that
runs the same frontend bundle and replaces the WASM core with embedded
Go code.
Artifacts:
- topic doc `ui/docs/wails-version.md` recording the v2-vs-v3
decision made at phase start with rationale
- `ui/desktop/main.go` Wails entry point
- `ui/desktop/app.go` IPC bindings exposing `ui/core` API to the
WebView through a structured adapter
- `ui/desktop/keychain/` per-OS secure-storage helpers (macOS Keychain
via `Security` framework, Windows DPAPI, Linux Secret Service / file
fallback at `~/.config/galaxy/keypair` with mode `0600`)
- `ui/desktop/sqlite/` `modernc.org/sqlite` cache wired through Wails
IPC
- `ui/frontend/src/platform/core/wails.ts` `WailsCore` adapter
- `ui/frontend/src/platform/store/wails.ts` `WailsKeyStore` and
`WailsCache` adapters
- `ui/desktop/build/icon.icns` macOS app icon
- `ui/desktop/build/icon.ico` Windows app icon
- `ui/desktop/build/icon.png` Linux app icon
- `ui/Makefile` targets `desktop-mac`, `desktop-win`, `desktop-linux`
- topic doc `ui/docs/desktop-secure-storage.md` documenting the
Linux/Windows file fallback for missing keychains
Dependencies: Phase 6 (KeyStore and Cache interfaces); Phases 7
through 30 in their web form (the desktop wrapper exercises the same
TypeScript code).
Acceptance criteria:
- the macOS, Windows, and Linux binaries each launch, complete login,
and preserve the keypair across restarts on a fresh user profile;
- a single source codebase produces all three OS bundles;
- the same `Core` and `Storage` TypeScript interfaces are satisfied as
on web, with no platform-specific code outside `platform/`;
- Linux file fallback activates when Secret Service is absent and
writes with `0600` permissions.
Targeted tests:
- Go unit tests for each keychain helper, including file fallback;
- desktop e2e smoke test driven by Wails headless mode running the
Phase 7 login Playwright scenario via CDP;
- regression test: keychain absence on a Linux container without
libsecret falls back to file storage.
## Phase 32. Capacitor Mobile Wrapper
Status: pending.
Goal: build native iOS and Android apps that run the same frontend
bundle and call into a gomobile-compiled `ui/core`.
Artifacts:
- `ui/mobile-bridge/bridge.go` gomobile-friendly façade over `ui/core`
- `ui/Makefile` target `gomobile` producing `Galaxy.framework` and
`galaxy.aar`
- `ui/mobile/capacitor.config.ts` Capacitor project configuration
- `ui/mobile/plugins/galaxy-core/` custom Capacitor plugin (Swift +
Kotlin) wrapping the gomobile artifacts
- `ui/frontend/src/platform/core/capacitor.ts` `CapacitorCore` adapter
- `ui/frontend/src/platform/store/capacitor.ts` `CapacitorKeyStore`
and `CapacitorCache` using `@capacitor-community/secure-storage-plugin`
and `@capacitor-community/sqlite`
- `ui/mobile/ios/App/Assets.xcassets/AppIcon.appiconset/` iOS app
icon set
- `ui/mobile/android/app/src/main/res/mipmap-*/` Android app icon
set
- iOS launch screen and Android splash screen
- `ui/Makefile` targets `ios` and `android`
- topic doc `ui/docs/mobile-bridge.md` describing the plugin
API, marshalling strategy, and the manual smoke procedure for this
phase
Dependencies: Phase 6; Phases 7 through 30 in their web form.
Acceptance criteria:
- both the iOS Simulator and an Android Emulator launch the app,
complete login, and preserve the keypair across restarts (validated
by manual smoke);
- the same `Core` and `Storage` TypeScript interfaces are satisfied as
on web and desktop;
- gomobile build produces deterministic outputs reproducible in CI on
a macOS runner.
Targeted tests:
- Go unit tests for the `mobile-bridge` façade;
- Capacitor plugin unit tests on iOS (XCTest) and Android (Espresso);
- manual smoke procedure: login flow on iOS Simulator and Android
Emulator, recorded in `ui/docs/mobile-bridge.md`. Full Appium
automation lands in Phase 36 as part of the acceptance pass.
## Phase 33. PWA — Service Worker, Manifest, Web Icons
Status: pending.
Goal: make the web build installable and offline-tolerant on every
browser. Native packaging icons live with their respective wrapper
phases (31 for desktop, 32 for mobile) — this phase is web-only.
Artifacts:
- `ui/frontend/src/service-worker.ts` cache-first asset strategy with
stale invalidation on app update
- `ui/frontend/static/manifest.webmanifest` PWA manifest
- `ui/frontend/static/icons/` web icon set sized per
`manifest.webmanifest` requirements
- topic doc `ui/docs/pwa-strategy.md` covering update flow and
offline scope
Dependencies: Phase 25 (offline order queue).
Acceptance criteria:
- the web app installs as a PWA on Chrome, Edge, and iOS Safari;
- the service worker survives an app update without serving stale code
on the next reload.
Targeted tests:
- Lighthouse PWA audit at score ≥ 90;
- Playwright test: install the app, take it offline, verify the cached
login route still loads;
- regression test: bumping the app version invalidates the prior
service worker.
## Phase 34. Multi-Turn Projection — Realistic Planet Forecast
Status: pending. Long-term scope deferred but this phase ships real
features.
Goal: ship a realistic multi-turn planet projection and surface it in
the planet inspector and in the calculator's planet area. Reach circles
already shipped in Phase 30 (auto-drawn from the calculator's selected
planet); this phase no longer owns them.
The Phase 30 planet area is single-turn (MAT-only): it answers "ships
this turn / turns per ship" at the current or overridden MAT. This phase
makes it realistic and multi-turn by extracting the planet economy into
`pkg/calc` and simulating turns: population growth (`×1.08`), material /
capital / colonist supply, and the capital/colonist unpacking that
mirrors `MakeTurn` steps 09/12/14/15. CAP and COL only affect future
turns (post-production unpacking), so they become meaningful here and
are added to the calculator's planet area as supply inputs alongside
MAT.
Artifacts:
- `pkg/calc/` planet-economy extraction (single-sourced, engine
delegates): `PlanetProduction`, `ProducePopulation`,
`UnpackColonists`, `UnpackCapital`, reusing `ProduceShipsInTurn`; a
multi-turn projector `ProjectPlanetBuild` answering "K ships in M
turns" under guaranteed per-turn supply
- thin bridges in `ui/core/calc/` + `Core` typings
- planet inspector forecast section (next-turn population, industry,
materials, production progress)
- calculator planet area gains CAP and COL supply inputs and switches
its readout to the multi-turn projector
- topic doc `ui/docs/multi-turn-projection.md` (long-term vision:
multi-turn planning mode, scenario branches)
Dependencies: Phases 17, 18, 30.
Acceptance criteria:
- projector output is byte-identical to running the engine's per-turn
planet update over the same turns (Go parity);
- the planet inspector shows a forecast section matching it;
- the calculator planet area honours MAT / CAP / COL supply and shows
"K ships in M turns" consistent with the projector.
Targeted tests:
- Go parity tests for each extracted economy formula and the projector;
- Vitest for the calculator planet area with supply inputs;
- Playwright e2e: planet inspector forecast section.
## Phase 35. Polish — Accessibility, Localisation, Error UX
Status: pending.
Goal: prepare the client for technical beta with end-user-quality
polish.
Artifacts:
- `ui/frontend/src/lib/i18n/` translation bundles for English and
Russian, covering every visible string
- `ui/frontend/src/lib/error/` central error surface with stable codes
and retry / escalation guidance
- accessibility audit results recorded under `ui/docs/a11y.md`
- keyboard-only navigation paths for lobby, game view, and login
- focus rings, ARIA labels, screen-reader-only text where needed
- mobile bottom-sheet swipe-down dismissal and tap-outside dismissal,
on top of the close button shipped in Phase 13
- selected-planet visual on the map (ring or halo), wired off the
Phase 13 `SelectionStore`
Dependencies: Phase 33.
Acceptance criteria:
- WCAG 2.2 AA compliance on lobby, login, and the in-game shell per
axe-core scan;
- the entire UI is reachable by keyboard only with visible focus
rings;
- every server-side error is mapped to a translated, actionable user
message in both languages;
- locale switch persists across reloads on every platform.
Targeted tests:
- axe-core integration tests on every top-level view;
- Vitest tests for the i18n bundle structure and missing-translation
detection;
- Playwright keyboard-only navigation tests.
## Phase 36. Acceptance Pass
Status: pending.
Goal: reconcile implementation, documentation, and regression coverage
before declaring the client ready for technical beta.
Artifacts:
- updated `ui/README.md`, topic docs, and any drift in
`docs/ARCHITECTURE.md` or `docs/FUNCTIONAL.md` (mirrored to
`docs/FUNCTIONAL_ru.md`)
- final cross-platform regression run on a release-candidate build
- `ui/docs/release-checklist.md` for repeatable releases
- visual regression baselines committed under
`ui/frontend/tests/__snapshots__/`; if maintenance proves heavy,
follow-up issue to switch to self-hosted Argos
- Appium harness for iOS Simulator and Android Emulator covering the
login flow, push-event flow, and at least one full turn loop;
`.gitea/workflows/ui-release.yaml` extended with macOS-runner Appium
job (mandatory pre-release gate)
Dependencies: Phases 1 through 35.
Acceptance criteria:
- implementation matches every documented contract and live topic
doc;
- the cross-cutting regression scenarios listed below pass on web,
desktop, and mobile;
- Appium smoke passes on both iOS and Android in CI.
Targeted tests:
- run focused package tests for `ui/core` and every TypeScript
module;
- rerun cross-platform Playwright suites against release-candidate
builds;
- run Tier 2 visual regression baselines;
- run Appium smoke suites on iOS and Android.
---
## Cross-Cutting Regression Scenarios
- A fresh device generates a keypair, completes email-code login, and
successfully signs a follow-up authenticated request on every target
platform.
- A returning device resumes its session without re-login, preserves
queued orders, and continues receiving push events without gaps.
- Server-side session revocation tears down the active push stream and
forces a re-login on every target platform within one second.
- Tampering with `payload_bytes`, `payload_hash`, `request_id`,
`message_type`, or any signature byte is rejected by the verifier
in `ui/core` with a stable error code.
- Requests outside the freshness window are rejected before they
reach network, and the client surfaces a clock-skew warning when
its local clock disagrees with the server time event by more than
the freshness window.
- The map renderer holds 60 fps with a 1000-primitive fixture on
mid-range hardware on web (Chrome, Edge, Safari, Firefox), desktop
(Wails on macOS, Windows, Linux), and mobile (latest iPhone, mid-
range Android).
- The single-tool sidebar preserves state across tab switches; the
active view preserves state across view switches; designers
preserve their in-progress state when navigating to the map and
back through a transient overlay.
- Order draft is preserved across page reloads, view switches, network
drops, and history-mode entry / exit.
- Orders queued offline are flushed in order on reconnect; a turn-
cutoff conflict surfaces as a clearly failed-order banner without
retrying forever.
- History mode applies to every view; the order tab disappears in
history mode and the prior draft is restored on return to the
current turn.
- The ship-class designer's calculations match `pkg/calc/` byte-for-
byte; any drift between client mirror and server fails CI.
- Linux desktop builds without Secret Service still complete login by
falling back to the `0600` file under `~/.config/galaxy/`.
- The web service worker invalidates correctly on app update and
never serves stale code on the first load after a deploy.
- Push-event signature verification is mandatory; any verification
failure tears down the stream and reconnects with backoff.
- Locale switch persists across reloads and applies to every visible
string on every platform.
## TODO — deferred follow-ups from Phases 1-5
These items are explicit decisions to defer, not unknown work. Each
should be picked up either as a follow-up patch or folded into the
phase listed in the parenthesis when that phase lands.
- **Build `core.wasm` in CI, drop the committed artefacts** — install
TinyGo on the Gitea Actions runner (`brew install tinygo` is not
available on Linux runners, so use the official tarball or
`curl … | tar -xz` step), add `make -C ui wasm` ahead of the Vitest
step in `.gitea/workflows/ui-test.yaml`, then remove
`ui/frontend/static/core.wasm` and `ui/frontend/static/wasm_exec.js`
from the repo and re-tighten `ui/.gitignore`. Phase 5 committed the
binaries only as a stop-gap so contributors did not have to install
TinyGo. (Phase 5 cleanup, blocks before Phase 33 PWA.)
- **Restore `js.CopyBytesToGo` when TinyGo fixes the
`instanceof Uint8Array` check** — the per-element loop in
`ui/wasm/main.go::copyBytesFromJS` is a workaround for TinyGo 0.41
panicking on Uint8Arrays whose prototype chain crosses Node's
`Buffer`. Track upstream
(<https://github.com/tinygo-org/tinygo/issues>) and revert the
helper once a release is pinned. (Phase 5 follow-up.)
- **Migrate TS codegen to Connect-ES v2 BSR plugin once published** —
`ui/buf.gen.yaml` runs `protoc-gen-es` v2 locally because
`buf.build/connectrpc/es` is still on v1.6.1 and emits
v1-incompatible imports. When the v2 plugin lands on the BSR, we
can either keep the local plugin (no network dep) or move back to
the remote, depending on whether buf.build rate limits are hit in
CI. (Phase 5 follow-up; revisit when next regenerating.)
- **Rename `gateway/internal/grpcapi/` → `gateway/internal/connectapi/`**
— the package now hosts a Connect-Go listener that natively serves
Connect, gRPC, and gRPC-Web; the `grpcapi` name is historical.
Touches imports in `gateway/cmd/gateway/main.go` and a couple of
cross-package refs. Pure rename, no behaviour change. (Phase 4
cleanup; do alongside the next gateway change.)
- **Rename `GATEWAY_AUTHENTICATED_GRPC_*` env vars to drop the `GRPC`
infix** — they label the authenticated-edge tier, not the wire
protocol. Affects `gateway/internal/config/`, the integration
testenv defaults in `integration/testenv/gateway.go`, the README,
and the runbook. Coordinated with the package rename above.
(Phase 4 cleanup; not before the env vars are referenced by
external operators.)
- **Add a Docker-stack integration test for Connect end-to-end** —
Phase 4 closed with service-level Connect tests only. Once a phase
already brings up the full stack (Phase 7 onward, since auth flow
needs backend), drop a `integration/connect_call_test.go` that
exercises a unary Connect call and a server-streaming Connect call
through `testenv.Bootstrap`. (Phase 7+, fold into the phase that
needs it.)
- **Battle viewer — push event `game.battle.new`** — when a battle
involving the current player lands, emit a backend notification
intent (idempotency `battle-new:<game_id>:<turn>:<battle_id>`,
payload `{game_id, turn, battle_id}`) so the in-game shell
surfaces a toast with a deep link into the Battle Viewer.
(Phase 27 deferred; needs an engine emit-side change.)
- **Battle viewer — richer ship-class visuals** — current MVP draws
one small circle plus `<class>:<numLeft>` label per `(race,
className)` pair. Future work derives shape / scale from mass,
armament, shields, and the number of ships in the group.
(Phase 27 deferred.)
- **Battle viewer — animated re-distribution on elimination** —
current implementation hard-jumps to the new spacing on the next
frame; replace with an easing so the survivors visibly slide
along the outer ring. (Phase 27 deferred.)