# UI Client — Post-MVP Roadmap The MVP web client (Phases 1–30 of [PLAN.md](PLAN.md)) is complete. The near-term web **finalization** pass — visual system, accessibility, localisation, error UX, PWA, docs — is tracked separately in [PLAN-finalize.md](PLAN-finalize.md). This roadmap holds the work deliberately deferred until after the web client is finalized: the native platform wrappers, the realistic multi-turn projection (a feature, not polish), the cross-platform acceptance pass, and a set of non-blocking follow-ups. Items keep their original PLAN.md phase numbers for continuity; their full original specs live in the PLAN.md git history. --- ## Phase 31 — Wails Desktop Wrapper Goal: native desktop app (macOS, Windows, Linux) running the same frontend bundle, with `ui/core` embedded as Go instead of WASM. Key artifacts: `ui/desktop/` Wails entry + IPC bindings; per-OS secure storage (macOS Keychain, Windows DPAPI, Linux Secret Service with a `0600` file fallback); `modernc.org/sqlite` cache; `WailsCore` / `WailsKeyStore` / `WailsCache` adapters; OS icons; `ui/Makefile` `desktop-*` targets; `ui/docs/desktop-secure-storage.md`. Re-evaluate Wails v2 vs v3 at phase start. Depends on: Phase 6 (KeyStore/Cache interfaces) and the web form of Phases 7–30. Acceptance: all three OS binaries launch, log in, and persist the keypair on a fresh profile from one codebase; Linux file fallback activates without libsecret and writes `0600`. ## Phase 32 — Capacitor Mobile Wrapper Goal: native iOS and Android apps running the same frontend bundle, calling a gomobile-compiled `ui/core`. Key artifacts: `ui/mobile-bridge/bridge.go` gomobile façade; `ui/Makefile` `gomobile`/`ios`/`android` targets; Capacitor project + custom `galaxy-core` plugin (Swift + Kotlin); `CapacitorCore` / `CapacitorKeyStore` / `CapacitorCache` adapters (secure-storage + sqlite community plugins); app icons + splash; `ui/docs/mobile-bridge.md`. Depends on: Phase 6 and the web form of Phases 7–30. Acceptance: iOS Simulator and Android Emulator launch, log in, and persist the keypair across restarts (manual smoke); same `Core`/`Storage` TS interfaces as web/desktop. Full Appium automation lands in Phase 36. ## Phase 34 — Multi-Turn Projection (realistic planet forecast) Goal: a realistic multi-turn planet projection, surfaced in the planet inspector and the calculator's planet area. (Reach circles already shipped in Phase 30; this phase no longer owns them.) The Phase 30 planet area is single-turn (MAT-only). This phase makes it realistic and multi-turn by extracting the planet economy into `pkg/calc` (single-sourced, engine delegates): `PlanetProduction`, `ProducePopulation`, `UnpackColonists`, `UnpackCapital`, reusing `ProduceShipsInTurn`; plus a `ProjectPlanetBuild` projector ("K ships in M turns" under guaranteed per-turn supply) mirroring `MakeTurn` steps 09/12/14/15. CAP and COL only affect future turns (post-production unpacking), so they become meaningful here and join MAT as supply inputs in the calculator's planet area. Key artifacts: the `pkg/calc` economy extraction + bridges + `Core` typings; planet-inspector forecast section (next-turn population, industry, materials, progress); calculator planet area gains CAP/COL supply; `ui/docs/multi-turn-projection.md`. Depends on: Phases 17, 18, 30. Acceptance: projector output byte- identical to running the engine's per-turn planet update over the same turns (Go parity); inspector + calculator readouts consistent with it. ## Phase 36 — Acceptance Pass Goal: reconcile implementation, documentation, and regression coverage before declaring the client ready for technical beta. Key artifacts: final cross-platform regression run on a release candidate; `ui/docs/release-checklist.md`; visual-regression baselines; Appium harness (iOS Simulator + Android Emulator) covering login, push, and a full turn loop, wired into a macOS-runner CI job as a pre-release gate; a docs/README/ARCHITECTURE/FUNCTIONAL drift sweep. Depends on: Phases 1–35 (incl. the finalization plan). Acceptance: implementation matches every documented contract; the cross-cutting regression scenarios below pass on web, desktop, and mobile; Appium smoke passes on iOS and Android in CI. ### Cross-cutting regression scenarios - A fresh device generates a keypair, completes email-code login, and signs a follow-up authenticated request on every platform. - A returning device resumes its session without re-login, preserves queued orders, and keeps receiving push events without gaps. - Server-side session revocation tears down the push stream and forces re-login on every platform within one second. - Tampering with `payload_bytes`, `payload_hash`, `request_id`, `message_type`, or any signature byte is rejected by `ui/core` with a stable error code. - Requests outside the freshness window are rejected before network; a clock-skew warning surfaces when the local clock disagrees beyond it. - The map renderer holds 60 fps with a 1000-primitive fixture on web (Chrome, Edge, Safari, Firefox), desktop (Wails on mac/win/linux), and mobile (latest iPhone, mid-range Android). - The sidebar preserves tool state across tab switches; the active view preserves state across view switches. - Order draft survives reloads, view switches, network drops, and history-mode entry/exit. - Orders queued offline flush in order on reconnect; a turn-cutoff conflict surfaces as a 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. - The calculator's math matches `pkg/calc/` byte-for-byte; drift fails CI. - Linux desktop without Secret Service completes login via the `0600` file fallback. - The web service worker invalidates on app update and never serves stale code on the first load after deploy. - Push-event signature verification is mandatory; any failure tears down the stream and reconnects with backoff. - Locale switch persists across reloads and applies to every visible string on every platform. --- ## Deferred follow-ups (non-blocking) Explicit deferral decisions, to fold into the phase noted when it lands. (The "build core.wasm in CI / drop the committed artefact" follow-up moved to [PLAN-finalize.md](PLAN-finalize.md) F6, since it is timely.) - **Restore `js.CopyBytesToGo`** when TinyGo fixes the `instanceof Uint8Array` check — the per-element loop in `ui/wasm/main.go::copyBytesFromJS` is a TinyGo 0.41 workaround. Track upstream and revert once a fixed release is pinned. - **Migrate TS codegen to the Connect-ES v2 BSR plugin** once published — `ui/buf.gen.yaml` runs `protoc-gen-es` v2 locally because `buf.build/connectrpc/es` still emits v1-incompatible imports. - **Rename `gateway/internal/grpcapi/` → `connectapi/`** — historical name; the package serves Connect/gRPC/gRPC-Web. Pure rename. (Fold into the next gateway change; not a UI task.) - **Rename `GATEWAY_AUTHENTICATED_GRPC_*` env vars** to drop the `GRPC` infix — they label the authenticated edge tier, not the wire protocol. (Coordinate with the rename above; not a UI task.) - **Add a Docker-stack Connect end-to-end integration test** — `integration/connect_call_test.go` exercising a unary + a server-streaming Connect call through `testenv.Bootstrap`. - **Battle viewer — push event `game.battle.new`** — emit a backend notification intent (idempotency `battle-new:::`) so the shell shows a toast deep-linking into the Battle Viewer. Needs an engine emit-side change. - **Battle viewer — richer ship-class visuals** — derive shape/scale from mass, armament, shields, and ship count instead of the MVP one-circle-plus-label per `(race, className)`.